diff options
Diffstat (limited to 'src/game')
41 files changed, 17950 insertions, 26024 deletions
diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt new file mode 100644 index 0000000..cec287d --- /dev/null +++ b/src/game/CMakeLists.txt @@ -0,0 +1,70 @@ +# +##    ____                                      _       +##   / ___| __ _ _ __ ___   ___    ___ ___   __| | ___  +##  | |  _ / _` | '_ ` _ \ / _ \  / __/ _ \ / _` |/ _ \ +##  | |_| | (_| | | | | | |  __/ | (_| (_) | (_| |  __/ +##   \____|\__,_|_| |_| |_|\___|  \___\___/ \__,_|\___| +##                                                      +# + +set(CMAKE_INSTALL_NAME_DIR ${PROJECT_BINARY_DIR}/gpp) + +set(QC_SOURCE_DIR ../qcommon) +#set(RC_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../renderercommon) +#set(CLIENT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../client) + +add_definitions( -DGAME ) + +set( GAME_SOURCES +    g_main.c # Must be listed first! +    bg_alloc.c +    bg_lib.c +    bg_lib.h +    bg_local.h +    bg_misc.c +    bg_pmove.c +    bg_public.h +    bg_slidemove.c +    bg_voice.c +    g_active.c +    g_admin.c +    g_admin.h +    g_buildable.c +    g_client.c +    g_cmds.c +    g_combat.c +    g_local.h +    g_maprotation.c +    g_misc.c +    g_missile.c +    g_mover.c +    g_namelog.c +    g_physics.c +    g_playermodel.c +    g_public.h +    g_session.c +    g_spawn.c +    g_svcmds.c +    g_target.c +    g_team.c +    g_trigger.c +    g_utils.c +    g_weapon.c +    g_weapondrop.c +    tremulous.h +    ${QC_SOURCE_DIR}/q_shared.h  +    ${QC_SOURCE_DIR}/q_shared.c +    ${QC_SOURCE_DIR}/q_math.c +    ) + +add_library( game SHARED ${GAME_SOURCES} g_syscalls.c ) + +include( ${CMAKE_SOURCE_DIR}/cmake/AddQVM.cmake ) +add_qvm( game ${GAME_SOURCES} g_syscalls.asm ) + +add_custom_command( +    TARGET game POST_BUILD +    COMMAND ${CMAKE_COMMAND} +    ARGS -E copy ${CMAKE_CURRENT_BINARY_DIR}/libgame${CMAKE_SHARED_LIBRARY_SUFFIX} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/gpp/game${CMAKE_SHARED_LIBRARY_SUFFIX} +    ) + diff --git a/src/game/g_mem.c b/src/game/bg_alloc.c index 6935194..d08c29f 100644 --- a/src/game/g_mem.c +++ b/src/game/bg_alloc.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,47 +17,53 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ -#include "g_local.h" +#include "qcommon/q_shared.h" +#include "bg_public.h" + +#ifdef GAME +# define  POOLSIZE ( 1024 * 1024 ) +#else +# define  POOLSIZE ( 256 * 1024 ) +#endif -#define  POOLSIZE ( 1024 * 1024 )  #define  FREEMEMCOOKIE  ((int)0xDEADBE3F)  // Any unlikely to be used value  #define  ROUNDBITS    31          // Round to 32 bytes -struct freememnode +typedef struct freeMemNode_s  {    // Size of ROUNDBITS    int cookie, size;        // Size includes node (obviously) -  struct freememnode *prev, *next; -}; +  struct freeMemNode_s *prev, *next; +} freeMemNode_t; -static char    memoryPool[POOLSIZE]; -static struct freememnode *freehead; -static int    freemem; +static char           memoryPool[POOLSIZE]; +static freeMemNode_t  *freeHead; +static int            freeMem; -void *G_Alloc( int size ) +void *BG_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; +  freeMemNode_t *fmn, *prev, *next, *smallest;    int allocsize, smallestsize;    char *endptr;    int *ptr; -  allocsize = ( size + sizeof(int) + ROUNDBITS ) & ~ROUNDBITS;    // Round to 32-byte boundary +  allocsize = ( size + (int)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 ) +  for( fmn = freeHead; fmn; fmn = fmn->next )    {      if( fmn->cookie != FREEMEMCOOKIE ) -      G_Error( "G_Alloc: Memory corruption detected!\n" ); +      Com_Error( ERR_DROP, "BG_Alloc: Memory corruption detected!" );      if( fmn->size >= allocsize )      { @@ -71,8 +78,8 @@ void *G_Alloc( int size )            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 +        if( fmn == freeHead ) +          freeHead = next;      // Set head pointer to next          ptr = (int *) fmn;          break;              // Stop the loop, this is fine        } @@ -98,34 +105,30 @@ void *G_Alloc( int size )    if( ptr )    { -    freemem -= allocsize; -    if( g_debugAlloc.integer ) -      G_Printf( "G_Alloc of %i bytes (%i left)\n", allocsize, freemem ); +    freeMem -= allocsize;      memset( ptr, 0, allocsize );      *ptr++ = allocsize;        // Store a copy of size for deallocation      return( (void *) ptr );    } -  G_Error( "G_Alloc: failed on allocation of %i bytes\n", size ); +  Com_Error( ERR_DROP, "BG_Alloc: failed on allocation of %i bytes", size );    return( NULL );  } -void G_Free( void *ptr ) +void BG_Free( void *ptr )  {    // Release allocated memory, add it to the free list. -  struct freememnode *fmn; +  freeMemNode_t *fmn;    char *freeend;    int *freeptr;    freeptr = ptr;    freeptr--; -  freemem += *freeptr; -  if( g_debugAlloc.integer ) -    G_Printf( "G_Free of %i bytes (%i left)\n", *freeptr, freemem ); +  freeMem += *freeptr; -  for( fmn = freehead; fmn; fmn = fmn->next ) +  for( fmn = freeHead; fmn; fmn = fmn->next )    {      freeend = ((char *) fmn) + fmn->size;      if( freeend == (char *) freeptr ) @@ -138,42 +141,42 @@ void G_Free( void *ptr )    }    // No merging, add to head of list -  fmn = (struct freememnode *) freeptr; +  fmn = (freeMemNode_t *) 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; +  fmn->next = freeHead; +  freeHead->prev = fmn; +  freeHead = fmn;  } -void G_InitMemory( void ) +void BG_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 ); +  freeHead = (freeMemNode_t *)memoryPool; +  freeHead->cookie = FREEMEMCOOKIE; +  freeHead->size = POOLSIZE; +  freeHead->next = NULL; +  freeHead->prev = NULL; +  freeMem = sizeof( memoryPool );  } -void G_DefragmentMemory( void ) +void BG_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; +  freeMemNode_t *startfmn, *endfmn, *fmn; -  for( startfmn = freehead; startfmn; ) +  for( startfmn = freeHead; startfmn; )    { -    endfmn = (struct freememnode *)(((char *) startfmn) + startfmn->size); -    for( fmn = freehead; fmn; ) +    endfmn = (freeMemNode_t *)(((char *) startfmn) + startfmn->size); +    for( fmn = freeHead; fmn; )      {        if( fmn->cookie != FREEMEMCOOKIE ) -        G_Error( "G_DefragmentMemory: Memory corruption detected!\n" ); +        Com_Error( ERR_DROP, "BG_DefragmentMemory: Memory corruption detected!" );        if( fmn == endfmn )        { @@ -184,12 +187,12 @@ void G_DefragmentMemory( void )          if( fmn->next )          {            if( !(fmn->next->prev = fmn->prev) ) -            freehead = fmn->next;  // We're removing the head node +            freeHead = fmn->next;  // We're removing the head node          }          startfmn->size += fmn->size; -        memset( fmn, 0, sizeof(struct freememnode) );  // A redundant call, really. +        memset( fmn, 0, sizeof(freeMemNode_t) );  // A redundant call, really. -        startfmn = freehead; +        startfmn = freeHead;          endfmn = fmn = NULL;        // Break out of current loop        }        else @@ -201,16 +204,39 @@ void G_DefragmentMemory( void )    }  } -void Svcmd_GameMem_f( void ) +void BG_MemoryInfo( void )  {    // Give a breakdown of memory -  struct freememnode *fmn; +  freeMemNode_t *fmn = (freeMemNode_t *)memoryPool; +  int size, chunks; +  freeMemNode_t *end = (freeMemNode_t *)( memoryPool + POOLSIZE ); +  void *p; -  G_Printf( "Game memory status: %i out of %i bytes allocated\n", POOLSIZE - freemem, POOLSIZE ); +  Com_Printf( "%p-%p: %d out of %d bytes allocated\n", +    fmn, end, POOLSIZE - freeMem, POOLSIZE ); -  for( fmn = freehead; fmn; fmn = fmn->next ) -    G_Printf( "  %dd: %d bytes free.\n", fmn, fmn->size ); -  G_Printf( "Status complete.\n" ); +  while( fmn < end ) +  { +    size = chunks = 0; +    p = fmn; +    while( fmn < end && fmn->cookie == FREEMEMCOOKIE ) +    { +      size += fmn->size; +      chunks++; +      fmn = (freeMemNode_t *)( (char *)fmn + fmn->size ); +    } +    if( size ) +      Com_Printf( "  %p: %d bytes free (%d chunks)\n", p, size, chunks ); +    size = chunks = 0; +    p = fmn; +    while( fmn < end && fmn->cookie != FREEMEMCOOKIE ) +    { +      size += *(int *)fmn; +      chunks++; +      fmn = (freeMemNode_t *)( (size_t)fmn + *(int *)fmn ); +    } +    if( size ) +      Com_Printf( "  %p: %d bytes allocated (%d chunks)\n", p, size, chunks ); +  }  } - diff --git a/src/game/bg_lib.c b/src/game/bg_lib.c index 69bca48..73b9a5d 100644 --- a/src/game/bg_lib.c +++ b/src/game/bg_lib.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -25,7 +26,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  // compiled for the virtual machine -#include "../qcommon/q_shared.h" +#ifdef Q3_VM + +#include "qcommon/q_shared.h"  /*-   * Copyright (c) 1992, 1993 @@ -67,21 +70,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93";  #endif  static const char rcsid[] = -  "$Id: bg_lib.c 965 2007-08-09 13:54:12Z msk $"; +  "$Id$";  #endif /* LIBC_SCCS and not lint */ -// bk001127 - needed for DLL's -#if !defined( Q3_VM ) -typedef int    cmp_t(const void *, const void *); -#endif -  static char* med3(char *, char *, char *, cmp_t *);  static void  swapfunc(char *, char *, int, int); -#ifndef min -#define min(a, b) (a) < (b) ? a : b -#endif -  /*   * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function".   */ @@ -197,9 +191,9 @@ loop: SWAPINIT(a, es);    }    pn = (char *)a + n * es; -  r = min(pa - (char *)a, pb - pa); +  r = MIN(pa - (char *)a, pb - pa);    vecswap(a, pb - r, r); -  r = min(pd - pc, pn - pd - es); +  r = MIN(pd - pc, pn - pd - es);    vecswap(pb, pn - r, r);    if ((r = pb - pa) > es)      qsort(a, r / es, es, cmp); @@ -215,11 +209,6 @@ loop: SWAPINIT(a, es);  //================================================================================== -// this file is excluded from release builds because of intrinsics - -// bk001211 - gcc errors on compiling strcpy:  parse error before `__extension__' -#if defined ( Q3_VM ) -  size_t strlen( const char *string )  {    const char  *s; @@ -272,13 +261,12 @@ int strcmp( const char *string1, const char *string2 )    return *string1 - *string2;  } -//TA:  char *strrchr( const char *string, int c )  {    int   i, length = strlen( string );    char  *p; -  for( i = length - 1; i >= 0; i-- ) +  for( i = length /*sic*/; i >= 0; i-- )    {      p = (char *)&string[ i ]; @@ -298,7 +286,7 @@ char *strchr( const char *string, int c )      string++;    } -  return (char *)0; +  return c == '\0' ? (char *)string : (char *)0;  }  char *strstr( const char *string, const char *strCharSet ) @@ -321,10 +309,6 @@ char *strstr( const char *string, const char *strCharSet )    return (char *)0;  } -#endif // bk001211 - -#if defined ( Q3_VM ) -  int tolower( int c )  {    if( c >= 'A' && c <= 'Z' ) @@ -342,21 +326,23 @@ int toupper( int c )    return c;  } -#endif -  void *memmove( void *dest, const void *src, size_t count )  { -  int   i; +  size_t i;    if( dest > src )    { -    for( i = count - 1; i >= 0; i-- ) -      ( (char *)dest )[ i ] = ( (char *)src )[ i ]; +    i = count; +    while( i > 0 ) +    { +      i--; +      ((char *)dest)[ i ] = ((char *)src)[ i ]; +    }    }    else    {      for( i = 0; i < count; i++ ) -      ( (char *)dest )[ i ] = ( (char *)src )[ i ]; +      ((char *) dest)[ i ] = ((char *)src)[ i ];    }    return dest; @@ -816,7 +802,6 @@ double atan2( double y, double x ) {  #endif -#ifdef Q3_VM  /*  ===============  rint @@ -830,8 +815,43 @@ double rint( double v )      return floor( v );  } -// bk001127 - guarded this tan replacement -// ld: undefined versioned symbol name tan@@GLIBC_2.0 +/* +=============== +powN + +Raise a double to a integer power +=============== +*/ +static double powN( double base, int exp ) +{ +  if( exp >= 0 ) +  { +    double result = 1.0; + +    // calculate x, x^2, x^4, ... by repeated squaring +    // and multiply together the ones corresponding to the +    // binary digits of the exponent +    // e.g. x^73 = x^(1 + 8 + 64) = x * x^8 * x^64 +    while( exp > 0 ) +    { +      if( exp % 2 == 1 ) +        result *= base; + +      base *= base; +      exp /= 2; +    } + +    return result; +  } +  // if exp is INT_MIN, the next clause will be upset, +  // because -exp isn't representable +  else if( exp == INT_MIN ) +    return powN( base, exp + 1 ) / base; +  // x < 0 +  else +    return 1.0 / powN( base, -exp ); +} +  double tan( double x )  {    return sin( x ) / cos( x ); @@ -1316,8 +1336,6 @@ float pow( float x, float y )    return s * z;  } -#endif -  static int randSeed = 0; @@ -1409,12 +1427,210 @@ double atof( const char *string )    return value * sign;  } +/* +============== +strtod + +Without an errno variable, this is a fair bit less useful than it is in libc +but it's still a fair bit more capable than atof or _atof +Handles inf[inity], nan (ignoring case), hexadecimals, and decimals +Handles decimal exponents like 10e10 and hex exponents like 0x7f8p20 +10e10 == 10000000000 (power of ten) +0x7f8p20 == 0x7f800000 (decimal power of two) +The variable pointed to by endptr will hold the location of the first character +in the nptr string that was not used in the conversion +============== +*/ +double strtod( const char *nptr, char **endptr ) +{ +  double res; +  qboolean neg = qfalse; + +  // skip whitespace +  while( isspace( *nptr ) ) +    nptr++; + +  // special string parsing +  if( Q_stricmpn( nptr, "nan", 3 ) == 0 ) +  { +    floatint_t nan; + +    if( endptr ) +      *endptr = (char *)&nptr[3]; + +    // nan can be followed by a bracketed number (in hex, octal, +    // or decimal) which is then put in the mantissa +    // this can be used to generate signalling or quiet NaNs, for +    // example (though I doubt it'll ever be used) +    // note that nan(0) is infinity! +    if( nptr[3] == '(' ) +    { +      char *end; +      int mantissa = strtol( &nptr[4], &end, 0 ); + +      if( *end == ')' ) +      { +        nan.ui = 0x7f800000 | ( mantissa & 0x7fffff ); + +        if( endptr ) +          *endptr = &end[1]; +        return nan.f; +      } +    } + +    nan.ui = 0x7fffffff; +    return nan.f; +  } + +  if( Q_stricmpn( nptr, "inf", 3 ) == 0 ) +  { +    floatint_t inf; +    inf.ui = 0x7f800000; + +    if( endptr == NULL ) +      return inf.f; + +    if( Q_stricmpn( &nptr[3], "inity", 5 ) == 0 ) +      *endptr = (char *)&nptr[8]; +    else +      *endptr = (char *)&nptr[3]; + +    return inf.f; +  } + +  // normal numeric parsing +  // sign +  if( *nptr == '-' ) +  { +    nptr++; +    neg = qtrue; +  } +  else if( *nptr == '+' ) +    nptr++; + +  // hex +  if( Q_stricmpn( nptr, "0x", 2 ) == 0 ) +  { +    // track if we use any digits +    const char *s = &nptr[1], *end = s; +    nptr += 2; + +    for( res = 0;; ) +    { +      if( isdigit( *nptr ) ) +        res = 16 * res + ( *nptr++ - '0' ); +      else if( *nptr >= 'A' && *nptr <= 'F' ) +        res = 16 * res + 10 + *nptr++ - 'A'; +      else if( *nptr >= 'a' && *nptr <= 'f' ) +        res = 16 * res + 10 + *nptr++ - 'a'; +      else +        break; +    } + +    // if nptr moved, save it +    if( end + 1 < nptr ) +      end = nptr; + +    if( *nptr == '.' ) +    { +      float place; +      nptr++; + +      // 1.0 / 16.0 == 0.0625 +      // I don't expect the float accuracy to hold out for +      // very long but since we need to know the length of +      // the string anyway we keep on going regardless +      for( place = 0.0625;; place /= 16.0 ) +      { +        if( isdigit( *nptr ) ) +          res += place * ( *nptr++ - '0' ); +        else if( *nptr >= 'A' && *nptr <= 'F' ) +          res += place * ( 10 + *nptr++ - 'A' ); +        else if( *nptr >= 'a' && *nptr <= 'f' ) +          res += place * ( 10 + *nptr++ - 'a' ); +        else +          break; +      } + +      if( end < nptr ) +        end = nptr; +    } + +    // parse an optional exponent, representing multiplication +    // by a power of two +    // exponents are only valid if we encountered at least one +    // digit already (and have therefore set end to something) +    if( end != s && tolower( *nptr ) == 'p' ) +    { +      int exp; +      // apparently (confusingly) the exponent should be +      // decimal +      exp = strtol( &nptr[1], (char **)&end, 10 ); +      if( &nptr[1] == end ) +      { +        // no exponent +        if( endptr ) +          *endptr = (char *)nptr; +        return res; +      } + +      res *= powN( 2, exp ); +    } +    if( endptr ) +      *endptr = (char *)end; +    return res; +  } +  // decimal +  else +  { +    // track if we find any digits +    const char *end = nptr, *p = nptr; +    // this is most of the work +    for( res = 0; isdigit( *nptr ); +      res = 10 * res + *nptr++ - '0' ); +    // if nptr moved, we read something +    if( end < nptr ) +      end = nptr; +    if( *nptr == '.' ) +    { +      // fractional part +      float place; +      nptr++; +      for( place = 0.1; isdigit( *nptr ); place /= 10.0 ) +        res += ( *nptr++ - '0' ) * place; +      // if nptr moved, we read something +      if( end + 1 < nptr ) +        end = nptr; +    } +    // exponent +    // meaningless without having already read digits, so check +    // we've set end to something +    if( p != end && tolower( *nptr ) == 'e' ) +    { +      int exp; +      exp = strtol( &nptr[1], (char **)&end, 10 ); +      if( &nptr[1] == end ) +      { +        // no exponent +        if( endptr ) +          *endptr = (char *)nptr; +        return res; +      } + +      res *= powN( 10, exp ); +    } +    if( endptr ) +      *endptr = (char *)end; +    return res; +  } +} +  double _atof( const char **stringPtr )  {    const char  *string;    float sign;    float value; -  int   c = '0'; // bk001211 - uninitialized use possible +  int   c = '0';    string = *stringPtr; @@ -1488,8 +1704,108 @@ double _atof( const char **stringPtr )    return value * sign;  } +/* +============== +strtol + +Handles any base from 2 to 36. If base is 0 then it guesses +decimal, hex, or octal based on the format of the number (leading 0 or 0x) +Will not overflow - returns LONG_MIN or LONG_MAX as appropriate +*endptr is set to the location of the first character not used +============== +*/ +long strtol( const char *nptr, char **endptr, int base ) +{ +  long res; +  qboolean pos = qtrue; + +  if( endptr ) +    *endptr = (char *)nptr; + +  // bases other than 0, 2, 8, 16 are very rarely used, but they're +  // not much extra effort to support +  if( base < 0 || base == 1 || base > 36 ) +    return 0; + +  // skip leading whitespace +  while( isspace( *nptr ) ) +    nptr++; + +  // sign +  if( *nptr == '-' ) +  { +    nptr++; +    pos = qfalse; +  } +  else if( *nptr == '+' ) +    nptr++; -#if defined ( Q3_VM ) +  // look for base-identifying sequences e.g. 0x for hex, 0 for octal +  if( nptr[0] == '0' ) +  { +    nptr++; + +    // 0 is always a valid digit +    if( endptr ) +      *endptr = (char *)nptr; + +    if( *nptr == 'x' || *nptr == 'X' ) +    { +      if( base != 0 && base != 16 ) +      { +        // can't be hex, reject x (accept 0) +        if( endptr ) +          *endptr = (char *)nptr; +        return 0; +      } + +      nptr++; +      base = 16; +    } +    else if( base == 0 ) +      base = 8; +  } +  else if( base == 0 ) +    base = 10; + +  for( res = 0;; ) +  { +    int val; + +    if( isdigit( *nptr ) ) +      val = *nptr - '0'; +    else if( islower( *nptr ) ) +      val = 10 + *nptr - 'a'; +    else if( isupper( *nptr ) ) +      val = 10 + *nptr - 'A'; +    else +      break; + +    if( val >= base ) +      break; + +    // we go negative because LONG_MIN is further from 0 than +    // LONG_MAX +    if( res < ( LONG_MIN + val ) / base ) +      res = LONG_MIN; // overflow +    else +      res = res * base - val; + +    nptr++; + +    if( endptr ) +      *endptr = (char *)nptr; +  } +  if( pos ) +  { +    // can't represent LONG_MIN positive +    if( res == LONG_MIN ) +      res = LONG_MAX; +    else +      res = -res; +  } +  return res; +}  int atoi( const char *string )  { @@ -1613,9 +1929,9 @@ unsigned int _hextoi( const char **stringPtr )    int          c;    int          i;    const char   *string; -  +    string = *stringPtr; -  +    // skip whitespace    while( *string <= ' ' )    { @@ -1653,356 +1969,751 @@ unsigned int _hextoi( const char **stringPtr )  //========================================================= +/* + * New implementation by Patrick Powell and others for vsnprintf. + * Supports length checking in strings. + */ -#define ALT       0x00000001    /* alternate form */ -#define HEX       0x00000002    /* hexadecimal  */ -#define LADJUST   0x00000004    /* left adjustment */ -#define LONGDBL   0x00000008    /* long double */ -#define LONGINT   0x00000010    /* long integer */ -#define QUADINT   0x00000020    /* quad integer */ -#define SHORTINT  0x00000040    /* short integer */ -#define ZEROPAD   0x00000080    /* zero (as opposed to blank) pad */ -#define FPT       0x00000100    /* floating point number */ -#define UNSIGNED  0x00000200    /* unsigned integer */     +/* + * Copyright Patrick Powell 1995 + * This code is based on code written by Patrick Powell (papowell@astart.com) + * It may be used for any purpose as long as this notice remains intact + * on all source code distributions + */ -#define to_digit(c)   ((c) - '0') -#define is_digit(c)   ((unsigned)to_digit(c) <= 9) -#define to_char(n)    ((n) + '0') +/************************************************************** + * Original: + * Patrick Powell Tue Apr 11 09:48:21 PDT 1995 + * A bombproof version of doprnt (dopr) included. + * Sigh.  This sort of thing is always nasty do deal with.  Note that + * the version here does not include floating point... + * + * snprintf() is used instead of sprintf() as it does limit checks + * for string length.  This covers a nasty loophole. + * + * The other functions are there to prevent NULL pointers from + * causing nast effects. + * + * More Recently: + *  Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43 + *  This was ugly.  It is still ugly.  I opted out of floating point + *  numbers, but the formatter understands just about everything + *  from the normal C string format, at least as far as I can tell from + *  the Solaris 2.5 printf(3S) man page. + * + *  Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1 + *    Ok, added some minimal floating point support, which means this + *    probably requires libm on most operating systems.  Don't yet + *    support the exponent (e,E) and sigfig (g,G).  Also, fmtint() + *    was pretty badly broken, it just wasn't being exercised in ways + *    which showed it, so that's been fixed.  Also, formated the code + *    to mutt conventions, and removed dead code left over from the + *    original.  Also, there is now a builtin-test, just compile with: + *           gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm + *    and run snprintf for results. + * + *  Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i + *    The PGP code was using unsigned hexadecimal formats. + *    Unfortunately, unsigned formats simply didn't work. + * + *  Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8 + *    The original code assumed that both snprintf() and vsnprintf() were + *    missing.  Some systems only have snprintf() but not vsnprintf(), so + *    the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF. + * + *  Andrew Tridgell (tridge@samba.org) Oct 1998 + *    fixed handling of %.0f + *    added test for HAVE_LONG_DOUBLE + * + *  Russ Allbery <rra@stanford.edu> 2000-08-26 + *    fixed return value to comply with C99 + *    fixed handling of snprintf(NULL, ...) + * + *  Hrvoje Niksic <hniksic@arsdigita.com> 2000-11-04 + *    include <config.h> instead of "config.h". + *    moved TEST_SNPRINTF stuff out of HAVE_SNPRINTF ifdef. + *    include <stdio.h> for NULL. + *    added support and test cases for long long. + *    don't declare argument types to (v)snprintf if stdarg is not used. + *    use int instead of short int as 2nd arg to va_arg. + * + **************************************************************/ -void AddInt( char **buf_p, int val, int width, int flags ) -{ -  char  text[ 32 ]; -  int   digits; -  char  *buf; +/* BDR 2002-01-13  %e and %g were being ignored.  Now do something, +   if not necessarily correctly */ + +#if (SIZEOF_LONG_DOUBLE > 0) +/* #ifdef HAVE_LONG_DOUBLE */ +#define LDOUBLE long double +#else +#define LDOUBLE double +#endif -  digits = 0; +#if (SIZEOF_LONG_LONG > 0) +/* #ifdef HAVE_LONG_LONG */ +# define LLONG long long +#else +# define LLONG long +#endif -  if( flags & UNSIGNED ) -    val = (unsigned) val; +static int dopr (char *buffer, size_t maxlen, const char *format, +    va_list args); +static int fmtstr (char *buffer, size_t *currlen, size_t maxlen, +    char *value, int flags, int min, int max); +static int fmtint (char *buffer, size_t *currlen, size_t maxlen, +    LLONG value, int base, int min, int max, int flags); +static int fmtfp (char *buffer, size_t *currlen, size_t maxlen, +    LDOUBLE fvalue, int min, int max, int flags); +static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c ); -  if( flags & HEX ) +/* + * dopr(): poor man's version of doprintf + */ + +/* format read states */ +#define DP_S_DEFAULT 0 +#define DP_S_FLAGS   1 +#define DP_S_MIN     2 +#define DP_S_DOT     3 +#define DP_S_MAX     4 +#define DP_S_MOD     5 +#define DP_S_MOD_L   6 +#define DP_S_CONV    7 +#define DP_S_DONE    8 + +/* format flags - Bits */ +#define DP_F_MINUS     (1 << 0) +#define DP_F_PLUS      (1 << 1) +#define DP_F_SPACE     (1 << 2) +#define DP_F_NUM       (1 << 3) +#define DP_F_ZERO      (1 << 4) +#define DP_F_UP        (1 << 5) +#define DP_F_UNSIGNED  (1 << 6) + +/* Conversion Flags */ +#define DP_C_SHORT   1 +#define DP_C_LONG    2 +#define DP_C_LLONG   3 +#define DP_C_LDOUBLE 4 + +#define char_to_int(p) (p - '0') + +static int dopr (char *buffer, size_t maxlen, const char *format, va_list args) +{ +  char ch; +  LLONG value; +  LDOUBLE fvalue; +  char *strvalue; +  int min; +  int max; +  int state; +  int flags; +  int cflags; +  int total; +  size_t currlen; + +  state = DP_S_DEFAULT; +  currlen = flags = cflags = min = 0; +  max = -1; +  ch = *format++; +  total = 0; + +  while (state != DP_S_DONE)    { -    char c; -    int n = 0; +    if (ch == '\0') +      state = DP_S_DONE; -    while( n < 32 ) +    switch(state)      { -      c = "0123456789abcdef"[ ( val >> n ) & 0xF ]; -      n += 4; -      if( c == '0' && !digits ) -        continue;     -      text[ digits++ ] = c; +      case DP_S_DEFAULT: +        if (ch == '%') +          state = DP_S_FLAGS; +        else +          total += dopr_outch (buffer, &currlen, maxlen, ch); +        ch = *format++; +        break; +      case DP_S_FLAGS: +        switch (ch) +        { +          case '-': +            flags |= DP_F_MINUS; +            ch = *format++; +            break; +          case '+': +            flags |= DP_F_PLUS; +            ch = *format++; +            break; +          case ' ': +            flags |= DP_F_SPACE; +            ch = *format++; +            break; +          case '#': +            flags |= DP_F_NUM; +            ch = *format++; +            break; +          case '0': +            flags |= DP_F_ZERO; +            ch = *format++; +            break; +          default: +            state = DP_S_MIN; +            break; +        } +        break; +      case DP_S_MIN: +        if ('0' <= ch && ch <= '9') +        { +          min = 10*min + char_to_int (ch); +          ch = *format++; +        } +        else if (ch == '*') +        { +          min = va_arg (args, int); +          ch = *format++; +          state = DP_S_DOT; +        } +        else +          state = DP_S_DOT; +        break; +      case DP_S_DOT: +        if (ch == '.') +        { +          state = DP_S_MAX; +          ch = *format++; +        } +        else +          state = DP_S_MOD; +        break; +      case DP_S_MAX: +        if ('0' <= ch && ch <= '9') +        { +          if (max < 0) +            max = 0; +          max = 10*max + char_to_int (ch); +          ch = *format++; +        } +        else if (ch == '*') +        { +          max = va_arg (args, int); +          ch = *format++; +          state = DP_S_MOD; +        } +        else +          state = DP_S_MOD; +        break; +      case DP_S_MOD: +        switch (ch) +        { +          case 'h': +            cflags = DP_C_SHORT; +            ch = *format++; +            break; +          case 'l': +            cflags = DP_C_LONG; +            ch = *format++; +            break; +          case 'L': +            cflags = DP_C_LDOUBLE; +            ch = *format++; +            break; +          default: +            break; +        } +        if (cflags != DP_C_LONG) +          state = DP_S_CONV; +        else +          state = DP_S_MOD_L; +        break; +      case DP_S_MOD_L: +        switch (ch) +        { +          case 'l': +            cflags = DP_C_LLONG; +            ch = *format++; +            break; +          default: +            break; +        } +        state = DP_S_CONV; +        break; +      case DP_S_CONV: +        switch (ch) +        { +          case 'd': +          case 'i': +            if (cflags == DP_C_SHORT) +              value = (short int)va_arg (args, int); +            else if (cflags == DP_C_LONG) +              value = va_arg (args, long int); +            else if (cflags == DP_C_LLONG) +              value = va_arg (args, LLONG); +            else +              value = va_arg (args, int); +            total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); +            break; +          case 'o': +            flags |= DP_F_UNSIGNED; +            if (cflags == DP_C_SHORT) +              //    value = (unsigned short int) va_arg (args, unsigned short int); // Thilo: This does not work because the rcc compiler cannot do that cast correctly. +              value = va_arg (args, unsigned int) & ( (1 << sizeof(unsigned short int) * 8) - 1); // Using this workaround instead. +            else if (cflags == DP_C_LONG) +              value = va_arg (args, unsigned long int); +            else if (cflags == DP_C_LLONG) +              value = va_arg (args, unsigned LLONG); +            else +              value = va_arg (args, unsigned int); +            total += fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags); +            break; +          case 'u': +            flags |= DP_F_UNSIGNED; +            if (cflags == DP_C_SHORT) +              value = va_arg (args, unsigned int) & ( (1 << sizeof(unsigned short int) * 8) - 1); +            else if (cflags == DP_C_LONG) +              value = va_arg (args, unsigned long int); +            else if (cflags == DP_C_LLONG) +              value = va_arg (args, unsigned LLONG); +            else +              value = va_arg (args, unsigned int); +            total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags); +            break; +          case 'X': +            flags |= DP_F_UP; +          case 'x': +            flags |= DP_F_UNSIGNED; +            if (cflags == DP_C_SHORT) +              value = va_arg (args, unsigned int) & ( (1 << sizeof(unsigned short int) * 8) - 1); +            else if (cflags == DP_C_LONG) +              value = va_arg (args, unsigned long int); +            else if (cflags == DP_C_LLONG) +              value = va_arg (args, unsigned LLONG); +            else +              value = va_arg (args, unsigned int); +            total += fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags); +            break; +          case 'f': +            if (cflags == DP_C_LDOUBLE) +              fvalue = va_arg (args, LDOUBLE); +            else +              fvalue = va_arg (args, double); +            /* um, floating point? */ +            total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); +            break; +          case 'E': +            flags |= DP_F_UP; +          case 'e': +            if (cflags == DP_C_LDOUBLE) +              fvalue = va_arg (args, LDOUBLE); +            else +              fvalue = va_arg (args, double); +            /* um, floating point? */ +            total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); +            break; +          case 'G': +            flags |= DP_F_UP; +          case 'g': +            if (cflags == DP_C_LDOUBLE) +              fvalue = va_arg (args, LDOUBLE); +            else +              fvalue = va_arg (args, double); +            /* um, floating point? */ +            total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags); +            break; +          case 'c': +            total += dopr_outch (buffer, &currlen, maxlen, va_arg (args, int)); +            break; +          case 's': +            strvalue = va_arg (args, char *); +            total += fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max); +            break; +          case 'p': +            strvalue = va_arg (args, void *); +            total += fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min, +                max, flags); +            break; +          case 'n': +            if (cflags == DP_C_SHORT) +            { +              short int *num; +              num = va_arg (args, short int *); +              *num = currlen; +            } +            else if (cflags == DP_C_LONG) +            { +              long int *num; +              num = va_arg (args, long int *); +              *num = currlen; +            } +            else if (cflags == DP_C_LLONG) +            { +              LLONG *num; +              num = va_arg (args, LLONG *); +              *num = currlen; +            } +            else +            { +              int *num; +              num = va_arg (args, int *); +              *num = currlen; +            } +            break; +          case '%': +            total += dopr_outch (buffer, &currlen, maxlen, ch); +            break; +          case 'w': +            /* not supported yet, treat as next char */ +            ch = *format++; +            break; +          default: +            /* Unknown, skip */ +            break; +        } +        ch = *format++; +        state = DP_S_DEFAULT; +        flags = cflags = min = 0; +        max = -1; +        break; +      case DP_S_DONE: +        break; +      default: +        /* hmm? */ +        break; /* some picky compilers need this */      } -    text[ digits ] = '\0';    } -  else -  { -    int   signedVal = val; +  if (maxlen > 0) +    buffer[currlen] = '\0'; +  return total; +} -    if( val < 0 ) -      val = -val; -    do -    { -      text[ digits++ ] = '0' + val % 10; -      val /= 10; -    } while( val ); +static int fmtstr (char *buffer, size_t *currlen, size_t maxlen, +    char *value, int flags, int min, int max) +{ +  int padlen, strln;     /* amount to pad */ +  int cnt = 0; +  int total = 0; -    if( signedVal < 0 ) -      text[ digits++ ] = '-'; +  if (value == 0) +  { +    value = "<NULL>";    } -  buf = *buf_p; +  for (strln = 0; value[strln]; ++strln); /* strlen */ +  if (max >= 0 && max < strln) +    strln = max; +  padlen = min - strln; +  if (padlen < 0) +    padlen = 0; +  if (flags & DP_F_MINUS) +    padlen = -padlen; /* Left Justify */ -  if( !( flags & LADJUST ) ) +  while (padlen > 0)    { -    while( digits < width ) -    { -      *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; -      width--; -    } +    total += dopr_outch (buffer, currlen, maxlen, ' '); +    --padlen;    } - -  while( digits-- ) +  while (*value && ((max < 0) || (cnt < max)))    { -    *buf++ = text[ digits ]; -    width--; +    total += dopr_outch (buffer, currlen, maxlen, *value++); +    ++cnt;    } - -  if( flags & LADJUST ) +  while (padlen < 0)    { -    while( width-- > 0 ) -      *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; +    total += dopr_outch (buffer, currlen, maxlen, ' '); +    ++padlen;    } - -  *buf_p = buf; +  return total;  } -void AddFloat( char **buf_p, float fval, int width, int prec ) +/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */ + +static int fmtint (char *buffer, size_t *currlen, size_t maxlen, +    LLONG value, int base, int min, int max, int flags)  { -  char  text[ 32 ]; -  int   digits; -  float signedVal; -  char  *buf; -  int   val; +  int signvalue = 0; +  unsigned LLONG uvalue; +  char convert[24]; +  int place = 0; +  int spadlen = 0; /* amount to space pad */ +  int zpadlen = 0; /* amount to zero pad */ +  const char *digits; +  int total = 0; -  // get the sign -  signedVal = fval; -  if( fval < 0 ) -    fval = -fval; +  if (max < 0) +    max = 0; -  // write the float number -  digits = 0; -  val = (int)fval; +  uvalue = value; -  do +  if(!(flags & DP_F_UNSIGNED))    { -    text[ digits++ ] = '0' + val % 10; -    val /= 10; -  } while( val ); - -  if( signedVal < 0 ) -    text[digits++] = '-'; +    if( value < 0 ) { +      signvalue = '-'; +      uvalue = -value; +    } +    else +      if (flags & DP_F_PLUS)  /* Do a sign (+/i) */ +        signvalue = '+'; +      else +        if (flags & DP_F_SPACE) +          signvalue = ' '; +  } -  buf = *buf_p; +  if (flags & DP_F_UP) +    /* Should characters be upper case? */ +    digits = "0123456789ABCDEF"; +  else +    digits = "0123456789abcdef"; -  while( digits < width ) +  do { +    convert[place++] = digits[uvalue % (unsigned)base]; +    uvalue = (uvalue / (unsigned)base ); +  } while(uvalue && (place < sizeof (convert))); +  if (place == sizeof (convert)) place--; +  convert[place] = 0; + +  zpadlen = max - place; +  spadlen = min - MAX (max, place) - (signvalue ? 1 : 0); +  if (zpadlen < 0) zpadlen = 0; +  if (spadlen < 0) spadlen = 0; +  if (flags & DP_F_ZERO)    { -    *buf++ = ' '; -    width--; +    zpadlen = MAX(zpadlen, spadlen); +    spadlen = 0;    } +  if (flags & DP_F_MINUS) +    spadlen = -spadlen; /* Left Justifty */ -  while( digits-- ) -    *buf++ = text[ digits ]; - -  *buf_p = buf; +#ifdef DEBUG_SNPRINTF +  dprint (1, (debugfile, "zpad: %d, spad: %d, min: %d, max: %d, place: %d\n", +        zpadlen, spadlen, min, max, place)); +#endif -  if( prec < 0 ) -    prec = 6; +  /* Spaces */ +  while (spadlen > 0) +  { +    total += dopr_outch (buffer, currlen, maxlen, ' '); +    --spadlen; +  } -  // write the fraction -  digits = 0; +  /* Sign */ +  if (signvalue) +    total += dopr_outch (buffer, currlen, maxlen, signvalue); -  while( digits < prec ) +  /* Zeros */ +  if (zpadlen > 0)    { -    fval -= (int)fval; -    fval *= 10.0; -    val = (int)fval; -    text[ digits++ ] = '0' + val % 10; +    while (zpadlen > 0) +    { +      total += dopr_outch (buffer, currlen, maxlen, '0'); +      --zpadlen; +    }    } -  if( digits > 0 ) -  { -    buf = *buf_p; -    *buf++ = '.'; -    for( prec = 0; prec < digits; prec++ ) -      *buf++ = text[ prec ]; +  /* Digits */ +  while (place > 0) +    total += dopr_outch (buffer, currlen, maxlen, convert[--place]); -    *buf_p = buf; +  /* Left Justified spaces */ +  while (spadlen < 0) { +    total += dopr_outch (buffer, currlen, maxlen, ' '); +    ++spadlen;    } + +  return total;  } -void AddVec3_t( char **buf_p, vec3_t v, int width, int prec ) +static LDOUBLE abs_val (LDOUBLE value)  { -  char *buf; +  LDOUBLE result = value; -  buf = *buf_p; +  if (value < 0) +    result = -value; -  *buf++ = '['; - -  AddFloat( &buf, v[ 0 ], width, prec ); -  buf += width; -  *buf++ = ' '; +  return result; +} -  AddFloat( &buf, v[ 1 ], width, prec ); -  buf += width; -  *buf++ = ' '; +static long round (LDOUBLE value) +{ +  long intpart; -  AddFloat( &buf, v[ 2 ], width, prec ); -  buf += width; -  *buf++ = ']'; +  intpart = value; +  value = value - intpart; +  if (value >= 0.5) +    intpart++; -  *buf_p = buf; +  return intpart;  } -void AddString( char **buf_p, char *string, int width, int prec ) +static int fmtfp (char *buffer, size_t *currlen, size_t maxlen, +    LDOUBLE fvalue, int min, int max, int flags)  { -  int   size; -  char  *buf; +  int signvalue = 0; +  LDOUBLE ufvalue; +  char iconvert[20]; +  char fconvert[20]; +  int iplace = 0; +  int fplace = 0; +  int padlen = 0; /* amount to pad */ +  int zpadlen = 0; +  int caps = 0; +  int total = 0; +  long intpart; +  long fracpart; -  buf = *buf_p; +  /* +   * AIX manpage says the default is 0, but Solaris says the default +   * is 6, and sprintf on AIX defaults to 6 +   */ +  if (max < 0) +    max = 6; -  if( string == NULL ) -  { -    string = "(null)"; -    prec = -1; -  } +  ufvalue = abs_val (fvalue); -  if( prec >= 0 ) -  { -    for( size = 0; size < prec; size++ ) -    { -      if( string[ size ] == '\0' ) -        break; -    } -  } +  if (fvalue < 0) +    signvalue = '-';    else -    size = strlen( string ); - -  width -= size; +    if (flags & DP_F_PLUS)  /* Do a sign (+/i) */ +      signvalue = '+'; +    else +      if (flags & DP_F_SPACE) +        signvalue = ' '; -  while( size-- ) -    *buf++ = *string++; +#if 0 +  if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */ +#endif -  while( width-- > 0 ) -    *buf++ = ' '; +  intpart = ufvalue; -  *buf_p = buf; -} +  /* +   * Sorry, we only support 9 digits past the decimal because of our +   * conversion method +   */ +  if (max > 9) +    max = 9; -/* -vsprintf +  /* We "cheat" by converting the fractional part to integer by +   * multiplying by a factor of 10 +   */ +  fracpart = round ((powN (10, max)) * (ufvalue - intpart)); -I'm not going to support a bunch of the more arcane stuff in here -just to keep it simpler.  For example, the '*' and '$' are not -currently supported.  I've tried to make it so that it will just -parse and ignore formats we don't support. -*/ -int vsprintf( char *buffer, const char *fmt, va_list argptr ) -{ -  int   *arg; -  char  *buf_p; -  char  ch; -  int   flags; -  int   width; -  int   prec; -  int   n; -  char  sign; - -  buf_p = buffer; -  arg = (int *)argptr; - -  while( qtrue ) +  if (fracpart >= powN (10, max))    { -    // run through the format string until we hit a '%' or '\0' -    for( ch = *fmt; ( ch = *fmt ) != '\0' && ch != '%'; fmt++ ) -      *buf_p++ = ch; +    intpart++; +    fracpart -= powN (10, max); +  } -    if( ch == '\0' ) -      goto done; +#ifdef DEBUG_SNPRINTF +  dprint (1, (debugfile, "fmtfp: %f =? %d.%d\n", fvalue, intpart, fracpart)); +#endif -    // skip over the '%' -    fmt++; +  /* Convert integer part */ +  do { +    iconvert[iplace++] = +      (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10]; +    intpart = (intpart / 10); +  } while(intpart && (iplace < 20)); +  if (iplace == 20) iplace--; +  iconvert[iplace] = 0; + +  /* Convert fractional part */ +  do { +    fconvert[fplace++] = +      (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10]; +    fracpart = (fracpart / 10); +  } while(fracpart && (fplace < 20)); +  if (fplace == 20) fplace--; +  fconvert[fplace] = 0; + +  /* -1 for decimal point, another -1 if we are printing a sign */ +  padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0); +  zpadlen = max - fplace; +  if (zpadlen < 0) +    zpadlen = 0; +  if (padlen < 0) +    padlen = 0; +  if (flags & DP_F_MINUS) +    padlen = -padlen; /* Left Justifty */ + +  if ((flags & DP_F_ZERO) && (padlen > 0)) +  { +    if (signvalue) +    { +      total += dopr_outch (buffer, currlen, maxlen, signvalue); +      --padlen; +      signvalue = 0; +    } +    while (padlen > 0) +    { +      total += dopr_outch (buffer, currlen, maxlen, '0'); +      --padlen; +    } +  } +  while (padlen > 0) +  { +    total += dopr_outch (buffer, currlen, maxlen, ' '); +    --padlen; +  } +  if (signvalue) +    total += dopr_outch (buffer, currlen, maxlen, signvalue); -    // reset formatting state -    flags = 0; -    width = 0; -    prec = -1; -    sign = '\0'; +  while (iplace > 0) +    total += dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]); -rflag: -    ch = *fmt++; -reswitch: -    switch( ch ) -    { -      case '-': -        flags |= LADJUST; -        goto rflag; - -      case '.': -        n = 0; -        while( is_digit( ( ch = *fmt++ ) ) ) -          n = 10 * n + ( ch - '0' ); - -        prec = n < 0 ? -1 : n; -        goto reswitch; - -      case '0': -        flags |= ZEROPAD; -        goto rflag; - -      case '1': -      case '2': -      case '3': -      case '4': -      case '5': -      case '6': -      case '7': -      case '8': -      case '9': -        n = 0; -        do -        { -          n = 10 * n + ( ch - '0' ); -          ch = *fmt++; -        } while( is_digit( ch ) ); +  /* +   * Decimal point.  This should probably use locale to find the correct +   * char to print out. +   */ +  if (max > 0) +  { +    total += dopr_outch (buffer, currlen, maxlen, '.'); -        width = n; -        goto reswitch; +    while (zpadlen-- > 0) +      total += dopr_outch (buffer, currlen, maxlen, '0'); -      case 'c': -        *buf_p++ = (char)*arg; -        arg++; -        break; +    while (fplace > 0) +      total += dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]); +  } -      case 'u': -        flags |= UNSIGNED; -      case 'd': -      case 'i': -        AddInt( &buf_p, *arg, width, flags ); -        arg++; -        break; +  while (padlen < 0) +  { +    total += dopr_outch (buffer, currlen, maxlen, ' '); +    ++padlen; +  } -      case 'f': -        AddFloat( &buf_p, *(double *)arg, width, prec ); -#ifdef Q3_VM -        arg += 1; // everything is 32 bit in my compiler -#else -        arg += 2; -#endif -        break; +  return total; +} -      case 's': -        AddString( &buf_p, (char *)*arg, width, prec ); -        arg++; -        break; +static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c) +{ +  if (*currlen + 1 < maxlen) +    buffer[(*currlen)++] = c; +  return 1; +} -      case 'v': -        AddVec3_t( &buf_p, (vec_t *)*arg, width, prec ); -        arg++; -        break; -       -      case 'x': -        flags |= HEX; -        AddInt( &buf_p, *arg, width, prec ); -        arg++; -        break; +int Q_vsnprintf(char *str, size_t length, const char *fmt, va_list args) +{ +  return dopr(str, length, fmt, args); +} -      case '%': -        *buf_p++ = ch; -        break; +int Q_snprintf(char *str, size_t length, const char *fmt, ...) +{ +  va_list ap; +  int retval; -      default: -        *buf_p++ = (char)*arg; -        arg++; -        break; -    } -  } +  va_start(ap, fmt); +  retval = Q_vsnprintf(str, length, fmt, ap); +  va_end(ap); -done: -  *buf_p = 0; -  return buf_p - buffer; +  return retval;  } -  /* this is really crappy */ -// FIXME: count is still inaccurate in some cases.   int sscanf( const char *buffer, const char *fmt, ... )  {    int cmd; -  int **arg; +  va_list ap;    int count; +  size_t len; -  arg = (int **)&fmt + 1; +  va_start( ap, fmt );    count = 0;    while( *fmt ) @@ -2013,33 +2724,70 @@ int sscanf( const char *buffer, const char *fmt, ... )        continue;      } -    if( !buffer[ 0 ] ) break; +    fmt++; +    cmd = *fmt; -    cmd = fmt[ 1 ]; -    fmt += 2; +    if( isdigit( cmd ) ) +    { +      len = (size_t)_atoi( &fmt ); +      cmd = *( fmt - 1 ); +    } +    else +    { +      len = MAX_STRING_CHARS - 1; +      fmt++; +    }      switch( cmd )      {        case 'i':        case 'd':        case 'u': -        **arg = _atoi( &buffer ); -        ++count; +        *( va_arg( ap, int * ) ) = _atoi( &buffer );          break;        case 'f': -        *(float *)*arg = _atof( &buffer ); -        ++count; +        *( va_arg( ap, float * ) ) = _atof( &buffer );          break;        case 'x': -        **arg = _hextoi( &buffer ); -        ++count; +        *( va_arg( ap, unsigned int * ) ) = _hextoi( &buffer ); +        break; +      case 's': +      { +        char *s = va_arg( ap, char * ); +        while( isspace( *buffer ) ) +          buffer++; +        while( *buffer && !isspace( *buffer) && len-- > 0 ) +          *s++ = *buffer++; +        *s++ = '\0';          break; +      }      } - -    arg++;    } +  va_end( ap );    return count;  } +void *bsearch( const void *key, const void *base, size_t nmemb, size_t size, +               cmp_t *compar ) +{ +  size_t low = 0, high = nmemb, mid; +  int    comp; +  void   *ptr; + +  while( low < high ) +  { +    mid = low + (high - low) / 2; +    ptr = (void *)((char *)base + ( mid * size )); +    comp = compar (key, ptr); +    if( comp < 0 ) +      high = mid; +    else if( comp > 0 ) +      low = mid + 1; +    else +      return ptr; +  } +  return NULL; +} +  #endif diff --git a/src/game/bg_lib.h b/src/game/bg_lib.h index 962a625..a853654 100644 --- a/src/game/bg_lib.h +++ b/src/game/bg_lib.h @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -25,14 +26,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  // compiled for the virtual machine  // This file is NOT included on native builds -#ifndef BG_LIB_H +#if !defined( BG_LIB_H ) && defined( Q3_VM )  #define BG_LIB_H  #ifndef NULL  #define NULL ((void *)0)  #endif -typedef int size_t; +typedef unsigned int size_t;  typedef char *  va_list;  #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) @@ -40,21 +41,28 @@ typedef char *  va_list;  #define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )  #define va_end(ap)      ( ap = (va_list)0 ) -#define CHAR_BIT      8         /* number of bits in a char */ -#define SCHAR_MIN   (-128)      /* minimum signed char value */ -#define SCHAR_MAX     127       /* maximum signed char value */ -#define UCHAR_MAX     0xff      /* maximum unsigned char value */ +#define CHAR_BIT      8             /* number of bits in a char */ +#define SCHAR_MAX     0x7f          /* maximum signed char value */ +#define SCHAR_MIN   (-SCHAR_MAX - 1)/* minimum signed char value */ +#define UCHAR_MAX     0xff          /* maximum unsigned char value */ -#define SHRT_MIN    (-32768)        /* minimum (signed) short value */ -#define SHRT_MAX      32767         /* maximum (signed) short value */ +#define SHRT_MAX      0x7fff        /* maximum (signed) short value */ +#define SHRT_MIN    (-SHRT_MAX - 1) /* minimum (signed) short value */  #define USHRT_MAX     0xffff        /* maximum unsigned short value */ -#define INT_MIN     (-2147483647 - 1) /* minimum (signed) int value */ -#define INT_MAX       2147483647    /* maximum (signed) int value */ +#define INT_MAX       0x7fffffff    /* maximum (signed) int value */ +#define INT_MIN     (-INT_MAX - 1)  /* minimum (signed) int value */  #define UINT_MAX      0xffffffff    /* maximum unsigned int value */ -#define LONG_MIN    (-2147483647L - 1) /* minimum (signed) long value */ -#define LONG_MAX      2147483647L   /* maximum (signed) long value */ +#define LONG_MAX      0x7fffffffL   /* maximum (signed) long value */ +#define LONG_MIN    (-LONG_MAX - 1) /* minimum (signed) long value */  #define ULONG_MAX     0xffffffffUL  /* maximum unsigned long value */ +typedef   signed  char int8_t; +typedef unsigned  char uint8_t; +typedef   signed short int16_t; +typedef unsigned short uint16_t; +typedef   signed  long int32_t; +typedef unsigned  long uint32_t; +  #define isalnum(c)  (isalpha(c) || isdigit(c))  #define isalpha(c)  (isupper(c) || islower(c))  #define isascii(c)  ((c) > 0 && (c) <= 0x7f) @@ -72,10 +80,17 @@ typedef char *  va_list;  #define isxupper(c) (isdigit(c) || (c >= 'A' && c <= 'F'))   // Misc functions +#define assert( expr )\ +    if( !( expr ) )\ +      Com_Error( ERR_DROP, "%s:%d: Assertion `%s' failed",\ +                 __FILE__, __LINE__, #expr )  typedef int cmp_t( const void *, const void * );  void        qsort( void *a, size_t n, size_t es, cmp_t *cmp ); +#define RAND_MAX 0x7fff  void        srand( unsigned seed );  int         rand( void ); +void        *bsearch( const void *key, const void *base, size_t nmemb, +                      size_t size, cmp_t *compar );  // String functions  size_t  strlen( const char *string ); @@ -91,11 +106,14 @@ int     toupper( int c );  double  atof( const char *string );  double  _atof( const char **stringPtr ); +double  strtod( const char *nptr, char **endptr );  int     atoi( const char *string );  int     _atoi( const char **stringPtr ); +long    strtol( const char *nptr, char **endptr, int base ); +int Q_vsnprintf( char *buffer, size_t length, const char *fmt, va_list argptr ); +int Q_snprintf( char *buffer, size_t length, const char *fmt, ... ) __attribute__ ((format (printf, 3, 4))); -int     vsprintf( char *buffer, const char *fmt, va_list argptr );  int     sscanf( const char *buffer, const char *fmt, ... );  // Memory functions diff --git a/src/game/bg_local.h b/src/game/bg_local.h index 354214c..1a67c49 100644 --- a/src/game/bg_local.h +++ b/src/game/bg_local.h @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -31,8 +32,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define TIMER_GESTURE     (34*66+50)  #define TIMER_ATTACK      500 //nonsegmented models -#define OVERCLIP    1.001f -  #define FALLING_THRESHOLD -900.0f //what vertical speed to start falling sound at @@ -65,20 +64,19 @@ extern  pml_t         pml;  extern  float pm_stopspeed;  extern  float pm_duckScale;  extern  float pm_swimScale; -extern  float pm_wadeScale;  extern  float pm_accelerate; -extern  float pm_airaccelerate;  extern  float pm_wateraccelerate;  extern  float pm_flyaccelerate;  extern  float pm_friction;  extern  float pm_waterfriction;  extern  float pm_flightfriction; +extern  float pm_spectatorfriction;  extern  int   c_pmove; -void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out );  void PM_AddTouchEnt( int entityNum );  void PM_AddEvent( int newEvent ); diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c index fd50509..e30a754 100644 --- a/src/game/bg_misc.c +++ b/src/game/bg_misc.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,45 +17,44 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */  // bg_misc.c -- both games misc functions, all completely stateless -#include "../qcommon/q_shared.h" +#include "qcommon/q_shared.h"  #include "bg_public.h" -int  trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +int  trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode );  void trap_FS_Read( void *buffer, int len, fileHandle_t f );  void trap_FS_Write( const void *buffer, int len, fileHandle_t f );  void trap_FS_FCloseFile( fileHandle_t f ); -void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t +void trap_FS_Seek( fileHandle_t f, long offset, enum FS_Origin  origin ); // fsOrigin_t +int  trap_FS_GetFileList(  const char *path, const char *extension, char *listbuf, int bufsize ); -buildableAttributes_t bg_buildableList[ ] = +static const buildableAttributes_t bg_buildableList[ ] =  {    { -    BA_A_SPAWN,            //int       buildNum; -    "eggpod",              //char      *buildName; +    BA_A_SPAWN,            //int       number; +    "eggpod",              //char      *name;      "Egg",                 //char      *humanName; +    "The most basic alien structure. It allows aliens to spawn " +      "and protect the Overmind. Without any of these, the Overmind " +      "is left nearly defenseless and defeat is imminent.",      "team_alien_spawn",    //char      *entityName; -    { "models/buildables/eggpod/eggpod.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -15, -15, -15 },     //vec3_t    mins; -    { 15, 15, 15 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce;      ASPAWN_BP,             //int       buildPoints; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      ASPAWN_HEALTH,         //int       health;      ASPAWN_REGEN,          //int       regenRate;      ASPAWN_SPLASHDAMAGE,   //int       splashDamage;      ASPAWN_SPLASHRADIUS,   //int       splashRadius;      MOD_ASPAWN,            //int       meansOfDeath; -    BIT_ALIENS,            //int       team; -    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ),    //weapon_t  buildWeapon; +    TEAM_ALIENS,           //int       team; +    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim;      100,                   //int       nextthink;      ASPAWN_BT,             //int       buildTime; @@ -68,70 +68,66 @@ buildableAttributes_t bg_buildableList[ ] =      ASPAWN_CREEPSIZE,      //int       creepSize;      qfalse,                //qboolean  dccTest;      qfalse,                //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qfalse,                //qboolean  replacable; +    qfalse,                //qboolean  uniqueTest; +    ASPAWN_VALUE,          //int       value;    },    { -    BA_A_BARRICADE,        //int       buildNum; -    "barricade",           //char      *buildName; -    "Barricade",           //char      *humanName; -    "team_alien_barricade",//char      *entityName; -    { "models/buildables/barricade/barricade.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -35, -35, -15 },     //vec3_t    mins; -    { 35, 35, 60 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset; +    BA_A_OVERMIND,         //int       number; +    "overmind",            //char      *name; +    "Overmind",            //char      *humanName; +    "A collective consciousness that controls all the alien structures " +      "in its vicinity. It must be protected at all costs, since its " +      "death will render alien structures defenseless.", +    "team_alien_overmind", //char      *entityName;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce; -    BARRICADE_BP,          //int       buildPoints; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages -    BARRICADE_HEALTH,      //int       health; -    BARRICADE_REGEN,       //int       regenRate; -    BARRICADE_SPLASHDAMAGE,//int       splashDamage; -    BARRICADE_SPLASHRADIUS,//int       splashRadius; +    OVERMIND_BP,           //int       buildPoints; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages; +    OVERMIND_HEALTH,       //int       health; +    OVERMIND_REGEN,        //int       regenRate; +    OVERMIND_SPLASHDAMAGE, //int       splashDamage; +    OVERMIND_SPLASHRADIUS, //int       splashRadius;      MOD_ASPAWN,            //int       meansOfDeath; -    BIT_ALIENS,            //int       team; -    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ),    //weapon_t  buildWeapon; +    TEAM_ALIENS,           //int       team; +    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim; -    100,                   //int       nextthink; -    BARRICADE_BT,          //int       buildTime; +    OVERMIND_ATTACK_REPEAT, //int      nextthink; +    OVERMIND_BT,           //int       buildTime;      qfalse,                //qboolean  usable;      0,                     //int       turretRange;      0,                     //int       turretFireSpeed;      WP_NONE,               //weapon_t  turretProjType; -    0.707f,                //float     minNormal; +    0.95f,                 //float     minNormal;      qfalse,                //qboolean  invertNormal; -    qtrue,                 //qboolean  creepTest; -    BARRICADE_CREEPSIZE,   //int       creepSize; +    qfalse,                //qboolean  creepTest; +    OVERMIND_CREEPSIZE,    //int       creepSize;      qfalse,                //qboolean  dccTest;      qfalse,                //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qfalse,                //qboolean  replaceable; +    qtrue,                 //qboolean  uniqueTest; +    OVERMIND_VALUE,        //int       value;    },    { -    BA_A_BOOSTER,          //int       buildNum; -    "booster",             //char      *buildName; -    "Booster",             //char      *humanName; -    "team_alien_booster",  //char      *entityName; -    { "models/buildables/booster/booster.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -26, -26, -9 },     //vec3_t     mins; -    { 26, 26, 9 },        //vec3_t     maxs; -    0.0f,                  //float     zOffset; +    BA_A_BARRICADE,        //int       number; +    "barricade",           //char      *name; +    "Barricade",           //char      *humanName; +    "Used to obstruct corridors and doorways, hindering humans from " +      "threatening the spawns and Overmind. Barricades will shrink " +      "to allow aliens to pass over them, however.", +    "team_alien_barricade", //char     *entityName;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce; -    BOOSTER_BP,            //int       buildPoints; -    ( 1 << S2 )|( 1 << S3 ), //int  stages -    BOOSTER_HEALTH,        //int       health; -    BOOSTER_REGEN,         //int       regenRate; -    BOOSTER_SPLASHDAMAGE,  //int       splashDamage; -    BOOSTER_SPLASHRADIUS,  //int       splashRadius; +    BARRICADE_BP,          //int       buildPoints; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages; +    BARRICADE_HEALTH,      //int       health; +    BARRICADE_REGEN,       //int       regenRate; +    BARRICADE_SPLASHDAMAGE, //int      splashDamage; +    BARRICADE_SPLASHRADIUS, //int      splashRadius;      MOD_ASPAWN,            //int       meansOfDeath; -    BIT_ALIENS,            //int       team; -    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ),    //weapon_t  buildWeapon; +    TEAM_ALIENS,           //int       team; +    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim;      100,                   //int       nextthink; -    BOOSTER_BT,            //int       buildTime; +    BARRICADE_BT,          //int       buildTime;      qfalse,                //qboolean  usable;      0,                     //int       turretRange;      0,                     //int       turretFireSpeed; @@ -139,33 +135,31 @@ buildableAttributes_t bg_buildableList[ ] =      0.707f,                //float     minNormal;      qfalse,                //qboolean  invertNormal;      qtrue,                 //qboolean  creepTest; -    BOOSTER_CREEPSIZE,     //int       creepSize; +    BARRICADE_CREEPSIZE,   //int       creepSize;      qfalse,                //qboolean  dccTest; -    qtrue,                 //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qtrue,                 //qboolean  replacable; +    qfalse,                //qboolean  transparentTest; +    qfalse,                //qboolean  uniqueTest; +    BARRICADE_VALUE,       //int       value;    },    { -    BA_A_ACIDTUBE,         //int       buildNum; -    "acid_tube",           //char      *buildName; +    BA_A_ACIDTUBE,         //int       number; +    "acid_tube",           //char      *name;      "Acid Tube",           //char      *humanName; -    "team_alien_acid_tube",//char      *entityName; -    { "models/buildables/acid_tube/acid_tube.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -25, -25, -25 },     //vec3_t    mins; -    { 25, 25, 25 },        //vec3_t    maxs; -    -15.0f,                //float     zOffset; +    "Ejects lethal poisonous acid at an approaching human. These " +      "are highly effective when used in conjunction with a trapper " +      "to hold the victim in place.", +    "team_alien_acid_tube", //char      *entityName;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce;      ACIDTUBE_BP,           //int       buildPoints; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      ACIDTUBE_HEALTH,       //int       health;      ACIDTUBE_REGEN,        //int       regenRate;      ACIDTUBE_SPLASHDAMAGE, //int       splashDamage;      ACIDTUBE_SPLASHRADIUS, //int       splashRadius; -    MOD_ATUBE,             //int       meansOfDeath; -    BIT_ALIENS,            //int       team; -    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ),    //weapon_t  buildWeapon; +    MOD_ASPAWN,            //int       meansOfDeath; +    TEAM_ALIENS,           //int       team; +    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim;      200,                   //int       nextthink;      ACIDTUBE_BT,           //int       buildTime; @@ -179,67 +173,28 @@ buildableAttributes_t bg_buildableList[ ] =      ACIDTUBE_CREEPSIZE,    //int       creepSize;      qfalse,                //qboolean  dccTest;      qfalse,                //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qfalse,                //qboolean  replacable; +    qfalse,                //qboolean  uniqueTest; +    ACIDTUBE_VALUE,        //int       value;    },    { -    BA_A_HIVE,             //int       buildNum; -    "hive",                //char      *buildName; -    "Hive",                //char      *humanName; -    "team_alien_hive",     //char      *entityName; -    { "models/buildables/acid_tube/acid_tube.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -35, -35, -25 },     //vec3_t    mins; -    { 35, 35, 25 },        //vec3_t    maxs; -    -15.0f,                //float     zOffset; -    TR_GRAVITY,            //trType_t  traj; -    0.0,                   //float     bounce; -    HIVE_BP,               //int       buildPoints; -    ( 1 << S3 ),           //int  stages -    HIVE_HEALTH,           //int       health; -    HIVE_REGEN,            //int       regenRate; -    HIVE_SPLASHDAMAGE,     //int       splashDamage; -    HIVE_SPLASHRADIUS,     //int       splashRadius; -    MOD_ASPAWN,            //int       meansOfDeath; -    BIT_ALIENS,            //int       team; -    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ),    //weapon_t  buildWeapon; -    BANIM_IDLE1,           //int       idleAnim; -    500,                   //int       nextthink; -    HIVE_BT,               //int       buildTime; -    qfalse,                //qboolean  usable; -    0,                     //int       turretRange; -    0,                     //int       turretFireSpeed; -    WP_HIVE,               //weapon_t  turretProjType; -    0.0f,                  //float     minNormal; -    qtrue,                 //qboolean  invertNormal; -    qtrue,                 //qboolean  creepTest; -    HIVE_CREEPSIZE,        //int       creepSize; -    qfalse,                //qboolean  dccTest; -    qfalse,                //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qfalse,                //qboolean  replacable; -  }, -  { -    BA_A_TRAPPER,          //int       buildNum; -    "trapper",             //char      *buildName; +    BA_A_TRAPPER,          //int       number; +    "trapper",             //char      *name;      "Trapper",             //char      *humanName; +    "Fires a blob of adhesive spit at any non-alien in its line of " +      "sight. This hinders their movement, making them an easy target " +      "for other defensive structures or aliens.",      "team_alien_trapper",  //char      *entityName; -    { "models/buildables/trapper/trapper.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -15, -15, -15 },     //vec3_t    mins; -    { 15, 15, 15 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce;      TRAPPER_BP,            //int       buildPoints; -    ( 1 << S2 )|( 1 << S3 ), //int  stages //NEEDS ADV BUILDER SO S2 AND UP +    ( 1 << S2 )|( 1 << S3 ), //int     stages; //NEEDS ADV BUILDER SO S2 AND UP      TRAPPER_HEALTH,        //int       health;      TRAPPER_REGEN,         //int       regenRate;      TRAPPER_SPLASHDAMAGE,  //int       splashDamage;      TRAPPER_SPLASHRADIUS,  //int       splashRadius;      MOD_ASPAWN,            //int       meansOfDeath; -    BIT_ALIENS,            //int       team; -    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ),    //weapon_t  buildWeapon; +    TEAM_ALIENS,           //int       team; +    ( 1 << WP_ABUILD2 ),   //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim;      100,                   //int       nextthink;      TRAPPER_BT,            //int       buildTime; @@ -253,104 +208,98 @@ buildableAttributes_t bg_buildableList[ ] =      TRAPPER_CREEPSIZE,     //int       creepSize;      qfalse,                //qboolean  dccTest;      qtrue,                 //qboolean  transparentTest; -    qfalse,                 //qboolean  reactorTest; -    qfalse,                //qboolean  replacable; +    qfalse,                //qboolean  uniqueTest; +    TRAPPER_VALUE,         //int       value;    },    { -    BA_A_OVERMIND,         //int       buildNum; -    "overmind",            //char      *buildName; -    "Overmind",            //char      *humanName; -    "team_alien_overmind", //char      *entityName; -    { "models/buildables/overmind/overmind.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -45, -45, -15 },     //vec3_t    mins; -    { 45, 45, 95 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset; +    BA_A_BOOSTER,          //int       number; +    "booster",             //char      *name; +    "Booster",             //char      *humanName; +    "Laces the attacks of any alien that touches it with a poison " +      "that will gradually deal damage to any humans exposed to it. " +      "The booster also increases the rate of health regeneration for " +      "any nearby aliens.", +    "team_alien_booster",  //char      *entityName;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce; -    OVERMIND_BP,           //int       buildPoints; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages -    OVERMIND_HEALTH,       //int       health; -    OVERMIND_REGEN,        //int       regenRate; -    OVERMIND_SPLASHDAMAGE, //int       splashDamage; -    OVERMIND_SPLASHRADIUS, //int       splashRadius; +    BOOSTER_BP,            //int       buildPoints; +    ( 1 << S2 )|( 1 << S3 ), //int     stages; +    BOOSTER_HEALTH,        //int       health; +    BOOSTER_REGEN,         //int       regenRate; +    BOOSTER_SPLASHDAMAGE,  //int       splashDamage; +    BOOSTER_SPLASHRADIUS,  //int       splashRadius;      MOD_ASPAWN,            //int       meansOfDeath; -    BIT_ALIENS,            //int       team; -    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ),    //weapon_t  buildWeapon; +    TEAM_ALIENS,           //int       team; +    ( 1 << WP_ABUILD2 ),   //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim; -    OVERMIND_ATTACK_REPEAT,//int       nextthink; -    OVERMIND_BT,           //int       buildTime; +    100,                   //int       nextthink; +    BOOSTER_BT,            //int       buildTime;      qfalse,                //qboolean  usable;      0,                     //int       turretRange;      0,                     //int       turretFireSpeed;      WP_NONE,               //weapon_t  turretProjType; -    0.95f,                 //float     minNormal; +    0.707f,                //float     minNormal;      qfalse,                //qboolean  invertNormal; -    qfalse,                //qboolean  creepTest; -    OVERMIND_CREEPSIZE,    //int       creepSize; +    qtrue,                 //qboolean  creepTest; +    BOOSTER_CREEPSIZE,     //int       creepSize;      qfalse,                //qboolean  dccTest; -    qfalse,                //qboolean  transparentTest; -    qtrue,                 //qboolean  reactorTest; -    qtrue,                 //qboolean  replacable; +    qtrue,                 //qboolean  transparentTest; +    qfalse,                //qboolean  uniqueTest; +    BOOSTER_VALUE,         //int       value;    },    { -    BA_A_HOVEL,            //int       buildNum; -    "hovel",               //char      *buildName; -    "Hovel",               //char      *humanName; -    "team_alien_hovel",    //char      *entityName; -    { "models/buildables/hovel/hovel.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -50, -50, -20 },     //vec3_t    mins; -    { 50, 50, 20 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset; +    BA_A_HIVE,             //int       number; +    "hive",                //char      *name; +    "Hive",                //char      *humanName; +    "Houses millions of tiny insectoid aliens. When a human " +      "approaches this structure, the insectoids attack.", +    "team_alien_hive",     //char      *entityName;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce; -    HOVEL_BP,              //int       buildPoints; -    ( 1 << S3 ),           //int  stages -    HOVEL_HEALTH,          //int       health; -    HOVEL_REGEN,           //int       regenRate; -    HOVEL_SPLASHDAMAGE,    //int       splashDamage; -    HOVEL_SPLASHRADIUS,    //int       splashRadius; +    HIVE_BP,               //int       buildPoints; +    ( 1 << S3 ),           //int       stages; +    HIVE_HEALTH,           //int       health; +    HIVE_REGEN,            //int       regenRate; +    HIVE_SPLASHDAMAGE,     //int       splashDamage; +    HIVE_SPLASHRADIUS,     //int       splashRadius;      MOD_ASPAWN,            //int       meansOfDeath; -    BIT_ALIENS,            //int       team; -    ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ),    //weapon_t  buildWeapon; +    TEAM_ALIENS,           //int       team; +    ( 1 << WP_ABUILD2 ),   //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim; -    150,                   //int       nextthink; -    HOVEL_BT,              //int       buildTime; -    qtrue,                 //qboolean  usable; +    500,                   //int       nextthink; +    HIVE_BT,               //int       buildTime; +    qfalse,                //qboolean  usable;      0,                     //int       turretRange;      0,                     //int       turretFireSpeed; -    WP_NONE,               //weapon_t  turretProjType; -    0.95f,                 //float     minNormal; -    qfalse,                //qboolean  invertNormal; +    WP_HIVE,               //weapon_t  turretProjType; +    0.0f,                  //float     minNormal; +    qtrue,                 //qboolean  invertNormal;      qtrue,                 //qboolean  creepTest; -    HOVEL_CREEPSIZE,       //int       creepSize; +    HIVE_CREEPSIZE,        //int       creepSize;      qfalse,                //qboolean  dccTest;      qfalse,                //qboolean  transparentTest; -    qtrue,                 //qboolean  reactorTest; -    qfalse,                //qboolean  replacable; +    qfalse,                //qboolean  uniqueTest; +    HIVE_VALUE,            //int       value;    },    { -    BA_H_SPAWN,            //int       buildNum; -    "telenode",            //char      *buildName; +    BA_H_SPAWN,            //int       number; +    "telenode",            //char      *name;      "Telenode",            //char      *humanName; +    "The most basic human structure. It provides a means for humans " +      "to enter the battle arena. Without any of these the humans " +      "cannot spawn and defeat is imminent.",      "team_human_spawn",    //char      *entityName; -    { "models/buildables/telenode/telenode.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -40, -40, -4 },      //vec3_t    mins; -    { 40, 40, 4 },         //vec3_t    maxs; -    0.0f,                  //float     zOffset;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce;      HSPAWN_BP,             //int       buildPoints; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      HSPAWN_HEALTH,         //int       health;      0,                     //int       regenRate;      HSPAWN_SPLASHDAMAGE,   //int       splashDamage;      HSPAWN_SPLASHRADIUS,   //int       splashRadius;      MOD_HSPAWN,            //int       meansOfDeath; -    BIT_HUMANS,            //int       team; -    ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ),    //weapon_t  buildWeapon; +    TEAM_HUMANS,           //int       team; +    ( 1 << WP_HBUILD ),    //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim;      100,                   //int       nextthink;      HSPAWN_BT,             //int       buildTime; @@ -364,69 +313,28 @@ buildableAttributes_t bg_buildableList[ ] =      0,                     //int       creepSize;      qfalse,                //qboolean  dccTest;      qtrue,                 //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qfalse,                //qboolean  replacable; -  }, -  { -    BA_H_MEDISTAT,         //int       buildNum; -    "medistat",            //char      *buildName; -    "Medistation",         //char      *humanName; -    "team_human_medistat", //char      *entityName; -    { "models/buildables/medistat/medistat.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -35, -35, -7 },      //vec3_t    mins; -    { 35, 35, 7 },         //vec3_t    maxs; -    0.0f,                  //float     zOffset; -    TR_GRAVITY,            //trType_t  traj; -    0.0,                   //float     bounce; -    MEDISTAT_BP,           //int       buildPoints; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages -    MEDISTAT_HEALTH,       //int       health; -    0,                     //int       regenRate; -    MEDISTAT_SPLASHDAMAGE, //int       splashDamage; -    MEDISTAT_SPLASHRADIUS, //int       splashRadius; -    MOD_HSPAWN,            //int       meansOfDeath; -    BIT_HUMANS,            //int       team; -    ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ),    //weapon_t  buildWeapon; -    BANIM_IDLE1,           //int       idleAnim; -    100,                   //int       nextthink; -    MEDISTAT_BT,           //int       buildTime; -    qfalse,                //qboolean  usable; -    0,                     //int       turretRange; -    0,                     //int       turretFireSpeed; -    WP_NONE,               //weapon_t  turretProjType; -    0.95f,                 //float     minNormal; -    qfalse,                //qboolean  invertNormal; -    qfalse,                //qboolean  creepTest; -    0,                     //int       creepSize; -    qfalse,                //qboolean  dccTest; -    qtrue,                 //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qtrue,                 //qboolean  replacable; +    qfalse,                //qboolean  uniqueTest; +    HSPAWN_VALUE,          //int       value;    },    { -    BA_H_MGTURRET,         //int       buildNum; -    "mgturret",            //char      *buildName; +    BA_H_MGTURRET,         //int       number; +    "mgturret",            //char      *name;      "Machinegun Turret",   //char      *humanName; +    "Automated base defense that is effective against large targets " +      "but slow to begin firing. Should always be " +      "backed up by physical support.",      "team_human_mgturret", //char      *entityName; -    { "models/buildables/mgturret/turret_base.md3", -      "models/buildables/mgturret/turret_barrel.md3", -      "models/buildables/mgturret/turret_top.md3", 0 }, -    1.0f,                  //float     modelScale; -    { -25, -25, -20 },     //vec3_t    mins; -    { 25, 25, 20 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce;      MGTURRET_BP,           //int       buildPoints; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      MGTURRET_HEALTH,       //int       health;      0,                     //int       regenRate;      MGTURRET_SPLASHDAMAGE, //int       splashDamage;      MGTURRET_SPLASHRADIUS, //int       splashRadius;      MOD_HSPAWN,            //int       meansOfDeath; -    BIT_HUMANS,            //int       team; -    ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ),   //weapon_t  buildWeapon; +    TEAM_HUMANS,           //int       team; +    ( 1 << WP_HBUILD ),    //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim;      50,                    //int       nextthink;      MGTURRET_BT,           //int       buildTime; @@ -440,30 +348,28 @@ buildableAttributes_t bg_buildableList[ ] =      0,                     //int       creepSize;      qfalse,                //qboolean  dccTest;      qtrue,                 //qboolean  transparentTest; -    qfalse,                 //qboolean  reactorTest; -    qfalse,                //qboolean  replacable; +    qfalse,                //qboolean  uniqueTest; +    MGTURRET_VALUE,        //int       value;    },    { -    BA_H_TESLAGEN,         //int       buildNum; -    "tesla",               //char      *buildName; +    BA_H_TESLAGEN,         //int       number; +    "tesla",               //char      *name;      "Tesla Generator",     //char      *humanName; +    "A structure equipped with a strong electrical attack that fires " +      "instantly and always hits its target. It is effective against smaller " +      "aliens and for consolidating basic defense.",      "team_human_tesla",    //char      *entityName; -    { "models/buildables/tesla/tesla.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -22, -22, -40 },     //vec3_t    mins; -    { 22, 22, 40 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce;      TESLAGEN_BP,           //int       buildPoints; -    ( 1 << S3 ),           //int       stages +    ( 1 << S3 ),           //int       stages;      TESLAGEN_HEALTH,       //int       health;      0,                     //int       regenRate;      TESLAGEN_SPLASHDAMAGE, //int       splashDamage;      TESLAGEN_SPLASHRADIUS, //int       splashRadius;      MOD_HSPAWN,            //int       meansOfDeath; -    BIT_HUMANS,            //int       team; -    ( 1 << WP_HBUILD2 ),   //weapon_t  buildWeapon; +    TEAM_HUMANS,           //int       team; +    ( 1 << WP_HBUILD ),    //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim;      150,                   //int       nextthink;      TESLAGEN_BT,           //int       buildTime; @@ -475,32 +381,65 @@ buildableAttributes_t bg_buildableList[ ] =      qfalse,                //qboolean  invertNormal;      qfalse,                //qboolean  creepTest;      0,                     //int       creepSize; -    qtrue,                 //qboolean  dccTest; +    qfalse,                //qboolean  dccTest;      qtrue,                 //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qfalse,                //qboolean  replacable; +    qfalse,                //qboolean  uniqueTest; +    TESLAGEN_VALUE,        //int       value;    },    { -    BA_H_DCC,              //int       buildNum; -    "dcc",                 //char      *buildName; +    BA_H_ARMOURY,          //int       number; +    "arm",                 //char      *name; +    "Armoury",             //char      *humanName; +    "An essential part of the human base, providing a means " +      "to upgrade the basic human equipment. A range of upgrades " +      "and weapons are available for sale from the armoury.", +    "team_human_armoury",  //char      *entityName; +    TR_GRAVITY,            //trType_t  traj; +    0.0,                   //float     bounce; +    ARMOURY_BP,            //int       buildPoints; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages; +    ARMOURY_HEALTH,        //int       health; +    0,                     //int       regenRate; +    ARMOURY_SPLASHDAMAGE,  //int       splashDamage; +    ARMOURY_SPLASHRADIUS,  //int       splashRadius; +    MOD_HSPAWN,            //int       meansOfDeath; +    TEAM_HUMANS,           //int       team; +    ( 1 << WP_HBUILD ),    //weapon_t  buildWeapon; +    BANIM_IDLE1,           //int       idleAnim; +    100,                   //int       nextthink; +    ARMOURY_BT,            //int       buildTime; +    qtrue,                 //qboolean  usable; +    0,                     //int       turretRange; +    0,                     //int       turretFireSpeed; +    WP_NONE,               //weapon_t  turretProjType; +    0.95f,                 //float     minNormal; +    qfalse,                //qboolean  invertNormal; +    qfalse,                //qboolean  creepTest; +    0,                     //int       creepSize; +    qfalse,                //qboolean  dccTest; +    qfalse,                //qboolean  transparentTest; +    qfalse,                //qboolean  uniqueTest; +    ARMOURY_VALUE,         //int       value; +  }, +  { +    BA_H_DCC,              //int       number; +    "dcc",                 //char      *name;      "Defence Computer",    //char      *humanName; +    "A structure that enables self-repair functionality in " +      "human structures. Each Defence Computer built increases " +      "repair rate slightly.",      "team_human_dcc",      //char      *entityName; -    { "models/buildables/dcc/dcc.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -35, -35, -13 },     //vec3_t    mins; -    { 35, 35, 47 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce;      DC_BP,                 //int       buildPoints; -    ( 1 << S2 )|( 1 << S3 ), //int       stages +    ( 1 << S2 )|( 1 << S3 ), //int     stages;      DC_HEALTH,             //int       health;      0,                     //int       regenRate;      DC_SPLASHDAMAGE,       //int       splashDamage;      DC_SPLASHRADIUS,       //int       splashRadius;      MOD_HSPAWN,            //int       meansOfDeath; -    BIT_HUMANS,            //int       team; -    ( 1 << WP_HBUILD2 ),   //weapon_t  buildWeapon; +    TEAM_HUMANS,           //int       team; +    ( 1 << WP_HBUILD ),    //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim;      100,                   //int       nextthink;      DC_BT,                 //int       buildTime; @@ -514,34 +453,33 @@ buildableAttributes_t bg_buildableList[ ] =      0,                     //int       creepSize;      qfalse,                //qboolean  dccTest;      qfalse,                //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qtrue,                 //qboolean  replacable; +    qfalse,                //qboolean  uniqueTest; +    DC_VALUE,              //int       value;    },    { -    BA_H_ARMOURY,          //int       buildNum; -    "arm",                 //char      *buildName; -    "Armoury",             //char      *humanName; -    "team_human_armoury",  //char      *entityName; -    { "models/buildables/arm/arm.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -40, -40, -13 },     //vec3_t    mins; -    { 40, 40, 50 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset; +    BA_H_MEDISTAT,         //int       number; +    "medistat",            //char      *name; +    "Medistation",         //char      *humanName; +    "A structure that automatically restores " +      "the health and stamina of any human that stands on it. " +      "It may only be used by one person at a time. This structure " +      "also issues medkits.", +    "team_human_medistat", //char      *entityName;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce; -    ARMOURY_BP,            //int       buildPoints; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages -    ARMOURY_HEALTH,        //int       health; +    MEDISTAT_BP,           //int       buildPoints; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages; +    MEDISTAT_HEALTH,       //int       health;      0,                     //int       regenRate; -    ARMOURY_SPLASHDAMAGE,  //int       splashDamage; -    ARMOURY_SPLASHRADIUS,  //int       splashRadius; +    MEDISTAT_SPLASHDAMAGE, //int       splashDamage; +    MEDISTAT_SPLASHRADIUS, //int       splashRadius;      MOD_HSPAWN,            //int       meansOfDeath; -    BIT_HUMANS,            //int       team; -    ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ),    //weapon_t  buildWeapon; +    TEAM_HUMANS,           //int       team; +    ( 1 << WP_HBUILD ),    //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim;      100,                   //int       nextthink; -    ARMOURY_BT,            //int       buildTime; -    qtrue,                 //qboolean  usable; +    MEDISTAT_BT,           //int       buildTime; +    qfalse,                //qboolean  usable;      0,                     //int       turretRange;      0,                     //int       turretFireSpeed;      WP_NONE,               //weapon_t  turretProjType; @@ -550,33 +488,31 @@ buildableAttributes_t bg_buildableList[ ] =      qfalse,                //qboolean  creepTest;      0,                     //int       creepSize;      qfalse,                //qboolean  dccTest; -    qfalse,                //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qtrue,                 //qboolean  replacable; +    qtrue,                 //qboolean  transparentTest; +    qfalse,                //qboolean  uniqueTest; +    MEDISTAT_VALUE,        //int       value;    },    { -    BA_H_REACTOR,          //int       buildNum; -    "reactor",             //char      *buildName; +    BA_H_REACTOR,          //int       number; +    "reactor",             //char      *name;      "Reactor",             //char      *humanName; +    "All structures except the telenode rely on a reactor to operate. " +      "The reactor provides power for all the human structures either " +      "directly or via repeaters. Only one reactor can be built at a time.",      "team_human_reactor",  //char      *entityName; -    { "models/buildables/reactor/reactor.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -50, -50, -15 },     //vec3_t    mins; -    { 50, 50, 95 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce;      REACTOR_BP,            //int       buildPoints; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      REACTOR_HEALTH,        //int       health;      0,                     //int       regenRate;      REACTOR_SPLASHDAMAGE,  //int       splashDamage;      REACTOR_SPLASHRADIUS,  //int       splashRadius;      MOD_HSPAWN,            //int       meansOfDeath; -    BIT_HUMANS,            //int       team; -    ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ),    //weapon_t  buildWeapon; +    TEAM_HUMANS,           //int       team; +    ( 1 << WP_HBUILD ),    //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim; -    REACTOR_ATTACK_REPEAT, //int       nextthink; +    REACTOR_ATTACK_DCC_REPEAT, //int   nextthink;      REACTOR_BT,            //int       buildTime;      qtrue,                 //qboolean  usable;      0,                     //int       turretRange; @@ -588,30 +524,28 @@ buildableAttributes_t bg_buildableList[ ] =      0,                     //int       creepSize;      qfalse,                //qboolean  dccTest;      qfalse,                //qboolean  transparentTest; -    qtrue,                 //qboolean  reactorTest; -    qtrue,                 //qboolean  replacable; +    qtrue,                 //qboolean  uniqueTest; +    REACTOR_VALUE,         //int       value;    },    { -    BA_H_REPEATER,         //int       buildNum; -    "repeater",            //char      *buildName; +    BA_H_REPEATER,         //int       number; +    "repeater",            //char      *name;      "Repeater",            //char      *humanName; +    "A power distributor that transmits power from the reactor " +      "to remote locations, so that bases may be built far " +      "from the reactor.",      "team_human_repeater", //char      *entityName; -    { "models/buildables/repeater/repeater.md3", 0, 0, 0 }, -    1.0f,                  //float     modelScale; -    { -15, -15, -15 },     //vec3_t    mins; -    { 15, 15, 25 },        //vec3_t    maxs; -    0.0f,                  //float     zOffset;      TR_GRAVITY,            //trType_t  traj;      0.0,                   //float     bounce;      REPEATER_BP,           //int       buildPoints; -    ( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      REPEATER_HEALTH,       //int       health;      0,                     //int       regenRate;      REPEATER_SPLASHDAMAGE, //int       splashDamage;      REPEATER_SPLASHRADIUS, //int       splashRadius;      MOD_HSPAWN,            //int       meansOfDeath; -    BIT_HUMANS,            //int       team; -    ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ),    //weapon_t  buildWeapon; +    TEAM_HUMANS,           //int       team; +    ( 1 << WP_HBUILD ),    //weapon_t  buildWeapon;      BANIM_IDLE1,           //int       idleAnim;      100,                   //int       nextthink;      REPEATER_BT,           //int       buildTime; @@ -625,757 +559,115 @@ buildableAttributes_t bg_buildableList[ ] =      0,                     //int       creepSize;      qfalse,                //qboolean  dccTest;      qfalse,                //qboolean  transparentTest; -    qfalse,                //qboolean  reactorTest; -    qtrue,                 //qboolean  replacable; +    qfalse,                //qboolean  uniqueTest; +    REPEATER_VALUE,        //int       value;    }  }; -int   bg_numBuildables = sizeof( bg_buildableList ) / sizeof( bg_buildableList[ 0 ] ); +size_t bg_numBuildables = ARRAY_LEN( bg_buildableList ); -//separate from bg_buildableList to work around char struct init bug -buildableAttributeOverrides_t bg_buildableOverrideList[ BA_NUM_BUILDABLES ]; +static const buildableAttributes_t nullBuildable = { 0 };  /*  ============== -BG_FindBuildNumForName +BG_BuildableByName  ==============  */ -int BG_FindBuildNumForName( char *name ) +const buildableAttributes_t *BG_BuildableByName( const char *name )  {    int i;    for( i = 0; i < bg_numBuildables; i++ )    { -    if( !Q_stricmp( bg_buildableList[ i ].buildName, name ) ) -      return bg_buildableList[ i ].buildNum; +    if( !Q_stricmp( bg_buildableList[ i ].name, name ) ) +      return &bg_buildableList[ i ];    } -  //wimp out -  return BA_NONE; +  return &nullBuildable;  }  /*  ============== -BG_FindBuildNumForEntityName +BG_BuildableByEntityName  ==============  */ -int BG_FindBuildNumForEntityName( char *name ) +const buildableAttributes_t *BG_BuildableByEntityName( const char *name )  {    int i;    for( i = 0; i < bg_numBuildables; i++ )    {      if( !Q_stricmp( bg_buildableList[ i ].entityName, name ) ) -      return bg_buildableList[ i ].buildNum; +      return &bg_buildableList[ i ];    } -  //wimp out -  return BA_NONE; +  return &nullBuildable;  }  /*  ============== -BG_FindNameForBuildNum +BG_Buildable  ==============  */ -char *BG_FindNameForBuildable( int bclass ) +const buildableAttributes_t *BG_Buildable( buildable_t buildable )  { -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -      return bg_buildableList[ i ].buildName; -  } - -  //wimp out -  return 0; -} - -/* -============== -BG_FindHumanNameForBuildNum -============== -*/ -char *BG_FindHumanNameForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -      return bg_buildableList[ i ].humanName; -  } - -  //wimp out -  return 0; +  return ( buildable > BA_NONE && buildable < BA_NUM_BUILDABLES ) ? +    &bg_buildableList[ buildable - 1 ] : &nullBuildable;  }  /*  ============== -BG_FindEntityNameForBuildNum +BG_BuildableAllowedInStage  ==============  */ -char *BG_FindEntityNameForBuildable( int bclass ) +qboolean BG_BuildableAllowedInStage( buildable_t buildable, +                                     stage_t stage )  { -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -      return bg_buildableList[ i ].entityName; -  } +  int stages = BG_Buildable( buildable )->stages; -  //wimp out -  return 0; +  if( stages & ( 1 << stage ) ) +    return qtrue; +  else +    return qfalse;  } -/* -============== -BG_FindModelsForBuildNum -============== -*/ -char *BG_FindModelsForBuildable( int bclass, int modelNum ) -{ -  int i; - -  if( bg_buildableOverrideList[ bclass ].models[ modelNum ][ 0 ] != 0 ) -    return bg_buildableOverrideList[ bclass ].models[ modelNum ]; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -      return bg_buildableList[ i ].models[ modelNum ]; -  } - -  //wimp out -  return 0; -} +static buildableConfig_t bg_buildableConfigList[ BA_NUM_BUILDABLES ];  /*  ============== -BG_FindModelScaleForBuildable +BG_BuildableConfig  ==============  */ -float BG_FindModelScaleForBuildable( int bclass ) +buildableConfig_t *BG_BuildableConfig( buildable_t buildable )  { -  int i; - -  if( bg_buildableOverrideList[ bclass ].modelScale != 0.0f ) -    return bg_buildableOverrideList[ bclass ].modelScale; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -      return bg_buildableList[ i ].modelScale; -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindModelScaleForBuildable( %d )\n", bclass ); -  return 1.0f; +  return &bg_buildableConfigList[ buildable ];  }  /*  ============== -BG_FindBBoxForBuildable +BG_BuildableBoundingBox  ==============  */ -void BG_FindBBoxForBuildable( int bclass, vec3_t mins, vec3_t maxs ) +void BG_BuildableBoundingBox( buildable_t buildable, +                              vec3_t mins, vec3_t maxs )  { -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      if( mins != NULL ) -      { -        VectorCopy( bg_buildableList[ i ].mins, mins ); - -        if( VectorLength( bg_buildableOverrideList[ bclass ].mins ) ) -          VectorCopy( bg_buildableOverrideList[ bclass ].mins, mins ); -      } - -      if( maxs != NULL ) -      { -        VectorCopy( bg_buildableList[ i ].maxs, maxs ); - -        if( VectorLength( bg_buildableOverrideList[ bclass ].maxs ) ) -          VectorCopy( bg_buildableOverrideList[ bclass ].maxs, maxs ); -      } - -      return; -    } -  } +  buildableConfig_t *buildableConfig = BG_BuildableConfig( buildable );    if( mins != NULL ) -    VectorCopy( bg_buildableList[ 0 ].mins, mins ); +    VectorCopy( buildableConfig->mins, mins );    if( maxs != NULL ) -    VectorCopy( bg_buildableList[ 0 ].maxs, maxs ); -} - -/* -============== -BG_FindZOffsetForBuildable -============== -*/ -float BG_FindZOffsetForBuildable( int bclass ) -{ -  int i; - -  if( bg_buildableOverrideList[ bclass ].zOffset != 0.0f ) -    return bg_buildableOverrideList[ bclass ].zOffset; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].zOffset; -    } -  } - -  return 0.0f; -} - -/* -============== -BG_FindTrajectoryForBuildable -============== -*/ -trType_t BG_FindTrajectoryForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].traj; -    } -  } - -  return TR_GRAVITY; -} - -/* -============== -BG_FindBounceForBuildable -============== -*/ -float BG_FindBounceForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].bounce; -    } -  } - -  return 0.0; -} - -/* -============== -BG_FindBuildPointsForBuildable -============== -*/ -int BG_FindBuildPointsForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].buildPoints; -    } -  } - -  return 1000; -} - -/* -============== -BG_FindStagesForBuildable -============== -*/ -qboolean BG_FindStagesForBuildable( int bclass, stage_t stage ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      if( bg_buildableList[ i ].stages & ( 1 << stage ) ) -        return qtrue; -      else -        return qfalse; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindHealthForBuildable -============== -*/ -int BG_FindHealthForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].health; -    } -  } - -  return 1000; -} - -/* -============== -BG_FindRegenRateForBuildable -============== -*/ -int BG_FindRegenRateForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].regenRate; -    } -  } - -  return 0; -} - -/* -============== -BG_FindSplashDamageForBuildable -============== -*/ -int BG_FindSplashDamageForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].splashDamage; -    } -  } - -  return 50; -} - -/* -============== -BG_FindSplashRadiusForBuildable -============== -*/ -int BG_FindSplashRadiusForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].splashRadius; -    } -  } - -  return 200; -} - -/* -============== -BG_FindMODForBuildable -============== -*/ -int BG_FindMODForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].meansOfDeath; -    } -  } - -  return MOD_UNKNOWN; -} - -/* -============== -BG_FindTeamForBuildable -============== -*/ -int BG_FindTeamForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].team; -    } -  } - -  return BIT_NONE; -} - -/* -============== -BG_FindBuildWeaponForBuildable -============== -*/ -weapon_t BG_FindBuildWeaponForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].buildWeapon; -    } -  } - -  return WP_NONE; -} - -/* -============== -BG_FindAnimForBuildable -============== -*/ -int BG_FindAnimForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].idleAnim; -    } -  } - -  return BANIM_IDLE1; -} - -/* -============== -BG_FindNextThinkForBuildable -============== -*/ -int BG_FindNextThinkForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].nextthink; -    } -  } - -  return 100; -} - -/* -============== -BG_FindBuildTimeForBuildable -============== -*/ -int BG_FindBuildTimeForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].buildTime; -    } -  } - -  return 10000; -} - -/* -============== -BG_FindUsableForBuildable -============== -*/ -qboolean BG_FindUsableForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].usable; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindFireSpeedForBuildable -============== -*/ -int BG_FindFireSpeedForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].turretFireSpeed; -    } -  } - -  return 1000; -} - -/* -============== -BG_FindRangeForBuildable -============== -*/ -int BG_FindRangeForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].turretRange; -    } -  } - -  return 1000; -} - -/* -============== -BG_FindProjTypeForBuildable -============== -*/ -weapon_t BG_FindProjTypeForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].turretProjType; -    } -  } - -  return WP_NONE; -} - -/* -============== -BG_FindMinNormalForBuildable -============== -*/ -float BG_FindMinNormalForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].minNormal; -    } -  } - -  return 0.707f; -} - -/* -============== -BG_FindInvertNormalForBuildable -============== -*/ -qboolean BG_FindInvertNormalForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].invertNormal; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindCreepTestForBuildable -============== -*/ -int BG_FindCreepTestForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].creepTest; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindCreepSizeForBuildable -============== -*/ -int BG_FindCreepSizeForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].creepSize; -    } -  } - -  return CREEP_BASESIZE; -} - -/* -============== -BG_FindDCCTestForBuildable -============== -*/ -int BG_FindDCCTestForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].dccTest; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindUniqueTestForBuildable -============== -*/ -int BG_FindUniqueTestForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].reactorTest; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindReplaceableTestForBuildable -============== -*/ -qboolean BG_FindReplaceableTestForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].replaceable; -    } -  } -  return qfalse; -} - -/* -============== -BG_FindOverrideForBuildable -============== -*/ -static buildableAttributeOverrides_t *BG_FindOverrideForBuildable( int bclass ) -{ -  return &bg_buildableOverrideList[ bclass ]; -} - -/* -============== -BG_FindTransparentTestForBuildable -============== -*/ -qboolean BG_FindTransparentTestForBuildable( int bclass ) -{ -  int i; - -  for( i = 0; i < bg_numBuildables; i++ ) -  { -    if( bg_buildableList[ i ].buildNum == bclass ) -    { -      return bg_buildableList[ i ].transparentTest; -    } -  } -  return qfalse;  +    VectorCopy( buildableConfig->maxs, maxs );  }  /*  ======================  BG_ParseBuildableFile -Parses a configuration file describing a builable +Parses a configuration file describing a buildable  ======================  */ -static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeOverrides_t *bao ) +static qboolean BG_ParseBuildableFile( const char *filename, buildableConfig_t *bc )  {    char          *text_p;    int           i; @@ -1384,12 +676,24 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO    char          text[ 20000 ];    fileHandle_t  f;    float         scale; +  int           defined = 0; +  enum +  { +      MODEL         = 1 << 0, +      MODELSCALE    = 1 << 1, +      MINS          = 1 << 2, +      MAXS          = 1 << 3, +      ZOFFSET       = 1 << 4 +  };    // load the file    len = trap_FS_FOpenFile( filename, &f, FS_READ );    if( len < 0 ) +  { +    Com_Printf( S_COLOR_RED "ERROR: Buildable file %s doesn't exist\n", filename );      return qfalse; +  }    if( len == 0 || len >= sizeof( text ) - 1 )    { @@ -1436,8 +740,9 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO        if( !token )          break; -      Q_strncpyz( bao->models[ index ], token, sizeof( bao->models[ 0 ] ) ); +      Q_strncpyz( bc->models[ index ], token, sizeof( bc->models[ 0 ] ) ); +      defined |= MODEL;        continue;      }      else if( !Q_stricmp( token, "modelScale" ) ) @@ -1451,8 +756,9 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO        if( scale < 0.0f )          scale = 0.0f; -      bao->modelScale = scale; +      bc->modelScale = scale; +      defined |= MODELSCALE;        continue;      }      else if( !Q_stricmp( token, "mins" ) ) @@ -1463,9 +769,10 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO          if( !token )            break; -        bao->mins[ i ] = atof( token ); +        bc->mins[ i ] = atof( token );        } +      defined |= MINS;        continue;      }      else if( !Q_stricmp( token, "maxs" ) ) @@ -1476,9 +783,10 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO          if( !token )            break; -        bao->maxs[ i ] = atof( token ); +        bc->maxs[ i ] = atof( token );        } +      defined |= MAXS;        continue;      }      else if( !Q_stricmp( token, "zOffset" ) ) @@ -1491,8 +799,9 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO        offset = atof( token ); -      bao->zOffset = offset; +      bc->zOffset = offset; +      defined |= ZOFFSET;        continue;      } @@ -1501,59 +810,62 @@ static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeO      return qfalse;    } +  if(      !( defined & MODEL      ) )  token = "model"; +  else if( !( defined & MODELSCALE ) )  token = "modelScale"; +  else if( !( defined & MINS       ) )  token = "mins"; +  else if( !( defined & MAXS       ) )  token = "maxs"; +  else if( !( defined & ZOFFSET    ) )  token = "zOffset"; +  else                                  token = ""; + +  if( strlen( token ) > 0 ) +  { +      Com_Printf( S_COLOR_RED "ERROR: %s not defined in %s\n", +                  token, filename ); +      return qfalse; +  } +    return qtrue;  }  /*  =============== -BG_InitBuildableOverrides - -Set any overrides specfied by file +BG_InitBuildableConfigs  ===============  */ -void BG_InitBuildableOverrides( void ) +void BG_InitBuildableConfigs( void )  { -  int                           i; -  buildableAttributeOverrides_t *bao; +  int               i; +  buildableConfig_t *bc;    for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ )    { -    bao = BG_FindOverrideForBuildable( i ); +    bc = BG_BuildableConfig( i ); +    Com_Memset( bc, 0, sizeof( buildableConfig_t ) ); -    BG_ParseBuildableFile( va( "overrides/buildables/%s.cfg", BG_FindNameForBuildable( i ) ), bao ); +    BG_ParseBuildableFile( va( "configs/buildables/%s.cfg", +                               BG_Buildable( i )->name ), bc );    }  }  //////////////////////////////////////////////////////////////////////////////// -classAttributes_t bg_classList[ ] = +static const classAttributes_t bg_classList[ ] =  {    { -    PCL_NONE,                                       //int     classnum; -    "spectator",                                    //char    *className; -    "Spectator",                                    //char    *humanName; -    "",                                             //char    *modelname; -    1.0f,                                           //float   modelScale; -    "",                                             //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "",                                             //char    *hudname; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int  stages -    { -15, -15, -15 },                              //vec3_t  mins; -    { 15, 15, 15 },                                 //vec3_t  maxs; -    { 15, 15, 15 },                                 //vec3_t  crouchmaxs; -    { -15, -15, -15 },                              //vec3_t  deadmins; -    { 15, 15, 15 },                                 //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    0, 0,                                           //int     viewheight, crouchviewheight; +    PCL_NONE,                                       //int     number; +    "spectator",                                    //char    *name; +    "", +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int     stages;      0,                                              //int     health;      0.0f,                                           //float   fallDamage; -    0,                                              //int     regenRate; +    0.0f,                                           //float   regenRate;      0,                                              //int     abilities; -    WP_NONE,                                        //weapon_t  startWeapon +    WP_NONE,                                        //weapon_t startWeapon;      0.0f,                                           //float   buildDist;      90,                                             //int     fov;      0.000f,                                         //float   bob;      1.0f,                                           //float   bobCycle; +    0.0f,                                           //float   landBob;      0,                                              //int     steptime;      600,                                            //float   speed;      10.0f,                                          //float   acceleration; @@ -1567,31 +879,21 @@ classAttributes_t bg_classList[ ] =      0                                               //int     value;    },    { -    PCL_ALIEN_BUILDER0,                             //int     classnum; -    "builder",                                      //char    *className; -    "Builder",                                      //char    *humanName; -    "builder",                                      //char    *modelname; -    1.0f,                                           //float   modelScale; -    "default",                                      //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "alien_builder_hud",                            //char    *hudname; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int  stages -    { -15, -15, -20 },                              //vec3_t  mins; -    { 15, 15, 20 },                                 //vec3_t  maxs; -    { 15, 15, 20 },                                 //vec3_t  crouchmaxs; -    { -15, -15, -4 },                               //vec3_t  deadmins; -    { 15, 15, 4 },                                  //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    0, 0,                                           //int     viewheight, crouchviewheight; +    PCL_ALIEN_BUILDER0,                             //int     number; +    "builder",                                      //char    *name; +    "Responsible for building and maintaining all the alien structures. " +      "Has a weak melee slash attack.", +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int     stages;      ABUILDER_HEALTH,                                //int     health;      0.2f,                                           //float   fallDamage; -    ABUILDER_REGEN,                                 //int     regenRate; -    SCA_TAKESFALLDAMAGE|SCA_FOVWARPS|SCA_ALIENSENSE,//int     abilities; -    WP_ABUILD,                                      //weapon_t  startWeapon +    ABUILDER_REGEN,                                 //float   regenRate; +    SCA_TAKESFALLDAMAGE|SCA_FOVWARPS|SCA_ALIENSENSE, //int    abilities; +    WP_ABUILD,                                      //weapon_t startWeapon;      95.0f,                                          //float   buildDist; -    80,                                             //int     fov; +    110,                                            //int     fov;      0.001f,                                         //float   bob;      2.0f,                                           //float   bobCycle; +    4.5f,                                           //float   landBob;      150,                                            //int     steptime;      ABUILDER_SPEED,                                 //float   speed;      10.0f,                                          //float   acceleration; @@ -1600,36 +902,27 @@ classAttributes_t bg_classList[ ] =      100.0f,                                         //float   stopSpeed;      195.0f,                                         //float   jumpMagnitude;      1.0f,                                           //float   knockbackScale; -    { PCL_ALIEN_BUILDER0_UPG, PCL_ALIEN_LEVEL0, PCL_NONE },       //int     children[ 3 ]; +    { PCL_ALIEN_BUILDER0_UPG, PCL_ALIEN_LEVEL0, PCL_NONE }, //int  children[ 3 ];      ABUILDER_COST,                                  //int     cost;      ABUILDER_VALUE                                  //int     value;    },    { -    PCL_ALIEN_BUILDER0_UPG,                         //int     classnum; -    "builderupg",                                   //char    *classname; -    "Advanced Builder",                             //char    *humanname; -    "builder",                                      //char    *modelname; -    1.0f,                                           //float   modelScale; -    "advanced",                                     //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "alien_builder_hud",                            //char    *hudname; -    ( 1 << S2 )|( 1 << S3 ),                        //int  stages -    { -20, -20, -20 },                              //vec3_t  mins; -    { 20, 20, 20 },                                 //vec3_t  maxs; -    { 20, 20, 20 },                                 //vec3_t  crouchmaxs; -    { -20, -20, -4 },                               //vec3_t  deadmins; -    { 20, 20, 4 },                                  //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    0, 0,                                           //int     viewheight, crouchviewheight; +    PCL_ALIEN_BUILDER0_UPG,                         //int     number; +    "builderupg",                                   //char    *name; +    "Similar to the base Granger, except that in addition to " +      "being able to build structures it has a spit attack " +      "that slows victims and the ability to crawl on walls.", +    ( 1 << S2 )|( 1 << S3 ),                        //int     stages;      ABUILDER_UPG_HEALTH,                            //int     health; -    0.0f,                                           //float   fallDamage; -    ABUILDER_UPG_REGEN,                             //int     regenRate; -    SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE,    //int     abilities; -    WP_ABUILD2,                                     //weapon_t  startWeapon +    0.2f,                                           //float   fallDamage; +    ABUILDER_UPG_REGEN,                             //float   regenRate; +    SCA_TAKESFALLDAMAGE|SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE, //int  abilities; +    WP_ABUILD2,                                     //weapon_t startWeapon;      105.0f,                                         //float   buildDist;      110,                                            //int     fov;      0.001f,                                         //float   bob;      2.0f,                                           //float   bobCycle; +    4.5f,                                           //float   landBob;      100,                                            //int     steptime;      ABUILDER_UPG_SPEED,                             //float   speed;      10.0f,                                          //float   acceleration; @@ -1643,32 +936,21 @@ classAttributes_t bg_classList[ ] =      ABUILDER_UPG_VALUE                              //int     value;    },    { -    PCL_ALIEN_LEVEL0,                               //int     classnum; -    "level0",                                       //char    *classname; -    "Soldier",                                      //char    *humanname; -    "jumper",                                       //char    *modelname; -    0.2f,                                           //float   modelScale; -    "default",                                      //char    *skinname; -    0.3f,                                           //float   shadowScale; -    "alien_general_hud",                            //char    *hudname; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int  stages -    { -15, -15, -15 },                              //vec3_t  mins; -    { 15, 15, 15 },                                 //vec3_t  maxs; -    { 15, 15, 15 },                                 //vec3_t  crouchmaxs; -    { -15, -15, -4 },                               //vec3_t  deadmins; -    { 15, 15, 4 },                                  //vec3_t  deadmaxs; -    -8.0f,                                          //float   zOffset -    0, 0,                                           //int     viewheight, crouchviewheight; +    PCL_ALIEN_LEVEL0,                               //int     number; +    "level0",                                       //char    *name; +    "Has a lethal reflexive bite and the ability to crawl on " +      "walls and ceilings.", +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int     stages;      LEVEL0_HEALTH,                                  //int     health;      0.0f,                                           //float   fallDamage; -    LEVEL0_REGEN,                                   //int     regenRate; -    SCA_WALLCLIMBER|SCA_NOWEAPONDRIFT| -      SCA_FOVWARPS|SCA_ALIENSENSE,                  //int     abilities; -    WP_ALEVEL0,                                     //weapon_t  startWeapon +    LEVEL0_REGEN,                                   //float   regenRate; +    SCA_WALLCLIMBER|SCA_FOVWARPS|SCA_ALIENSENSE,    //int     abilities; +    WP_ALEVEL0,                                     //weapon_t startWeapon;      0.0f,                                           //float   buildDist;      140,                                            //int     fov;      0.0f,                                           //float   bob;      2.5f,                                           //float   bobCycle; +    0.0f,                                           //float   landBob;      25,                                             //int     steptime;      LEVEL0_SPEED,                                   //float   speed;      10.0f,                                          //float   acceleration; @@ -1682,188 +964,138 @@ classAttributes_t bg_classList[ ] =      LEVEL0_VALUE                                    //int     value;    },    { -    PCL_ALIEN_LEVEL1,                               //int     classnum; -    "level1",                                       //char    *classname; -    "Hydra",                                        //char    *humanname; -    "spitter",                                      //char    *modelname; -    0.6f,                                           //float   modelScale; -    "default",                                      //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "alien_general_hud",                            //char    *hudname; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int  stages -    { -18, -18, -18 },                              //vec3_t  mins; -    { 18, 18, 18 },                                 //vec3_t  maxs; -    { 18, 18, 18 },                                 //vec3_t  crouchmaxs; -    { -18, -18, -4 },                               //vec3_t  deadmins; -    { 18, 18, 4 },                                  //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    0, 0,                                           //int     viewheight, crouchviewheight; +    PCL_ALIEN_LEVEL1,                               //int     number; +    "level1",                                       //char    *name; +    "A support class able to crawl on walls and ceilings. Its melee " +      "attack is most effective when combined with the ability to grab " +      "and hold its victims in place. Provides a weak healing aura " +      "that accelerates the healing rate of nearby aliens.", +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int     stages;      LEVEL1_HEALTH,                                  //int     health;      0.0f,                                           //float   fallDamage; -    LEVEL1_REGEN,                                   //int     regenRate; -    SCA_NOWEAPONDRIFT| -      SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE,  //int     abilities; -    WP_ALEVEL1,                                     //weapon_t  startWeapon +    LEVEL1_REGEN,                                   //float   regenRate; +    SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE,    //int     abilities; +    WP_ALEVEL1,                                     //weapon_t startWeapon;      0.0f,                                           //float   buildDist;      120,                                            //int     fov;      0.001f,                                         //float   bob;      1.8f,                                           //float   bobCycle; +    0.0f,                                           //float   landBob;      60,                                             //int     steptime;      LEVEL1_SPEED,                                   //float   speed;      10.0f,                                          //float   acceleration;      1.0f,                                           //float   airAcceleration;      6.0f,                                           //float   friction;      300.0f,                                         //float   stopSpeed; -    270.0f,                                         //float   jumpMagnitude; +    310.0f,                                         //float   jumpMagnitude;      1.2f,                                           //float   knockbackScale; -    { PCL_ALIEN_LEVEL2, PCL_ALIEN_LEVEL1_UPG, PCL_NONE },   //int     children[ 3 ]; -    LEVEL1_COST,                                     //int     cost; -    LEVEL1_VALUE                                     //int     value; +    { PCL_ALIEN_LEVEL2, PCL_ALIEN_LEVEL1_UPG, PCL_NONE }, //int  children[ 3 ]; +    LEVEL1_COST,                                    //int     cost; +    LEVEL1_VALUE                                    //int     value;    },    { -    PCL_ALIEN_LEVEL1_UPG,                           //int     classnum; -    "level1upg",                                    //char    *classname; -    "Hydra Upgrade",                                //char    *humanname; -    "spitter",                                      //char    *modelname; -    0.7f,                                           //float   modelScale; -    "blue",                                         //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "alien_general_hud",                            //char    *hudname; -    ( 1 << S2 )|( 1 << S3 ),                        //int  stages -    { -20, -20, -20 },                              //vec3_t  mins; -    { 20, 20, 20 },                                 //vec3_t  maxs; -    { 20, 20, 20 },                                 //vec3_t  crouchmaxs; -    { -20, -20, -4 },                               //vec3_t  deadmins; -    { 20, 20, 4 },                                  //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    0, 0,                                           //int     viewheight, crouchviewheight; +    PCL_ALIEN_LEVEL1_UPG,                           //int     number; +    "level1upg",                                    //char    *name; +    "In addition to the basic Basilisk abilities, the Advanced " +      "Basilisk sprays a poisonous gas which disorients any " +      "nearby humans. Has a strong healing aura that " +      "that accelerates the healing rate of nearby aliens.", +    ( 1 << S2 )|( 1 << S3 ),                        //int     stages;      LEVEL1_UPG_HEALTH,                              //int     health;      0.0f,                                           //float   fallDamage; -    LEVEL1_UPG_REGEN,                               //int     regenRate; -    SCA_NOWEAPONDRIFT|SCA_FOVWARPS| -      SCA_WALLCLIMBER|SCA_ALIENSENSE,               //int     abilities; -    WP_ALEVEL1_UPG,                                 //weapon_t  startWeapon +    LEVEL1_UPG_REGEN,                               //float   regenRate; +    SCA_FOVWARPS|SCA_WALLCLIMBER|SCA_ALIENSENSE,    //int     abilities; +    WP_ALEVEL1_UPG,                                 //weapon_t startWeapon;      0.0f,                                           //float   buildDist;      120,                                            //int     fov;      0.001f,                                         //float   bob;      1.8f,                                           //float   bobCycle; +    0.0f,                                           //float   landBob;      60,                                             //int     steptime;      LEVEL1_UPG_SPEED,                               //float   speed;      10.0f,                                          //float   acceleration;      1.0f,                                           //float   airAcceleration;      6.0f,                                           //float   friction;      300.0f,                                         //float   stopSpeed; -    270.0f,                                         //float   jumpMagnitude; +    310.0f,                                         //float   jumpMagnitude;      1.1f,                                           //float   knockbackScale;      { PCL_ALIEN_LEVEL2, PCL_NONE, PCL_NONE },       //int     children[ 3 ];      LEVEL1_UPG_COST,                                //int     cost;      LEVEL1_UPG_VALUE                                //int     value;    },    { -    PCL_ALIEN_LEVEL2,                               //int     classnum; -    "level2",                                       //char    *classname; -    "Chimera",                                      //char    *humanname; -    "tarantula",                                    //char    *modelname; -    0.75f,                                          //float   modelScale; -    "default",                                      //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "alien_general_hud",                            //char    *hudname; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int  stages -    { -22, -22, -22 },                              //vec3_t  mins; -    { 22, 22, 22 },                                 //vec3_t  maxs; -    { 22, 22, 22 },                                 //vec3_t  crouchmaxs; -    { -22, -22, -4 },                               //vec3_t  deadmins; -    { 22, 22, 4 },                                  //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    10, 10,                                         //int     viewheight, crouchviewheight; +    PCL_ALIEN_LEVEL2,                               //int     number; +    "level2",                                       //char    *name; +    "Has a melee attack and the ability to jump off walls. This " +      "allows the Marauder to gather great speed in enclosed areas.", +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int     stages;      LEVEL2_HEALTH,                                  //int     health;      0.0f,                                           //float   fallDamage; -    LEVEL2_REGEN,                                   //int     regenRate; -    SCA_NOWEAPONDRIFT|SCA_WALLJUMPER| -      SCA_FOVWARPS|SCA_ALIENSENSE,                  //int     abilities; -    WP_ALEVEL2,                                     //weapon_t  startWeapon +    LEVEL2_REGEN,                                   //float   regenRate; +    SCA_WALLJUMPER|SCA_FOVWARPS|SCA_ALIENSENSE,     //int     abilities; +    WP_ALEVEL2,                                     //weapon_t startWeapon;      0.0f,                                           //float   buildDist;      90,                                             //int     fov;      0.001f,                                         //float   bob;      1.5f,                                           //float   bobCycle; +    0.0f,                                           //float   landBob;      80,                                             //int     steptime;      LEVEL2_SPEED,                                   //float   speed;      10.0f,                                          //float   acceleration; -    2.0f,                                           //float   airAcceleration; +    3.0f,                                           //float   airAcceleration;      6.0f,                                           //float   friction;      100.0f,                                         //float   stopSpeed; -    400.0f,                                         //float   jumpMagnitude; +    380.0f,                                         //float   jumpMagnitude;      0.8f,                                           //float   knockbackScale; -    { PCL_ALIEN_LEVEL3, PCL_ALIEN_LEVEL2_UPG, PCL_NONE },   //int     children[ 3 ]; +    { PCL_ALIEN_LEVEL3, PCL_ALIEN_LEVEL2_UPG, PCL_NONE }, //int  children[ 3 ];      LEVEL2_COST,                                    //int     cost;      LEVEL2_VALUE                                    //int     value;    },    { -    PCL_ALIEN_LEVEL2_UPG,                           //int     classnum; -    "level2upg",                                    //char    *classname; -    "Chimera Upgrade",                              //char    *humanname; -    "tarantula",                                    //char    *modelname; -    0.9f,                                           //float   modelScale; -    "red",                                          //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "alien_general_hud",                            //char    *hudname; -    ( 1 << S2 )|( 1 << S3 ),                        //int  stages -    { -24, -24, -24 },                              //vec3_t  mins; -    { 24, 24, 24 },                                 //vec3_t  maxs; -    { 24, 24, 24 },                                 //vec3_t  crouchmaxs; -    { -24, -24, -4 },                               //vec3_t  deadmins; -    { 24, 24, 4 },                                  //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    12, 12,                                         //int     viewheight, crouchviewheight; +    PCL_ALIEN_LEVEL2_UPG,                           //int     number; +    "level2upg",                                    //char    *name; +    "The Advanced Marauder has all the abilities of the basic Marauder " +      "with the addition of an area effect electric shock attack.", +    ( 1 << S2 )|( 1 << S3 ),                        //int     stages;      LEVEL2_UPG_HEALTH,                              //int     health;      0.0f,                                           //float   fallDamage; -    LEVEL2_UPG_REGEN,                               //int     regenRate; -    SCA_NOWEAPONDRIFT|SCA_WALLJUMPER| -      SCA_FOVWARPS|SCA_ALIENSENSE,                  //int     abilities; -    WP_ALEVEL2_UPG,                                 //weapon_t  startWeapon +    LEVEL2_UPG_REGEN,                               //float   regenRate; +    SCA_WALLJUMPER|SCA_FOVWARPS|SCA_ALIENSENSE,     //int     abilities; +    WP_ALEVEL2_UPG,                                 //weapon_t startWeapon;      0.0f,                                           //float   buildDist;      90,                                             //int     fov;      0.001f,                                         //float   bob;      1.5f,                                           //float   bobCycle; +    0.0f,                                           //float   landBob;      80,                                             //int     steptime;      LEVEL2_UPG_SPEED,                               //float   speed;      10.0f,                                          //float   acceleration; -    2.0f,                                           //float   airAcceleration; +    3.0f,                                           //float   airAcceleration;      6.0f,                                           //float   friction;      100.0f,                                         //float   stopSpeed; -    400.0f,                                         //float   jumpMagnitude; +    380.0f,                                         //float   jumpMagnitude;      0.7f,                                           //float   knockbackScale;      { PCL_ALIEN_LEVEL3, PCL_NONE, PCL_NONE },       //int     children[ 3 ];      LEVEL2_UPG_COST,                                //int     cost;      LEVEL2_UPG_VALUE                                //int     value;    },    { -    PCL_ALIEN_LEVEL3,                               //int     classnum; -    "level3",                                       //char    *classname; -    "Dragoon",                                      //char    *humanname; -    "prowl",                                        //char    *modelname; -    1.0f,                                           //float   modelScale; -    "default",                                      //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "alien_general_hud",                            //char    *hudname; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int  stages -    { -32, -32, -21 },                              //vec3_t  mins; -    { 32, 32, 21 },                                 //vec3_t  maxs; -    { 32, 32, 21 },                                 //vec3_t  crouchmaxs; -    { -32, -32, -4 },                               //vec3_t  deadmins; -    { 32, 32, 4 },                                  //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    24, 24,                                         //int     viewheight, crouchviewheight; +    PCL_ALIEN_LEVEL3,                               //int     number; +    "level3",                                       //char    *name; +    "Possesses a melee attack and the pounce ability, which may " +      "be used as both an attack and a means to reach remote " +      "locations inaccessible from the ground.", +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int     stages;      LEVEL3_HEALTH,                                  //int     health;      0.0f,                                           //float   fallDamage; -    LEVEL3_REGEN,                                   //int     regenRate; -    SCA_NOWEAPONDRIFT| -      SCA_FOVWARPS|SCA_ALIENSENSE,                  //int     abilities; -    WP_ALEVEL3,                                     //weapon_t  startWeapon +    LEVEL3_REGEN,                                   //float   regenRate; +    SCA_FOVWARPS|SCA_ALIENSENSE,                    //int     abilities; +    WP_ALEVEL3,                                     //weapon_t startWeapon;      0.0f,                                           //float   buildDist;      110,                                            //int     fov;      0.0005f,                                        //float   bob;      1.3f,                                           //float   bobCycle; +    0.0f,                                           //float   landBob;      90,                                             //int     steptime;      LEVEL3_SPEED,                                   //float   speed;      10.0f,                                          //float   acceleration; @@ -1872,37 +1104,27 @@ classAttributes_t bg_classList[ ] =      200.0f,                                         //float   stopSpeed;      270.0f,                                         //float   jumpMagnitude;      0.5f,                                           //float   knockbackScale; -    { PCL_ALIEN_LEVEL4, PCL_ALIEN_LEVEL3_UPG, PCL_NONE },   //int     children[ 3 ]; +    { PCL_ALIEN_LEVEL4, PCL_ALIEN_LEVEL3_UPG, PCL_NONE }, //int  children[ 3 ];      LEVEL3_COST,                                    //int     cost;      LEVEL3_VALUE                                    //int     value;    },    { -    PCL_ALIEN_LEVEL3_UPG,                           //int     classnum; -    "level3upg",                                    //char    *classname; -    "Dragoon Upgrade",                              //char    *humanname; -    "prowl",                                        //char    *modelname; -    1.0f,                                           //float   modelScale; -    "default",                                      //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "alien_general_hud",                            //char    *hudname; -    ( 1 << S3 ),                                    //int  stages -    { -32, -32, -21 },                              //vec3_t  mins; -    { 32, 32, 21 },                                 //vec3_t  maxs; -    { 32, 32, 21 },                                 //vec3_t  crouchmaxs; -    { -32, -32, -4 },                               //vec3_t  deadmins; -    { 32, 32, 4 },                                  //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    27, 27,                                         //int     viewheight, crouchviewheight; +    PCL_ALIEN_LEVEL3_UPG,                           //int     number; +    "level3upg",                                    //char    *name; +    "In addition to the basic Dragoon abilities, the Advanced " +      "Dragoon has 3 barbs which may be used to attack humans " +      "from a distance.", +    ( 1 << S2 )|( 1 << S3 ),                        //int     stages;      LEVEL3_UPG_HEALTH,                              //int     health;      0.0f,                                           //float   fallDamage; -    LEVEL3_UPG_REGEN,                               //int     regenRate; -    SCA_NOWEAPONDRIFT| -      SCA_FOVWARPS|SCA_ALIENSENSE,                  //int     abilities; -    WP_ALEVEL3_UPG,                                 //weapon_t  startWeapon +    LEVEL3_UPG_REGEN,                               //float   regenRate; +    SCA_FOVWARPS|SCA_ALIENSENSE,                    //int     abilities; +    WP_ALEVEL3_UPG,                                 //weapon_t startWeapon;      0.0f,                                           //float   buildDist;      110,                                            //int     fov;      0.0005f,                                        //float   bob;      1.3f,                                           //float   bobCycle; +    0.0f,                                           //float   landBob;      90,                                             //int     steptime;      LEVEL3_UPG_SPEED,                               //float   speed;      10.0f,                                          //float   acceleration; @@ -1916,32 +1138,23 @@ classAttributes_t bg_classList[ ] =      LEVEL3_UPG_VALUE                                //int     value;    },    { -    PCL_ALIEN_LEVEL4,                               //int     classnum; -    "level4",                                       //char    *classname; -    "Big Mofo",                                     //char    *humanname; -    "mofo",                                         //char    *modelname; -    1.0f,                                           //float   modelScale; -    "default",                                      //char    *skinname; -    2.0f,                                           //float   shadowScale; -    "alien_general_hud",                            //char    *hudname; -    ( 1 << S3 ),                                    //int  stages -    { -30, -30, -20 },                              //vec3_t  mins; -    { 30, 30, 20 },                                 //vec3_t  maxs; -    { 30, 30, 20 },                                 //vec3_t  crouchmaxs; -    { -15, -15, -4 },                               //vec3_t  deadmins; -    { 15, 15, 4 },                                  //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    35, 35,                                         //int     viewheight, crouchviewheight; +    PCL_ALIEN_LEVEL4,                               //int     number; +    "level4",                                       //char    *name; +    "A large alien with a strong melee attack, this class can " +      "also charge at enemy humans and structures, inflicting " +      "great damage. Any humans or their structures caught under " +      "a falling Tyrant will be crushed by its weight.", +    ( 1 << S3 ),                                    //int     stages;      LEVEL4_HEALTH,                                  //int     health;      0.0f,                                           //float   fallDamage; -    LEVEL4_REGEN,                                   //int     regenRate; -    SCA_NOWEAPONDRIFT| -      SCA_FOVWARPS|SCA_ALIENSENSE,                  //int     abilities; -    WP_ALEVEL4,                                     //weapon_t  startWeapon +    LEVEL4_REGEN,                                   //float   regenRate; +    SCA_FOVWARPS|SCA_ALIENSENSE,                    //int     abilities; +    WP_ALEVEL4,                                     //weapon_t startWeapon;      0.0f,                                           //float   buildDist;      90,                                             //int     fov;      0.001f,                                         //float   bob;      1.1f,                                           //float   bobCycle; +    0.0f,                                           //float   landBob;      100,                                            //int     steptime;      LEVEL4_SPEED,                                   //float   speed;      10.0f,                                          //float   acceleration; @@ -1955,32 +1168,20 @@ classAttributes_t bg_classList[ ] =      LEVEL4_VALUE                                    //int     value;    },    { -    PCL_HUMAN,                                      //int     classnum; -    "human_base",                                   //char    *classname; -    "Human",                                        //char    *humanname; -    "sarge",                                        //char    *modelname; -    1.0f,                                           //float   modelScale; -    "default",                                      //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "human_hud",                                    //char    *hudname; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int  stages -    { -15, -15, -24 },                              //vec3_t  mins; -    { 15, 15, 32 },                                 //vec3_t  maxs; -    { 15, 15, 16 },                                 //vec3_t  crouchmaxs; -    { -15, -15, -4 },                               //vec3_t  deadmins; -    { 15, 15, 4 },                                  //vec3_t  deadmaxs; -    0.0f,                                           //float   zOffset -    26, 12,                                         //int     viewheight, crouchviewheight; +    PCL_HUMAN,                                      //int     number; +    "human_base",                                   //char    *name; +    "", +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ),            //int     stages;      100,                                            //int     health;      1.0f,                                           //float   fallDamage; -    0,                                              //int     regenRate; -    SCA_TAKESFALLDAMAGE| -      SCA_CANUSELADDERS,                            //int     abilities; -    WP_NONE, //special-cased in g_client.c          //weapon_t  startWeapon +    0.0f,                                           //float   regenRate; +    SCA_TAKESFALLDAMAGE|SCA_CANUSELADDERS,          //int     abilities; +    WP_NONE, //special-cased in g_client.c          //weapon_t startWeapon;      110.0f,                                         //float   buildDist;      90,                                             //int     fov;      0.002f,                                         //float   bob;      1.0f,                                           //float   bobCycle; +    8.0f,                                           //float   landBob;      100,                                            //int     steptime;      1.0f,                                           //float   speed;      10.0f,                                          //float   acceleration; @@ -1991,684 +1192,120 @@ classAttributes_t bg_classList[ ] =      1.0f,                                           //float   knockbackScale;      { PCL_NONE, PCL_NONE, PCL_NONE },               //int     children[ 3 ];      0,                                              //int     cost; -    0                                               //int     value; +    ALIEN_CREDITS_PER_KILL                          //int     value;    },    { -    PCL_HUMAN_BSUIT,                                //int     classnum; -    "human_bsuit",                                  //char    *classname; -    "bsuit",                                        //char    *humanname; -    "keel",                                         //char    *modelname; -    1.0f,                                           //float   modelScale; -    "default",                                      //char    *skinname; -    1.0f,                                           //float   shadowScale; -    "human_hud",                                    //char    *hudname; -    ( 1 << S3 ),                                    //int  stages -    { -15, -15, -38 },                              //vec3_t  mins; -    { 15, 15, 38 },                                 //vec3_t  maxs; -    { 15, 15, 38 },                                 //vec3_t  crouchmaxs; -    { -15, -15, -4 },                               //vec3_t  deadmins; -    { 15, 15, 4 },                                  //vec3_t  deadmaxs; -    -16.0f,                                           //float   zOffset -    35, 35,                                         //int     viewheight, crouchviewheight; +    PCL_HUMAN_BSUIT,                                //int     number; +    "human_bsuit",                                  //char    *name; +    "", +    ( 1 << S3 ),                                    //int     stages;      100,                                            //int     health;      1.0f,                                           //float   fallDamage; -    0,                                              //int     regenRate; -    SCA_TAKESFALLDAMAGE| -      SCA_CANUSELADDERS,                            //int     abilities; -    WP_NONE, //special-cased in g_client.c          //weapon_t  startWeapon +    0.0f,                                           //float   regenRate; +    SCA_TAKESFALLDAMAGE|SCA_CANUSELADDERS,          //int     abilities; +    WP_NONE, //special-cased in g_client.c          //weapon_t startWeapon;      110.0f,                                         //float   buildDist;      90,                                             //int     fov;      0.002f,                                         //float   bob;      1.0f,                                           //float   bobCycle; +    5.0f,                                           //float   landBob;      100,                                            //int     steptime;      1.0f,                                           //float   speed;      10.0f,                                          //float   acceleration;      1.0f,                                           //float   airAcceleration;      6.0f,                                           //float   friction;      100.0f,                                         //float   stopSpeed; -    270.0f,                                         //float   jumpMagnitude; +    220.0f,                                         //float   jumpMagnitude;      1.0f,                                           //float   knockbackScale;      { PCL_NONE, PCL_NONE, PCL_NONE },               //int     children[ 3 ];      0,                                              //int     cost; -    0                                               //int     value; -  }, -}; - -int   bg_numPclasses = sizeof( bg_classList ) / sizeof( bg_classList[ 0 ] ); - -//separate from bg_classList to work around char struct init bug -classAttributeOverrides_t bg_classOverrideList[ PCL_NUM_CLASSES ]; - -/* -============== -BG_FindClassNumForName -============== -*/ -int BG_FindClassNumForName( char *name ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( !Q_stricmp( bg_classList[ i ].className, name ) ) -      return bg_classList[ i ].classNum; -  } - -  //wimp out -  return PCL_NONE; -} - -/* -============== -BG_FindNameForClassNum -============== -*/ -char *BG_FindNameForClassNum( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -      return bg_classList[ i ].className; +    ALIEN_CREDITS_PER_KILL                          //int     value;    } +}; -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindNameForClassNum\n" ); -  //wimp out -  return 0; -} - -/* -============== -BG_FindHumanNameForClassNum -============== -*/ -char *BG_FindHumanNameForClassNum( int pclass ) -{ -  int i; - -  if( bg_classOverrideList[ pclass ].humanName[ 0 ] != 0 ) -    return bg_classOverrideList[ pclass ].humanName; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -      return bg_classList[ i ].humanName; -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindHumanNameForClassNum\n" ); -  //wimp out -  return 0; -} - -/* -============== -BG_FindModelNameForClass -============== -*/ -char *BG_FindModelNameForClass( int pclass ) -{ -  int i; - -  if( bg_classOverrideList[ pclass ].modelName[ 0 ] != 0 ) -    return bg_classOverrideList[ pclass ].modelName; +size_t bg_numClasses = ARRAY_LEN( bg_classList ); -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -      return bg_classList[ i ].modelName; -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindModelNameForClass\n" ); -  //note: must return a valid modelName! -  return bg_classList[ 0 ].modelName; -} +static const classAttributes_t nullClass = { 0 };  /*  ============== -BG_FindModelScaleForClass +BG_ClassByName  ==============  */ -float BG_FindModelScaleForClass( int pclass ) +const classAttributes_t *BG_ClassByName( const char *name )  {    int i; -  if( bg_classOverrideList[ pclass ].modelScale != 0.0f ) -    return bg_classOverrideList[ pclass ].modelScale; - -  for( i = 0; i < bg_numPclasses; i++ ) +  for( i = 0; i < bg_numClasses; i++ )    { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].modelScale; -    } +    if( !Q_stricmp( bg_classList[ i ].name, name ) ) +      return &bg_classList[ i ];    } -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindModelScaleForClass( %d )\n", pclass ); -  return 1.0f; +  return &nullClass;  }  /*  ============== -BG_FindSkinNameForClass +BG_Class  ==============  */ -char *BG_FindSkinNameForClass( int pclass ) +const classAttributes_t *BG_Class( class_t class )  { -  int i; - -  if( bg_classOverrideList[ pclass ].skinName[ 0 ] != 0 ) -    return bg_classOverrideList[ pclass ].skinName; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -      return bg_classList[ i ].skinName; -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindSkinNameForClass\n" ); -  //note: must return a valid modelName! -  return bg_classList[ 0 ].skinName; +  return ( class >= PCL_NONE && class < PCL_NUM_CLASSES ) ? +    &bg_classList[ class ] : &nullClass;  }  /*  ============== -BG_FindShadowScaleForClass +BG_ClassAllowedInStage  ==============  */ -float BG_FindShadowScaleForClass( int pclass ) +qboolean BG_ClassAllowedInStage( class_t class, +                                 stage_t stage )  { -  int i; +  int stages = BG_Class( class )->stages; -  if( bg_classOverrideList[ pclass ].shadowScale != 0.0f ) -    return bg_classOverrideList[ pclass ].shadowScale; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].shadowScale; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindShadowScaleForClass( %d )\n", pclass ); -  return 1.0f; +  return stages & ( 1 << stage );  } -/* -============== -BG_FindHudNameForClass -============== -*/ -char *BG_FindHudNameForClass( int pclass ) -{ -  int i; - -  if( bg_classOverrideList[ pclass ].hudName[ 0 ] != 0 ) -    return bg_classOverrideList[ pclass ].hudName; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -      return bg_classList[ i ].hudName; -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindHudNameForClass\n" ); -  //note: must return a valid hudName! -  return bg_classList[ 0 ].hudName; -} +static classConfig_t bg_classConfigList[ PCL_NUM_CLASSES ];  /*  ============== -BG_FindStagesForClass +BG_ClassConfig  ==============  */ -qboolean BG_FindStagesForClass( int pclass, stage_t stage ) +classConfig_t *BG_ClassConfig( class_t class )  { -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      if( bg_classList[ i ].stages & ( 1 << stage ) ) -        return qtrue; -      else -        return qfalse; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindStagesForClass\n" ); -  return qfalse; +  return &bg_classConfigList[ class ];  }  /*  ============== -BG_FindBBoxForClass +BG_ClassBoundingBox  ==============  */ -void BG_FindBBoxForClass( int pclass, vec3_t mins, vec3_t maxs, vec3_t cmaxs, vec3_t dmins, vec3_t dmaxs ) +void BG_ClassBoundingBox( class_t class, +                          vec3_t mins, vec3_t maxs, +                          vec3_t cmaxs, vec3_t dmins, vec3_t dmaxs )  { -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      if( mins != NULL ) -      { -        VectorCopy( bg_classList[ i ].mins, mins ); - -        if( VectorLength( bg_classOverrideList[ pclass ].mins ) ) -          VectorCopy( bg_classOverrideList[ pclass ].mins, mins ); -      } - -      if( maxs != NULL ) -      { -        VectorCopy( bg_classList[ i ].maxs, maxs ); - -        if( VectorLength( bg_classOverrideList[ pclass ].maxs ) ) -          VectorCopy( bg_classOverrideList[ pclass ].maxs, maxs ); -      } - -      if( cmaxs != NULL ) -      { -        VectorCopy( bg_classList[ i ].crouchMaxs, cmaxs ); - -        if( VectorLength( bg_classOverrideList[ pclass ].crouchMaxs ) ) -          VectorCopy( bg_classOverrideList[ pclass ].crouchMaxs, cmaxs ); -      } - -      if( dmins != NULL ) -      { -        VectorCopy( bg_classList[ i ].deadMins, dmins ); - -        if( VectorLength( bg_classOverrideList[ pclass ].deadMins ) ) -          VectorCopy( bg_classOverrideList[ pclass ].deadMins, dmins ); -      } - -      if( dmaxs != NULL ) -      { -        VectorCopy( bg_classList[ i ].deadMaxs, dmaxs ); - -        if( VectorLength( bg_classOverrideList[ pclass ].deadMaxs ) ) -          VectorCopy( bg_classOverrideList[ pclass ].deadMaxs, dmaxs ); -      } - -      return; -    } -  } +  classConfig_t *classConfig = BG_ClassConfig( class );    if( mins != NULL ) -    VectorCopy( bg_classList[ 0 ].mins,        mins ); +    VectorCopy( classConfig->mins, mins );    if( maxs != NULL ) -    VectorCopy( bg_classList[ 0 ].maxs,        maxs ); +    VectorCopy( classConfig->maxs, maxs );    if( cmaxs != NULL ) -    VectorCopy( bg_classList[ 0 ].crouchMaxs,  cmaxs ); +    VectorCopy( classConfig->crouchMaxs, cmaxs );    if( dmins != NULL ) -    VectorCopy( bg_classList[ 0 ].deadMins,    dmins ); +    VectorCopy( classConfig->deadMins, dmins );    if( dmaxs != NULL ) -    VectorCopy( bg_classList[ 0 ].deadMaxs,    dmaxs ); -} - -/* -============== -BG_FindZOffsetForClass -============== -*/ -float BG_FindZOffsetForClass( int pclass ) -{ -  int i; - -  if( bg_classOverrideList[ pclass ].zOffset != 0.0f ) -    return bg_classOverrideList[ pclass ].zOffset; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].zOffset; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindZOffsetForClass\n" ); -  return 0.0f; -} - -/* -============== -BG_FindViewheightForClass -============== -*/ -void BG_FindViewheightForClass( int pclass, int *viewheight, int *cViewheight ) -{ -  int i; -  int vh = 0; -  int cvh = 0; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      vh = bg_classList[ i ].viewheight; -      cvh = bg_classList[ i ].crouchViewheight; -      break; -    } -  } -   -  if( bg_classOverrideList[ pclass ].viewheight != 0 ) -    vh = bg_classOverrideList[ pclass ].viewheight; -  if( bg_classOverrideList[ pclass ].crouchViewheight != 0 ) -    cvh = bg_classOverrideList[ pclass ].crouchViewheight; - - -  if( vh == 0 ) -    vh = bg_classList[ 0 ].viewheight; -  if( cvh == 0 ) -    cvh = bg_classList[ 0 ].crouchViewheight; - -  if( viewheight != NULL ) -    *viewheight = vh; -  if( cViewheight != NULL ) -    *cViewheight = cvh; -} - -/* -============== -BG_FindHealthForClass -============== -*/ -int BG_FindHealthForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].health; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindHealthForClass\n" ); -  return 100; -} - -/* -============== -BG_FindFallDamageForClass -============== -*/ -float BG_FindFallDamageForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].fallDamage; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindFallDamageForClass\n" ); -  return 100; -} - -/* -============== -BG_FindRegenRateForClass -============== -*/ -int BG_FindRegenRateForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].regenRate; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindRegenRateForClass\n" ); -  return 0; -} - -/* -============== -BG_FindFovForClass -============== -*/ -int BG_FindFovForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].fov; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindFovForClass\n" ); -  return 90; -} - -/* -============== -BG_FindBobForClass -============== -*/ -float BG_FindBobForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].bob; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindBobForClass\n" ); -  return 0.002; -} - -/* -============== -BG_FindBobCycleForClass -============== -*/ -float BG_FindBobCycleForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].bobCycle; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindBobCycleForClass\n" ); -  return 1.0f; -} - -/* -============== -BG_FindSpeedForClass -============== -*/ -float BG_FindSpeedForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].speed; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindSpeedForClass\n" ); -  return 1.0f; -} - -/* -============== -BG_FindAccelerationForClass -============== -*/ -float BG_FindAccelerationForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].acceleration; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindAccelerationForClass\n" ); -  return 10.0f; -} - -/* -============== -BG_FindAirAccelerationForClass -============== -*/ -float BG_FindAirAccelerationForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].airAcceleration; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindAirAccelerationForClass\n" ); -  return 1.0f; -} - -/* -============== -BG_FindFrictionForClass -============== -*/ -float BG_FindFrictionForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].friction; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindFrictionForClass\n" ); -  return 6.0f; -} - -/* -============== -BG_FindStopSpeedForClass -============== -*/ -float BG_FindStopSpeedForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].stopSpeed; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindStopSpeedForClass\n" ); -  return 100.0f; -} - -/* -============== -BG_FindJumpMagnitudeForClass -============== -*/ -float BG_FindJumpMagnitudeForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].jumpMagnitude; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindJumpMagnitudeForClass\n" ); -  return 270.0f; -} - -/* -============== -BG_FindKnockbackScaleForClass -============== -*/ -float BG_FindKnockbackScaleForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].knockbackScale; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindKnockbackScaleForClass\n" ); -  return 1.0f; -} - -/* -============== -BG_FindSteptimeForClass -============== -*/ -int BG_FindSteptimeForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].steptime; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindSteptimeForClass\n" ); -  return 200; +    VectorCopy( classConfig->deadMaxs, dmaxs );  }  /* @@ -2676,61 +1313,11 @@ int BG_FindSteptimeForClass( int pclass )  BG_ClassHasAbility  ==============  */ -qboolean BG_ClassHasAbility( int pclass, int ability ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return ( bg_classList[ i ].abilities & ability ); -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindStartWeaponForClass -============== -*/ -weapon_t BG_FindStartWeaponForClass( int pclass ) +qboolean BG_ClassHasAbility( class_t class, int ability )  { -  int i; +  int abilities = BG_Class( class )->abilities; -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].startWeapon; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindStartWeaponForClass\n" ); -  return WP_NONE; -} - -/* -============== -BG_FindBuildDistForClass -============== -*/ -float BG_FindBuildDistForClass( int pclass ) -{ -  int i; - -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].buildDist; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindBuildDistForClass\n" ); -  return 0.0f; +  return abilities & ability;  }  /* @@ -2738,95 +1325,78 @@ float BG_FindBuildDistForClass( int pclass )  BG_ClassCanEvolveFromTo  ==============  */ -int BG_ClassCanEvolveFromTo( int fclass, int tclass, int credits, int num ) +int BG_ClassCanEvolveFromTo( class_t fclass, +                             class_t tclass, +                             int credits, int stage, +                             int cost )  { -  int i, j, cost; - -  cost = BG_FindCostOfClass( tclass ); +  int i, j, best, value; -  //base case -  if( credits < cost ) +  if( credits < cost || fclass == PCL_NONE || tclass == PCL_NONE || +      fclass == tclass )      return -1; -  if( fclass == PCL_NONE || tclass == PCL_NONE ) -    return -1; - -  for( i = 0; i < bg_numPclasses; i++ ) +  for( i = 0; i < bg_numClasses; i++ )    { -    if( bg_classList[ i ].classNum == fclass ) -    { -      for( j = 0; j < 3; j++ ) -        if( bg_classList[ i ].children[ j ] == tclass ) -          return num + cost; +    if( bg_classList[ i ].number != fclass ) +      continue; -      for( j = 0; j < 3; j++ ) -      { -        int sub; +    best = credits + 1; +    for( j = 0; j < 3; j++ ) +    { +      int thruClass, evolveCost; +       +      thruClass = bg_classList[ i ].children[ j ]; +      if( thruClass == PCL_NONE || !BG_ClassAllowedInStage( thruClass, stage ) || +          !BG_ClassIsAllowed( thruClass ) ) +        continue; -        cost = BG_FindCostOfClass( bg_classList[ i ].children[ j ] ); -        sub = BG_ClassCanEvolveFromTo( bg_classList[ i ].children[ j ], -                                       tclass, credits - cost, num + cost ); -        if( sub >= 0 ) -          return sub; -      } +      evolveCost = BG_Class( thruClass )->cost * ALIEN_CREDITS_PER_KILL; +      if( thruClass == tclass ) +        value = cost + evolveCost; +      else +        value = BG_ClassCanEvolveFromTo( thruClass, tclass, credits, stage, +                                         cost + evolveCost ); -      return -1; //may as well return by this point +      if( value >= 0 && value < best ) +        best = value;      } + +    return best <= credits ? best : -1;    } +  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_ClassCanEvolveFromTo\n" );    return -1;  }  /*  ============== -BG_FindValueOfClass +BG_AlienCanEvolve  ==============  */ -int BG_FindValueOfClass( int pclass ) +qboolean BG_AlienCanEvolve( class_t class, int credits, int stage )  { -  int i; +  int i, j, tclass; -  for( i = 0; i < bg_numPclasses; i++ ) +  for( i = 0; i < bg_numClasses; i++ )    { -    if( bg_classList[ i ].classNum == pclass ) -    { -      return bg_classList[ i ].value; -    } -  } - -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindValueOfClass\n" ); -  return 0; -} - -/* -============== -BG_FindCostOfClass -============== -*/ -int BG_FindCostOfClass( int pclass ) -{ -  int i; +    if( bg_classList[ i ].number != class ) +      continue; -  for( i = 0; i < bg_numPclasses; i++ ) -  { -    if( bg_classList[ i ].classNum == pclass ) +    for( j = 0; j < 3; j++ )      { -      return bg_classList[ i ].cost; +      tclass = bg_classList[ i ].children[ j ]; +      if( tclass != PCL_NONE && BG_ClassAllowedInStage( tclass, stage ) && +          BG_ClassIsAllowed( tclass ) && +          credits >= BG_Class( tclass )->cost * ALIEN_CREDITS_PER_KILL ) +        return qtrue;      } -  } -  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindCostOfClass\n" ); -  return 0; -} +    return qfalse; +  } -/* -============== -BG_FindOverrideForClass -============== -*/ -static classAttributeOverrides_t *BG_FindOverrideForClass( int pclass ) -{ -  return &bg_classOverrideList[ pclass ]; +  Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_AlienCanEvolve\n" ); +  return qfalse;  }  /* @@ -2836,7 +1406,7 @@ BG_ParseClassFile  Parses a configuration file describing a class  ======================  */ -static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides_t *cao ) +static qboolean BG_ParseClassFile( const char *filename, classConfig_t *cc )  {    char          *text_p;    int           i; @@ -2845,7 +1415,25 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides    char          text[ 20000 ];    fileHandle_t  f;    float         scale = 0.0f; - +  int           defined = 0; +  enum +  { +      MODEL           = 1 << 0, +      SKIN            = 1 << 1, +      HUD             = 1 << 2, +      MODELSCALE      = 1 << 3, +      SHADOWSCALE     = 1 << 4, +      MINS            = 1 << 5, +      MAXS            = 1 << 6, +      DEADMINS        = 1 << 7, +      DEADMAXS        = 1 << 8, +      CROUCHMAXS      = 1 << 9, +      VIEWHEIGHT      = 1 << 10, +      CVIEWHEIGHT     = 1 << 11, +      ZOFFSET         = 1 << 12, +      NAME            = 1 << 13, +      SHOULDEROFFSETS = 1 << 14 +  };    // load the file    len = trap_FS_FOpenFile( filename, &f, FS_READ ); @@ -2884,8 +1472,9 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides        if( !token )          break; -      Q_strncpyz( cao->modelName, token, sizeof( cao->modelName ) ); +      Q_strncpyz( cc->modelName, token, sizeof( cc->modelName ) ); +      defined |= MODEL;        continue;      }      else if( !Q_stricmp( token, "skin" ) ) @@ -2894,8 +1483,9 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides        if( !token )          break; -      Q_strncpyz( cao->skinName, token, sizeof( cao->skinName ) ); +      Q_strncpyz( cc->skinName, token, sizeof( cc->skinName ) ); +      defined |= SKIN;        continue;      }      else if( !Q_stricmp( token, "hud" ) ) @@ -2904,8 +1494,9 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides        if( !token )          break; -      Q_strncpyz( cao->hudName, token, sizeof( cao->hudName ) ); +      Q_strncpyz( cc->hudName, token, sizeof( cc->hudName ) ); +      defined |= HUD;        continue;      }      else if( !Q_stricmp( token, "modelScale" ) ) @@ -2919,8 +1510,9 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides        if( scale < 0.0f )          scale = 0.0f; -      cao->modelScale = scale; +      cc->modelScale = scale; +      defined |= MODELSCALE;        continue;      }      else if( !Q_stricmp( token, "shadowScale" ) ) @@ -2934,8 +1526,9 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides        if( scale < 0.0f )          scale = 0.0f; -      cao->shadowScale = scale; +      cc->shadowScale = scale; +      defined |= SHADOWSCALE;        continue;      }      else if( !Q_stricmp( token, "mins" ) ) @@ -2946,9 +1539,10 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides          if( !token )            break; -        cao->mins[ i ] = atof( token ); +        cc->mins[ i ] = atof( token );        } +      defined |= MINS;        continue;      }      else if( !Q_stricmp( token, "maxs" ) ) @@ -2959,9 +1553,10 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides          if( !token )            break; -        cao->maxs[ i ] = atof( token ); +        cc->maxs[ i ] = atof( token );        } +      defined |= MAXS;        continue;      }      else if( !Q_stricmp( token, "deadMins" ) ) @@ -2972,9 +1567,10 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides          if( !token )            break; -        cao->deadMins[ i ] = atof( token ); +        cc->deadMins[ i ] = atof( token );        } +      defined |= DEADMINS;        continue;      }      else if( !Q_stricmp( token, "deadMaxs" ) ) @@ -2985,9 +1581,10 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides          if( !token )            break; -        cao->deadMaxs[ i ] = atof( token ); +        cc->deadMaxs[ i ] = atof( token );        } +      defined |= DEADMAXS;        continue;      }      else if( !Q_stricmp( token, "crouchMaxs" ) ) @@ -2998,21 +1595,24 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides          if( !token )            break; -        cao->crouchMaxs[ i ] = atof( token ); +        cc->crouchMaxs[ i ] = atof( token );        } +      defined |= CROUCHMAXS;        continue;      }      else if( !Q_stricmp( token, "viewheight" ) )      {        token = COM_Parse( &text_p ); -      cao->viewheight = atoi( token ); +      cc->viewheight = atoi( token ); +      defined |= VIEWHEIGHT;        continue;      }      else if( !Q_stricmp( token, "crouchViewheight" ) )      {        token = COM_Parse( &text_p ); -      cao->crouchViewheight = atoi( token ); +      cc->crouchViewheight = atoi( token ); +      defined |= CVIEWHEIGHT;        continue;      }      else if( !Q_stricmp( token, "zOffset" ) ) @@ -3025,8 +1625,9 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides        offset = atof( token ); -      cao->zOffset = offset; +      cc->zOffset = offset; +      defined |= ZOFFSET;        continue;      }      else if( !Q_stricmp( token, "name" ) ) @@ -3035,275 +1636,341 @@ static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides        if( !token )          break; -      Q_strncpyz( cao->humanName, token, sizeof( cao->humanName ) ); +      Q_strncpyz( cc->humanName, token, sizeof( cc->humanName ) ); +      defined |= NAME;        continue;      } +    else if( !Q_stricmp( token, "shoulderOffsets" ) ) +    { +      for( i = 0; i <= 2; i++ ) +      { +        token = COM_Parse( &text_p ); +        if( !token ) +          break; +        cc->shoulderOffsets[ i ] = atof( token ); +      } + +      defined |= SHOULDEROFFSETS; +      continue; +    }      Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token );      return qfalse;    } +  if(      !( defined & MODEL           ) ) token = "model"; +  else if( !( defined & SKIN            ) ) token = "skin"; +  else if( !( defined & HUD             ) ) token = "hud"; +  else if( !( defined & MODELSCALE      ) ) token = "modelScale"; +  else if( !( defined & SHADOWSCALE     ) ) token = "shadowScale"; +  else if( !( defined & MINS            ) ) token = "mins"; +  else if( !( defined & MAXS            ) ) token = "maxs"; +  else if( !( defined & DEADMINS        ) ) token = "deadMins"; +  else if( !( defined & DEADMAXS        ) ) token = "deadMaxs"; +  else if( !( defined & CROUCHMAXS      ) ) token = "crouchMaxs"; +  else if( !( defined & VIEWHEIGHT      ) ) token = "viewheight"; +  else if( !( defined & CVIEWHEIGHT     ) ) token = "crouchViewheight"; +  else if( !( defined & ZOFFSET         ) ) token = "zOffset"; +  else if( !( defined & NAME            ) ) token = "name"; +  else if( !( defined & SHOULDEROFFSETS ) ) token = "shoulderOffsets"; +  else                                      token = ""; + +  if( strlen( token ) > 0 ) +  { +      Com_Printf( S_COLOR_RED "ERROR: %s not defined in %s\n", +                  token, filename ); +      return qfalse; +  } +    return qtrue;  }  /*  =============== -BG_InitClassOverrides - -Set any overrides specfied by file +BG_InitClassConfigs  ===============  */ -void BG_InitClassOverrides( void ) +void BG_InitClassConfigs( void )  { -  int                       i; -  classAttributeOverrides_t *cao; +  int           i; +  classConfig_t *cc; -  for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) +  for( i = PCL_NONE; i < PCL_NUM_CLASSES; i++ )    { -    cao = BG_FindOverrideForClass( i ); +    cc = BG_ClassConfig( i ); -    BG_ParseClassFile( va( "overrides/classes/%s.cfg", BG_FindNameForClassNum( i ) ), cao ); +    BG_ParseClassFile( va( "configs/classes/%s.cfg", +                           BG_Class( i )->name ), cc );    }  }  //////////////////////////////////////////////////////////////////////////////// -weaponAttributes_t bg_weapons[ ] = +static const weaponAttributes_t bg_weapons[ ] =  {    { -    WP_BLASTER,           //int       weaponNum; +    WP_ALEVEL0,           //int       number;      0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages -    0,                    //int       slots; -    "blaster",            //char      *weaponName; -    "Blaster",            //char      *weaponHumanName; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages; +    SLOT_WEAPON,          //int       slots; +    "level0",             //char      *name; +    "Bite",               //char      *humanName; +    "",      0,                    //int       maxAmmo;      0,                    //int       maxClips;      qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    BLASTER_REPEAT,       //int       repeatRate1; +    LEVEL0_BITE_REPEAT,   //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3;      0,                    //int       reloadTime; -    BLASTER_K_SCALE,      //float     knockbackScale; +    LEVEL0_BITE_K_SCALE,  //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov;      qfalse,               //qboolean  purchasable; -    qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    qfalse,               //qboolean  longRanged; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_MACHINEGUN,        //int       weaponNum; -    RIFLE_PRICE,          //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_ALEVEL1,           //int       number; +    0,                    //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "rifle",              //char      *weaponName; -    "Rifle",              //char      *weaponHumanName; -    RIFLE_CLIPSIZE,       //int       maxAmmo; -    RIFLE_MAXCLIPS,       //int       maxClips; -    qfalse,               //int       infiniteAmmo; +    "level1",             //char      *name; +    "Claws",              //char      *humanName; +    "", +    0,                    //int       maxAmmo; +    0,                    //int       maxClips; +    qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    RIFLE_REPEAT,         //int       repeatRate1; +    LEVEL1_CLAW_REPEAT,   //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3; -    RIFLE_RELOAD,         //int       reloadTime; -    RIFLE_K_SCALE,        //float     knockbackScale; +    0,                    //int       reloadTime; +    LEVEL1_CLAW_K_SCALE,  //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qtrue,                //qboolean  purchasable; -    qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    qfalse,               //qboolean  purchasable; +    qfalse,               //qboolean  longRanged; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_SHOTGUN,           //int       weaponNum; -    SHOTGUN_PRICE,        //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_ALEVEL1_UPG,       //int       number; +    0,                    //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "shotgun",            //char      *weaponName; -    "Shotgun",            //char      *weaponHumanName; -    SHOTGUN_SHELLS,       //int       maxAmmo; -    SHOTGUN_MAXCLIPS,     //int       maxClips; -    qfalse,               //int       infiniteAmmo; +    "level1upg",          //char      *name; +    "Claws Upgrade",      //char      *humanName; +    "", +    0,                    //int       maxAmmo; +    0,                    //int       maxClips; +    qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    SHOTGUN_REPEAT,       //int       repeatRate1; -    0,                    //int       repeatRate2; +    LEVEL1_CLAW_U_REPEAT, //int       repeatRate1; +    LEVEL1_PCLOUD_REPEAT, //int       repeatRate2;      0,                    //int       repeatRate3; -    SHOTGUN_RELOAD,       //int       reloadTime; -    SHOTGUN_K_SCALE,        //float     knockbackScale; -    qfalse,               //qboolean  hasAltMode; +    0,                    //int       reloadTime; +    LEVEL1_CLAW_U_K_SCALE, //float    knockbackScale; +    qtrue,                //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qtrue,                //qboolean  purchasable; +    qfalse,               //qboolean  purchasable;      qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_FLAMER,            //int       weaponNum; -    FLAMER_PRICE,         //int       price; -    ( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_ALEVEL2,           //int       number; +    0,                    //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "flamer",             //char      *weaponName; -    "Flame Thrower",      //char      *weaponHumanName; -    FLAMER_GAS,           //int       maxAmmo; +    "level2",             //char      *name; +    "Bite",               //char      *humanName; +    "", +    0,                    //int       maxAmmo;      0,                    //int       maxClips; -    qfalse,               //int       infiniteAmmo; +    qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    FLAMER_REPEAT,        //int       repeatRate1; +    LEVEL2_CLAW_REPEAT,   //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3;      0,                    //int       reloadTime; -    FLAMER_K_SCALE,       //float     knockbackScale; +    LEVEL2_CLAW_K_SCALE,  //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qtrue,                //qboolean  purchasable; -    qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    qfalse,               //qboolean  purchasable; +    qfalse,               //qboolean  longRanged; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_CHAINGUN,          //int       weaponNum; -    CHAINGUN_PRICE,       //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_ALEVEL2_UPG,       //int       number; +    0,                    //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "chaingun",           //char      *weaponName; -    "Chaingun",           //char      *weaponHumanName; -    CHAINGUN_BULLETS,     //int       maxAmmo; +    "level2upg",          //char      *name; +    "Zap",                //char      *humanName; +    "", +    0,                    //int       maxAmmo;      0,                    //int       maxClips; -    qfalse,               //int       infiniteAmmo; +    qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    CHAINGUN_REPEAT,      //int       repeatRate1; -    0,                    //int       repeatRate2; +    LEVEL2_CLAW_U_REPEAT, //int       repeatRate1; +    LEVEL2_AREAZAP_REPEAT, //int      repeatRate2;      0,                    //int       repeatRate3;      0,                    //int       reloadTime; -    CHAINGUN_K_SCALE,     //float     knockbackScale; -    qfalse,               //qboolean  hasAltMode; +    LEVEL2_CLAW_U_K_SCALE, //float    knockbackScale; +    qtrue,                //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qtrue,                //qboolean  purchasable; -    qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    qfalse,               //qboolean  purchasable; +    qfalse,               //qboolean  longRanged; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_MASS_DRIVER,       //int       weaponNum; -    MDRIVER_PRICE,        //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_ALEVEL3,           //int       number; +    0,                    //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "mdriver",            //char      *weaponName; -    "Mass Driver",        //char      *weaponHumanName; -    MDRIVER_CLIPSIZE,     //int       maxAmmo; -    MDRIVER_MAXCLIPS,     //int       maxClips; -    qfalse,               //int       infiniteAmmo; -    qtrue,                //int       usesEnergy; -    MDRIVER_REPEAT,       //int       repeatRate1; +    "level3",             //char      *name; +    "Pounce",             //char      *humanName; +    "", +    0,                    //int       maxAmmo; +    0,                    //int       maxClips; +    qtrue,                //int       infiniteAmmo; +    qfalse,               //int       usesEnergy; +    LEVEL3_CLAW_REPEAT,   //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3; -    MDRIVER_RELOAD,       //int       reloadTime; -    MDRIVER_K_SCALE,      //float     knockbackScale; +    0,                    //int       reloadTime; +    LEVEL3_CLAW_K_SCALE,  //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode; -    qtrue,                //qboolean  canZoom; -    20.0f,                //float     zoomFov; -    qtrue,                //qboolean  purchasable; +    qfalse,               //qboolean  canZoom; +    90.0f,                //float     zoomFov; +    qfalse,               //qboolean  purchasable; +    qfalse,               //qboolean  longRanged; +    TEAM_ALIENS           //team_t    team; +  }, +  { +    WP_ALEVEL3_UPG,       //int       number; +    0,                    //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages; +    SLOT_WEAPON,          //int       slots; +    "level3upg",          //char      *name; +    "Pounce (upgrade)",   //char      *humanName; +    "", +    3,                    //int       maxAmmo; +    0,                    //int       maxClips; +    qtrue,                //int       infiniteAmmo; +    qfalse,               //int       usesEnergy; +    LEVEL3_CLAW_U_REPEAT, //int       repeatRate1; +    0,                    //int       repeatRate2; +    LEVEL3_BOUNCEBALL_REPEAT, //int   repeatRate3; +    0,                    //int       reloadTime; +    LEVEL3_CLAW_U_K_SCALE, //float    knockbackScale; +    qfalse,               //qboolean  hasAltMode; +    qtrue,                //qboolean  hasThirdMode; +    qfalse,               //qboolean  canZoom; +    90.0f,                //float     zoomFov; +    qfalse,               //qboolean  purchasable;      qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_PULSE_RIFLE,       //int       weaponNum; -    PRIFLE_PRICE,         //int       price; -    ( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_ALEVEL4,           //int       number; +    0,                    //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "prifle",             //char      *weaponName; -    "Pulse Rifle",        //char      *weaponHumanName; -    PRIFLE_CLIPS,         //int       maxAmmo; -    PRIFLE_MAXCLIPS,      //int       maxClips; -    qfalse,               //int       infiniteAmmo; -    qtrue,                //int       usesEnergy; -    PRIFLE_REPEAT,        //int       repeatRate1; +    "level4",             //char      *name; +    "Charge",             //char      *humanName; +    "", +    0,                    //int       maxAmmo; +    0,                    //int       maxClips; +    qtrue,                //int       infiniteAmmo; +    qfalse,               //int       usesEnergy; +    LEVEL4_CLAW_REPEAT,   //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3; -    PRIFLE_RELOAD,        //int       reloadTime; -    PRIFLE_K_SCALE,       //float     knockbackScale; +    0,                    //int       reloadTime; +    LEVEL4_CLAW_K_SCALE,  //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qtrue,                //qboolean  purchasable; -    qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    qfalse,               //qboolean  purchasable; +    qfalse,               //qboolean  longRanged; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_LUCIFER_CANNON,    //int       weaponNum; -    LCANNON_PRICE,        //int       price; -    ( 1 << S3 ),          //int  stages -    SLOT_WEAPON,          //int       slots; -    "lcannon",            //char      *weaponName; -    "Lucifer Cannon",     //char      *weaponHumanName; -    LCANNON_AMMO,         //int       maxAmmo; +    WP_BLASTER,           //int       number; +    0,                    //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages; +    0,                    //int       slots; +    "blaster",            //char      *name; +    "Blaster",            //char      *humanName; +    "", +    0,                    //int       maxAmmo;      0,                    //int       maxClips; -    qfalse,               //int       infiniteAmmo; -    qtrue,                //int       usesEnergy; -    LCANNON_REPEAT,       //int       repeatRate1; -    LCANNON_CHARGEREPEAT, //int       repeatRate2; +    qtrue,                //int       infiniteAmmo; +    qfalse,               //int       usesEnergy; +    BLASTER_REPEAT,       //int       repeatRate1; +    0,                    //int       repeatRate2;      0,                    //int       repeatRate3; -    LCANNON_RELOAD,       //int       reloadTime; -    LCANNON_K_SCALE,      //float     knockbackScale; -    qtrue,                //qboolean  hasAltMode; +    0,                    //int       reloadTime; +    BLASTER_K_SCALE,      //float     knockbackScale; +    qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qtrue,                //qboolean  purchasable; +    qfalse,               //qboolean  purchasable;      qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_LAS_GUN,           //int       weaponNum; -    LASGUN_PRICE,         //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_MACHINEGUN,        //int       number; +    RIFLE_PRICE,          //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "lgun",               //char      *weaponName; -    "Las Gun",            //char      *weaponHumanName; -    LASGUN_AMMO,          //int       maxAmmo; -    0,                    //int       maxClips; +    "rifle",              //char      *name; +    "Rifle",              //char      *humanName; +    "Basic weapon. Cased projectile weapon, with a slow clip based " +      "reload system.", +    RIFLE_CLIPSIZE,       //int       maxAmmo; +    RIFLE_MAXCLIPS,       //int       maxClips;      qfalse,               //int       infiniteAmmo; -    qtrue,                //int       usesEnergy; -    LASGUN_REPEAT,        //int       repeatRate1; +    qfalse,               //int       usesEnergy; +    RIFLE_REPEAT,         //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3; -    LASGUN_RELOAD,        //int       reloadTime; -    LASGUN_K_SCALE,       //float     knockbackScale; +    RIFLE_RELOAD,         //int       reloadTime; +    RIFLE_K_SCALE,        //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov;      qtrue,                //qboolean  purchasable;      qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_PAIN_SAW,          //int       weaponNum; +    WP_PAIN_SAW,          //int       number;      PAINSAW_PRICE,        //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "psaw",               //char      *weaponName; -    "Pain Saw",           //char      *weaponHumanName; +    "psaw",               //char      *name; +    "Pain Saw",           //char      *humanName; +    "Similar to a chainsaw, but instead of a chain it has an " +      "electric arc capable of dealing a great deal of damage at " +      "close range.",      0,                    //int       maxAmmo;      0,                    //int       maxClips;      qtrue,                //int       infiniteAmmo; @@ -3319,1173 +1986,609 @@ weaponAttributes_t bg_weapons[ ] =      90.0f,                //float     zoomFov;      qtrue,                //qboolean  purchasable;      qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_GRENADE,           //int       weaponNum; -    GRENADE_PRICE,        //int       price; -    ( 1 << S2 )|( 1 << S3 ), //int  stages -    SLOT_NONE,            //int       slots; -    "grenade",            //char      *weaponName; -    "Grenade",            //char      *weaponHumanName; -    1,                    //int       maxAmmo; -    0,                    //int       maxClips; +    WP_SHOTGUN,           //int       number; +    SHOTGUN_PRICE,        //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages; +    SLOT_WEAPON,          //int       slots; +    "shotgun",            //char      *name; +    "Shotgun",            //char      *humanName; +    "Close range weapon that is useful against larger foes. " +      "It has a slow repeat rate, but can be devastatingly " +      "effective.", +    SHOTGUN_SHELLS,       //int       maxAmmo; +    SHOTGUN_MAXCLIPS,     //int       maxClips;      qfalse,               //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    GRENADE_REPEAT,       //int       repeatRate1; +    SHOTGUN_REPEAT,       //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3; -    0,                    //int       reloadTime; -    GRENADE_K_SCALE,      //float     knockbackScale; +    SHOTGUN_RELOAD,       //int       reloadTime; +    SHOTGUN_K_SCALE,      //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qfalse,               //qboolean  purchasable; -    qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    qtrue,                //qboolean  purchasable; +    qtrue,                //qboolean  longRanged; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_HBUILD,            //int       weaponNum; -    HBUILD_PRICE,         //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_LAS_GUN,           //int       number; +    LASGUN_PRICE,         //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "ckit",               //char      *weaponName; -    "Construction Kit",   //char      *weaponHumanName; -    0,                    //int       maxAmmo; +    "lgun",               //char      *name; +    "Las Gun",            //char      *humanName; +    "Slightly more powerful than the basic rifle, rapidly fires " +      "small packets of energy.", +    LASGUN_AMMO,          //int       maxAmmo;      0,                    //int       maxClips; -    qtrue,                //int       infiniteAmmo; -    qfalse,               //int       usesEnergy; -    HBUILD_REPEAT,        //int       repeatRate1; -    HBUILD_REPEAT,        //int       repeatRate2; +    qfalse,               //int       infiniteAmmo; +    qtrue,                //int       usesEnergy; +    LASGUN_REPEAT,        //int       repeatRate1; +    0,                    //int       repeatRate2;      0,                    //int       repeatRate3; -    0,                    //int       reloadTime; -    0.0f,                 //float     knockbackScale; -    qtrue,                //qboolean  hasAltMode; +    LASGUN_RELOAD,        //int       reloadTime; +    LASGUN_K_SCALE,       //float     knockbackScale; +    qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov;      qtrue,                //qboolean  purchasable; -    qfalse,               //qboolean  longRanged; -    HBUILD_DELAY,         //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    qtrue,                //qboolean  longRanged; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_HBUILD2,           //int       weaponNum; -    HBUILD2_PRICE,        //int       price; -    ( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_MASS_DRIVER,       //int       number; +    MDRIVER_PRICE,        //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "ackit",              //char      *weaponName; -    "Adv Construction Kit",//char      *weaponHumanName; -    0,                    //int       maxAmmo; -    0,                    //int       maxClips; -    qtrue,                //int       infiniteAmmo; -    qfalse,               //int       usesEnergy; -    HBUILD2_REPEAT,       //int       repeatRate1; -    HBUILD2_REPEAT,       //int       repeatRate2; +    "mdriver",            //char      *name; +    "Mass Driver",        //char      *humanName; +    "A portable particle accelerator which causes minor nuclear " +      "reactions at the point of impact. It has a very large " +      "payload, but fires slowly.", +    MDRIVER_CLIPSIZE,     //int       maxAmmo; +    MDRIVER_MAXCLIPS,     //int       maxClips; +    qfalse,               //int       infiniteAmmo; +    qtrue,                //int       usesEnergy; +    MDRIVER_REPEAT,       //int       repeatRate1; +    0,                    //int       repeatRate2;      0,                    //int       repeatRate3; -    0,                    //int       reloadTime; -    0.0f,                 //float     knockbackScale; -    qtrue,                //qboolean  hasAltMode; +    MDRIVER_RELOAD,       //int       reloadTime; +    MDRIVER_K_SCALE,      //float     knockbackScale; +    qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode; -    qfalse,               //qboolean  canZoom; -    90.0f,                //float     zoomFov; +    qtrue,                //qboolean  canZoom; +    20.0f,                //float     zoomFov;      qtrue,                //qboolean  purchasable; -    qfalse,               //qboolean  longRanged; -    HBUILD2_DELAY,        //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    qtrue,                //qboolean  longRanged; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_ABUILD,            //int       weaponNum; -    0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_CHAINGUN,          //int       number; +    CHAINGUN_PRICE,       //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "abuild",             //char      *weaponName; -    "Alien build weapon", //char      *weaponHumanName; -    0,                    //int       maxAmmo; +    "chaingun",           //char      *name; +    "Chaingun",           //char      *humanName; +    "Belt drive, cased projectile weapon. It has a high repeat " +      "rate but a wide firing angle and is therefore relatively " +      "inaccurate.", +    CHAINGUN_BULLETS,     //int       maxAmmo;      0,                    //int       maxClips; -    qtrue,                //int       infiniteAmmo; +    qfalse,               //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    ABUILDER_BUILD_REPEAT,//int       repeatRate1; -    ABUILDER_BUILD_REPEAT,//int       repeatRate2; +    CHAINGUN_REPEAT,      //int       repeatRate1; +    0,                    //int       repeatRate2;      0,                    //int       repeatRate3;      0,                    //int       reloadTime; -    0.0f,                 //float     knockbackScale; -    qtrue,                //qboolean  hasAltMode; +    CHAINGUN_K_SCALE,     //float     knockbackScale; +    qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov;      qtrue,                //qboolean  purchasable; -    qfalse,               //qboolean  longRanged; -    ABUILDER_BASE_DELAY,  //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; -  }, -  { -    WP_ABUILD2,           //int       weaponNum; -    0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages -    SLOT_WEAPON,          //int       slots; -    "abuildupg",          //char      *weaponName; -    "Alien build weapon2",//char      *weaponHumanName; -    0,                    //int       maxAmmo; -    0,                    //int       maxClips; -    qtrue,                //int       infiniteAmmo; -    qfalse,               //int       usesEnergy; -    ABUILDER_BUILD_REPEAT,//int       repeatRate1; -    ABUILDER_CLAW_REPEAT, //int       repeatRate2; -    ABUILDER_BLOB_REPEAT, //int       repeatRate3; -    0,                    //int       reloadTime; -    ABUILDER_CLAW_K_SCALE,//float     knockbackScale; -    qtrue,                //qboolean  hasAltMode; -    qtrue,                //qboolean  hasThirdMode; -    qfalse,               //qboolean  canZoom; -    90.0f,                //float     zoomFov; -    qtrue,                //qboolean  purchasable; -    qfalse,               //qboolean  longRanged; -    ABUILDER_ADV_DELAY,   //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    qtrue,                //qboolean  longRanged; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_ALEVEL0,           //int       weaponNum; -    0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_FLAMER,            //int       number; +    FLAMER_PRICE,         //int       price; +    ( 1 << S2 )|( 1 << S3 ), //int    stages;      SLOT_WEAPON,          //int       slots; -    "level0",             //char      *weaponName; -    "Bite",               //char      *weaponHumanName; -    0,                    //int       maxAmmo; +    "flamer",             //char      *name; +    "Flame Thrower",      //char      *humanName; +    "Sprays fire at its target. It is powered by compressed " +      "gas. The relatively low rate of fire means this weapon is most " +      "effective against static targets.", +    FLAMER_GAS,           //int       maxAmmo;      0,                    //int       maxClips; -    qtrue,                //int       infiniteAmmo; +    qfalse,               //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    LEVEL0_BITE_REPEAT,   //int       repeatRate1; +    FLAMER_REPEAT,        //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3;      0,                    //int       reloadTime; -    LEVEL0_BITE_K_SCALE,  //float     knockbackScale; +    FLAMER_K_SCALE,       //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qfalse,               //qboolean  purchasable; -    qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    qtrue,                //qboolean  purchasable; +    qtrue,                //qboolean  longRanged; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_ALEVEL1,           //int       weaponNum; -    0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_PULSE_RIFLE,       //int       number; +    PRIFLE_PRICE,         //int       price; +    ( 1 << S2 )|( 1 << S3 ), //int    stages;      SLOT_WEAPON,          //int       slots; -    "level1",             //char      *weaponName; -    "Claws",              //char      *weaponHumanName; -    0,                    //int       maxAmmo; -    0,                    //int       maxClips; -    qtrue,                //int       infiniteAmmo; -    qfalse,               //int       usesEnergy; -    LEVEL1_CLAW_REPEAT,   //int       repeatRate1; +    "prifle",             //char      *name; +    "Pulse Rifle",        //char      *humanName; +    "An energy weapon that fires rapid pulses of concentrated energy.", +    PRIFLE_CLIPS,         //int       maxAmmo; +    PRIFLE_MAXCLIPS,      //int       maxClips; +    qfalse,               //int       infiniteAmmo; +    qtrue,                //int       usesEnergy; +    PRIFLE_REPEAT,        //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3; -    0,                    //int       reloadTime; -    LEVEL1_CLAW_K_SCALE,  //float     knockbackScale; +    PRIFLE_RELOAD,        //int       reloadTime; +    PRIFLE_K_SCALE,       //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qfalse,               //qboolean  purchasable; -    qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    qtrue,                //qboolean  purchasable; +    qtrue,                //qboolean  longRanged; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_ALEVEL1_UPG,       //int       weaponNum; -    0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_LUCIFER_CANNON,    //int       number; +    LCANNON_PRICE,        //int       price; +    ( 1 << S3 ),          //int       stages;      SLOT_WEAPON,          //int       slots; -    "level1upg",          //char      *weaponName; -    "Claws Upgrade",      //char      *weaponHumanName; -    0,                    //int       maxAmmo; +    "lcannon",            //char      *name; +    "Lucifer Cannon",     //char      *humanName; +    "Blaster technology scaled up to deliver devastating power. " +      "Primary fire must be charged before firing. It has a quick " +      "secondary attack that does not require charging.", +    LCANNON_AMMO,         //int       maxAmmo;      0,                    //int       maxClips; -    qtrue,                //int       infiniteAmmo; -    qfalse,               //int       usesEnergy; -    LEVEL1_CLAW_U_REPEAT, //int       repeatRate1; -    LEVEL1_PCLOUD_REPEAT, //int       repeatRate2; +    qfalse,               //int       infiniteAmmo; +    qtrue,                //int       usesEnergy; +    LCANNON_REPEAT,       //int       repeatRate1; +    LCANNON_SECONDARY_REPEAT, //int   repeatRate2;      0,                    //int       repeatRate3; -    0,                    //int       reloadTime; -    LEVEL1_CLAW_U_K_SCALE,//float     knockbackScale; +    LCANNON_RELOAD,       //int       reloadTime; +    LCANNON_K_SCALE,      //float     knockbackScale;      qtrue,                //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qfalse,               //qboolean  purchasable; +    qtrue,                //qboolean  purchasable;      qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_ALEVEL2,           //int       weaponNum; -    0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages -    SLOT_WEAPON,          //int       slots; -    "level2",             //char      *weaponName; -    "Bite",               //char      *weaponHumanName; -    0,                    //int       maxAmmo; +    WP_GRENADE,           //int       number; +    GRENADE_PRICE,        //int       price; +    ( 1 << S2 )|( 1 << S3 ), //int    stages; +    SLOT_NONE,            //int       slots; +    "grenade",            //char      *name; +    "Grenade",            //char      *humanName; +    "", +    1,                    //int       maxAmmo;      0,                    //int       maxClips; -    qtrue,                //int       infiniteAmmo; +    qfalse,               //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    LEVEL2_CLAW_REPEAT,   //int       repeatRate1; +    GRENADE_REPEAT,       //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3;      0,                    //int       reloadTime; -    LEVEL2_CLAW_K_SCALE,        //float     knockbackScale; +    GRENADE_K_SCALE,      //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov;      qfalse,               //qboolean  purchasable;      qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_ALEVEL2_UPG,       //int       weaponNum; +    WP_LOCKBLOB_LAUNCHER, //int       number;      0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "level2upg",          //char      *weaponName; -    "Zap",                //char      *weaponHumanName; +    "lockblob",           //char      *name; +    "Lock Blob",          //char      *humanName; +    "",      0,                    //int       maxAmmo;      0,                    //int       maxClips;      qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    LEVEL2_CLAW_U_REPEAT, //int       repeatRate1; -    LEVEL2_AREAZAP_REPEAT,//int       repeatRate2; -    0,                    //int       repeatRate3; +    500,                  //int       repeatRate1; +    500,                  //int       repeatRate2; +    500,                  //int       repeatRate3;      0,                    //int       reloadTime; -    LEVEL2_CLAW_U_K_SCALE,//float     knockbackScale; -    qtrue,                //qboolean  hasAltMode; +    LOCKBLOB_K_SCALE,     //float     knockbackScale; +    qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov;      qfalse,               //qboolean  purchasable;      qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_ALEVEL3,           //int       weaponNum; +    WP_HIVE,              //int       number;      0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "level3",             //char      *weaponName; -    "Pounce",             //char      *weaponHumanName; +    "hive",               //char      *name; +    "Hive",               //char      *humanName; +    "",      0,                    //int       maxAmmo;      0,                    //int       maxClips;      qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    LEVEL3_CLAW_REPEAT,   //int       repeatRate1; -    0,                    //int       repeatRate2; -    0,                    //int       repeatRate3; +    500,                  //int       repeatRate1; +    500,                  //int       repeatRate2; +    500,                  //int       repeatRate3;      0,                    //int       reloadTime; -    LEVEL3_CLAW_K_SCALE,  //float     knockbackScale; +    HIVE_K_SCALE,         //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov;      qfalse,               //qboolean  purchasable;      qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_ALEVEL3_UPG,       //int       weaponNum; +    WP_TESLAGEN,          //int       number;      0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "level3upg",          //char      *weaponName; -    "Pounce (upgrade)",   //char      *weaponHumanName; -    3,                    //int       maxAmmo; +    "teslagen",           //char      *name; +    "Tesla Generator",    //char      *humanName; +    "", +    0,                    //int       maxAmmo;      0,                    //int       maxClips;      qtrue,                //int       infiniteAmmo; -    qfalse,               //int       usesEnergy; -    LEVEL3_CLAW_U_REPEAT, //int       repeatRate1; -    0,                    //int       repeatRate2; -    LEVEL3_BOUNCEBALL_REPEAT,//int       repeatRate3; +    qtrue,                //int       usesEnergy; +    500,                  //int       repeatRate1; +    500,                  //int       repeatRate2; +    500,                  //int       repeatRate3;      0,                    //int       reloadTime; -    LEVEL3_CLAW_U_K_SCALE,//float     knockbackScale; +    TESLAGEN_K_SCALE,     //float     knockbackScale;      qfalse,               //qboolean  hasAltMode; -    qtrue,                //qboolean  hasThirdMode; +    qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov;      qfalse,               //qboolean  purchasable; -    qtrue,                //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    qfalse,               //qboolean  longRanged; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_ALEVEL4,           //int       weaponNum; +    WP_MGTURRET,          //int       number;      0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "level4",             //char      *weaponName; -    "Charge",             //char      *weaponHumanName; +    "mgturret",           //char      *name; +    "Machinegun Turret",  //char      *humanName; +    "",      0,                    //int       maxAmmo;      0,                    //int       maxClips;      qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    LEVEL4_CLAW_REPEAT,   //int       repeatRate1; +    0,                    //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3;      0,                    //int       reloadTime; -    LEVEL4_CLAW_K_SCALE,  //float     knockbackScale; +    MGTURRET_K_SCALE,     //float     knockbackScale;      qfalse,               //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov;      qfalse,               //qboolean  purchasable;      qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    TEAM_HUMANS           //team_t    team;    },    { -    WP_LOCKBLOB_LAUNCHER, //int       weaponNum; +    WP_ABUILD,            //int       number;      0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "lockblob",           //char      *weaponName; -    "Lock Blob",          //char      *weaponHumanName; +    "abuild",             //char      *name; +    "Alien build weapon", //char      *humanName; +    "",      0,                    //int       maxAmmo;      0,                    //int       maxClips;      qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    500,                  //int       repeatRate1; -    500,                  //int       repeatRate2; -    500,                  //int       repeatRate3; +    ABUILDER_BUILD_REPEAT, //int      repeatRate1; +    ABUILDER_CLAW_REPEAT, //int       repeatRate2; +    0,                    //int       repeatRate3;      0,                    //int       reloadTime; -    LOCKBLOB_K_SCALE,     //float     knockbackScale; -    qfalse,               //qboolean  hasAltMode; +    ABUILDER_CLAW_K_SCALE, //float    knockbackScale; +    qtrue,                //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qfalse,               //qboolean  purchasable; +    qtrue,                //qboolean  purchasable;      qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_HIVE,              //int       weaponNum; +    WP_ABUILD2,           //int       number;      0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "hive",               //char      *weaponName; -    "Hive",               //char      *weaponHumanName; +    "abuildupg",          //char      *name; +    "Alien build weapon2", //char     *humanName; +    "",      0,                    //int       maxAmmo;      0,                    //int       maxClips;      qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    500,                  //int       repeatRate1; -    500,                  //int       repeatRate2; -    500,                  //int       repeatRate3; +    ABUILDER_BUILD_REPEAT, //int      repeatRate1; +    ABUILDER_CLAW_REPEAT, //int       repeatRate2; +    ABUILDER_BLOB_REPEAT, //int       repeatRate3;      0,                    //int       reloadTime; -    HIVE_K_SCALE,         //float     knockbackScale; -    qfalse,               //qboolean  hasAltMode; -    qfalse,               //qboolean  hasThirdMode; +    ABUILDER_CLAW_K_SCALE, //float    knockbackScale; +    qtrue,                //qboolean  hasAltMode; +    qtrue,                //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qfalse,               //qboolean  purchasable; +    qtrue,                //qboolean  purchasable;      qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_ALIENS            //WUTeam_t  team; +    TEAM_ALIENS           //team_t    team;    },    { -    WP_MGTURRET,          //int       weaponNum; -    0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    WP_HBUILD,            //int       number; +    HBUILD_PRICE,         //int       price; +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_WEAPON,          //int       slots; -    "mgturret",           //char      *weaponName; -    "Machinegun Turret",  //char      *weaponHumanName; +    "ckit",               //char      *name; +    "Construction Kit",   //char      *humanName; +    "Used for building structures. This includes " +      "spawns, power and basic defense. More structures become " +      "available with new stages.",      0,                    //int       maxAmmo;      0,                    //int       maxClips;      qtrue,                //int       infiniteAmmo;      qfalse,               //int       usesEnergy; -    0,                    //int       repeatRate1; +    HBUILD_REPEAT,        //int       repeatRate1;      0,                    //int       repeatRate2;      0,                    //int       repeatRate3;      0,                    //int       reloadTime; -    MGTURRET_K_SCALE,     //float     knockbackScale; -    qfalse,               //qboolean  hasAltMode; -    qfalse,               //qboolean  hasThirdMode; -    qfalse,               //qboolean  canZoom; -    90.0f,                //float     zoomFov; -    qfalse,               //qboolean  purchasable; -    qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; -  }, -  { -    WP_TESLAGEN,          //int       weaponNum; -    0,                    //int       price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages -    SLOT_WEAPON,          //int       slots; -    "teslagen",           //char      *weaponName; -    "Tesla Generator",    //char      *weaponHumanName; -    0,                    //int       maxAmmo; -    0,                    //int       maxClips; -    qtrue,                //int       infiniteAmmo; -    qtrue,                //int       usesEnergy; -    500,                  //int       repeatRate1; -    500,                  //int       repeatRate2; -    500,                  //int       repeatRate3; -    0,                    //int       reloadTime; -    TESLAGEN_K_SCALE,     //float     knockbackScale; -    qfalse,               //qboolean  hasAltMode; +    0.0f,                 //float     knockbackScale; +    qtrue,                //qboolean  hasAltMode;      qfalse,               //qboolean  hasThirdMode;      qfalse,               //qboolean  canZoom;      90.0f,                //float     zoomFov; -    qfalse,               //qboolean  purchasable; +    qtrue,                //qboolean  purchasable;      qfalse,               //qboolean  longRanged; -    0,                    //int       buildDelay; -    WUT_HUMANS            //WUTeam_t  team; +    TEAM_HUMANS           //team_t    team;    }  }; -int   bg_numWeapons = sizeof( bg_weapons ) / sizeof( bg_weapons[ 0 ] ); - -/* -============== -BG_FindPriceForWeapon -============== -*/ -int BG_FindPriceForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].price; -    } -  } +size_t bg_numWeapons = ARRAY_LEN( bg_weapons ); -  return 100; -} +static const weaponAttributes_t nullWeapon = { 0 };  /*  ============== -BG_FindStagesForWeapon +BG_WeaponByName  ==============  */ -qboolean BG_FindStagesForWeapon( int weapon, stage_t stage ) +const weaponAttributes_t *BG_WeaponByName( const char *name )  {    int i;    for( i = 0; i < bg_numWeapons; i++ )    { -    if( bg_weapons[ i ].weaponNum == weapon ) +    if( !Q_stricmp( bg_weapons[ i ].name, name ) )      { -      if( bg_weapons[ i ].stages & ( 1 << stage ) ) -        return qtrue; -      else -        return qfalse; +      return &bg_weapons[ i ];      }    } -  return qfalse; -} - -/* -============== -BG_FindSlotsForWeapon -============== -*/ -int BG_FindSlotsForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].slots; -    } -  } - -  return SLOT_WEAPON; -} - -/* -============== -BG_FindNameForWeapon -============== -*/ -char *BG_FindNameForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -      return bg_weapons[ i ].weaponName; -  } - -  //wimp out -  return 0; +  return &nullWeapon;  }  /*  ============== -BG_FindWeaponNumForName +BG_Weapon  ==============  */ -int BG_FindWeaponNumForName( char *name ) +const weaponAttributes_t *BG_Weapon( weapon_t weapon )  { -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( !Q_stricmp( bg_weapons[ i ].weaponName, name ) ) -      return bg_weapons[ i ].weaponNum; -  } - -  //wimp out -  return WP_NONE; +  return ( weapon > WP_NONE && weapon < WP_NUM_WEAPONS ) ? +    &bg_weapons[ weapon - 1 ] : &nullWeapon;  }  /*  ============== -BG_FindHumanNameForWeapon +BG_WeaponAllowedInStage  ==============  */ -char *BG_FindHumanNameForWeapon( int weapon ) +qboolean BG_WeaponAllowedInStage( weapon_t weapon, stage_t stage )  { -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -      return bg_weapons[ i ].weaponHumanName; -  } +  int stages = BG_Weapon( weapon )->stages; -  //wimp out -  return 0; -} - -/* -============== -BG_FindAmmoForWeapon -============== -*/ -void BG_FindAmmoForWeapon( int weapon, int *maxAmmo, int *maxClips ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      if( maxAmmo != NULL ) -        *maxAmmo = bg_weapons[ i ].maxAmmo; -      if( maxClips != NULL ) -        *maxClips = bg_weapons[ i ].maxClips; - -      //no need to keep going -      break; -    } -  } -} - -/* -============== -BG_FindInfinteAmmoForWeapon -============== -*/ -qboolean BG_FindInfinteAmmoForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].infiniteAmmo; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindUsesEnergyForWeapon -============== -*/ -qboolean BG_FindUsesEnergyForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].usesEnergy; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindRepeatRate1ForWeapon -============== -*/ -int BG_FindRepeatRate1ForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -      return bg_weapons[ i ].repeatRate1; -  } - -  return 1000; -} - -/* -============== -BG_FindRepeatRate2ForWeapon -============== -*/ -int BG_FindRepeatRate2ForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -      return bg_weapons[ i ].repeatRate2; -  } - -  return 1000; -} - -/* -============== -BG_FindRepeatRate3ForWeapon -============== -*/ -int BG_FindRepeatRate3ForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -      return bg_weapons[ i ].repeatRate3; -  } - -  return 1000; -} - -/* -============== -BG_FindReloadTimeForWeapon -============== -*/ -int BG_FindReloadTimeForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].reloadTime; -    } -  } - -  return 1000; -} - -/* -============== -BG_FindKnockbackScaleForWeapon -============== -*/ -float BG_FindKnockbackScaleForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].knockbackScale; -    } -  } - -  return 1.0f; -} - -/* -============== -BG_WeaponHasAltMode -============== -*/ -qboolean BG_WeaponHasAltMode( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].hasAltMode; -    } -  } - -  return qfalse; -} - -/* -============== -BG_WeaponHasThirdMode -============== -*/ -qboolean BG_WeaponHasThirdMode( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].hasThirdMode; -    } -  } - -  return qfalse; -} - -/* -============== -BG_WeaponCanZoom -============== -*/ -qboolean BG_WeaponCanZoom( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].canZoom; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindZoomFovForWeapon -============== -*/ -float BG_FindZoomFovForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].zoomFov; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindPurchasableForWeapon -============== -*/ -qboolean BG_FindPurchasableForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].purchasable; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindLongRangeForWeapon -============== -*/ -qboolean BG_FindLongRangedForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].longRanged; -    } -  } - -  return qfalse; -} - -/* -============== -BG_FindBuildDelayForWeapon -============== -*/ -int BG_FindBuildDelayForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].buildDelay; -    } -  } - -  return 0; -} - -/* -============== -BG_FindTeamForWeapon -============== -*/ -WUTeam_t BG_FindTeamForWeapon( int weapon ) -{ -  int i; - -  for( i = 0; i < bg_numWeapons; i++ ) -  { -    if( bg_weapons[ i ].weaponNum == weapon ) -    { -      return bg_weapons[ i ].team; -    } -  } - -  return WUT_NONE; +  return stages & ( 1 << stage );  }  //////////////////////////////////////////////////////////////////////////////// -upgradeAttributes_t bg_upgrades[ ] = +static const upgradeAttributes_t bg_upgrades[ ] =  {    { -    UP_LIGHTARMOUR,         //int   upgradeNum; +    UP_LIGHTARMOUR,         //int   number;      LIGHTARMOUR_PRICE,      //int   price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_TORSO|SLOT_ARMS|SLOT_LEGS, //int   slots; -    "larmour",              //char  *upgradeName; -    "Light Armour",         //char  *upgradeHumanName; +    "larmour",              //char  *name; +    "Light Armour",         //char  *humanName; +    "Protective armour that helps to defend against light alien melee " +      "attacks.",      "icons/iconu_larmour", -    qtrue,                  //qboolean purchasable -    qfalse,                 //qboolean usable -    WUT_HUMANS              //WUTeam_t  team; +    qtrue,                  //qboolean  purchasable; +    qfalse,                 //qboolean  usable; +    TEAM_HUMANS             //team_t    team;    },    { -    UP_HELMET,              //int   upgradeNum; +    UP_HELMET,              //int   number;      HELMET_PRICE,           //int   price; -    ( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_HEAD,              //int   slots; -    "helmet",               //char  *upgradeName; -    "Helmet",               //char  *upgradeHumanName; +    "helmet",               //char  *name; +    "Helmet",               //char  *humanName; +    "In addition to protecting your head, the helmet provides a " +      "scanner indicating the presence of any friendly or hostile " +      "lifeforms and structures in your immediate vicinity.",      "icons/iconu_helmet", -    qtrue,                  //qboolean purchasable -    qfalse,                 //qboolean usable -    WUT_HUMANS              //WUTeam_t  team; +    qtrue,                  //qboolean  purchasable; +    qfalse,                 //qboolean  usable; +    TEAM_HUMANS             //team_t    team;    },    { -    UP_MEDKIT,              //int   upgradeNum; +    UP_MEDKIT,              //int   number;      MEDKIT_PRICE,           //int   price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_NONE,              //int   slots; -    "medkit",               //char  *upgradeName; -    "Medkit",               //char  *upgradeHumanName; +    "medkit",               //char  *name; +    "Medkit",               //char  *humanName; +    "",      "icons/iconu_atoxin", -    qfalse,                 //qboolean purchasable -    qtrue,                  //qboolean usable -    WUT_HUMANS              //WUTeam_t  team; +    qfalse,                 //qboolean  purchasable; +    qtrue,                  //qboolean  usable; +    TEAM_HUMANS             //team_t    team;    },    { -    UP_BATTPACK,            //int   upgradeNum; +    UP_BATTPACK,            //int   number;      BATTPACK_PRICE,         //int   price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_BACKPACK,          //int   slots; -    "battpack",             //char  *upgradeName; -    "Battery Pack",         //char  *upgradeHumanName; +    "battpack",             //char  *name; +    "Battery Pack",         //char  *humanName; +    "Back-mounted battery pack that permits storage of one and a half " +      "times the normal energy capacity for energy weapons.",      "icons/iconu_battpack", -    qtrue,                  //qboolean purchasable -    qfalse,                 //qboolean usable -    WUT_HUMANS              //WUTeam_t  team; +    qtrue,                  //qboolean  purchasable; +    qfalse,                 //qboolean  usable; +    TEAM_HUMANS             //team_t    team;    },    { -    UP_JETPACK,             //int   upgradeNum; +    UP_JETPACK,             //int   number;      JETPACK_PRICE,          //int   price; -    ( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_BACKPACK,          //int   slots; -    "jetpack",              //char  *upgradeName; -    "Jet Pack",             //char  *upgradeHumanName; +    "jetpack",              //char  *name; +    "Jet Pack",             //char  *humanName; +    "Back-mounted jet pack that enables the user to fly to remote " +      "locations. It is very useful against alien spawns in hard " +      "to reach spots.",      "icons/iconu_jetpack", -    qtrue,                  //qboolean purchasable -    qtrue,                  //qboolean usable -    WUT_HUMANS              //WUTeam_t  team; +    qtrue,                  //qboolean  purchasable; +    qtrue,                  //qboolean  usable; +    TEAM_HUMANS             //team_t    team;    },    { -    UP_BATTLESUIT,          //int   upgradeNum; +    UP_BATTLESUIT,          //int   number;      BSUIT_PRICE,            //int   price; -    ( 1 << S3 ),            //int  stages -    SLOT_HEAD|SLOT_TORSO|SLOT_ARMS|SLOT_LEGS|SLOT_BACKPACK, //int   slots; -    "bsuit",                //char  *upgradeName; -    "Battlesuit",           //char  *upgradeHumanName; +    ( 1 << S3 ),            //int   stages; +    SLOT_HEAD|SLOT_TORSO|SLOT_ARMS|SLOT_LEGS|SLOT_BACKPACK, //int  slots; +    "bsuit",                //char  *name; +    "Battlesuit",           //char  *humanName; +    "A full body armour that is highly effective at repelling alien attacks. " +      "It allows the user to enter hostile situations with a greater degree " +      "of confidence.",      "icons/iconu_bsuit", -    qtrue,                  //qboolean purchasable -    qfalse,                 //qboolean usable -    WUT_HUMANS              //WUTeam_t  team; +    qtrue,                  //qboolean  purchasable; +    qfalse,                 //qboolean  usable; +    TEAM_HUMANS             //team_t    team;    },    { -    UP_GRENADE,             //int   upgradeNum; +    UP_GRENADE,             //int   number;      GRENADE_PRICE,          //int   price; -    ( 1 << S2 )|( 1 << S3 ),//int  stages +    ( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_NONE,              //int   slots; -    "gren",                 //char  *upgradeName; -    "Grenade",              //char  *upgradeHumanName; +    "gren",                 //char  *name; +    "Grenade",              //char  *humanName; +    "A small incendinary device ideal for damaging tightly packed " +      "alien structures. Has a five second timer.",      0, -    qtrue,                  //qboolean purchasable -    qtrue,                  //qboolean usable -    WUT_HUMANS              //WUTeam_t  team; +    qtrue,                  //qboolean  purchasable; +    qtrue,                  //qboolean  usable; +    TEAM_HUMANS             //team_t    team;    },    { -    UP_AMMO,                //int   upgradeNum; +    UP_AMMO,                //int   number;      0,                      //int   price; -    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages +    ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int  stages;      SLOT_NONE,              //int   slots; -    "ammo",                 //char  *upgradeName; -    "Ammunition",           //char  *upgradeHumanName; +    "ammo",                 //char  *name; +    "Ammunition",           //char  *humanName; +    "Ammunition for the currently held weapon.",      0, -    qtrue,                  //qboolean purchasable -    qfalse,                 //qboolean usable -    WUT_HUMANS              //WUTeam_t  team; +    qtrue,                  //qboolean  purchasable; +    qfalse,                 //qboolean  usable; +    TEAM_HUMANS             //team_t    team;    }  }; -int   bg_numUpgrades = sizeof( bg_upgrades ) / sizeof( bg_upgrades[ 0 ] ); - -/* -============== -BG_FindPriceForUpgrade -============== -*/ -int BG_FindPriceForUpgrade( int upgrade ) -{ -  int i; - -  for( i = 0; i < bg_numUpgrades; i++ ) -  { -    if( bg_upgrades[ i ].upgradeNum == upgrade ) -    { -      return bg_upgrades[ i ].price; -    } -  } +size_t bg_numUpgrades = ARRAY_LEN( bg_upgrades ); -  return 100; -} +static const upgradeAttributes_t nullUpgrade = { 0 };  /*  ============== -BG_FindStagesForUpgrade +BG_UpgradeByName  ==============  */ -qboolean BG_FindStagesForUpgrade( int upgrade, stage_t stage ) +const upgradeAttributes_t *BG_UpgradeByName( const char *name )  {    int i;    for( i = 0; i < bg_numUpgrades; i++ )    { -    if( bg_upgrades[ i ].upgradeNum == upgrade ) +    if( !Q_stricmp( bg_upgrades[ i ].name, name ) )      { -      if( bg_upgrades[ i ].stages & ( 1 << stage ) ) -        return qtrue; -      else -        return qfalse; +      return &bg_upgrades[ i ];      }    } -  return qfalse; +  return &nullUpgrade;  }  /*  ============== -BG_FindSlotsForUpgrade +BG_Upgrade  ==============  */ -int BG_FindSlotsForUpgrade( int upgrade ) +const upgradeAttributes_t *BG_Upgrade( upgrade_t upgrade )  { -  int i; - -  for( i = 0; i < bg_numUpgrades; i++ ) -  { -    if( bg_upgrades[ i ].upgradeNum == upgrade ) -    { -      return bg_upgrades[ i ].slots; -    } -  } - -  return SLOT_NONE; +  return ( upgrade > UP_NONE && upgrade < UP_NUM_UPGRADES ) ? +    &bg_upgrades[ upgrade - 1 ] : &nullUpgrade;  }  /*  ============== -BG_FindNameForUpgrade +BG_UpgradeAllowedInStage  ==============  */ -char *BG_FindNameForUpgrade( int upgrade ) +qboolean BG_UpgradeAllowedInStage( upgrade_t upgrade, stage_t stage )  { -  int i; - -  for( i = 0; i < bg_numUpgrades; i++ ) -  { -    if( bg_upgrades[ i ].upgradeNum == upgrade ) -      return bg_upgrades[ i ].upgradeName; -  } +  int stages = BG_Upgrade( upgrade )->stages; -  //wimp out -  return 0; -} - -/* -============== -BG_FindUpgradeNumForName -============== -*/ -int BG_FindUpgradeNumForName( char *name ) -{ -  int i; - -  for( i = 0; i < bg_numUpgrades; i++ ) -  { -    if( !Q_stricmp( bg_upgrades[ i ].upgradeName, name ) ) -      return bg_upgrades[ i ].upgradeNum; -  } - -  //wimp out -  return UP_NONE; -} - -/* -============== -BG_FindHumanNameForUpgrade -============== -*/ -char *BG_FindHumanNameForUpgrade( int upgrade ) -{ -  int i; - -  for( i = 0; i < bg_numUpgrades; i++ ) -  { -    if( bg_upgrades[ i ].upgradeNum == upgrade ) -      return bg_upgrades[ i ].upgradeHumanName; -  } - -  //wimp out -  return 0; -} - -/* -============== -BG_FindIconForUpgrade -============== -*/ -char *BG_FindIconForUpgrade( int upgrade ) -{ -  int i; - -  for( i = 0; i < bg_numUpgrades; i++ ) -  { -    if( bg_upgrades[ i ].upgradeNum == upgrade ) -      return bg_upgrades[ i ].icon; -  } - -  //wimp out -  return 0; -} - -/* -============== -BG_FindPurchasableForUpgrade -============== -*/ -qboolean BG_FindPurchasableForUpgrade( int upgrade ) -{ -  int i; - -  for( i = 0; i < bg_numUpgrades; i++ ) -  { -    if( bg_upgrades[ i ].upgradeNum == upgrade ) -      return bg_upgrades[ i ].purchasable; -  } - -  return qfalse; -} - -/* -============== -BG_FindUsableForUpgrade -============== -*/ -qboolean BG_FindUsableForUpgrade( int upgrade ) -{ -  int i; - -  for( i = 0; i < bg_numUpgrades; i++ ) -  { -    if( bg_upgrades[ i ].upgradeNum == upgrade ) -      return bg_upgrades[ i ].usable; -  } - -  return qfalse; -} - -/* -============== -BG_FindTeamForUpgrade -============== -*/ -WUTeam_t BG_FindTeamForUpgrade( int upgrade ) -{ -  int i; - -  for( i = 0; i < bg_numUpgrades; i++ ) -  { -    if( bg_upgrades[ i ].upgradeNum == upgrade ) -    { -      return bg_upgrades[ i ].team; -    } -  } - -  return WUT_NONE; +  return stages & ( 1 << stage );  }  //////////////////////////////////////////////////////////////////////////////// @@ -4574,12 +2677,12 @@ void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t resu      case TR_SINE:        deltaTime = ( atTime - tr->trTime ) / (float)tr->trDuration;        phase = cos( deltaTime * M_PI * 2 );  // derivative of sin = cos -      phase *= 0.5; +      phase *= 2 * M_PI * 1000 / (float)tr->trDuration;        VectorScale( tr->trDelta, phase, result );        break;      case TR_LINEAR_STOP: -      if( atTime > tr->trTime + tr->trDuration ) +      if( atTime > tr->trTime + tr->trDuration || atTime < tr->trTime )        {          VectorClear( result );          return; @@ -4643,7 +2746,7 @@ char *eventnames[ ] =    "EV_FIRE_WEAPON2",    "EV_FIRE_WEAPON3", -  "EV_PLAYER_RESPAWN", //TA: for fovwarp effects +  "EV_PLAYER_RESPAWN", // for fovwarp effects    "EV_PLAYER_TELEPORT_IN",    "EV_PLAYER_TELEPORT_OUT", @@ -4656,6 +2759,7 @@ char *eventnames[ ] =    "EV_BULLET_HIT_WALL",    "EV_SHOTGUN", +  "EV_MASS_DRIVER",    "EV_MISSILE_HIT",    "EV_MISSILE_MISS", @@ -4664,8 +2768,8 @@ char *eventnames[ ] =    "EV_BULLET",        // otherEntity is the shooter    "EV_LEV1_GRAB", -  "EV_LEV4_CHARGE_PREPARE", -  "EV_LEV4_CHARGE_START", +  "EV_LEV4_TRAMPLE_PREPARE", +  "EV_LEV4_TRAMPLE_START",    "EV_PAIN",    "EV_DEATH1", @@ -4673,13 +2777,13 @@ char *eventnames[ ] =    "EV_DEATH3",    "EV_OBITUARY", -  "EV_GIB_PLAYER",      // gib a previously living player +  "EV_GIB_PLAYER", -  "EV_BUILD_CONSTRUCT", //TA -  "EV_BUILD_DESTROY",   //TA -  "EV_BUILD_DELAY",     //TA: can't build yet -  "EV_BUILD_REPAIR",    //TA: repairing buildable -  "EV_BUILD_REPAIRED",  //TA: buildable has full health +  "EV_BUILD_CONSTRUCT", +  "EV_BUILD_DESTROY", +  "EV_BUILD_DELAY",     // can't build yet +  "EV_BUILD_REPAIR",    // repairing buildable +  "EV_BUILD_REPAIRED",  // buildable has full health    "EV_HUMAN_BUILDABLE_EXPLOSION",    "EV_ALIEN_BUILDABLE_EXPLOSION",    "EV_ALIEN_ACIDTUBE", @@ -4693,17 +2797,33 @@ char *eventnames[ ] =    "EV_STOPLOOPINGSOUND",    "EV_TAUNT", -  "EV_OVERMIND_ATTACK", //TA: overmind under attack -  "EV_OVERMIND_DYING",  //TA: overmind close to death -  "EV_OVERMIND_SPAWNS", //TA: overmind needs spawns +  "EV_OVERMIND_ATTACK", // overmind under attack +  "EV_OVERMIND_DYING",  // overmind close to death +  "EV_OVERMIND_SPAWNS", // overmind needs spawns + +  "EV_DCC_ATTACK",      // dcc under attack -  "EV_DCC_ATTACK",      //TA: dcc under attack +  "EV_MGTURRET_SPINUP", // trigger a sound -  "EV_RPTUSE_SOUND"     //TA: trigger a sound +  "EV_RPTUSE_SOUND",    // trigger a sound +  "EV_LEV2_ZAP"  };  /*  =============== +BG_EventName +=============== +*/ +const char *BG_EventName( int num ) +{ +  if( num < 0 || num >= ARRAY_LEN( eventnames ) ) +    return "UNKNOWN"; + +  return eventnames[ num ]; +} + +/* +===============  BG_AddPredictableEventToPlayerstate  Handles the sequence numbers @@ -4714,19 +2834,21 @@ void  trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bu  void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps )  { -#ifdef _DEBUG +#ifdef DEBUG_EVENTS    {      char buf[ 256 ];      trap_Cvar_VariableStringBuffer( "showevents", buf, sizeof( buf ) );      if( atof( buf ) != 0 )      { -#ifdef QAGAME +#ifdef GAME        Com_Printf( " game event svt %5d -> %5d: num = %20s parm %d\n", -                  ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[ newEvent ], eventParm); +                  ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, +                  BG_EventName( newEvent ), eventParm );  #else        Com_Printf( "Cgame event svt %5d -> %5d: num = %20s parm %d\n", -                  ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[ newEvent ], eventParm); +                  ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, +                  BG_EventName( newEvent ), eventParm );  #endif      }    } @@ -4751,7 +2873,7 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean    if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_type == PM_FREEZE )      s->eType = ET_INVISIBLE; -  else if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) +  else if( ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT )      s->eType = ET_INVISIBLE;    else      s->eType = ET_PLAYER; @@ -4773,11 +2895,10 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean    if( snap )      SnapVector( s->apos.trBase ); -  //TA: i need for other things :) -  //s->angles2[YAW] = ps->movementDir;    s->time2 = ps->movementDir;    s->legsAnim = ps->legsAnim;    s->torsoAnim = ps->torsoAnim; +  s->weaponAnim = ps->weaponAnim;    s->clientNum = ps->clientNum;   // ET_PLAYER looks here instead of at number                      // so corpses can also reference the proper config    s->eFlags = ps->eFlags; @@ -4827,12 +2948,10 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean    }    // use misc field to store team/class info: -  s->misc = ps->stats[ STAT_PTEAM ] | ( ps->stats[ STAT_PCLASS ] << 8 ); +  s->misc = ps->stats[ STAT_TEAM ] | ( ps->stats[ STAT_CLASS ] << 8 ); -  //TA: have to get the surfNormal thru somehow... +  // have to get the surfNormal through somehow...    VectorCopy( ps->grapplePoint, s->angles2 ); -  if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) -    s->eFlags |= EF_WALLCLIMBCEILING;    s->loopSound = ps->loopSound;    s->generic1 = ps->generic1; @@ -4840,7 +2959,7 @@ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean    if( s->generic1 <= WPM_NONE || s->generic1 >= WPM_NUM_WEAPONMODES )      s->generic1 = WPM_PRIMARY; -  s->otherEntityNum = ps->otherEntityNum; +  s->otherEntityNum = ps->otherEntityNum;    } @@ -4858,7 +2977,7 @@ void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s    if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR || ps->pm_type == PM_FREEZE )      s->eType = ET_INVISIBLE; -  else if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) +  else if( ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT )      s->eType = ET_INVISIBLE;    else      s->eType = ET_PLAYER; @@ -4883,11 +3002,10 @@ void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s    if( snap )      SnapVector( s->apos.trBase ); -  //TA: i need for other things :) -  //s->angles2[YAW] = ps->movementDir;    s->time2 = ps->movementDir;    s->legsAnim = ps->legsAnim;    s->torsoAnim = ps->torsoAnim; +  s->weaponAnim = ps->weaponAnim;    s->clientNum = ps->clientNum;   // ET_PLAYER looks here instead of at number                      // so corpses can also reference the proper config    s->eFlags = ps->eFlags; @@ -4939,12 +3057,10 @@ void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s    }    // use misc field to store team/class info: -  s->misc = ps->stats[ STAT_PTEAM ] | ( ps->stats[ STAT_PCLASS ] << 8 ); +  s->misc = ps->stats[ STAT_TEAM ] | ( ps->stats[ STAT_CLASS ] << 8 ); -  //TA: have to get the surfNormal thru somehow... +  // have to get the surfNormal through somehow...    VectorCopy( ps->grapplePoint, s->angles2 ); -  if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) -    s->eFlags |= EF_WALLCLIMBCEILING;    s->loopSound = ps->loopSound;    s->generic1 = ps->generic1; @@ -4966,7 +3082,8 @@ qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int ammo, int clips )  {    int maxAmmo, maxClips; -  BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips ); +  maxAmmo = BG_Weapon( weapon )->maxAmmo; +  maxClips = BG_Weapon( weapon )->maxClips;    if( BG_InventoryContainsUpgrade( UP_BATTPACK, stats ) )      maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); @@ -4976,63 +3093,53 @@ qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int ammo, int clips )  /*  ======================== -BG_AddWeaponToInventory +BG_InventoryContainsWeapon -Give a player a weapon +Does the player hold a weapon?  ========================  */ -void BG_AddWeaponToInventory( int weapon, int stats[ ] ) +qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] )  { -  int  weaponList; - -  weaponList = ( stats[ STAT_WEAPONS ] & 0x0000FFFF ) | ( ( stats[ STAT_WEAPONS2 ] << 16 ) & 0xFFFF0000 ); - -  weaponList |= ( 1 << weapon ); - -  stats[ STAT_WEAPONS ] = weaponList & 0x0000FFFF; -  stats[ STAT_WEAPONS2 ] = ( weaponList & 0xFFFF0000 ) >> 16; - -  if( stats[ STAT_SLOTS ] & BG_FindSlotsForWeapon( weapon ) ) -    Com_Printf( S_COLOR_YELLOW "WARNING: Held items conflict with weapon %d\n", weapon ); +  // humans always have a blaster +  if( stats[ STAT_TEAM ] == TEAM_HUMANS && weapon == WP_BLASTER ) +    return qtrue; -  stats[ STAT_SLOTS ] |= BG_FindSlotsForWeapon( weapon ); +  return ( stats[ STAT_WEAPON ] == weapon );  }  /*  ======================== -BG_RemoveWeaponToInventory +BG_SlotsForInventory -Take a weapon from a player +Calculate the slots used by an inventory and warn of conflicts  ========================  */ -void BG_RemoveWeaponFromInventory( int weapon, int stats[ ] ) +int BG_SlotsForInventory( int stats[ ] )  { -  int  weaponList; +  int i, slot, slots; -  weaponList = ( stats[ STAT_WEAPONS ] & 0x0000FFFF ) | ( ( stats[ STAT_WEAPONS2 ] << 16 ) & 0xFFFF0000 ); +  slots = BG_Weapon( stats[ STAT_WEAPON ] )->slots; +  if( stats[ STAT_TEAM ] == TEAM_HUMANS ) +    slots |= BG_Weapon( WP_BLASTER )->slots; -  weaponList &= ~( 1 << weapon ); - -  stats[ STAT_WEAPONS ] = weaponList & 0x0000FFFF; -  stats[ STAT_WEAPONS2 ] = ( weaponList & 0xFFFF0000 ) >> 16; - -  stats[ STAT_SLOTS ] &= ~BG_FindSlotsForWeapon( weapon ); -} - -/* -======================== -BG_InventoryContainsWeapon +  for( i = UP_NONE; i < UP_NUM_UPGRADES; i++ ) +  { +    if( BG_InventoryContainsUpgrade( i, stats ) ) +    { +      slot = BG_Upgrade( i )->slots; -Does the player hold a weapon? -======================== -*/ -qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] ) -{ -  int  weaponList; +      // this check should never be true +      if( slots & slot ) +      { +        Com_Printf( S_COLOR_YELLOW "WARNING: held item %d conflicts with " +                    "inventory slot %d\n", i, slot ); +      } -  weaponList = ( stats[ STAT_WEAPONS ] & 0x0000FFFF ) | ( ( stats[ STAT_WEAPONS2 ] << 16 ) & 0xFFFF0000 ); +      slots |= slot; +    } +  } -  return( weaponList & ( 1 << weapon ) ); +  return slots;  }  /* @@ -5045,11 +3152,6 @@ Give the player an upgrade  void BG_AddUpgradeToInventory( int item, int stats[ ] )  {    stats[ STAT_ITEMS ] |= ( 1 << item ); - -  if( stats[ STAT_SLOTS ] & BG_FindSlotsForUpgrade( item ) ) -    Com_Printf( S_COLOR_YELLOW "WARNING: Held items conflict with upgrade %d\n", item ); - -  stats[ STAT_SLOTS ] |= BG_FindSlotsForUpgrade( item );  }  /* @@ -5062,8 +3164,6 @@ Take an upgrade from the player  void BG_RemoveUpgradeFromInventory( int item, int stats[ ] )  {    stats[ STAT_ITEMS ] &= ~( 1 << item ); - -  stats[ STAT_SLOTS ] &= ~BG_FindSlotsForUpgrade( item );  }  /* @@ -5166,6 +3266,40 @@ qboolean BG_RotateAxis( vec3_t surfNormal, vec3_t inAxis[ 3 ],  /*  =============== +BG_GetClientNormal + +Get the normal for the surface the client is walking on +=============== +*/ +void BG_GetClientNormal( const playerState_t *ps, vec3_t normal ) +{ +  if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) +  { +    if( ps->eFlags & EF_WALLCLIMBCEILING ) +      VectorSet( normal, 0.0f, 0.0f, -1.0f ); +    else +      VectorCopy( ps->grapplePoint, normal ); +  } +  else +    VectorSet( normal, 0.0f, 0.0f, 1.0f ); +} + +/* +=============== +BG_GetClientViewOrigin + +Get the position of the client's eye, based on the client's position, the surface's normal, and client's view height +=============== +*/ +void BG_GetClientViewOrigin( const playerState_t *ps, vec3_t viewOrigin ) +{ +  vec3_t normal; +  BG_GetClientNormal( ps, normal ); +  VectorMA( ps->origin, ps->viewheight, normal, viewOrigin ); +} + +/* +===============  BG_PositionBuildableRelativeToPlayer  Find a place to build a buildable @@ -5181,19 +3315,11 @@ void BG_PositionBuildableRelativeToPlayer( const playerState_t *ps,    vec3_t  angles, playerOrigin, playerNormal;    float   buildDist; -  if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) -  { -    if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) -      VectorSet( playerNormal, 0.0f, 0.0f, -1.0f ); -    else -      VectorCopy( ps->grapplePoint, playerNormal ); -  } -  else -    VectorSet( playerNormal, 0.0f, 0.0f, 1.0f ); +  BG_GetClientNormal( ps, playerNormal );    VectorCopy( ps->viewangles, angles );    VectorCopy( ps->origin, playerOrigin ); -  buildDist = BG_FindBuildDistForClass( ps->stats[ STAT_PCLASS ] ); +  buildDist = BG_Class( ps->stats[ STAT_CLASS ] )->buildDist;    AngleVectors( angles, forward, NULL, NULL );    ProjectPointOnPlane( forward, forward, playerNormal ); @@ -5209,54 +3335,90 @@ void BG_PositionBuildableRelativeToPlayer( const playerState_t *ps,    //so buildings drop to floor    VectorMA( targetOrigin, -128, playerNormal, targetOrigin ); -  (*trace)( tr, entityOrigin, mins, maxs, targetOrigin, ps->clientNum, MASK_PLAYERSOLID ); -  VectorCopy( tr->endpos, entityOrigin ); -  VectorMA( entityOrigin, 0.1f, playerNormal, outOrigin ); +  // The mask is MASK_DEADSOLID on purpose to avoid collisions with other entities +  (*trace)( tr, entityOrigin, mins, maxs, targetOrigin, ps->clientNum, MASK_DEADSOLID ); +  VectorCopy( tr->endpos, outOrigin );    vectoangles( forward, outAngles );  }  /*  =============== -BG_GetValueOfEquipment +BG_GetValueOfPlayer -Returns the equipment value of some human player's gear +Returns the credit value of a player  ===============  */ -  int BG_GetValueOfEquipment( playerState_t *ps ) { -  int i, worth = 0; +int BG_GetValueOfPlayer( playerState_t *ps ) +{ +  int worth = 0; -  for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) -  { -    if( BG_InventoryContainsUpgrade( i, ps->stats ) ) -      worth += BG_FindPriceForUpgrade( i ); -  } +  worth = BG_Class( ps->stats[ STAT_CLASS ] )->value; -  for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) +  // Humans have worth from their equipment as well +  if( ps->stats[ STAT_TEAM ] == TEAM_HUMANS )    { -    if( BG_InventoryContainsWeapon( i, ps->stats ) ) -      worth += BG_FindPriceForWeapon( i ); -  } +    upgrade_t i; +    for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) +    { +      if( BG_InventoryContainsUpgrade( i, ps->stats ) ) +        worth += BG_Upgrade( i )->price; +    } -    return worth; +    if( ps->stats[ STAT_WEAPON ] != WP_NONE ) +      worth += BG_Weapon( ps->stats[ STAT_WEAPON ] )->price;    } + +  return worth; +} +  /* -=============== -BG_GetValueOfHuman +================= +BG_PlayerCanChangeWeapon +================= +*/ +qboolean BG_PlayerCanChangeWeapon( playerState_t *ps ) +{ +  // Do not allow Lucifer Cannon "canceling" via weapon switch +  if( ps->weapon == WP_LUCIFER_CANNON && +      ps->stats[ STAT_MISC ] > LCANNON_CHARGE_TIME_MIN ) +    return qfalse; -Returns the kills value of some human player -=============== +  return ps->weaponTime <= 0 || ps->weaponstate != WEAPON_FIRING; +} + +/* +================= +BG_PlayerPoisonCloudTime +=================  */ -int BG_GetValueOfHuman( playerState_t *ps ) +int BG_PlayerPoisonCloudTime( playerState_t *ps )  { -  float portion = BG_GetValueOfEquipment( ps ) / (float)HUMAN_MAXED; +  int time = LEVEL1_PCLOUD_TIME; +  if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ps->stats ) ) +    time -= BSUIT_PCLOUD_PROTECTION; +  if( BG_InventoryContainsUpgrade( UP_HELMET, ps->stats ) ) +    time -= HELMET_PCLOUD_PROTECTION; +  if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, ps->stats ) ) +    time -= LIGHTARMOUR_PCLOUD_PROTECTION; +     +  return time; +} + +/* +================= +BG_GetPlayerWeapon -  if( portion < 0.01f ) -    portion = 0.01f; -  else if( portion > 1.0f ) -    portion = 1.0f; +Returns the players current weapon or the weapon they are switching to. +Only needs to be used for human weapons. +================= +*/ +weapon_t BG_GetPlayerWeapon( playerState_t *ps ) +{ +  if( ps->persistant[ PERS_NEWWEAPON ] ) +    return ps->persistant[ PERS_NEWWEAPON ]; -  return ceil( ALIEN_MAX_SINGLE_KILLS * portion ); +  return ps->weapon;  }  /* @@ -5297,6 +3459,103 @@ int atoi_neg( char *token, qboolean allowNegative )    return value;  } +#define MAX_NUM_PACKED_ENTITY_NUMS 10 + +/* +=============== +BG_PackEntityNumbers + +Pack entity numbers into an entityState_t +=============== +*/ +void BG_PackEntityNumbers( entityState_t *es, const int *entityNums, int count ) +{ +  int i; + +  if( count > MAX_NUM_PACKED_ENTITY_NUMS ) +  { +    count = MAX_NUM_PACKED_ENTITY_NUMS; +    Com_Printf( S_COLOR_YELLOW "WARNING: A maximum of %d entity numbers can be " +      "packed, but BG_PackEntityNumbers was passed %d entities", +      MAX_NUM_PACKED_ENTITY_NUMS, count ); +  } + +  es->misc = es->time = es->time2 = es->constantLight = 0; + +  for( i = 0; i < MAX_NUM_PACKED_ENTITY_NUMS; i++ ) +  { +    int entityNum; + +    if( i < count ) +      entityNum = entityNums[ i ]; +    else +      entityNum = ENTITYNUM_NONE; + +    if( entityNum & ~GENTITYNUM_MASK ) +    { +      Com_Error( ERR_FATAL, "BG_PackEntityNumbers passed an entity number (%d) which " +        "exceeds %d bits", entityNum, GENTITYNUM_BITS ); +    } + +    switch( i ) +    { +      case 0: es->misc |= entityNum;                                       break; +      case 1: es->time |= entityNum;                                       break; +      case 2: es->time |= entityNum << GENTITYNUM_BITS;                    break; +      case 3: es->time |= entityNum << (GENTITYNUM_BITS * 2);              break; +      case 4: es->time2 |= entityNum;                                      break; +      case 5: es->time2 |= entityNum << GENTITYNUM_BITS;                   break; +      case 6: es->time2 |= entityNum << (GENTITYNUM_BITS * 2);             break; +      case 7: es->constantLight |= entityNum;                              break; +      case 8: es->constantLight |= entityNum << GENTITYNUM_BITS;           break; +      case 9: es->constantLight |= entityNum << (GENTITYNUM_BITS * 2);     break; +      default: Com_Error( ERR_FATAL, "Entity index %d not handled", i );   break; +    } +  } +} + +/* +=============== +BG_UnpackEntityNumbers + +Unpack entity numbers from an entityState_t +=============== +*/ +int BG_UnpackEntityNumbers( entityState_t *es, int *entityNums, int count ) +{ +  int i; + +  if( count > MAX_NUM_PACKED_ENTITY_NUMS ) +    count = MAX_NUM_PACKED_ENTITY_NUMS; + +  for( i = 0; i < count; i++ ) +  { +    int *entityNum = &entityNums[ i ]; + +    switch( i ) +    { +      case 0: *entityNum = es->misc;                                      break; +      case 1: *entityNum = es->time;                                      break; +      case 2: *entityNum = (es->time >> GENTITYNUM_BITS);                 break; +      case 3: *entityNum = (es->time >> (GENTITYNUM_BITS * 2));           break; +      case 4: *entityNum = es->time2;                                     break; +      case 5: *entityNum = (es->time2 >> GENTITYNUM_BITS);                break; +      case 6: *entityNum = (es->time2 >> (GENTITYNUM_BITS * 2));          break; +      case 7: *entityNum = es->constantLight;                             break; +      case 8: *entityNum = (es->constantLight >> GENTITYNUM_BITS);        break; +      case 9: *entityNum = (es->constantLight >> (GENTITYNUM_BITS * 2));  break; +      default: Com_Error( ERR_FATAL, "Entity index %d not handled", i );  break; +    } + +    *entityNum &= GENTITYNUM_MASK; + +    if( *entityNum == ENTITYNUM_NONE ) +      break; +  } + +  return i; +} +  /*  ===============  BG_ParseCSVEquipmentList @@ -5330,10 +3589,10 @@ void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weapon        q++;      if( weaponsSize ) -      weapons[ i ] = BG_FindWeaponNumForName( q ); +      weapons[ i ] = BG_WeaponByName( q )->number;      if( upgradesSize ) -      upgrades[ j ] = BG_FindUpgradeNumForName( q ); +      upgrades[ j ] = BG_UpgradeByName( q )->number;      if( weaponsSize && weapons[ i ] == WP_NONE &&          upgradesSize && upgrades[ j ] == UP_NONE ) @@ -5367,7 +3626,7 @@ void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weapon  BG_ParseCSVClassList  ===============  */ -void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSize ) +void BG_ParseCSVClassList( const char *string, class_t *classes, int classesSize )  {    char      buffer[ MAX_STRING_CHARS ];    int       i = 0; @@ -5378,7 +3637,7 @@ void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSiz    p = q = buffer; -  while( *p != '\0' ) +  while( *p != '\0' && i < classesSize - 1 )    {      //skip to first , or EOS      while( *p != ',' && *p != '\0' ) @@ -5393,7 +3652,7 @@ void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSiz      while( *q == ' ' )        q++; -    classes[ i ] = BG_FindClassNumForName( q ); +    classes[ i ] = BG_ClassByName( q )->number;      if( classes[ i ] == PCL_NONE )        Com_Printf( S_COLOR_YELLOW "WARNING: unknown class %s\n", q ); @@ -5428,7 +3687,7 @@ void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int    p = q = buffer; -  while( *p != '\0' ) +  while( *p != '\0' && i < buildablesSize - 1 )    {      //skip to first , or EOS      while( *p != ',' && *p != '\0' ) @@ -5443,7 +3702,7 @@ void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int      while( *q == ' ' )        q++; -    buildables[ i ] = BG_FindBuildNumForName( q ); +    buildables[ i ] = BG_BuildableByName( q )->number;      if( buildables[ i ] == BA_NONE )        Com_Printf( S_COLOR_YELLOW "WARNING: unknown buildable %s\n", q ); @@ -5462,38 +3721,10 @@ void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int    buildables[ i ] = BA_NONE;  } -/* -============ -BG_UpgradeClassAvailable -============ -*/ -qboolean BG_UpgradeClassAvailable( playerState_t *ps ) -{ -  int     i; -  char    buffer[ MAX_STRING_CHARS ]; -  stage_t currentStage; - -  trap_Cvar_VariableStringBuffer( "g_alienStage", buffer, MAX_STRING_CHARS ); -  currentStage = atoi( buffer ); - -  for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) -  { -    if( BG_ClassCanEvolveFromTo( ps->stats[ STAT_PCLASS ], i, -            ps->persistant[ PERS_CREDIT ], 0 ) >= 0 && -        BG_FindStagesForClass( i, currentStage ) && -        BG_ClassIsAllowed( i ) ) -    { -      return qtrue; -    } -  } - -  return qfalse; -} -  typedef struct gameElements_s  {    buildable_t       buildables[ BA_NUM_BUILDABLES ]; -  pClass_t          classes[ PCL_NUM_CLASSES ]; +  class_t           classes[ PCL_NUM_CLASSES ];    weapon_t          weapons[ WP_NUM_WEAPONS ];    upgrade_t         upgrades[ UP_NUM_UPGRADES ];  } gameElements_t; @@ -5572,7 +3803,7 @@ qboolean BG_UpgradeIsAllowed( upgrade_t upgrade )  BG_ClassIsAllowed  ============  */ -qboolean BG_ClassIsAllowed( pClass_t class ) +qboolean BG_ClassIsAllowed( class_t class )  {    int i; @@ -5607,81 +3838,86 @@ qboolean BG_BuildableIsAllowed( buildable_t buildable )  /*  ============ -BG_ClientListTest +BG_LoadEmoticons  ============  */ -qboolean BG_ClientListTest( clientList_t *list, int clientNum ) +int BG_LoadEmoticons( emoticon_t *emoticons, int num )  { -  if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) -    return qfalse; -  if( clientNum < 32 ) -    return ( ( list->lo & ( 1 << clientNum ) ) != 0 );  -  else -    return ( ( list->hi & ( 1 << ( clientNum - 32 ) ) ) != 0 );  -} +  int numFiles; +  char fileList[ MAX_EMOTICONS * ( MAX_EMOTICON_NAME_LEN + 9 ) ] = {""}; +  int i; +  char *filePtr; +  int fileLen; +  int count; -/* -============ -BG_ClientListAdd -============ -*/ -void BG_ClientListAdd( clientList_t *list, int clientNum ) -{ -  if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) -    return; -  if( clientNum < 32 ) -    list->lo |= ( 1 << clientNum );  -  else -    list->hi |= ( 1 << ( clientNum - 32 ) ); +  numFiles = trap_FS_GetFileList( "emoticons", "x1.tga", fileList, +    sizeof( fileList ) ); + +  if( numFiles < 1 ) +    return 0; + +  filePtr = fileList; +  fileLen = 0; +  count = 0; +  for( i = 0; i < numFiles && count < num; i++, filePtr += fileLen + 1 ) +  { +    fileLen = strlen( filePtr ); +    if( fileLen < 9 || filePtr[ fileLen - 8 ] != '_' || +        filePtr[ fileLen - 7 ] < '1' || filePtr[ fileLen - 7 ] > '9' ) +    { +      Com_Printf( S_COLOR_YELLOW "skipping invalidly named emoticon \"%s\"\n", +        filePtr ); +      continue; +    } +    if( fileLen - 8 > MAX_EMOTICON_NAME_LEN ) +    { +      Com_Printf( S_COLOR_YELLOW "emoticon file name \"%s\" too long (>%d)\n", +        filePtr, MAX_EMOTICON_NAME_LEN + 8 ); +      continue; +    } +    if( !trap_FS_FOpenFile( va( "emoticons/%s", filePtr ), NULL, FS_READ ) ) +    { +      Com_Printf( S_COLOR_YELLOW "could not open \"emoticons/%s\"\n", filePtr ); +      continue; +    } + +    Q_strncpyz( emoticons[ count ].name, filePtr, fileLen - 8 + 1 ); +#ifndef GAME +    emoticons[ count ].width = filePtr[ fileLen - 7 ] - '0'; +#endif +    count++; +  } + +  Com_Printf( "Loaded %d of %d emoticons (MAX_EMOTICONS is %d)\n", +    count, numFiles, MAX_EMOTICONS ); +  return count;  }  /*  ============ -BG_ClientListRemove +BG_TeamName  ============  */ -void BG_ClientListRemove( clientList_t *list, int clientNum ) +char *BG_TeamName( team_t team )  { -  if( clientNum < 0 || clientNum >= MAX_CLIENTS || !list ) -    return; -  if( clientNum < 32 ) -    list->lo &= ~( 1 << clientNum );  -  else -    list->hi &= ~( 1 << ( clientNum - 32 ) ); +  if( team == TEAM_NONE ) +    return "spectator"; +  if( team == TEAM_ALIENS ) +    return "alien"; +  if( team == TEAM_HUMANS ) +    return "human"; +  return "<team>";  } -/* -============ -BG_ClientListString -============ -*/ -char *BG_ClientListString( clientList_t *list ) +int cmdcmp( const void *a, const void *b )  { -  static char s[ 17 ]; - -  s[ 0 ] = '\0'; -  if( !list ) -    return s; -  Com_sprintf( s, sizeof( s ), "%08x%08x", list->hi, list->lo ); -  return s; +  return Q_stricmp( (const char *)a, ((dummyCmd_t *)b)->name );  } -/* -============ -BG_ClientListParse -============ -*/ -void BG_ClientListParse( clientList_t *list, const char *s ) +char *G_CopyString( const char *str )  { -  if( !list ) -    return; -  list->lo = 0; -  list->hi = 0; -  if( !s ) -    return; -  if( strlen( s ) != 16 ) -    return; -  sscanf( s, "%x%x", &list->hi, &list->lo ); +  size_t size = strlen( str ) + 1; +  char *cp = BG_Alloc( size ); +  memcpy( cp, str, size ); +  return cp;  } - - diff --git a/src/game/bg_pmove.c b/src/game/bg_pmove.c index 542b585..c5f2293 100644 --- a/src/game/bg_pmove.c +++ b/src/game/bg_pmove.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,15 +17,15 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */  // bg_pmove.c -- both games player movement code  // takes a playerstate and a usercmd as input and returns a modifed playerstate -#include "../qcommon/q_shared.h" +#include "qcommon/q_shared.h"  #include "bg_public.h"  #include "bg_local.h" @@ -35,10 +36,8 @@ pml_t       pml;  float pm_stopspeed = 100.0f;  float pm_duckScale = 0.25f;  float pm_swimScale = 0.50f; -float pm_wadeScale = 0.70f;  float pm_accelerate = 10.0f; -float pm_airaccelerate = 1.0f;  float pm_wateraccelerate = 4.0f;  float pm_flyaccelerate = 4.0f; @@ -92,9 +91,9 @@ void PM_AddTouchEnt( int entityNum )  PM_StartTorsoAnim  ===================  */ -static void PM_StartTorsoAnim( int anim ) +void PM_StartTorsoAnim( int anim )  { -  if( pm->ps->pm_type >= PM_DEAD ) +  if( PM_Paralyzed( pm->ps->pm_type ) )      return;    pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) @@ -103,12 +102,26 @@ static void PM_StartTorsoAnim( int anim )  /*  =================== +PM_StartWeaponAnim +=================== +*/ +static void PM_StartWeaponAnim( int anim ) +{ +  if( PM_Paralyzed( pm->ps->pm_type ) ) +    return; + +  pm->ps->weaponAnim = ( ( pm->ps->weaponAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) +    | anim; +} + +/* +===================  PM_StartLegsAnim  ===================  */  static void PM_StartLegsAnim( int anim )  { -  if( pm->ps->pm_type >= PM_DEAD ) +  if( PM_Paralyzed( pm->ps->pm_type ) )      return;    //legsTimer is clamped too tightly for nonsegmented models @@ -170,6 +183,19 @@ static void PM_ContinueTorsoAnim( int anim )  /*  =================== +PM_ContinueWeaponAnim +=================== +*/ +static void PM_ContinueWeaponAnim( int anim ) +{ +  if( ( pm->ps->weaponAnim & ~ANIM_TOGGLEBIT ) == anim ) +    return; + +  PM_StartWeaponAnim( anim ); +} + +/* +===================  PM_ForceLegsAnim  ===================  */ @@ -192,29 +218,10 @@ PM_ClipVelocity  Slide off of the impacting surface  ==================  */ -void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out )  { -  float backoff; -  float change; -  int   i; - -  backoff = DotProduct( in, normal ); - -  //Com_Printf( "%1.0f ", backoff ); - -  if( backoff < 0 ) -    backoff *= overbounce; -  else -    backoff /= overbounce; - -  for( i = 0; i < 3; i++ ) -  { -    change = normal[ i ] * backoff; -    //Com_Printf( "%1.0f ", change ); -    out[ i ] = in[ i ] - change; -  } - -  //Com_Printf( "   " ); +  float t = -DotProduct( in, normal ); +  VectorMA( in, t, normal, out );  } @@ -234,20 +241,15 @@ static void PM_Friction( void )    vel = pm->ps->velocity; -  //TA: make sure vertical velocity is NOT set to zero when wall climbing +  // make sure vertical velocity is NOT set to zero when wall climbing    VectorCopy( vel, vec );    if( pml.walking && !( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) )      vec[ 2 ] = 0; // ignore slope movement    speed = VectorLength( vec ); -  if( speed < 1 ) -  { -    vel[ 0 ] = 0; -    vel[ 1 ] = 0;   // allow sinking underwater -    // FIXME: still have z friction underwater? +  if( speed < 0.1 )      return; -  }    drop = 0; @@ -259,10 +261,11 @@ static void PM_Friction( void )        // if getting knocked back, no friction        if( !( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) )        { -        float stopSpeed = BG_FindStopSpeedForClass( pm->ps->stats[ STAT_PCLASS ] ); +        float stopSpeed = BG_Class( pm->ps->stats[ STAT_CLASS ] )->stopSpeed; +        float friction = BG_Class( pm->ps->stats[ STAT_CLASS ] )->friction;          control = speed < stopSpeed ? stopSpeed : speed; -        drop += control * BG_FindFrictionForClass( pm->ps->stats[ STAT_PCLASS ] ) * pml.frametime; +        drop += control * friction * pml.frametime;        }      }    } @@ -346,16 +349,43 @@ This allows the clients to use axial -127 to 127 values for all directions  without getting a sqrt(2) distortion in speed.  ============  */ -static float PM_CmdScale( usercmd_t *cmd ) +static float PM_CmdScale( usercmd_t *cmd, qboolean zFlight )  {    int         max;    float       total;    float       scale;    float       modifier = 1.0f; - -  if( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS && pm->ps->pm_type == PM_NORMAL ) +   +  if( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS && pm->ps->pm_type == PM_NORMAL )    { -    if( pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST ) +    qboolean wasSprinting; +    qboolean sprint; +    wasSprinting = sprint = pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST; + +    if( pm->ps->persistant[ PERS_STATE ] & PS_SPRINTTOGGLE ) +    { +      if( cmd->buttons & BUTTON_SPRINT && +          !( pm->ps->pm_flags & PMF_SPRINTHELD ) ) +      { +        sprint = !sprint; +        pm->ps->pm_flags |= PMF_SPRINTHELD; +      } +      else if( pm->ps->pm_flags & PMF_SPRINTHELD && +               !( cmd->buttons & BUTTON_SPRINT ) ) +        pm->ps->pm_flags &= ~PMF_SPRINTHELD; +    } +    else +      sprint = cmd->buttons & BUTTON_SPRINT; + +    if( sprint ) +      pm->ps->stats[ STAT_STATE ] |= SS_SPEEDBOOST;       +    else if( wasSprinting && !sprint ) +      pm->ps->stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; + +    // Walk overrides sprint. We keep the state that we want to be sprinting +    //  (above), but don't apply the modifier, and in g_active we skip taking +    //  the stamina too. +    if( sprint && !( cmd->buttons & BUTTON_WALKING ) )        modifier *= HUMAN_SPRINT_MODIFIER;      else        modifier *= HUMAN_JOG_MODIFIER; @@ -371,13 +401,16 @@ static float PM_CmdScale( usercmd_t *cmd )        modifier *= HUMAN_SIDE_MODIFIER;      } -    //must have +ve stamina to jump -    if( pm->ps->stats[ STAT_STAMINA ] < 0 ) -      cmd->upmove = 0; +    if( !zFlight ) +    { +      //must have have stamina to jump +      if( pm->ps->stats[ STAT_STAMINA ] < STAMINA_SLOW_LEVEL + STAMINA_JUMP_TAKE ) +        cmd->upmove = 0; +    }      //slow down once stamina depletes -    if( pm->ps->stats[ STAT_STAMINA ] <= -500 ) -      modifier *= (float)( pm->ps->stats[ STAT_STAMINA ] + 1000 ) / 500.0f; +    if( pm->ps->stats[ STAT_STAMINA ] <= STAMINA_SLOW_LEVEL ) +      modifier *= (float)( pm->ps->stats[ STAT_STAMINA ] + STAMINA_MAX ) / (float)(STAMINA_SLOW_LEVEL + STAMINA_MAX);      if( pm->ps->stats[ STAT_STATE ] & SS_CREEPSLOWED )      { @@ -387,11 +420,20 @@ static float PM_CmdScale( usercmd_t *cmd )        else          modifier *= CREEP_MODIFIER;      } +    if( pm->ps->eFlags & EF_POISONCLOUDED ) +    { +      if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, pm->ps->stats ) || +          BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) ) +        modifier *= PCLOUD_ARMOUR_MODIFIER; +      else +        modifier *= PCLOUD_MODIFIER; +    }    }    if( pm->ps->weapon == WP_ALEVEL4 && pm->ps->pm_flags & PMF_CHARGE ) -    modifier *= ( 1.0f + ( pm->ps->stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) * -        ( LEVEL4_CHARGE_SPEED - 1.0f ) ); +    modifier *= 1.0f + ( pm->ps->stats[ STAT_MISC ] * +                         ( LEVEL4_TRAMPLE_SPEED - 1.0f ) / +                         LEVEL4_TRAMPLE_DURATION );    //slow player if charging up for a pounce    if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) && @@ -405,28 +447,22 @@ static float PM_CmdScale( usercmd_t *cmd )    if( pm->ps->pm_type == PM_GRABBED )      modifier = 0.0f; -  if( pm->ps->pm_type != PM_SPECTATOR && pm->ps->pm_type != PM_NOCLIP ) -  { -    if( BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ) == 0.0f ) -      cmd->upmove = 0; - -    //prevent speed distortions for non ducking classes -    if( !( pm->ps->pm_flags & PMF_DUCKED ) && pm->ps->pm_type != PM_JETPACK && cmd->upmove < 0 ) -      cmd->upmove = 0; -  } -    max = abs( cmd->forwardmove );    if( abs( cmd->rightmove ) > max )      max = abs( cmd->rightmove ); +  total = cmd->forwardmove * cmd->forwardmove + cmd->rightmove * cmd->rightmove; -  if( abs( cmd->upmove ) > max ) -    max = abs( cmd->upmove ); +  if( zFlight ) +  { +    if( abs( cmd->upmove ) > max ) +      max = abs( cmd->upmove ); +    total += cmd->upmove * cmd->upmove; +  }    if( !max )      return 0; -  total = sqrt( cmd->forwardmove * cmd->forwardmove -    + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); +  total = sqrt( total );    scale = (float)pm->ps->speed * max / ( 127.0 * total ) * modifier; @@ -438,7 +474,7 @@ static float PM_CmdScale( usercmd_t *cmd )  ================  PM_SetMovementDir -Determine the rotation of the legs reletive +Determine the rotation of the legs relative  to the facing dir  ================  */ @@ -506,40 +542,49 @@ PM_CheckPounce  */  static qboolean PM_CheckPounce( void )  { +  int jumpMagnitude; +    if( pm->ps->weapon != WP_ALEVEL3 &&        pm->ps->weapon != WP_ALEVEL3_UPG )      return qfalse; -  // we were pouncing, but we've landed   -  if( pm->ps->groundEntityNum != ENTITYNUM_NONE -    && ( pm->ps->pm_flags & PMF_CHARGE ) ) +  // We were pouncing, but we've landed +  if( pm->ps->groundEntityNum != ENTITYNUM_NONE && +      ( pm->ps->pm_flags & PMF_CHARGE ) )    { -    pm->ps->weaponTime += LEVEL3_POUNCE_TIME;      pm->ps->pm_flags &= ~PMF_CHARGE; +    pm->ps->weaponTime += LEVEL3_POUNCE_REPEAT; +    return qfalse;    } -  // we're building up for a pounce +  // We're building up for a pounce    if( pm->cmd.buttons & BUTTON_ATTACK2 ) +  { +    pm->ps->pm_flags &= ~PMF_CHARGE;      return qfalse; +  } -  // already a pounce in progress -  if( pm->ps->pm_flags & PMF_CHARGE ) -    return qfalse; - -  if( pm->ps->stats[ STAT_MISC ] == 0 ) +  // Can't start a pounce +  if( ( pm->ps->pm_flags & PMF_CHARGE ) || +      pm->ps->stats[ STAT_MISC ] < LEVEL3_POUNCE_TIME_MIN || +      pm->ps->groundEntityNum == ENTITYNUM_NONE )      return qfalse; -  pml.groundPlane = qfalse;   // jumping away +  // Give the player forward velocity and simulate a jump +  pml.groundPlane = qfalse;    pml.walking = qfalse; -    pm->ps->pm_flags |= PMF_CHARGE; -    pm->ps->groundEntityNum = ENTITYNUM_NONE; - -  VectorMA( pm->ps->velocity, pm->ps->stats[ STAT_MISC ], pml.forward, pm->ps->velocity ); - +  if( pm->ps->weapon == WP_ALEVEL3 ) +    jumpMagnitude = pm->ps->stats[ STAT_MISC ] * +                    LEVEL3_POUNCE_JUMP_MAG / LEVEL3_POUNCE_TIME; +  else +    jumpMagnitude = pm->ps->stats[ STAT_MISC ] * +                    LEVEL3_POUNCE_JUMP_MAG_UPG / LEVEL3_POUNCE_TIME_UPG; +  VectorMA( pm->ps->velocity, jumpMagnitude, pml.forward, pm->ps->velocity );    PM_AddEvent( EV_JUMP ); +  // Play jumping animation    if( pm->cmd.forwardmove >= 0 )    {      if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) @@ -572,15 +617,47 @@ PM_CheckWallJump  */  static qboolean PM_CheckWallJump( void )  { -  vec3_t  dir, forward, right; +  vec3_t  dir, forward, right, movedir, point;    vec3_t  refNormal = { 0.0f, 0.0f, 1.0f };    float   normalFraction = 1.5f;    float   cmdFraction = 1.0f;    float   upFraction = 1.5f; +  trace_t trace; + +  if( !( BG_Class( pm->ps->stats[ STAT_CLASS ] )->abilities & SCA_WALLJUMPER ) ) +    return qfalse; +  ProjectPointOnPlane( movedir, pml.forward, refNormal ); +  VectorNormalize( movedir ); +   +  if( pm->cmd.forwardmove < 0 ) +    VectorNegate( movedir, movedir ); +   +  //allow strafe transitions +  if( pm->cmd.rightmove ) +  { +    VectorCopy( pml.right, movedir ); +     +    if( pm->cmd.rightmove < 0 ) +      VectorNegate( movedir, movedir ); +  } +   +  //trace into direction we are moving +  VectorMA( pm->ps->origin, 0.25f, movedir, point ); +  pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); +   +  if( trace.fraction < 1.0f && +      !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) && +      trace.plane.normal[ 2 ] < MIN_WALK_NORMAL ) +  { +    VectorCopy( trace.plane.normal, pm->ps->grapplePoint ); +  } +  else +    return qfalse; +      if( pm->ps->pm_flags & PMF_RESPAWNED )      return qfalse;    // don't allow jump until all buttons are up - +      if( pm->cmd.upmove < 10 )      // not holding jump      return qfalse; @@ -592,8 +669,6 @@ static qboolean PM_CheckWallJump( void )    if( pm->ps->pm_flags & PMF_JUMP_HELD &&        pm->ps->grapplePoint[ 2 ] == 1.0f )    { -    // clear upmove so cmdscale doesn't lower running speed -    pm->cmd.upmove = 0;      return qfalse;    } @@ -624,7 +699,7 @@ static qboolean PM_CheckWallJump( void )    VectorMA( dir, upFraction, refNormal, dir );    VectorNormalize( dir ); -  VectorMA( pm->ps->velocity, BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ), +  VectorMA( pm->ps->velocity, BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude,              dir, pm->ps->velocity );    //for a long run of wall jumps the velocity can get pretty large, this caps it @@ -665,11 +740,13 @@ PM_CheckJump  */  static qboolean PM_CheckJump( void )  { -  if( BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ) == 0.0f ) +  vec3_t normal; + +  if( pm->ps->groundEntityNum == ENTITYNUM_NONE )      return qfalse; -  if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) -    return PM_CheckWallJump( ); +  if( BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude == 0.0f ) +    return qfalse;    //can't jump and pounce at the same time    if( ( pm->ps->weapon == WP_ALEVEL3 || @@ -682,8 +759,13 @@ static qboolean PM_CheckJump( void )        pm->ps->stats[ STAT_MISC ] > 0 )      return qfalse; -  if( ( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS ) && -      ( pm->ps->stats[ STAT_STAMINA ] < 0 ) ) +  if( ( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS ) && +      ( pm->ps->stats[ STAT_STAMINA ] < STAMINA_SLOW_LEVEL + STAMINA_JUMP_TAKE ) ) +    return qfalse; + +  //no bunny hopping off a dodge +  if( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS &&  +      pm->ps->pm_time )      return qfalse;    if( pm->ps->pm_flags & PMF_RESPAWNED ) @@ -695,42 +777,37 @@ static qboolean PM_CheckJump( void )    //can't jump whilst grabbed    if( pm->ps->pm_type == PM_GRABBED ) -  { -    pm->cmd.upmove = 0;      return qfalse; -  }    // must wait for jump to be released    if( pm->ps->pm_flags & PMF_JUMP_HELD ) -  { -    // clear upmove so cmdscale doesn't lower running speed -    pm->cmd.upmove = 0;      return qfalse; + +  //don't allow walljump for a short while after jumping from the ground +  if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLJUMPER ) ) +  { +    pm->ps->pm_flags |= PMF_TIME_WALLJUMP; +    pm->ps->pm_time = 200;    }    pml.groundPlane = qfalse;   // jumping away    pml.walking = qfalse;    pm->ps->pm_flags |= PMF_JUMP_HELD; -  //TA: take some stamina off -  if( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS ) -    pm->ps->stats[ STAT_STAMINA ] -= 500; +  // take some stamina off +  if( pm->ps->stats[ STAT_TEAM ] == TEAM_HUMANS ) +    pm->ps->stats[ STAT_STAMINA ] -= STAMINA_JUMP_TAKE;    pm->ps->groundEntityNum = ENTITYNUM_NONE; -  //TA: jump away from wall -  if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) -  { -    vec3_t normal = { 0, 0, -1 }; +  // jump away from wall +  BG_GetClientNormal( pm->ps, normal ); +   +  if( pm->ps->velocity[ 2 ] < 0 ) +    pm->ps->velocity[ 2 ] = 0; -    if( !( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) -      VectorCopy( pm->ps->grapplePoint, normal ); - -    VectorMA( pm->ps->velocity, BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ), -              normal, pm->ps->velocity ); -  } -  else -    pm->ps->velocity[ 2 ] = BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ); +  VectorMA( pm->ps->velocity, BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude, +            normal, pm->ps->velocity );    PM_AddEvent( EV_JUMP ); @@ -802,6 +879,108 @@ static qboolean PM_CheckWaterJump( void )    return qtrue;  } + +/* +================== +PM_CheckDodge + +Checks the dodge key and starts a human dodge or sprint +================== +*/ +static qboolean PM_CheckDodge( void ) +{ +  vec3_t right, forward, velocity = { 0.0f, 0.0f, 0.0f }; +  float jump, sideModifier; +  int i; +   +  if( pm->ps->stats[ STAT_TEAM ] != TEAM_HUMANS ) +    return qfalse; + +  // Landed a dodge +  if( ( pm->ps->pm_flags & PMF_CHARGE ) && +      pm->ps->groundEntityNum != ENTITYNUM_NONE ) +  { +    pm->ps->pm_flags = ( pm->ps->pm_flags & ~PMF_CHARGE ) | PMF_TIME_LAND; +    pm->ps->pm_time = HUMAN_DODGE_TIMEOUT; +  } + +  // Reasons why we can't start a dodge or sprint +  if( pm->ps->pm_type != PM_NORMAL || pm->ps->stats[ STAT_STAMINA ] < STAMINA_SLOW_LEVEL + STAMINA_DODGE_TAKE || +      ( pm->ps->pm_flags & PMF_DUCKED ) ) +    return qfalse; + +  // Can't dodge forward +  if( pm->cmd.forwardmove > 0 ) +    return qfalse; + +  // Reasons why we can't start a dodge only +  if( pm->ps->pm_flags & ( PMF_TIME_LAND | PMF_CHARGE ) || +      pm->ps->groundEntityNum == ENTITYNUM_NONE || +      !( pm->cmd.buttons & BUTTON_DODGE ) ) +    return qfalse; + +  // Dodge direction specified with movement keys +  if( ( !pm->cmd.rightmove && !pm->cmd.forwardmove ) || pm->cmd.upmove ) +    return qfalse; + +  AngleVectors( pm->ps->viewangles, NULL, right, NULL ); +  forward[ 0 ] = -right[ 1 ]; +  forward[ 1 ] = right[ 0 ]; +  forward[ 2 ] = 0.0f; + +  // Dodge magnitude is based on the jump magnitude scaled by the modifiers +  jump = BG_Class( pm->ps->stats[ STAT_CLASS ] )->jumpMagnitude; +  if( pm->cmd.rightmove && pm->cmd.forwardmove ) +    jump *= ( 0.5f * M_SQRT2 ); +   +  // Weaken dodge if slowed +  if( ( pm->ps->stats[ STAT_STATE ] & SS_SLOWLOCKED )  || +      ( pm->ps->stats[ STAT_STATE ] & SS_CREEPSLOWED ) || +      ( pm->ps->eFlags & EF_POISONCLOUDED ) ) +    sideModifier = HUMAN_DODGE_SLOWED_MODIFIER; +  else +    sideModifier = HUMAN_DODGE_SIDE_MODIFIER; + +  // The dodge sets minimum velocity +  if( pm->cmd.rightmove ) +  { +    if( pm->cmd.rightmove < 0 ) +      VectorNegate( right, right ); +    VectorMA( velocity, jump * sideModifier, right, velocity ); +  } + +  if( pm->cmd.forwardmove ) +  { +    if( pm->cmd.forwardmove < 0 ) +      VectorNegate( forward, forward ); +    VectorMA( velocity, jump * sideModifier, forward, velocity ); +  } + +  velocity[ 2 ] = jump * HUMAN_DODGE_UP_MODIFIER; + +  // Make sure client has minimum velocity +  for( i = 0; i < 3; i++ ) +  { +    if( ( velocity[ i ] < 0.0f && +          pm->ps->velocity[ i ] > velocity[ i ] ) || +        ( velocity[ i ] > 0.0f && +          pm->ps->velocity[ i ] < velocity[ i ] ) ) +      pm->ps->velocity[ i ] = velocity[ i ]; +  } + +  // Jumped away +  pml.groundPlane = qfalse; +  pml.walking = qfalse; +  pm->ps->groundEntityNum = ENTITYNUM_NONE; +  pm->ps->pm_flags |= PMF_CHARGE; +  pm->ps->stats[ STAT_STAMINA ] -= STAMINA_DODGE_TAKE; +  pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ +                       ANIM_TOGGLEBIT ) | LEGS_JUMP; +  PM_AddEvent( EV_JUMP ); + +  return qtrue; +} +  //============================================================================ @@ -863,23 +1042,15 @@ static void PM_WaterMove( void )  #endif    PM_Friction( ); -  scale = PM_CmdScale( &pm->cmd ); +  scale = PM_CmdScale( &pm->cmd, qtrue );    //    // user intentions    // -  if( !scale ) -  { -    wishvel[ 0 ] = 0; -    wishvel[ 1 ] = 0; -    wishvel[ 2 ] = -60;   // sink towards bottom -  } -  else -  { -    for( i = 0; i < 3; i++ ) -      wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; -    wishvel[ 2 ] += scale * pm->cmd.upmove; -  } +  for( i = 0; i < 3; i++ ) +    wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; + +  wishvel[ 2 ] += scale * pm->cmd.upmove;    VectorCopy( wishvel, wishdir );    wishspeed = VectorNormalize( wishdir ); @@ -894,8 +1065,7 @@ static void PM_WaterMove( void )    {      vel = VectorLength( pm->ps->velocity );      // slide along the ground plane -    PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, -      pm->ps->velocity, OVERCLIP ); +    PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity );      VectorNormalize( pm->ps->velocity );      VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); @@ -922,7 +1092,7 @@ static void PM_JetPackMove( void )    //normal slowdown    PM_Friction( ); -  scale = PM_CmdScale( &pm->cmd ); +  scale = PM_CmdScale( &pm->cmd, qfalse );    // user intentions    for( i = 0; i < 2; i++ ) @@ -969,7 +1139,7 @@ static void PM_FlyMove( void )    // normal slowdown    PM_Friction( ); -  scale = PM_CmdScale( &pm->cmd ); +  scale = PM_CmdScale( &pm->cmd, qtrue );    //    // user intentions    // @@ -1012,13 +1182,14 @@ static void PM_AirMove( void )    float     scale;    usercmd_t cmd; +  PM_CheckWallJump( );    PM_Friction( );    fmove = pm->cmd.forwardmove;    smove = pm->cmd.rightmove;    cmd = pm->cmd; -  scale = PM_CmdScale( &cmd ); +  scale = PM_CmdScale( &cmd, qfalse );    // set the movementDir so clients can rotate the legs for strafing    PM_SetMovementDir( ); @@ -1040,14 +1211,13 @@ static void PM_AirMove( void )    // not on ground, so little effect on velocity    PM_Accelerate( wishdir, wishspeed, -    BG_FindAirAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ) ); +    BG_Class( pm->ps->stats[ STAT_CLASS ] )->airAcceleration );    // we may have a ground plane that is very steep, even    // though we don't have a groundentity    // slide along the steep plane    if( pml.groundPlane ) -    PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, -      pm->ps->velocity, OVERCLIP ); +    PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity );    PM_StepSlideMove( qtrue, qfalse );  } @@ -1095,14 +1265,14 @@ static void PM_ClimbMove( void )    smove = pm->cmd.rightmove;    cmd = pm->cmd; -  scale = PM_CmdScale( &cmd ); +  scale = PM_CmdScale( &cmd, qfalse );    // set the movementDir so clients can rotate the legs for strafing    PM_SetMovementDir( );    // project the forward and right directions onto the ground plane -  PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); -  PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); +  PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward ); +  PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right );    //    VectorNormalize( pml.forward );    VectorNormalize( pml.right ); @@ -1138,9 +1308,9 @@ static void PM_ClimbMove( void )    // when a player gets hit, they temporarily lose    // full control, which allows them to be moved a bit    if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) -    accelerate = BG_FindAirAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ); +    accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->airAcceleration;    else -    accelerate = BG_FindAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ); +    accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->acceleration;    PM_Accelerate( wishdir, wishspeed, accelerate ); @@ -1150,8 +1320,7 @@ static void PM_ClimbMove( void )    vel = VectorLength( pm->ps->velocity );    // slide along the ground plane -  PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, -    pm->ps->velocity, OVERCLIP ); +  PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity );    // don't decrease velocity when going up or down a slope    VectorNormalize( pm->ps->velocity ); @@ -1189,7 +1358,6 @@ static void PM_WalkMove( void )      return;    } -    if( PM_CheckJump( ) || PM_CheckPounce( ) )    {      // jumped away @@ -1210,7 +1378,7 @@ static void PM_WalkMove( void )    smove = pm->cmd.rightmove;    cmd = pm->cmd; -  scale = PM_CmdScale( &cmd ); +  scale = PM_CmdScale( &cmd, qfalse );    // set the movementDir so clients can rotate the legs for strafing    PM_SetMovementDir( ); @@ -1220,8 +1388,8 @@ static void PM_WalkMove( void )    pml.right[ 2 ] = 0;    // project the forward and right directions onto the ground plane -  PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); -  PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); +  PM_ClipVelocity( pml.forward, pml.groundTrace.plane.normal, pml.forward ); +  PM_ClipVelocity( pml.right, pml.groundTrace.plane.normal, pml.right );    //    VectorNormalize( pml.forward );    VectorNormalize( pml.right ); @@ -1257,9 +1425,9 @@ static void PM_WalkMove( void )    // when a player gets hit, they temporarily lose    // full control, which allows them to be moved a bit    if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) -    accelerate = BG_FindAirAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ); +    accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->airAcceleration;    else -    accelerate = BG_FindAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ); +    accelerate = BG_Class( pm->ps->stats[ STAT_CLASS ] )->acceleration;    PM_Accelerate( wishdir, wishspeed, accelerate ); @@ -1275,8 +1443,7 @@ static void PM_WalkMove( void )    }    // slide along the ground plane -  PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, -    pm->ps->velocity, OVERCLIP ); +  PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity );    // don't do anything if standing still    if( !pm->ps->velocity[ 0 ] && !pm->ps->velocity[ 1 ] ) @@ -1307,7 +1474,7 @@ static void PM_LadderMove( void )    PM_Friction( ); -  scale = PM_CmdScale( &pm->cmd ); +  scale = PM_CmdScale( &pm->cmd, qtrue );    for( i = 0; i < 3; i++ )      wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; @@ -1328,8 +1495,7 @@ static void PM_LadderMove( void )      vel = VectorLength( pm->ps->velocity );      // slide along the ground plane -    PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, -      pm->ps->velocity, OVERCLIP ); +    PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity );      VectorNormalize( pm->ps->velocity );      VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); @@ -1352,7 +1518,7 @@ static void PM_CheckLadder( void )    trace_t trace;    //test if class can use ladders -  if( !BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_CANUSELADDERS ) ) +  if( !BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_CANUSELADDERS ) )    {      pml.ladder = qfalse;      return; @@ -1414,8 +1580,6 @@ static void PM_NoclipMove( void )    float   wishspeed;    float   scale; -  pm->ps->viewheight = DEFAULT_VIEWHEIGHT; -    // friction    speed = VectorLength( pm->ps->velocity ); @@ -1444,7 +1608,7 @@ static void PM_NoclipMove( void )    }    // accelerate -  scale = PM_CmdScale( &pm->cmd ); +  scale = PM_CmdScale( &pm->cmd, qtrue );    fmove = pm->cmd.forwardmove;    smove = pm->cmd.rightmove; @@ -1475,7 +1639,6 @@ Returns an event number apropriate for the groundsurface  */  static int PM_FootstepForSurface( void )  { -  //TA:    if( pm->ps->stats[ STAT_STATE ] & SS_CREEPSLOWED )      return EV_FOOTSTEP_SQUELCH; @@ -1543,10 +1706,6 @@ static void PM_CrashLand( void )    delta = vel + t * acc;    delta = delta*delta * 0.0001; -  // ducking while falling doubles damage -  if( pm->ps->pm_flags & PMF_DUCKED ) -    delta *= 2; -    // never take falling damage if completely underwater    if( pm->waterlevel == 3 )      return; @@ -1571,12 +1730,12 @@ static void PM_CrashLand( void )      if( delta > AVG_FALL_DISTANCE )      { -      PM_AddEvent( EV_FALL_FAR ); +      if( PM_Alive( pm->ps->pm_type ) ) +        PM_AddEvent( EV_FALL_FAR );      }      else if( delta > MIN_FALL_DISTANCE )      { -      // this is a pain grunt, so don't play it if dead -      if( pm->ps->stats[STAT_HEALTH] > 0 ) +      if( PM_Alive( pm->ps->pm_type ) )          PM_AddEvent( EV_FALL_MEDIUM );      }      else @@ -1688,7 +1847,7 @@ static void PM_GroundTraceMissed( void )      }    } -  if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_TAKESFALLDAMAGE ) ) +  if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_TAKESFALLDAMAGE ) )    {      if( pm->ps->velocity[ 2 ] < FALLING_THRESHOLD && pml.previous_velocity[ 2 ] >= FALLING_THRESHOLD )        PM_AddEvent( EV_FALLING ); @@ -1699,7 +1858,6 @@ static void PM_GroundTraceMissed( void )    pml.walking = qfalse;  } -  /*  =============  PM_GroundClimbTrace @@ -1707,12 +1865,13 @@ PM_GroundClimbTrace  */  static void PM_GroundClimbTrace( void )  { -  vec3_t    surfNormal, movedir, lookdir, point; -  vec3_t    refNormal = { 0.0f, 0.0f, 1.0f }; -  vec3_t    ceilingNormal = { 0.0f, 0.0f, -1.0f }; -  vec3_t    toAngles, surfAngles; -  trace_t   trace; -  int       i; +  vec3_t      surfNormal, movedir, lookdir, point; +  vec3_t      refNormal = { 0.0f, 0.0f, 1.0f }; +  vec3_t      ceilingNormal = { 0.0f, 0.0f, -1.0f }; +  vec3_t      toAngles, surfAngles; +  trace_t     trace; +  int         i; +  const float eps = 0.000001f;    //used for delta correction    vec3_t    traceCROSSsurf, traceCROSSref, surfCROSSref; @@ -1724,12 +1883,7 @@ static void PM_GroundClimbTrace( void )    float     ldDOTtCs, d;    vec3_t    abc; -  //TA: If we're on the ceiling then grapplePoint is a rotation normal.. otherwise its a surface normal. -  //    would have been nice if Carmack had left a few random variables in the ps struct for mod makers -  if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) -    VectorCopy( ceilingNormal, surfNormal ); -  else -    VectorCopy( pm->ps->grapplePoint, surfNormal ); +  BG_GetClientNormal( pm->ps, surfNormal );    //construct a vector which reflects the direction the player is looking wrt the surface normal    ProjectPointOnPlane( movedir, pml.forward, surfNormal ); @@ -1765,8 +1919,10 @@ static void PM_GroundClimbTrace( void )        case 1:          //trace straight down anto "ground" surface +        //mask out CONTENTS_BODY to not hit other players and avoid the camera flipping out when +        // wallwalkers touch          VectorMA( pm->ps->origin, -0.25f, surfNormal, point ); -        pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); +        pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask & ~CONTENTS_BODY );          break;        case 2: @@ -1801,7 +1957,7 @@ static void PM_GroundClimbTrace( void )      }      //if we hit something -    if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) && +    if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) &&           !( trace.entityNum != ENTITYNUM_WORLD && i != 4 ) )      {        if( i == 2 || i == 3 ) @@ -1811,7 +1967,7 @@ static void PM_GroundClimbTrace( void )          VectorCopy( trace.endpos, pm->ps->origin );        } - +              //calculate a bunch of stuff...        CrossProduct( trace.plane.normal, surfNormal, traceCROSSsurf );        VectorNormalize( traceCROSSsurf ); @@ -1845,11 +2001,11 @@ static void PM_GroundClimbTrace( void )        //if the trace result and old surface normal are different then we must have transided to a new        //surface... do some stuff... -      if( !VectorCompare( trace.plane.normal, surfNormal ) ) +      if( !VectorCompareEpsilon( trace.plane.normal, surfNormal, eps ) )        {          //if the trace result or the old vector is not the floor or ceiling correct the YAW angle -        if( !VectorCompare( trace.plane.normal, refNormal ) && !VectorCompare( surfNormal, refNormal ) && -            !VectorCompare( trace.plane.normal, ceilingNormal ) && !VectorCompare( surfNormal, ceilingNormal ) ) +        if( !VectorCompareEpsilon( trace.plane.normal, refNormal, eps ) && !VectorCompareEpsilon( surfNormal, refNormal, eps ) && +            !VectorCompareEpsilon( trace.plane.normal, ceilingNormal, eps ) && !VectorCompareEpsilon( surfNormal, ceilingNormal, eps ) )          {            //behold the evil mindfuck from hell            //it has fucked mind like nothing has fucked mind before @@ -1907,16 +2063,16 @@ static void PM_GroundClimbTrace( void )          //transition from wall to ceiling          //normal for subsequent viewangle rotations -        if( VectorCompare( trace.plane.normal, ceilingNormal ) ) +        if( VectorCompareEpsilon( trace.plane.normal, ceilingNormal, eps ) )          {            CrossProduct( surfNormal, trace.plane.normal, pm->ps->grapplePoint );            VectorNormalize( pm->ps->grapplePoint ); -          pm->ps->stats[ STAT_STATE ] |= SS_WALLCLIMBINGCEILING; +          pm->ps->eFlags |= EF_WALLCLIMBCEILING;          }          //transition from ceiling to wall          //we need to do some different angle correction here cos GPISROTVEC -        if( VectorCompare( surfNormal, ceilingNormal ) ) +        if( VectorCompareEpsilon( surfNormal, ceilingNormal, eps ) )          {            vectoangles( trace.plane.normal, toAngles );            vectoangles( pm->ps->grapplePoint, surfAngles ); @@ -1931,11 +2087,11 @@ static void PM_GroundClimbTrace( void )        pm->ps->eFlags |= EF_WALLCLIMB;        //if we're not stuck to the ceiling then set grapplePoint to be a surface normal -      if( !VectorCompare( trace.plane.normal, ceilingNormal ) ) +      if( !VectorCompareEpsilon( trace.plane.normal, ceilingNormal, eps ) )        {          //so we know what surface we're stuck to          VectorCopy( trace.plane.normal, pm->ps->grapplePoint ); -        pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; +        pm->ps->eFlags &= ~EF_WALLCLIMBCEILING;        }        //IMPORTANT: break out of the for loop if we've hit something @@ -1958,7 +2114,7 @@ static void PM_GroundClimbTrace( void )      pm->ps->eFlags &= ~EF_WALLCLIMB;      //just transided from ceiling to floor... apply delta correction -    if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) +    if( pm->ps->eFlags & EF_WALLCLIMBCEILING )      {        vec3_t  forward, rotated, angles; @@ -1970,7 +2126,7 @@ static void PM_GroundClimbTrace( void )        pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( angles[ YAW ] - pm->ps->viewangles[ YAW ] );      } -    pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; +    pm->ps->eFlags &= ~EF_WALLCLIMBCEILING;      //we get very bizarre effects if we don't do this :0      VectorCopy( refNormal, pm->ps->grapplePoint ); @@ -1983,7 +2139,7 @@ static void PM_GroundClimbTrace( void )    // hitting solid ground will end a waterjump    if( pm->ps->pm_flags & PMF_TIME_WATERJUMP )    { -    pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); +    pm->ps->pm_flags &= ~PMF_TIME_WATERJUMP;      pm->ps->pm_time = 0;    } @@ -1995,7 +2151,6 @@ static void PM_GroundClimbTrace( void )    PM_AddTouchEnt( trace.entityNum );  } -  /*  =============  PM_GroundTrace @@ -2004,11 +2159,10 @@ PM_GroundTrace  static void PM_GroundTrace( void )  {    vec3_t      point; -  vec3_t      movedir;    vec3_t      refNormal = { 0.0f, 0.0f, 1.0f };    trace_t     trace; -  if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) ) +  if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) )    {      if( pm->ps->persistant[ PERS_STATE ] & PS_WALLCLIMBINGTOGGLE )      { @@ -2043,7 +2197,7 @@ static void PM_GroundTrace( void )      }      //just transided from ceiling to floor... apply delta correction -    if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) +    if( pm->ps->eFlags & EF_WALLCLIMBCEILING )      {        vec3_t  forward, rotated, angles; @@ -2057,8 +2211,7 @@ static void PM_GroundTrace( void )    }    pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; -  pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; -  pm->ps->eFlags &= ~EF_WALLCLIMB; +  pm->ps->eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING );    point[ 0 ] = pm->ps->origin[ 0 ];    point[ 1 ] = pm->ps->origin[ 1 ]; @@ -2105,38 +2258,6 @@ static void PM_GroundTrace( void )        pml.groundPlane = qfalse;        pml.walking = qfalse; -      if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) -      { -        ProjectPointOnPlane( movedir, pml.forward, refNormal ); -        VectorNormalize( movedir ); - -        if( pm->cmd.forwardmove < 0 ) -          VectorNegate( movedir, movedir ); - -        //allow strafe transitions -        if( pm->cmd.rightmove ) -        { -          VectorCopy( pml.right, movedir ); - -          if( pm->cmd.rightmove < 0 ) -            VectorNegate( movedir, movedir ); -        } - -        //trace into direction we are moving -        VectorMA( pm->ps->origin, 0.25f, movedir, point ); -        pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); - -        if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) && -            ( trace.entityNum == ENTITYNUM_WORLD ) ) -        { -          if( !VectorCompare( trace.plane.normal, pm->ps->grapplePoint ) ) -          { -            VectorCopy( trace.plane.normal, pm->ps->grapplePoint ); -            PM_CheckWallJump( ); -          } -        } -      } -        return;      }    } @@ -2193,7 +2314,7 @@ static void PM_GroundTrace( void )    // hitting solid ground will end a waterjump    if( pm->ps->pm_flags & PMF_TIME_WATERJUMP )    { -    pm->ps->pm_flags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND ); +    pm->ps->pm_flags &= ~PMF_TIME_WATERJUMP;      pm->ps->pm_time = 0;    } @@ -2203,16 +2324,11 @@ static void PM_GroundTrace( void )      if( pm->debugLevel )        Com_Printf( "%i:Land\n", c_pmove ); -    if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_TAKESFALLDAMAGE ) ) -      PM_CrashLand( ); +    // communicate the fall velocity to the server +    pm->pmext->fallVelocity = pml.previous_velocity[ 2 ]; -    // don't do landing time if we were just going down a slope -    if( pml.previous_velocity[ 2 ] < -200 ) -    { -      // don't allow another jump for a little while -      pm->ps->pm_flags |= PMF_TIME_LAND; -      pm->ps->pm_time = 250; -    } +    if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_TAKESFALLDAMAGE ) ) +      PM_CrashLand( );    }    pm->ps->groundEntityNum = trace.entityNum; @@ -2273,23 +2389,29 @@ static void PM_SetWaterLevel( void )  /*  ============== +PM_SetViewheight +============== +*/ +static void PM_SetViewheight( void ) +{ +  pm->ps->viewheight = ( pm->ps->pm_flags & PMF_DUCKED ) +      ? BG_ClassConfig( pm->ps->stats[ STAT_CLASS ] )->crouchViewheight +      : BG_ClassConfig( pm->ps->stats[ STAT_CLASS ] )->viewheight; +} + +/* +==============  PM_CheckDuck -Sets mins, maxs, and pm->ps->viewheight +Sets mins and maxs, and calls PM_SetViewheight  ==============  */  static void PM_CheckDuck (void)  {    trace_t trace;    vec3_t PCmins, PCmaxs, PCcmaxs; -  int PCvh, PCcvh; -  BG_FindBBoxForClass( pm->ps->stats[ STAT_PCLASS ], PCmins, PCmaxs, PCcmaxs, NULL, NULL ); -  BG_FindViewheightForClass( pm->ps->stats[ STAT_PCLASS ], &PCvh, &PCcvh ); - -  //TA: iD bug? you can still crouch when you're a spectator -  if( pm->ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) -    PCcvh = PCvh; +  BG_ClassBoundingBox( pm->ps->stats[ STAT_CLASS ], PCmins, PCmaxs, PCcmaxs, NULL, NULL );    pm->mins[ 0 ] = PCmins[ 0 ];    pm->mins[ 1 ] = PCmins[ 1 ]; @@ -2302,14 +2424,13 @@ static void PM_CheckDuck (void)    if( pm->ps->pm_type == PM_DEAD )    {      pm->maxs[ 2 ] = -8; -    pm->ps->viewheight = DEAD_VIEWHEIGHT; +    pm->ps->viewheight = PCmins[ 2 ] + DEAD_VIEWHEIGHT;      return;    } -  //TA: If the standing and crouching viewheights are the same the class can't crouch -  if( ( pm->cmd.upmove < 0 ) && ( PCvh != PCcvh ) && -      pm->ps->pm_type != PM_JETPACK && -      !BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) ) +  // If the standing and crouching bboxes are the same the class can't crouch +  if( ( pm->cmd.upmove < 0 ) && !VectorCompare( PCmaxs, PCcmaxs ) && +      pm->ps->pm_type != PM_JETPACK )    {      // duck      pm->ps->pm_flags |= PMF_DUCKED; @@ -2328,15 +2449,11 @@ static void PM_CheckDuck (void)    }    if( pm->ps->pm_flags & PMF_DUCKED ) -  {      pm->maxs[ 2 ] = PCcmaxs[ 2 ]; -    pm->ps->viewheight = PCcvh; -  }    else -  {      pm->maxs[ 2 ] = PCmaxs[ 2 ]; -    pm->ps->viewheight = PCvh; -  } + +  PM_SetViewheight( );  } @@ -2359,9 +2476,9 @@ static void PM_Footsteps( void )    // calculate speed and cycle to be used for    // all cyclic walking effects    // -  if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) && ( pml.groundPlane ) ) +  if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) && ( pml.groundPlane ) )    { -    //TA: FIXME: yes yes i know this is wrong +    // FIXME: yes yes i know this is wrong      pm->xyspeed = sqrt( pm->ps->velocity[ 0 ] * pm->ps->velocity[ 0 ]                        + pm->ps->velocity[ 1 ] * pm->ps->velocity[ 1 ]                        + pm->ps->velocity[ 2 ] * pm->ps->velocity[ 2 ] ); @@ -2519,7 +2636,7 @@ static void PM_Footsteps( void )      }    } -  bobmove *= BG_FindBobCycleForClass( pm->ps->stats[ STAT_PCLASS ] ); +  bobmove *= BG_Class( pm->ps->stats[ STAT_CLASS ] )->bobCycle;    if( pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST )      bobmove *= HUMAN_SPRINT_MODIFIER; @@ -2606,19 +2723,15 @@ static void PM_BeginWeaponChange( int weapon )    if( pm->ps->weaponstate == WEAPON_DROPPING )      return; -  //special case to prevent storing a charged up lcannon -  if( pm->ps->weapon == WP_LUCIFER_CANNON ) -    pm->ps->stats[ STAT_MISC ] = 0; -    // cancel a reload    pm->ps->pm_flags &= ~PMF_WEAPON_RELOAD;    if( pm->ps->weaponstate == WEAPON_RELOADING )      pm->ps->weaponTime = 0; -  // force this here to prevent flamer effect from continuing, among other issues -  pm->ps->generic1 = WPM_NOTFIRING; +  //special case to prevent storing a charged up lcannon +  if( pm->ps->weapon == WP_LUCIFER_CANNON ) +    pm->ps->stats[ STAT_MISC ] = 0; -  PM_AddEvent( EV_CHANGE_WEAPON );    pm->ps->weaponstate = WEAPON_DROPPING;    pm->ps->weaponTime += 200;    pm->ps->persistant[ PERS_NEWWEAPON ] = weapon; @@ -2627,7 +2740,10 @@ static void PM_BeginWeaponChange( int weapon )    pm->ps->stats[ STAT_BUILDABLE ] = BA_NONE;    if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) +  {      PM_StartTorsoAnim( TORSO_DROP ); +    PM_StartWeaponAnim( WANIM_DROP ); +  }  } @@ -2640,6 +2756,7 @@ static void PM_FinishWeaponChange( void )  {    int   weapon; +  PM_AddEvent( EV_CHANGE_WEAPON );    weapon = pm->ps->persistant[ PERS_NEWWEAPON ];    if( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS )      weapon = WP_NONE; @@ -2652,7 +2769,10 @@ static void PM_FinishWeaponChange( void )    pm->ps->weaponTime += 250;    if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) +  {      PM_StartTorsoAnim( TORSO_RAISE ); +    PM_StartWeaponAnim( WANIM_RAISE ); +  }  } @@ -2664,15 +2784,17 @@ PM_TorsoAnimation  */  static void PM_TorsoAnimation( void )  { -  if( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) -    return; -    if( pm->ps->weaponstate == WEAPON_READY )    { -    if( pm->ps->weapon == WP_BLASTER ) -      PM_ContinueTorsoAnim( TORSO_STAND2 ); -    else -      PM_ContinueTorsoAnim( TORSO_STAND ); +    if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) +    { +      if( pm->ps->weapon == WP_BLASTER ) +        PM_ContinueTorsoAnim( TORSO_STAND2 ); +      else +        PM_ContinueTorsoAnim( TORSO_STAND ); +    } + +    PM_ContinueWeaponAnim( WANIM_IDLE );    }  } @@ -2687,61 +2809,169 @@ Generates weapon events and modifes the weapon counter  static void PM_Weapon( void )  {    int           addTime = 200; //default addTime - should never be used -  int           ammo, clips, maxClips; -  qboolean      attack1 = qfalse; -  qboolean      attack2 = qfalse; -  qboolean      attack3 = qfalse; +  qboolean      attack1 = pm->cmd.buttons & BUTTON_ATTACK; +  qboolean      attack2 = pm->cmd.buttons & BUTTON_ATTACK2; +  qboolean      attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE; -  // don't allow attack until all buttons are up -  if( pm->ps->pm_flags & PMF_RESPAWNED ) +  // Ignore weapons in some cases +  if( pm->ps->persistant[ PERS_SPECSTATE ] != SPECTATOR_NOT )      return; -  // ignore if spectator -  if( pm->ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) +  // Check for dead player +  if( pm->ps->stats[ STAT_HEALTH ] <= 0 ) +  { +    pm->ps->weapon = WP_NONE;      return; +  } -  if( pm->ps->stats[ STAT_STATE ] & SS_INFESTING ) -    return; +  // Charging for a pounce or canceling a pounce +  if( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) +  { +    int max; +     +    max = pm->ps->weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_TIME : +                                         LEVEL3_POUNCE_TIME_UPG; +    if( pm->cmd.buttons & BUTTON_ATTACK2 ) +      pm->ps->stats[ STAT_MISC ] += pml.msec; +    else +      pm->ps->stats[ STAT_MISC ] -= pml.msec; -  if( pm->ps->stats[ STAT_STATE ] & SS_HOVELING ) -    return; +    if( pm->ps->stats[ STAT_MISC ] > max ) +      pm->ps->stats[ STAT_MISC ] = max; +    else if( pm->ps->stats[ STAT_MISC ] < 0 ) +      pm->ps->stats[ STAT_MISC ] = 0; +  } -  // check for dead player -  if( pm->ps->stats[ STAT_HEALTH ] <= 0 ) +  // Trample charge mechanics +  if( pm->ps->weapon == WP_ALEVEL4 )    { -    pm->ps->weapon = WP_NONE; +    // Charging up +    if( !( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) ) +    { +      // Charge button held +      if( pm->ps->stats[ STAT_MISC ] < LEVEL4_TRAMPLE_CHARGE_TRIGGER && +          ( pm->cmd.buttons & BUTTON_ATTACK2 ) ) +      { +        pm->ps->stats[ STAT_STATE ] &= ~SS_CHARGING; +        if( pm->cmd.forwardmove > 0 ) +        { +          int charge = pml.msec; +          vec3_t dir,vel; + +          AngleVectors( pm->ps->viewangles, dir, NULL, NULL ); +          VectorCopy( pm->ps->velocity, vel ); +          vel[2] = 0; +          dir[2] = 0; +          VectorNormalize( vel ); +          VectorNormalize( dir ); + +          charge *= DotProduct( dir, vel ); + +          pm->ps->stats[ STAT_MISC ] += charge; +        } +        else +          pm->ps->stats[ STAT_MISC ] = 0; +      } +       +      // Charge button released +      else if( !( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) ) +      { +        if( pm->ps->stats[ STAT_MISC ] > LEVEL4_TRAMPLE_CHARGE_MIN ) +        { +          if( pm->ps->stats[ STAT_MISC ] > LEVEL4_TRAMPLE_CHARGE_MAX ) +            pm->ps->stats[ STAT_MISC ] = LEVEL4_TRAMPLE_CHARGE_MAX; +          pm->ps->stats[ STAT_MISC ] = pm->ps->stats[ STAT_MISC ] * +                                       LEVEL4_TRAMPLE_DURATION / +                                       LEVEL4_TRAMPLE_CHARGE_MAX; +          pm->ps->stats[ STAT_STATE ] |= SS_CHARGING; +          PM_AddEvent( EV_LEV4_TRAMPLE_START ); +        } +        else +          pm->ps->stats[ STAT_MISC ] -= pml.msec; +      } +    } +     +    // Discharging +    else +    { +      if( pm->ps->stats[ STAT_MISC ] < LEVEL4_TRAMPLE_CHARGE_MIN ) +        pm->ps->stats[ STAT_MISC ] = 0; +      else +        pm->ps->stats[ STAT_MISC ] -= pml.msec; + +      // If the charger has stopped moving take a chunk of charge away +      if( VectorLength( pm->ps->velocity ) < 64.0f || pm->cmd.rightmove ) +        pm->ps->stats[ STAT_MISC ] -= LEVEL4_TRAMPLE_STOP_PENALTY * pml.msec; +    } +     +    // Charge is over +    if( pm->ps->stats[ STAT_MISC ] <= 0 || pm->cmd.forwardmove <= 0 ) +    { +      pm->ps->stats[ STAT_MISC ] = 0; +      pm->ps->stats[ STAT_STATE ] &= ~SS_CHARGING; +    } +  } + +  // Charging up a Lucifer Cannon +  pm->ps->eFlags &= ~EF_WARN_CHARGE; + +  // don't allow attack until all buttons are up +  if( pm->ps->pm_flags & PMF_RESPAWNED )      return; + +  if( pm->ps->weapon == WP_LUCIFER_CANNON ) +  { +    // Charging up +    if( !pm->ps->weaponTime && +        ( pm->cmd.buttons & BUTTON_ATTACK ) ) +    { +      pm->ps->stats[ STAT_MISC ] += pml.msec; +      if( pm->ps->stats[ STAT_MISC ] >= LCANNON_CHARGE_TIME_MAX ) +        pm->ps->stats[ STAT_MISC ] = LCANNON_CHARGE_TIME_MAX; +      if( pm->ps->stats[ STAT_MISC ] > pm->ps->ammo * LCANNON_CHARGE_TIME_MAX / +                                              LCANNON_CHARGE_AMMO ) +        pm->ps->stats[ STAT_MISC ] = pm->ps->ammo * LCANNON_CHARGE_TIME_MAX / +                                            LCANNON_CHARGE_AMMO; +    } + +    // Set overcharging flag so other players can hear the warning beep +    if( pm->ps->stats[ STAT_MISC ] > LCANNON_CHARGE_TIME_WARN ) +      pm->ps->eFlags |= EF_WARN_CHARGE;    } -      // no bite during pounce -  if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG )  -    && ( pm->cmd.buttons & BUTTON_ATTACK ) -    && ( pm->ps->pm_flags & PMF_CHARGE ) ) -  { +  if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) +      && ( pm->cmd.buttons & BUTTON_ATTACK ) +      && ( pm->ps->pm_flags & PMF_CHARGE ) )      return; -  } +  // pump weapon delays (repeat times etc)    if( pm->ps->weaponTime > 0 )      pm->ps->weaponTime -= pml.msec; +  if( pm->ps->weaponTime < 0 ) +    pm->ps->weaponTime = 0; + +  // no slash during charge +  if( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) +    return;    // check for weapon change    // can't change if weapon is firing, but can change    // again if lowering or raising -  if( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) +  if( BG_PlayerCanChangeWeapon( pm->ps ) )    { -    //TA: must press use to switch weapons +    // must press use to switch weapons      if( pm->cmd.buttons & BUTTON_USE_HOLDABLE )      {        if( !( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) )        { -        if( pm->cmd.weapon <= 32 ) +        if( pm->cmd.weapon < 32 )          {            //if trying to select a weapon, select it            if( pm->ps->weapon != pm->cmd.weapon )              PM_BeginWeaponChange( pm->cmd.weapon );          } -        else if( pm->cmd.weapon > 32 ) +        else          {            //if trying to toggle an upgrade, toggle it            if( BG_InventoryContainsUpgrade( pm->cmd.weapon - 32, pm->ps->stats ) ) //sanity check @@ -2762,7 +2992,16 @@ static void PM_Weapon( void )      if( pm->ps->pm_flags & PMF_WEAPON_SWITCH )      {        pm->ps->pm_flags &= ~PMF_WEAPON_SWITCH; -      PM_BeginWeaponChange( pm->ps->persistant[ PERS_NEWWEAPON ] ); +      if( pm->ps->weapon != WP_NONE ) +      { +        // drop the current weapon +        PM_BeginWeaponChange( pm->ps->persistant[ PERS_NEWWEAPON ] ); +      } +      else +      { +        // no current weapon, so just raise the new one +        PM_FinishWeaponChange( ); +      }      }    } @@ -2776,10 +3015,10 @@ static void PM_Weapon( void )      return;    } +  // Set proper animation    if( pm->ps->weaponstate == WEAPON_RAISING )    {      pm->ps->weaponstate = WEAPON_READY; -      if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )      {        if( pm->ps->weapon == WP_BLASTER ) @@ -2788,19 +3027,21 @@ static void PM_Weapon( void )          PM_ContinueTorsoAnim( TORSO_STAND );      } +    PM_ContinueWeaponAnim( WANIM_IDLE ); +      return;    } -  // start the animation even if out of ammo -  ammo = pm->ps->ammo; -  clips = pm->ps->clips; -  BG_FindAmmoForWeapon( pm->ps->weapon, NULL, &maxClips ); -    // check for out of ammo -  if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) +  if( !pm->ps->ammo && !pm->ps->clips && !BG_Weapon( pm->ps->weapon )->infiniteAmmo )    { -    PM_AddEvent( EV_NOAMMO ); -    pm->ps->weaponTime += 200; +    if( attack1 || +        ( BG_Weapon( pm->ps->weapon )->hasAltMode && attack2 ) || +        ( BG_Weapon( pm->ps->weapon )->hasThirdMode && attack3 ) ) +    { +      PM_AddEvent( EV_NOAMMO ); +      pm->ps->weaponTime += 500; +    }      if( pm->ps->weaponstate == WEAPON_FIRING )        pm->ps->weaponstate = WEAPON_READY; @@ -2811,18 +3052,12 @@ static void PM_Weapon( void )    //done reloading so give em some ammo    if( pm->ps->weaponstate == WEAPON_RELOADING )    { -    if( maxClips > 0 ) -    { -      clips--; -      BG_FindAmmoForWeapon( pm->ps->weapon, &ammo, NULL ); -    } +    pm->ps->clips--; +    pm->ps->ammo = BG_Weapon( pm->ps->weapon )->maxAmmo; -    if( BG_FindUsesEnergyForWeapon( pm->ps->weapon ) && +    if( BG_Weapon( pm->ps->weapon )->usesEnergy &&          BG_InventoryContainsUpgrade( UP_BATTPACK, pm->ps->stats ) ) -      ammo = (int)( (float)ammo * BATTPACK_MODIFIER ); - -    pm->ps->ammo = ammo; -    pm->ps->clips = clips; +      pm->ps->ammo *= BATTPACK_MODIFIER;      //allow some time for the weapon to be raised      pm->ps->weaponstate = WEAPON_RAISING; @@ -2832,19 +3067,18 @@ static void PM_Weapon( void )    }    // check for end of clip -  if( !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) && -      ( !ammo || pm->ps->pm_flags & PMF_WEAPON_RELOAD ) && clips ) +  if( !BG_Weapon( pm->ps->weapon )->infiniteAmmo && +      ( pm->ps->ammo <= 0 || ( pm->ps->pm_flags & PMF_WEAPON_RELOAD ) ) && +      pm->ps->clips > 0 )    {      pm->ps->pm_flags &= ~PMF_WEAPON_RELOAD; -      pm->ps->weaponstate = WEAPON_RELOADING;      //drop the weapon      PM_StartTorsoAnim( TORSO_DROP ); +    PM_StartWeaponAnim( WANIM_RELOAD ); -    addTime = BG_FindReloadTimeForWeapon( pm->ps->weapon ); - -    pm->ps->weaponTime += addTime; +    pm->ps->weaponTime += BG_Weapon( pm->ps->weapon )->reloadTime;      return;    } @@ -2853,62 +3087,60 @@ static void PM_Weapon( void )    {      case WP_ALEVEL0:        //venom is only autohit -      attack1 = attack2 = attack3 = qfalse; - -      if( !pm->autoWeaponHit[ pm->ps->weapon ] ) -      { -        pm->ps->weaponTime = 0; -        pm->ps->weaponstate = WEAPON_READY; -        return; -      } -      break; +      return;      case WP_ALEVEL3:      case WP_ALEVEL3_UPG:        //pouncing has primary secondary AND autohit procedures -      attack1 = pm->cmd.buttons & BUTTON_ATTACK; -      attack2 = pm->cmd.buttons & BUTTON_ATTACK2; -      attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE; - -      if( !pm->autoWeaponHit[ pm->ps->weapon ] && !attack1 && !attack2 && !attack3 ) -      { -        pm->ps->weaponTime = 0; -        pm->ps->weaponstate = WEAPON_READY; +      // pounce is autohit +      if( !attack1 && !attack2 && !attack3 )          return; -      }        break;      case WP_LUCIFER_CANNON: -      attack1 = pm->cmd.buttons & BUTTON_ATTACK; -      attack2 = pm->cmd.buttons & BUTTON_ATTACK2; -      attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE; +      attack3 = qfalse; -      if( ( attack1 || pm->ps->stats[ STAT_MISC ] == 0 ) && !attack2 && !attack3 ) +      // Can't fire secondary while primary is charging +      if( attack1 || pm->ps->stats[ STAT_MISC ] > 0 ) +        attack2 = qfalse; +         +      if( ( attack1 || pm->ps->stats[ STAT_MISC ] == 0 ) && !attack2 )        { -        if( pm->ps->stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE ) +        pm->ps->weaponTime = 0; + +        // Charging +        if( pm->ps->stats[ STAT_MISC ] < LCANNON_CHARGE_TIME_MAX )          { -          pm->ps->weaponTime = 0;            pm->ps->weaponstate = WEAPON_READY;            return;          } -        else -          attack1 = !attack1;        } -      //erp this looks confusing -      if( pm->ps->stats[ STAT_MISC ] > LCANNON_MIN_CHARGE ) -        attack1 = !attack1; +      if( pm->ps->stats[ STAT_MISC ] > LCANNON_CHARGE_TIME_MIN ) +      { +        // Fire primary attack +        attack1 = qtrue; +        attack2 = qfalse; +      }        else if( pm->ps->stats[ STAT_MISC ] > 0 )        { +        // Not enough charge          pm->ps->stats[ STAT_MISC ] = 0;          pm->ps->weaponTime = 0;          pm->ps->weaponstate = WEAPON_READY;          return;        } +      else if( !attack2 ) +      { +        // Idle +        pm->ps->weaponTime = 0; +        pm->ps->weaponstate = WEAPON_READY; +        return; +      }        break;      case WP_MASS_DRIVER: -      attack1 = pm->cmd.buttons & BUTTON_ATTACK; +      attack2 = attack3 = qfalse;        // attack2 is handled on the client for zooming (cg_view.c)        if( !attack1 ) @@ -2920,11 +3152,6 @@ static void PM_Weapon( void )        break;      default: -      //by default primary and secondary attacks are allowed -      attack1 = pm->cmd.buttons & BUTTON_ATTACK; -      attack2 = pm->cmd.buttons & BUTTON_ATTACK2; -      attack3 = pm->cmd.buttons & BUTTON_USE_HOLDABLE; -        if( !attack1 && !attack2 && !attack3 )        {          pm->ps->weaponTime = 0; @@ -2933,29 +3160,22 @@ static void PM_Weapon( void )        }        break;    } -   -  if ( pm->ps->weapon == WP_LUCIFER_CANNON && pm->ps->stats[ STAT_MISC ] > 0 && attack3 ) -  { -    attack1 = qtrue; -    attack3 = qfalse; -  } -	   -  //TA: fire events for non auto weapons + +  // fire events for non auto weapons    if( attack3 )    { -    if( BG_WeaponHasThirdMode( pm->ps->weapon ) ) +    if( BG_Weapon( pm->ps->weapon )->hasThirdMode )      {        //hacky special case for slowblob -      if( pm->ps->weapon == WP_ALEVEL3_UPG && !ammo ) +      if( pm->ps->weapon == WP_ALEVEL3_UPG && !pm->ps->ammo )        { -        PM_AddEvent( EV_NOAMMO );          pm->ps->weaponTime += 200;          return;        }        pm->ps->generic1 = WPM_TERTIARY;        PM_AddEvent( EV_FIRE_WEAPON3 ); -      addTime = BG_FindRepeatRate3ForWeapon( pm->ps->weapon ); +      addTime = BG_Weapon( pm->ps->weapon )->repeatRate3;      }      else      { @@ -2967,11 +3187,11 @@ static void PM_Weapon( void )    }    else if( attack2 )    { -    if( BG_WeaponHasAltMode( pm->ps->weapon ) ) +    if( BG_Weapon( pm->ps->weapon )->hasAltMode )      {        pm->ps->generic1 = WPM_SECONDARY;        PM_AddEvent( EV_FIRE_WEAPON2 ); -      addTime = BG_FindRepeatRate2ForWeapon( pm->ps->weapon ); +      addTime = BG_Weapon( pm->ps->weapon )->repeatRate2;      }      else      { @@ -2985,10 +3205,10 @@ static void PM_Weapon( void )    {      pm->ps->generic1 = WPM_PRIMARY;      PM_AddEvent( EV_FIRE_WEAPON ); -    addTime = BG_FindRepeatRate1ForWeapon( pm->ps->weapon ); +    addTime = BG_Weapon( pm->ps->weapon )->repeatRate1;    } -  //TA: fire events for autohit weapons +  // fire events for autohit weapons    if( pm->autoWeaponHit[ pm->ps->weapon ] )    {      switch( pm->ps->weapon ) @@ -2996,14 +3216,14 @@ static void PM_Weapon( void )        case WP_ALEVEL0:          pm->ps->generic1 = WPM_PRIMARY;          PM_AddEvent( EV_FIRE_WEAPON ); -        addTime = BG_FindRepeatRate1ForWeapon( pm->ps->weapon ); +        addTime = BG_Weapon( pm->ps->weapon )->repeatRate1;          break;        case WP_ALEVEL3:        case WP_ALEVEL3_UPG:          pm->ps->generic1 = WPM_SECONDARY;          PM_AddEvent( EV_FIRE_WEAPON2 ); -        addTime = BG_FindRepeatRate2ForWeapon( pm->ps->weapon ); +        addTime = BG_Weapon( pm->ps->weapon )->repeatRate2;          break;        default: @@ -3020,41 +3240,77 @@ static void PM_Weapon( void )          if( pm->ps->weaponstate == WEAPON_READY )          {            PM_StartTorsoAnim( TORSO_ATTACK ); +          PM_StartWeaponAnim( WANIM_ATTACK1 );          }          break;        case WP_BLASTER:          PM_StartTorsoAnim( TORSO_ATTACK2 ); +        PM_StartWeaponAnim( WANIM_ATTACK1 );          break;        default:          PM_StartTorsoAnim( TORSO_ATTACK ); +        PM_StartWeaponAnim( WANIM_ATTACK1 );          break;      }    }    else    { -    if( pm->ps->weapon == WP_ALEVEL4 ) -    { -      //hack to get random attack animations -      //FIXME: does pm->ps->weaponTime cycle enough? -      int num = abs( pm->ps->weaponTime ) % 3; +    int num = rand( ); -      if( num == 0 ) -        PM_ForceLegsAnim( NSPA_ATTACK1 ); -      else if( num == 1 ) -        PM_ForceLegsAnim( NSPA_ATTACK2 ); -      else if( num == 2 ) -        PM_ForceLegsAnim( NSPA_ATTACK3 ); -    } -    else +    //FIXME: it would be nice to have these hard coded policies in +    //       weapon.cfg +    switch( pm->ps->weapon )      { -      if( attack1 ) -        PM_ForceLegsAnim( NSPA_ATTACK1 ); -      else if( attack2 ) -        PM_ForceLegsAnim( NSPA_ATTACK2 ); -      else if( attack3 ) -        PM_ForceLegsAnim( NSPA_ATTACK3 ); +      case WP_ALEVEL1_UPG: +      case WP_ALEVEL1: +        if( attack1 ) +        { +          num /= RAND_MAX / 6 + 1; +          PM_ForceLegsAnim( NSPA_ATTACK1 ); +          PM_StartWeaponAnim( WANIM_ATTACK1 + num ); +        } +        break; + +      case WP_ALEVEL2_UPG: +        if( attack2 ) +        { +          PM_ForceLegsAnim( NSPA_ATTACK2 ); +          PM_StartWeaponAnim( WANIM_ATTACK7 ); +        } +      case WP_ALEVEL2: +        if( attack1 ) +        { +          num /= RAND_MAX / 6 + 1; +          PM_ForceLegsAnim( NSPA_ATTACK1 ); +          PM_StartWeaponAnim( WANIM_ATTACK1 + num ); +        } +        break; + +      case WP_ALEVEL4: +        num /= RAND_MAX / 3 + 1; +        PM_ForceLegsAnim( NSPA_ATTACK1 + num ); +        PM_StartWeaponAnim( WANIM_ATTACK1 + num ); +        break; + +      default: +        if( attack1 ) +        { +          PM_ForceLegsAnim( NSPA_ATTACK1 ); +          PM_StartWeaponAnim( WANIM_ATTACK1 ); +        } +        else if( attack2 ) +        { +          PM_ForceLegsAnim( NSPA_ATTACK2 ); +          PM_StartWeaponAnim( WANIM_ATTACK2 ); +        } +        else if( attack3 ) +        { +          PM_ForceLegsAnim( NSPA_ATTACK3 ); +          PM_StartWeaponAnim( WANIM_ATTACK3 ); +        } +        break;      }      pm->ps->torsoTimer = TIMER_ATTACK; @@ -3063,29 +3319,19 @@ static void PM_Weapon( void )    pm->ps->weaponstate = WEAPON_FIRING;    // take an ammo away if not infinite -  if( !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) +  if( !BG_Weapon( pm->ps->weapon )->infiniteAmmo || +      ( pm->ps->weapon == WP_ALEVEL3_UPG && attack3 ) )    { -    //special case for lCanon +    // Special case for lcannon      if( pm->ps->weapon == WP_LUCIFER_CANNON && attack1 && !attack2 ) -    { -      ammo -= (int)( ceil( ( (float)pm->ps->stats[ STAT_MISC ] / (float)LCANNON_TOTAL_CHARGE ) * 10.0f ) ); - -      //stay on the safe side -      if( ammo < 0 ) -        ammo = 0; -    } +      pm->ps->ammo -= ( pm->ps->stats[ STAT_MISC ] * LCANNON_CHARGE_AMMO + +                LCANNON_CHARGE_TIME_MAX - 1 ) / LCANNON_CHARGE_TIME_MAX;      else -      ammo--; +      pm->ps->ammo--; -    pm->ps->ammo = ammo; -    pm->ps->clips = clips; -  } -  else if( pm->ps->weapon == WP_ALEVEL3_UPG && attack3 ) -  { -    //special case for slowblob -    ammo--; -    pm->ps->ammo = ammo; -    pm->ps->clips = clips; +    // Stay on the safe side +    if( pm->ps->ammo < 0 ) +      pm->ps->ammo = 0;    }    //FIXME: predicted angles miss a problem?? @@ -3114,14 +3360,21 @@ PM_Animate  */  static void PM_Animate( void )  { +  if( PM_Paralyzed( pm->ps->pm_type ) ) +    return; +    if( pm->cmd.buttons & BUTTON_GESTURE )    { +    if( pm->ps->tauntTimer > 0 ) +        return; +      if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) )      {        if( pm->ps->torsoTimer == 0 )        {          PM_StartTorsoAnim( TORSO_GESTURE );          pm->ps->torsoTimer = TIMER_GESTURE; +        pm->ps->tauntTimer = TIMER_GESTURE;          PM_AddEvent( EV_TAUNT );        } @@ -3132,6 +3385,7 @@ static void PM_Animate( void )        {          PM_ForceLegsAnim( NSPA_GESTURE );          pm->ps->torsoTimer = TIMER_GESTURE; +        pm->ps->tauntTimer = TIMER_GESTURE;          PM_AddEvent( EV_TAUNT );        } @@ -3175,6 +3429,16 @@ static void PM_DropTimers( void )      if( pm->ps->torsoTimer < 0 )        pm->ps->torsoTimer = 0;    } + +  if( pm->ps->tauntTimer > 0 ) +  { +    pm->ps->tauntTimer -= pml.msec; + +    if( pm->ps->tauntTimer < 0 ) +    { +      pm->ps->tauntTimer = 0; +    } +  }  } @@ -3193,7 +3457,7 @@ void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd )    vec3_t  axis[ 3 ], rotaxis[ 3 ];    vec3_t  tempang; -  if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION ) +  if( ps->pm_type == PM_INTERMISSION )      return;   // no view changes at all    if( ps->pm_type != PM_SPECTATOR && ps->stats[ STAT_HEALTH ] <= 0 ) @@ -3202,7 +3466,21 @@ void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd )    // circularly clamp the angles with deltas    for( i = 0; i < 3; i++ )    { -    temp[ i ] = cmd->angles[ i ] + ps->delta_angles[ i ]; +    if( i == ROLL )  +    { +      // Guard against speed hack +      temp[ i ] = ps->delta_angles[ i ]; + +#ifdef CGAME +      // Assert here so that if cmd->angles[ i ] becomes non-zero +      // for a legitimate reason we can tell where and why it's +      // being ignored +      assert( cmd->angles[ i ] == 0 ); +#endif + +    } +    else +      temp[ i ] = cmd->angles[ i ] + ps->delta_angles[ i ];      if( i == PITCH )      { @@ -3226,7 +3504,7 @@ void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd )    if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ||        !BG_RotateAxis( ps->grapplePoint, axis, rotaxis, qfalse, -                      ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) +                      ps->eFlags & EF_WALLCLIMBCEILING ) )      AxisCopy( axis, rotaxis );    //convert the new axis back to angles @@ -3238,7 +3516,7 @@ void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd )      while( tempang[ i ] > 180.0f )        tempang[ i ] -= 360.0f; -    while( tempang[ i ] < 180.0f ) +    while( tempang[ i ] < -180.0f )        tempang[ i ] += 360.0f;    } @@ -3287,15 +3565,10 @@ void trap_SnapVector( float *v );  void PmoveSingle( pmove_t *pmove )  { -  int ammo, clips; -    pm = pmove; -  ammo = pm->ps->ammo; -  clips = pm->ps->clips; -    // this counter lets us debug movement problems with a journal -  // by setting a conditional breakpoint fot the previous frame +  // by setting a conditional breakpoint for the previous frame    c_pmove++;    // clear results @@ -3311,16 +3584,10 @@ void PmoveSingle( pmove_t *pmove )    if( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 )      pm->cmd.buttons &= ~BUTTON_WALKING; -  // set the talk balloon flag -  if( pm->cmd.buttons & BUTTON_TALK ) -    pm->ps->eFlags |= EF_TALK; -  else -    pm->ps->eFlags &= ~EF_TALK; -    // set the firing flag for continuous beam weapons    if( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION &&        ( pm->cmd.buttons & BUTTON_ATTACK ) && -      ( ( ammo > 0 || clips > 0 ) || BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) ) +      ( ( pm->ps->ammo > 0 || pm->ps->clips > 0 ) || BG_Weapon( pm->ps->weapon )->infiniteAmmo ) )      pm->ps->eFlags |= EF_FIRING;    else      pm->ps->eFlags &= ~EF_FIRING; @@ -3328,7 +3595,7 @@ void PmoveSingle( pmove_t *pmove )    // set the firing flag for continuous beam weapons    if( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION &&        ( pm->cmd.buttons & BUTTON_ATTACK2 ) && -      ( ( ammo > 0 || clips > 0 ) || BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) ) +      ( ( pm->ps->ammo > 0 || pm->ps->clips > 0 ) || BG_Weapon( pm->ps->weapon )->infiniteAmmo ) )      pm->ps->eFlags |= EF_FIRING2;    else      pm->ps->eFlags &= ~EF_FIRING2; @@ -3336,7 +3603,7 @@ void PmoveSingle( pmove_t *pmove )    // set the firing flag for continuous beam weapons    if( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION &&        ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) && -      ( ( ammo > 0 || clips > 0 ) || BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) ) +      ( ( pm->ps->ammo > 0 || pm->ps->clips > 0 ) || BG_Weapon( pm->ps->weapon )->infiniteAmmo ) )      pm->ps->eFlags |= EF_FIRING3;    else      pm->ps->eFlags &= ~EF_FIRING3; @@ -3355,7 +3622,9 @@ void PmoveSingle( pmove_t *pmove )      pmove->cmd.buttons = BUTTON_TALK;      pmove->cmd.forwardmove = 0;      pmove->cmd.rightmove = 0; -    pmove->cmd.upmove = 0; + +    if( pmove->cmd.upmove > 0 ) +      pmove->cmd.upmove = 0;    }    // clear all pmove local vars @@ -3393,7 +3662,7 @@ void PmoveSingle( pmove_t *pmove )    else if( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) )      pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; -  if( pm->ps->pm_type >= PM_DEAD ) +  if( PM_Paralyzed( pm->ps->pm_type ) )    {      pm->cmd.forwardmove = 0;      pm->cmd.rightmove = 0; @@ -3414,6 +3683,8 @@ void PmoveSingle( pmove_t *pmove )    {      PM_UpdateViewAngles( pm->ps, &pm->cmd );      PM_NoclipMove( ); +    PM_SetViewheight( ); +    PM_Weapon( );      PM_DropTimers( );      return;    } @@ -3421,7 +3692,7 @@ void PmoveSingle( pmove_t *pmove )    if( pm->ps->pm_type == PM_FREEZE)      return;   // no movement at all -  if( pm->ps->pm_type == PM_INTERMISSION || pm->ps->pm_type == PM_SPINTERMISSION ) +  if( pm->ps->pm_type == PM_INTERMISSION )      return;   // no movement at all    // set watertype, and waterlevel @@ -3443,6 +3714,7 @@ void PmoveSingle( pmove_t *pmove )      PM_DeadMove( );    PM_DropTimers( ); +  PM_CheckDodge( );    if( pm->ps->pm_type == PM_JETPACK )      PM_JetPackMove( ); @@ -3454,9 +3726,9 @@ void PmoveSingle( pmove_t *pmove )      PM_LadderMove( );    else if( pml.walking )    { -    if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) && +    if( BG_ClassHasAbility( pm->ps->stats[ STAT_CLASS ], SCA_WALLCLIMBER ) &&          ( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) -      PM_ClimbMove( ); //TA: walking on any surface +      PM_ClimbMove( ); // walking on any surface      else        PM_WalkMove( ); // walking on ground    } @@ -3467,7 +3739,7 @@ void PmoveSingle( pmove_t *pmove )    // set groundentity, watertype, and waterlevel    PM_GroundTrace( ); -  //TA: must update after every GroundTrace() - yet more clock cycles down the drain :( (14 vec rotations/frame) +    // update the viewangles    PM_UpdateViewAngles( pm->ps, &pm->cmd ); @@ -3530,11 +3802,7 @@ void Pmove( pmove_t *pmove )          msec = 66;      } -      pmove->cmd.serverTime = pmove->ps->commandTime + msec;      PmoveSingle( pmove ); - -    if( pmove->ps->pm_flags & PMF_JUMP_HELD ) -      pmove->cmd.upmove = 20;    }  } diff --git a/src/game/bg_public.h b/src/game/bg_public.h index 462bf95..f850a5d 100644 --- a/src/game/bg_public.h +++ b/src/game/bg_public.h @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,15 +17,19 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ +#ifndef _BG_PUBLIC_H_ +#define _BG_PUBLIC_H_ +  // bg_public.h -- definitions shared by both the server game and client game modules  //tremulous balance header -#include "tremulous.h" +#include "qcommon/q_shared.h" +#include "game/tremulous.h"  // because games can change separately from the main system version, we need a  // second version that must match between game and cgame @@ -37,7 +42,17 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define MINS_Z                  -24  #define DEFAULT_VIEWHEIGHT      26  #define CROUCH_VIEWHEIGHT       12 -#define DEAD_VIEWHEIGHT         -14 //TA: watch for mins[ 2 ] less than this causing +#define DEAD_VIEWHEIGHT         4 // height from ground + +// player teams +typedef enum +{ +  TEAM_NONE, +  TEAM_ALIENS, +  TEAM_HUMANS, + +  NUM_TEAMS +} team_t;  //  // config strings are a general means of communicating variable length strings @@ -45,47 +60,42 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  //  // CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h -#define CS_MUSIC            2 -#define CS_MESSAGE          3   // from the map worldspawn's message field -#define CS_MOTD             4   // g_motd string for server message of the day -#define CS_WARMUP           5   // server time when the match will be restarted -// 6 UNUSED -// 7 UNUSED -#define CS_VOTE_TIME        8 -#define CS_VOTE_STRING      9 -#define CS_VOTE_YES         10 -#define CS_VOTE_NO          11 - -#define CS_TEAMVOTE_TIME    12 -#define CS_TEAMVOTE_STRING  14 -#define CS_TEAMVOTE_YES     16 -#define CS_TEAMVOTE_NO      18 - -#define CS_GAME_VERSION     20 -#define CS_LEVEL_START_TIME 21    // so the timer only shows the current level -#define CS_INTERMISSION     22    // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two -#define CS_WINNER           23    // string indicating round winner -#define CS_SHADERSTATE      24 -#define CS_BOTINFO          25 -#define CS_CLIENTS_READY    26    //TA: following suggestion in STAT_ enum STAT_CLIENTS_READY becomes a configstring - -//TA: extra stuff: -#define CS_BUILDPOINTS      28 -#define CS_STAGES           29 -#define CS_SPAWNS           30 - -#define CS_MODELS           33 -#define CS_SOUNDS           (CS_MODELS+MAX_MODELS) -#define CS_SHADERS          (CS_SOUNDS+MAX_SOUNDS) -#define CS_PARTICLE_SYSTEMS (CS_SHADERS+MAX_GAME_SHADERS) -#define CS_PLAYERS          (CS_PARTICLE_SYSTEMS+MAX_GAME_PARTICLE_SYSTEMS) -#define CS_PRECACHES        (CS_PLAYERS+MAX_CLIENTS) -#define CS_LOCATIONS        (CS_PRECACHES+MAX_CLIENTS) - -#define CS_MAX              (CS_LOCATIONS+MAX_LOCATIONS) - -#if (CS_MAX) > MAX_CONFIGSTRINGS -#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +enum +{ +  CS_MUSIC            = 2, +  CS_MESSAGE,               // from the map worldspawn's message field +  CS_MOTD,                  // g_motd string for server message of the day +  CS_WARMUP           = 5,  // server time when the match will be restarted !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + +  CS_VOTE_TIME,             // Vote stuff each needs NUM_TEAMS slots +  CS_VOTE_STRING      = CS_VOTE_TIME + NUM_TEAMS, +  CS_VOTE_YES         = CS_VOTE_STRING + NUM_TEAMS, +  CS_VOTE_NO          = CS_VOTE_YES + NUM_TEAMS, +  CS_VOTE_CALLER      = CS_VOTE_NO + NUM_TEAMS, +   +  CS_GAME_VERSION     = CS_VOTE_CALLER + NUM_TEAMS, +  CS_LEVEL_START_TIME,      // so the timer only shows the current level +  CS_INTERMISSION,          // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two +  CS_WINNER ,               // string indicating round winner +  CS_SHADERSTATE, +  CS_BOTINFO, +  CS_CLIENTS_READY, + +  CS_ALIEN_STAGES, +  CS_HUMAN_STAGES, + +  CS_MODELS, +  CS_SOUNDS           = CS_MODELS + MAX_MODELS, +  CS_SHADERS          = CS_SOUNDS + MAX_SOUNDS, +  CS_PARTICLE_SYSTEMS = CS_SHADERS + MAX_GAME_SHADERS, +  CS_PLAYERS          = CS_PARTICLE_SYSTEMS + MAX_GAME_PARTICLE_SYSTEMS, +  CS_LOCATIONS        = CS_PLAYERS + MAX_CLIENTS, + +  CS_MAX              = CS_LOCATIONS + MAX_LOCATIONS +}; + +#if CS_MAX > MAX_CONFIGSTRINGS +#error overflow: CS_MAX > MAX_CONFIGSTRINGS  #endif  typedef enum @@ -112,39 +122,45 @@ typedef enum    PM_NOCLIP,        // noclip movement    PM_SPECTATOR,     // still run into walls    PM_JETPACK,       // jetpack physics -  PM_GRABBED,       // like dead, but for when the player is still live +  PM_GRABBED,       // like dead, but for when the player is still alive    PM_DEAD,          // no acceleration or turning, but free falling    PM_FREEZE,        // stuck in place with no control -  PM_INTERMISSION,  // no movement or status bar -  PM_SPINTERMISSION // no movement or status bar +  PM_INTERMISSION   // no movement or status bar  } pmtype_t; +// pmtype_t categories +#define PM_Paralyzed( x ) ( (x) == PM_DEAD || (x) == PM_FREEZE ||\ +                            (x) == PM_INTERMISSION ) +#define PM_Alive( x )     ( (x) == PM_NORMAL || (x) == PM_JETPACK ||\ +                            (x) == PM_GRABBED ) +  typedef enum  {    WEAPON_READY,    WEAPON_RAISING,    WEAPON_DROPPING,    WEAPON_FIRING, -  WEAPON_RELOADING +  WEAPON_RELOADING,  } weaponstate_t;  // pmove->pm_flags -#define PMF_DUCKED          1 -#define PMF_JUMP_HELD       2 -#define PMF_CROUCH_HELD     4 -#define PMF_BACKWARDS_JUMP  8       // go into backwards land -#define PMF_BACKWARDS_RUN   16      // coast down to backwards run -#define PMF_TIME_LAND       32      // pm_time is time before rejump -#define PMF_TIME_KNOCKBACK  64      // pm_time is an air-accelerate only time -#define PMF_TIME_WATERJUMP  256     // pm_time is waterjump -#define PMF_RESPAWNED       512     // clear after attack and jump buttons come up -#define PMF_USE_ITEM_HELD   1024 -#define PMF_WEAPON_RELOAD   2048    //TA: force a weapon switch -#define PMF_FOLLOW          4096    // spectate following another player -#define PMF_QUEUED          8192    //TA: player is queued -#define PMF_TIME_WALLJUMP   16384   //TA: for limiting wall jumping -#define PMF_CHARGE          32768   //TA: keep track of pouncing -#define PMF_WEAPON_SWITCH   65536   //TA: force a weapon switch +#define PMF_DUCKED          0x000001 +#define PMF_JUMP_HELD       0x000002 +#define PMF_CROUCH_HELD     0x000004 +#define PMF_BACKWARDS_JUMP  0x000008 // go into backwards land +#define PMF_BACKWARDS_RUN   0x000010 // coast down to backwards run +#define PMF_TIME_LAND       0x000020 // pm_time is time before rejump +#define PMF_TIME_KNOCKBACK  0x000040 // pm_time is an air-accelerate only time +#define PMF_TIME_WATERJUMP  0x000080 // pm_time is waterjump +#define PMF_RESPAWNED       0x000100 // clear after attack and jump buttons come up +#define PMF_USE_ITEM_HELD   0x000200 +#define PMF_WEAPON_RELOAD   0x000400 // force a weapon switch +#define PMF_FOLLOW          0x000800 // spectate following another player +#define PMF_QUEUED          0x001000 // player is queued +#define PMF_TIME_WALLJUMP   0x002000 // for limiting wall jumping +#define PMF_CHARGE          0x004000 // keep track of pouncing +#define PMF_WEAPON_SWITCH   0x008000 // force a weapon switch +#define PMF_SPRINTHELD      0x010000  #define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK|PMF_TIME_WALLJUMP) @@ -152,10 +168,11 @@ typedef enum  typedef struct  {    int pouncePayload; +  float fallVelocity;  } pmoveExt_t;  #define MAXTOUCH  32 -typedef struct +typedef struct pmove_s  {    // state (in / out)    playerState_t *ps; @@ -165,7 +182,7 @@ typedef struct    int           tracemask;      // collide against these types of surfaces    int           debugLevel;     // if set, diagnostic output will be printed    qboolean      noFootsteps;    // if the game is setup for no footsteps by the server -  qboolean      autoWeaponHit[ 32 ]; //FIXME: TA: remind myself later this might be a problem +  qboolean      autoWeaponHit[ 32 ];    int           framecount; @@ -206,49 +223,43 @@ typedef enum  {    STAT_HEALTH,    STAT_ITEMS, -  STAT_SLOTS,           //TA: tracks the amount of stuff human players are carrying    STAT_ACTIVEITEMS, -  STAT_WEAPONS,         // 16 bit fields -  STAT_WEAPONS2,        //TA: another 16 bits to push the max weapon count up -  STAT_MAX_HEALTH, // health / armor limit, changable by handicap -  STAT_PCLASS,    //TA: player class (for aliens AND humans) -  STAT_PTEAM,     //TA: player team -  STAT_STAMINA,   //TA: stamina (human only) -  STAT_STATE,     //TA: client states e.g. wall climbing -  STAT_MISC,      //TA: for uh...misc stuff -  STAT_BUILDABLE, //TA: which ghost model to display for building -  STAT_BOOSTTIME, //TA: time left for boost (alien only) -  STAT_FALLDIST,  //TA: the distance the player fell -  STAT_VIEWLOCK   //TA: direction to lock the view in +  STAT_WEAPON,    // current primary weapon +  STAT_MAX_HEALTH,// health / armor limit, changable by handicap +  STAT_CLASS,     // player class (for aliens AND humans) +  STAT_TEAM,      // player team +  STAT_STAMINA,   // stamina (human only) +  STAT_STATE,     // client states e.g. wall climbing +  STAT_MISC,      // for uh...misc stuff (pounce, trample, lcannon) +  STAT_BUILDABLE, // which ghost model to display for building +  STAT_FALLDIST,  // the distance the player fell +  STAT_VIEWLOCK   // direction to lock the view in +  // netcode has space for 3 more  } statIndex_t;  #define SCA_WALLCLIMBER         0x00000001  #define SCA_TAKESFALLDAMAGE     0x00000002  #define SCA_CANZOOM             0x00000004 -#define SCA_NOWEAPONDRIFT       0x00000008 -#define SCA_FOVWARPS            0x00000010 -#define SCA_ALIENSENSE          0x00000020 -#define SCA_CANUSELADDERS       0x00000040 -#define SCA_WALLJUMPER          0x00000080 +#define SCA_FOVWARPS            0x00000008 +#define SCA_ALIENSENSE          0x00000010 +#define SCA_CANUSELADDERS       0x00000020 +#define SCA_WALLJUMPER          0x00000040  #define SS_WALLCLIMBING         0x00000001 -#define SS_WALLCLIMBINGCEILING  0x00000002 -#define SS_CREEPSLOWED          0x00000004 -#define SS_SPEEDBOOST           0x00000008 -#define SS_INFESTING            0x00000010 -#define SS_GRABBED              0x00000020 -#define SS_BLOBLOCKED           0x00000040 -#define SS_POISONED             0x00000080 -#define SS_HOVELING             0x00000100 -#define SS_BOOSTED              0x00000200 -#define SS_SLOWLOCKED           0x00000400 -#define SS_POISONCLOUDED        0x00000800 -#define SS_MEDKIT_ACTIVE        0x00001000 -#define SS_CHARGING             0x00002000 - -#define SB_VALID_TOGGLEBIT      0x00004000 - -#define MAX_STAMINA             1000 +#define SS_CREEPSLOWED          0x00000002 +#define SS_SPEEDBOOST           0x00000004 +#define SS_GRABBED              0x00000008 +#define SS_BLOBLOCKED           0x00000010 +#define SS_POISONED             0x00000020 +#define SS_BOOSTED              0x00000040 +#define SS_BOOSTEDWARNING       0x00000080 // booster poison is running out +#define SS_SLOWLOCKED           0x00000100 +#define SS_CHARGING             0x00000200 +#define SS_HEALING_ACTIVE       0x00000400 // medistat for humans, creep for aliens +#define SS_HEALING_2X           0x00000800 // medkit or double healing rate +#define SS_HEALING_3X           0x00001000 // triple healing rate + +#define SB_VALID_TOGGLEBIT      0x00002000  // player_state->persistant[] indexes  // these fields are the only part of player_state that isn't @@ -257,63 +268,53 @@ typedef enum  {    PERS_SCORE,           // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!!    PERS_HITS,            // total points damage inflicted so damage beeps can sound on change -  PERS_UNUSED,          // used to be PERS_RANK, no longer used -  PERS_TEAM, +  PERS_SPAWNS,          // how many spawns your team has +  PERS_SPECSTATE,    PERS_SPAWN_COUNT,     // incremented every respawn    PERS_ATTACKER,        // clientnum of last damage inflicter    PERS_KILLED,          // count of the number of times you died -  //TA:    PERS_STATE,    PERS_CREDIT,    // human credit -  PERS_BANK,      // human credit in the bank    PERS_QUEUEPOS,  // position in the spawn queue -  PERS_NEWWEAPON  // weapon to switch to +  PERS_NEWWEAPON,  // weapon to switch to +  PERS_BP, +  PERS_MARKEDBP +  // netcode has space for 3 more  } persEnum_t;  #define PS_WALLCLIMBINGFOLLOW   0x00000001  #define PS_WALLCLIMBINGTOGGLE   0x00000002  #define PS_NONSEGMODEL          0x00000004 +#define PS_SPRINTTOGGLE         0x00000008  // entityState_t->eFlags -#define EF_DEAD             0x00000001    // don't draw a foe marker over players with EF_DEAD -#define EF_TELEPORT_BIT     0x00000002    // toggled every time the origin abruptly changes -#define EF_PLAYER_EVENT     0x00000004 -#define EF_BOUNCE           0x00000008    // for missiles -#define EF_BOUNCE_HALF      0x00000010    // for missiles -#define EF_NO_BOUNCE_SOUND  0x00000020    // for missiles -#define EF_WALLCLIMB        0x00000040    // TA: wall walking -#define EF_WALLCLIMBCEILING 0x00000080    // TA: wall walking ceiling hack -#define EF_NODRAW           0x00000100    // may have an event, but no model (unspawned items) -#define EF_FIRING           0x00000200    // for lightning gun -#define EF_FIRING2          0x00000400    // alt fire -#define EF_FIRING3          0x00000800    // third fire -#define EF_MOVER_STOP       0x00001000    // will push otherwise -#define EF_TALK             0x00002000    // draw a talk balloon -#define EF_CONNECTION       0x00004000    // draw a connection trouble sprite -#define EF_VOTED            0x00008000    // already cast a vote -#define EF_TEAMVOTED        0x00010000    // already cast a vote -#define EF_BLOBLOCKED       0x00020000    // TA: caught by a trapper -#define EF_REAL_LIGHT       0x00040000    // TA: light sprites according to ambient light -#define EF_DBUILDER         0x00080000    // designated builder protection - -typedef enum -{ -  PW_NONE, - -  PW_QUAD, -  PW_BATTLESUIT, -  PW_HASTE, -  PW_INVIS, -  PW_REGEN, -  PW_FLIGHT, - -  PW_REDFLAG, -  PW_BLUEFLAG, -  PW_BALL, - -  PW_NUM_POWERUPS -} powerup_t; +// notice that some flags are overlapped, so their meaning depends on context +#define EF_DEAD             0x0001    // don't draw a foe marker over players with EF_DEAD +#define EF_TELEPORT_BIT     0x0002    // toggled every time the origin abruptly changes +#define EF_PLAYER_EVENT     0x0004    // only used for eType > ET_EVENTS + +// for missiles: +#define EF_BOUNCE           0x0008    // for missiles +#define EF_BOUNCE_HALF      0x0010    // for missiles +#define EF_NO_BOUNCE_SOUND  0x0020    // for missiles + +// buildable flags: +#define EF_B_SPAWNED        0x0008 +#define EF_B_POWERED        0x0010 +#define EF_B_MARKED         0x0020 + +#define EF_WARN_CHARGE      0x0020    // Lucifer Cannon is about to overcharge +#define EF_WALLCLIMB        0x0040    // wall walking +#define EF_WALLCLIMBCEILING 0x0080    // wall walking ceiling hack +#define EF_NODRAW           0x0100    // may have an event, but no model (unspawned items) +#define EF_FIRING           0x0200    // for lightning gun +#define EF_FIRING2          0x0400    // alt fire +#define EF_FIRING3          0x0800    // third fire +#define EF_MOVER_STOP       0x1000    // will push otherwise +#define EF_POISONCLOUDED    0x2000    // player hit with basilisk gas +#define EF_CONNECTION       0x4000    // draw a connection trouble sprite +#define EF_BLOBLOCKED       0x8000    // caught by a trapper  typedef enum  { @@ -358,8 +359,8 @@ typedef enum    WP_LAS_GUN,    WP_MASS_DRIVER,    WP_CHAINGUN, -  WP_PULSE_RIFLE,    WP_FLAMER, +  WP_PULSE_RIFLE,    WP_LUCIFER_CANNON,    WP_GRENADE, @@ -371,7 +372,6 @@ typedef enum    //build weapons must remain in a block    WP_ABUILD,    WP_ABUILD2, -  WP_HBUILD2,    WP_HBUILD,    //ok? @@ -395,17 +395,7 @@ typedef enum    UP_NUM_UPGRADES  } upgrade_t; -typedef enum -{ -  WUT_NONE, - -  WUT_ALIENS, -  WUT_HUMANS, - -  WUT_NUM_TEAMS -} WUTeam_t; - -//TA: bitmasks for upgrade slots +// bitmasks for upgrade slots  #define SLOT_NONE       0x00000000  #define SLOT_HEAD       0x00000001  #define SLOT_TORSO      0x00000002 @@ -428,8 +418,6 @@ typedef enum    BA_A_BOOSTER,    BA_A_HIVE, -  BA_A_HOVEL, -    BA_H_SPAWN,    BA_H_MGTURRET, @@ -447,30 +435,13 @@ typedef enum  typedef enum  { -  BIT_NONE, - -  BIT_ALIENS, -  BIT_HUMANS, - -  BIT_NUM_TEAMS -} buildableTeam_t; - -#define B_HEALTH_BITS       5 -#define B_HEALTH_MASK       ((1<<B_HEALTH_BITS)-1) - -#define B_DCCED_TOGGLEBIT    0x00000000 -#define B_SPAWNED_TOGGLEBIT  0x00000020 -#define B_POWERED_TOGGLEBIT  0x00000040 -#define B_MARKED_TOGGLEBIT   0x00000080 - - -// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) -#define PLAYEREVENT_DENIEDREWARD      0x0001 -#define PLAYEREVENT_GAUNTLETREWARD    0x0002 -#define PLAYEREVENT_HOLYSHIT          0x0004 +  RMT_SPHERE, +  RMT_SPHERICAL_CONE_64, +  RMT_SPHERICAL_CONE_240 +} rangeMarkerType_t;  // entityState_t->event values -// entity events are for effects that take place reletive +// entity events are for effects that take place relative  // to an existing entities origin.  Very network efficient.  // two bits at the top of the entityState->event field @@ -484,6 +455,8 @@ typedef enum  #define EVENT_VALID_MSEC  300 +const char *BG_EventName( int num ); +  typedef enum  {    EV_NONE, @@ -522,7 +495,7 @@ typedef enum    EV_FIRE_WEAPON2,    EV_FIRE_WEAPON3, -  EV_PLAYER_RESPAWN, //TA: for fovwarp effects +  EV_PLAYER_RESPAWN, // for fovwarp effects    EV_PLAYER_TELEPORT_IN,    EV_PLAYER_TELEPORT_OUT, @@ -535,6 +508,7 @@ typedef enum    EV_BULLET_HIT_WALL,    EV_SHOTGUN, +  EV_MASS_DRIVER,    EV_MISSILE_HIT,    EV_MISSILE_MISS, @@ -543,8 +517,8 @@ typedef enum    EV_BULLET,        // otherEntity is the shooter    EV_LEV1_GRAB, -  EV_LEV4_CHARGE_PREPARE, -  EV_LEV4_CHARGE_START, +  EV_LEV4_TRAMPLE_PREPARE, +  EV_LEV4_TRAMPLE_START,    EV_PAIN,    EV_DEATH1, @@ -552,13 +526,13 @@ typedef enum    EV_DEATH3,    EV_OBITUARY, -  EV_GIB_PLAYER,      // gib a previously living player +  EV_GIB_PLAYER, -  EV_BUILD_CONSTRUCT, //TA -  EV_BUILD_DESTROY,   //TA -  EV_BUILD_DELAY,     //TA: can't build yet -  EV_BUILD_REPAIR,    //TA: repairing buildable -  EV_BUILD_REPAIRED,  //TA: buildable has full health +  EV_BUILD_CONSTRUCT, +  EV_BUILD_DESTROY, +  EV_BUILD_DELAY,     // can't build yet +  EV_BUILD_REPAIR,    // repairing buildable +  EV_BUILD_REPAIRED,  // buildable has full health    EV_HUMAN_BUILDABLE_EXPLOSION,    EV_ALIEN_BUILDABLE_EXPLOSION,    EV_ALIEN_ACIDTUBE, @@ -572,73 +546,91 @@ typedef enum    EV_STOPLOOPINGSOUND,    EV_TAUNT, -  EV_OVERMIND_ATTACK, //TA: overmind under attack -  EV_OVERMIND_DYING,  //TA: overmind close to death -  EV_OVERMIND_SPAWNS, //TA: overmind needs spawns +  EV_OVERMIND_ATTACK, // overmind under attack +  EV_OVERMIND_DYING,  // overmind close to death +  EV_OVERMIND_SPAWNS, // overmind needs spawns + +  EV_DCC_ATTACK,      // dcc under attack -  EV_DCC_ATTACK,      //TA: dcc under attack +  EV_MGTURRET_SPINUP, // turret spinup sound should play -  EV_RPTUSE_SOUND     //TA: trigger a sound +  EV_RPTUSE_SOUND,    // trigger a sound +  EV_LEV2_ZAP  } entity_event_t;  typedef enum  { +  MN_NONE, +    MN_TEAM,    MN_A_TEAMFULL,    MN_H_TEAMFULL, +  MN_A_TEAMLOCKED, +  MN_H_TEAMLOCKED, +  MN_PLAYERLIMIT, + +  // cmd stuff +  MN_CMD_CHEAT, +  MN_CMD_CHEAT_TEAM, +  MN_CMD_TEAM, +  MN_CMD_SPEC, +  MN_CMD_ALIEN, +  MN_CMD_HUMAN, +  MN_CMD_ALIVE,    //alien stuff    MN_A_CLASS,    MN_A_BUILD,    MN_A_INFEST, -  MN_A_HOVEL_OCCUPIED, -  MN_A_HOVEL_BLOCKED,    MN_A_NOEROOM,    MN_A_TOOCLOSE,    MN_A_NOOVMND_EVOLVE, +  MN_A_EVOLVEBUILDTIMER, +  MN_A_CANTEVOLVE, +  MN_A_EVOLVEWALLWALK, +  MN_A_UNKNOWNCLASS, +  MN_A_CLASSNOTSPAWN, +  MN_A_CLASSNOTALLOWED, +  MN_A_CLASSNOTATSTAGE, + +  //shared build +  MN_B_NOROOM, +  MN_B_NORMAL, +  MN_B_CANNOT, +  MN_B_LASTSPAWN, +  MN_B_SUDDENDEATH, +  MN_B_REVOKED, +  MN_B_SURRENDER,    //alien build -  MN_A_SPWNWARN, -  MN_A_OVERMIND, -  MN_A_NOASSERT, +  MN_A_ONEOVERMIND, +  MN_A_NOBP,    MN_A_NOCREEP,    MN_A_NOOVMND, -  MN_A_NOROOM, -  MN_A_NORMAL, -  MN_A_HOVEL, -  MN_A_HOVEL_EXIT,    //human stuff    MN_H_SPAWN,    MN_H_BUILD,    MN_H_ARMOURY, +  MN_H_UNKNOWNITEM,    MN_H_NOSLOTS,    MN_H_NOFUNDS,    MN_H_ITEMHELD, +  MN_H_NOARMOURYHERE, +  MN_H_NOENERGYAMMOHERE, +  MN_H_NOROOMBSUITON, +  MN_H_NOROOMBSUITOFF, +  MN_H_ARMOURYBUILDTIMER, +  MN_H_DEADTOCLASS, +  MN_H_UNKNOWNSPAWNITEM,    //human build -  MN_H_REPEATER, -  MN_H_NOPOWER, +  MN_H_NOPOWERHERE, +  MN_H_NOBP,    MN_H_NOTPOWERED,    MN_H_NODCC, -  MN_H_REACTOR, -  MN_H_NOROOM, -  MN_H_NORMAL, -  MN_H_TNODEWARN, -  MN_H_RPTWARN, -  MN_H_RPTWARN2, - -  //not used -  MN_A_TEAMCHANGEBUILDTIMER, -  MN_H_TEAMCHANGEBUILDTIMER, - -  MN_A_EVOLVEBUILDTIMER, - -  MN_H_NOENERGYAMMOHERE, -  MN_H_NOARMOURYHERE, -  MN_H_NOROOMBSUITON, -  MN_H_NOROOMBSUITOFF, -  MN_H_ARMOURYBUILDTIMER +  MN_H_ONEREACTOR, +  MN_H_RPTPOWERHERE,  } dynMenu_t;  // animations @@ -744,7 +736,7 @@ typedef enum    MAX_NONSEG_PLAYER_TOTALANIMATIONS  } nonSegPlayerAnimNumber_t; -//TA: for buildable animations +// for buildable animations  typedef enum  {    BANIM_NONE, @@ -772,6 +764,28 @@ typedef enum    MAX_BUILDABLE_ANIMATIONS  } buildableAnimNumber_t; +typedef enum +{ +  WANIM_NONE, + +  WANIM_IDLE, + +  WANIM_DROP, +  WANIM_RELOAD, +  WANIM_RAISE, + +  WANIM_ATTACK1, +  WANIM_ATTACK2, +  WANIM_ATTACK3, +  WANIM_ATTACK4, +  WANIM_ATTACK5, +  WANIM_ATTACK6, +  WANIM_ATTACK7, +  WANIM_ATTACK8, + +  MAX_WEAPON_ANIMATIONS +} weaponAnimNumber_t; +  typedef struct animation_s  {    int   firstFrame; @@ -789,22 +803,10 @@ typedef struct animation_s  #define ANIM_TOGGLEBIT    0x80  #define ANIM_FORCEBIT     0x40 - -typedef enum -{ -  TEAM_FREE, -  TEAM_SPECTATOR, - -  TEAM_NUM_TEAMS -} team_t; -  // Time between location updates -#define TEAM_LOCATION_UPDATE_TIME   1000 +#define TEAM_LOCATION_UPDATE_TIME   500 -// How many players on the overlay -#define TEAM_MAXOVERLAY   32 - -//TA: player classes +// player classes  typedef enum  {    PCL_NONE, @@ -828,19 +830,30 @@ typedef enum    PCL_HUMAN_BSUIT,    PCL_NUM_CLASSES -} pClass_t; - +} class_t; -//TA: player teams +// spectator state  typedef enum  { -  PTE_NONE, -  PTE_ALIENS, -  PTE_HUMANS, - -  PTE_NUM_TEAMS -} pTeam_t; - +  SPECTATOR_NOT, +  SPECTATOR_FREE, +  SPECTATOR_LOCKED, +  SPECTATOR_FOLLOW, +  SPECTATOR_SCOREBOARD +} spectatorState_t; + +// modes of text communication +typedef enum +{ +  SAY_ALL, +  SAY_TEAM, +  SAY_PRIVMSG, +  SAY_TPRIVMSG, +  SAY_AREA, +  SAY_ADMINS, +  SAY_ADMINS_PUBLIC, +  SAY_RAW +} saymode_t;  // means of death  typedef enum @@ -879,7 +892,8 @@ typedef enum    MOD_LEVEL2_CLAW,    MOD_LEVEL2_ZAP,    MOD_LEVEL4_CLAW, -  MOD_LEVEL4_CHARGE, +  MOD_LEVEL4_TRAMPLE, +  MOD_LEVEL4_CRUSH,    MOD_SLOWBLOB,    MOD_POISON, @@ -893,42 +907,27 @@ typedef enum    MOD_ASPAWN,    MOD_ATUBE,    MOD_OVERMIND, -  MOD_SLAP +  MOD_DECONSTRUCT, +  MOD_REPLACE, +  MOD_NOCREEP  } meansOfDeath_t;  //--------------------------------------------------------- -//TA: player class record +// player class record  typedef struct  { -  int       classNum; +  class_t   number; -  char      *className; -  char      *humanName; - -  char      *modelName; -  float     modelScale; -  char      *skinName; -  float     shadowScale; - -  char      *hudName; +  char      *name; +  char      *info;    int       stages; -  vec3_t    mins; -  vec3_t    maxs; -  vec3_t    crouchMaxs; -  vec3_t    deadMins; -  vec3_t    deadMaxs; -  float     zOffset; - -  int       viewheight; -  int       crouchViewheight; -    int       health;    float     fallDamage; -  int       regenRate; +  float     regenRate;    int       abilities; @@ -939,6 +938,7 @@ typedef struct    int       fov;    float     bob;    float     bobCycle; +  float     landBob;    int       steptime;    float     speed; @@ -971,7 +971,8 @@ typedef struct    int       viewheight;    int       crouchViewheight;    float     zOffset; -} classAttributeOverrides_t; +  vec3_t    shoulderOffsets; +} classConfig_t;  //stages  typedef enum @@ -983,59 +984,54 @@ typedef enum  #define MAX_BUILDABLE_MODELS 4 -//TA: buildable item record +// buildable item record  typedef struct  { -  int       buildNum; +  buildable_t   number; -  char      *buildName; -  char      *humanName; -  char      *entityName; +  char          *name; +  char          *humanName; +  char          *info; +  char          *entityName; -  char      *models[ MAX_BUILDABLE_MODELS ]; -  float     modelScale; +  trType_t      traj; +  float         bounce; -  vec3_t    mins; -  vec3_t    maxs; -  float     zOffset; +  int           buildPoints; +  int           stages; -  trType_t  traj; -  float     bounce; +  int           health; +  int           regenRate; -  int       buildPoints; -  int       stages; +  int           splashDamage; +  int           splashRadius; -  int       health; -  int       regenRate; - -  int       splashDamage; -  int       splashRadius; +  int           meansOfDeath; -  int       meansOfDeath; +  team_t        team; +  weapon_t      buildWeapon; -  int       team; -  weapon_t  buildWeapon; +  int           idleAnim; -  int       idleAnim; +  int           nextthink; +  int           buildTime; +  qboolean      usable; -  int       nextthink; -  int       buildTime; -  qboolean  usable; +  int           turretRange; +  int           turretFireSpeed; +  weapon_t      turretProjType; -  int       turretRange; -  int       turretFireSpeed; -  weapon_t  turretProjType; +  float         minNormal; +  qboolean      invertNormal; -  float     minNormal; -  qboolean  invertNormal; +  qboolean      creepTest; +  int           creepSize; -  qboolean  creepTest; -  int       creepSize; - -  qboolean  dccTest; -  qboolean  transparentTest; -  qboolean  reactorTest; -  qboolean  replaceable; +  qboolean      dccTest; +  qboolean      transparentTest; +  qboolean      uniqueTest; +   +  int       value;  } buildableAttributes_t;  typedef struct @@ -1046,20 +1042,21 @@ typedef struct    vec3_t    mins;    vec3_t    maxs;    float     zOffset; -} buildableAttributeOverrides_t; +} buildableConfig_t; -//TA: weapon record +// weapon record  typedef struct  { -  int       weaponNum; +  weapon_t  number;    int       price;    int       stages;    int       slots; -  char      *weaponName; -  char      *weaponHumanName; +  char      *name; +  char      *humanName; +  char      *info;    int       maxAmmo;    int       maxClips; @@ -1081,38 +1078,34 @@ typedef struct    qboolean  purchasable;    qboolean  longRanged; -  int       buildDelay; - -  WUTeam_t  team; +  team_t    team;  } weaponAttributes_t; -//TA: upgrade record +// upgrade record  typedef struct  { -  int       upgradeNum; +  upgrade_t number;    int       price;    int       stages;    int       slots; -  char      *upgradeName; -  char      *upgradeHumanName; +  char      *name; +  char      *humanName; +  char      *info;    char      *icon;    qboolean  purchasable;    qboolean  usable; -  WUTeam_t  team; +  team_t    team;  } upgradeAttributes_t; - -//TA:  qboolean  BG_WeaponIsFull( weapon_t weapon, int stats[ ], int ammo, int clips ); -void      BG_AddWeaponToInventory( int weapon, int stats[ ] ); -void      BG_RemoveWeaponFromInventory( int weapon, int stats[ ] );  qboolean  BG_InventoryContainsWeapon( int weapon, int stats[ ] ); +int       BG_SlotsForInventory( int stats[ ] );  void      BG_AddUpgradeToInventory( int item, int stats[ ] );  void      BG_RemoveUpgradeFromInventory( int item, int stats[ ] );  qboolean  BG_InventoryContainsUpgrade( int item, int stats[ ] ); @@ -1121,122 +1114,60 @@ void      BG_DeactivateUpgrade( int item, int stats[ ] );  qboolean  BG_UpgradeIsActive( int item, int stats[ ] );  qboolean  BG_RotateAxis( vec3_t surfNormal, vec3_t inAxis[ 3 ],                           vec3_t outAxis[ 3 ], qboolean inverse, qboolean ceiling ); +void      BG_GetClientNormal( const playerState_t *ps, vec3_t normal ); +void      BG_GetClientViewOrigin( const playerState_t *ps, vec3_t viewOrigin );  void      BG_PositionBuildableRelativeToPlayer( const playerState_t *ps,                                                  const vec3_t mins, const vec3_t maxs,                                                  void (*trace)( trace_t *, const vec3_t, const vec3_t,                                                                 const vec3_t, const vec3_t, int, int ),                                                  vec3_t outOrigin, vec3_t outAngles, trace_t *tr ); -int       BG_GetValueOfHuman( playerState_t *ps ); -int       BG_GetValueOfEquipment( playerState_t *ps ); - -int       BG_FindBuildNumForName( char *name ); -int       BG_FindBuildNumForEntityName( char *name ); -char      *BG_FindNameForBuildable( int bclass ); -char      *BG_FindHumanNameForBuildable( int bclass ); -char      *BG_FindEntityNameForBuildable( int bclass ); -char      *BG_FindModelsForBuildable( int bclass, int modelNum ); -float     BG_FindModelScaleForBuildable( int bclass ); -void      BG_FindBBoxForBuildable( int bclass, vec3_t mins, vec3_t maxs ); -float     BG_FindZOffsetForBuildable( int pclass ); -int       BG_FindHealthForBuildable( int bclass ); -int       BG_FindRegenRateForBuildable( int bclass ); -trType_t  BG_FindTrajectoryForBuildable( int bclass ); -float     BG_FindBounceForBuildable( int bclass ); -int       BG_FindBuildPointsForBuildable( int bclass ); -qboolean  BG_FindStagesForBuildable( int bclass, stage_t stage ); -int       BG_FindSplashDamageForBuildable( int bclass ); -int       BG_FindSplashRadiusForBuildable( int bclass ); -int       BG_FindMODForBuildable( int bclass ); -int       BG_FindTeamForBuildable( int bclass ); -weapon_t  BG_FindBuildWeaponForBuildable( int bclass ); -int       BG_FindAnimForBuildable( int bclass ); -int       BG_FindNextThinkForBuildable( int bclass ); -int       BG_FindBuildTimeForBuildable( int bclass ); -qboolean  BG_FindUsableForBuildable( int bclass ); -int       BG_FindRangeForBuildable( int bclass ); -int       BG_FindFireSpeedForBuildable( int bclass ); -weapon_t  BG_FindProjTypeForBuildable( int bclass ); -float     BG_FindMinNormalForBuildable( int bclass ); -qboolean  BG_FindInvertNormalForBuildable( int bclass ); -int       BG_FindCreepTestForBuildable( int bclass ); -int       BG_FindCreepSizeForBuildable( int bclass ); -int       BG_FindDCCTestForBuildable( int bclass ); -int       BG_FindUniqueTestForBuildable( int bclass ); -qboolean  BG_FindReplaceableTestForBuildable( int bclass ); -qboolean  BG_FindTransparentTestForBuildable( int bclass ); -void      BG_InitBuildableOverrides( void ); - -int       BG_FindClassNumForName( char *name ); -char      *BG_FindNameForClassNum( int pclass ); -char      *BG_FindHumanNameForClassNum( int pclass ); -char      *BG_FindModelNameForClass( int pclass ); -float     BG_FindModelScaleForClass( int pclass ); -char      *BG_FindSkinNameForClass( int pclass ); -float     BG_FindShadowScaleForClass( int pclass ); -char      *BG_FindHudNameForClass( int pclass ); -qboolean  BG_FindStagesForClass( int pclass, stage_t stage ); -void      BG_FindBBoxForClass( int pclass, vec3_t mins, vec3_t maxs, vec3_t cmaxs, vec3_t dmins, vec3_t dmaxs ); -float     BG_FindZOffsetForClass( int pclass ); -void      BG_FindViewheightForClass( int pclass, int *viewheight, int *cViewheight ); -int       BG_FindHealthForClass( int pclass ); -float     BG_FindFallDamageForClass( int pclass ); -int       BG_FindRegenRateForClass( int pclass ); -int       BG_FindFovForClass( int pclass ); -float     BG_FindBobForClass( int pclass ); -float     BG_FindBobCycleForClass( int pclass ); -float     BG_FindSpeedForClass( int pclass ); -float     BG_FindAccelerationForClass( int pclass ); -float     BG_FindAirAccelerationForClass( int pclass ); -float     BG_FindFrictionForClass( int pclass ); -float     BG_FindStopSpeedForClass( int pclass ); -float     BG_FindJumpMagnitudeForClass( int pclass ); -float     BG_FindKnockbackScaleForClass( int pclass ); -int       BG_FindSteptimeForClass( int pclass ); -qboolean  BG_ClassHasAbility( int pclass, int ability ); -weapon_t  BG_FindStartWeaponForClass( int pclass ); -float     BG_FindBuildDistForClass( int pclass ); -int       BG_ClassCanEvolveFromTo( int fclass, int tclass, int credits, int num ); -int       BG_FindCostOfClass( int pclass ); -int       BG_FindValueOfClass( int pclass ); -void      BG_InitClassOverrides( void ); - -int       BG_FindPriceForWeapon( int weapon ); -qboolean  BG_FindStagesForWeapon( int weapon, stage_t stage ); -int       BG_FindSlotsForWeapon( int weapon ); -char      *BG_FindNameForWeapon( int weapon ); -int       BG_FindWeaponNumForName( char *name ); -char      *BG_FindHumanNameForWeapon( int weapon ); -char      *BG_FindModelsForWeapon( int weapon, int modelNum ); -char      *BG_FindIconForWeapon( int weapon ); -char      *BG_FindCrosshairForWeapon( int weapon ); -int       BG_FindCrosshairSizeForWeapon( int weapon ); -void      BG_FindAmmoForWeapon( int weapon, int *maxAmmo, int *maxClips ); -qboolean  BG_FindInfinteAmmoForWeapon( int weapon ); -qboolean  BG_FindUsesEnergyForWeapon( int weapon ); -int       BG_FindRepeatRate1ForWeapon( int weapon ); -int       BG_FindRepeatRate2ForWeapon( int weapon ); -int       BG_FindRepeatRate3ForWeapon( int weapon ); -int       BG_FindReloadTimeForWeapon( int weapon ); -float     BG_FindKnockbackScaleForWeapon( int weapon ); -qboolean  BG_WeaponHasAltMode( int weapon ); -qboolean  BG_WeaponHasThirdMode( int weapon ); -qboolean  BG_WeaponCanZoom( int weapon ); -float     BG_FindZoomFovForWeapon( int weapon ); -qboolean  BG_FindPurchasableForWeapon( int weapon ); -qboolean  BG_FindLongRangedForWeapon( int weapon ); -int       BG_FindBuildDelayForWeapon( int weapon ); -WUTeam_t  BG_FindTeamForWeapon( int weapon ); - -int       BG_FindPriceForUpgrade( int upgrade ); -qboolean  BG_FindStagesForUpgrade( int upgrade, stage_t stage ); -int       BG_FindSlotsForUpgrade( int upgrade ); -char      *BG_FindNameForUpgrade( int upgrade ); -int       BG_FindUpgradeNumForName( char *name ); -char      *BG_FindHumanNameForUpgrade( int upgrade ); -char      *BG_FindIconForUpgrade( int upgrade ); -qboolean  BG_FindPurchasableForUpgrade( int upgrade ); -qboolean  BG_FindUsableForUpgrade( int upgrade ); -WUTeam_t  BG_FindTeamForUpgrade( int upgrade ); +int       BG_GetValueOfPlayer( playerState_t *ps ); +qboolean  BG_PlayerCanChangeWeapon( playerState_t *ps ); +int       BG_PlayerPoisonCloudTime( playerState_t *ps ); +weapon_t  BG_GetPlayerWeapon( playerState_t *ps ); +qboolean  BG_HasEnergyWeapon( playerState_t *ps ); + +void      BG_PackEntityNumbers( entityState_t *es, const int *entityNums, int count ); +int       BG_UnpackEntityNumbers( entityState_t *es, int *entityNums, int count ); + +const buildableAttributes_t *BG_BuildableByName( const char *name ); +const buildableAttributes_t *BG_BuildableByEntityName( const char *name ); +const buildableAttributes_t *BG_Buildable( buildable_t buildable ); +qboolean                    BG_BuildableAllowedInStage( buildable_t buildable, +                                                        stage_t stage ); + +buildableConfig_t           *BG_BuildableConfig( buildable_t buildable ); +void                        BG_BuildableBoundingBox( buildable_t buildable, +                                                     vec3_t mins, vec3_t maxs ); +void                        BG_InitBuildableConfigs( void ); + +const classAttributes_t     *BG_ClassByName( const char *name ); +const classAttributes_t     *BG_Class( class_t klass ); +qboolean                    BG_ClassAllowedInStage( class_t klass, +                                                    stage_t stage ); + +classConfig_t               *BG_ClassConfig( class_t klass ); + +void                        BG_ClassBoundingBox( class_t klass, vec3_t mins, +                                                 vec3_t maxs, vec3_t cmaxs, +                                                 vec3_t dmins, vec3_t dmaxs ); +qboolean                    BG_ClassHasAbility( class_t klass, int ability ); +int                         BG_ClassCanEvolveFromTo( class_t fclass, +                                                     class_t tclass, +                                                     int credits, int alienStage, int num ); +qboolean                    BG_AlienCanEvolve( class_t klass, int credits, int alienStage ); + +void                        BG_InitClassConfigs( void ); + +const weaponAttributes_t    *BG_WeaponByName( const char *name ); +const weaponAttributes_t    *BG_Weapon( weapon_t weapon ); +qboolean                    BG_WeaponAllowedInStage( weapon_t weapon, +                                                     stage_t stage ); + +const upgradeAttributes_t   *BG_UpgradeByName( const char *name ); +const upgradeAttributes_t   *BG_Upgrade( upgrade_t upgrade ); +qboolean                    BG_UpgradeAllowedInStage( upgrade_t upgrade, +                                                      stage_t stage );  // content masks  #define MASK_ALL          (-1) @@ -1257,7 +1188,10 @@ typedef enum    ET_PLAYER,    ET_ITEM, -  ET_BUILDABLE,       //TA: buildable type +  ET_BUILDABLE, +  ET_RANGE_MARKER, + +  ET_LOCATION,    ET_MISSILE,    ET_MOVER, @@ -1275,12 +1209,19 @@ typedef enum    ET_MODELDOOR,    ET_LIGHTFLARE,    ET_LEV2_ZAP_CHAIN, +  ET_WEAPON_DROP,    ET_EVENTS       // any of the EV_* events can be added freestanding                // by setting eType to ET_EVENTS + eventNum                // this avoids having to set eFlags and eventNum  } entityType_t; +void  *BG_Alloc( int size ); +void  BG_InitMemory( void ); +void  BG_Free( void *ptr ); +void  BG_DefragmentMemory( void ); +void  BG_MemoryInfo( void ); +  void  BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result );  void  BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); @@ -1303,28 +1244,83 @@ int     atoi_neg( char *token, qboolean allowNegative );  void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weaponsSize,      upgrade_t *upgrades, int upgradesSize ); -void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSize ); +void BG_ParseCSVClassList( const char *string, class_t *classes, int classesSize );  void BG_ParseCSVBuildableList( const char *string, buildable_t *buildables, int buildablesSize );  void BG_InitAllowedGameElements( void );  qboolean BG_WeaponIsAllowed( weapon_t weapon );  qboolean BG_UpgradeIsAllowed( upgrade_t upgrade ); -qboolean BG_ClassIsAllowed( pClass_t class ); +qboolean BG_ClassIsAllowed( class_t klass );  qboolean BG_BuildableIsAllowed( buildable_t buildable ); -qboolean BG_UpgradeClassAvailable( playerState_t *ps ); - -typedef struct  -{ -  unsigned int hi; -  unsigned int lo; -} clientList_t; -qboolean BG_ClientListTest( clientList_t *list, int clientNum ); -void BG_ClientListAdd( clientList_t *list, int clientNum ); -void BG_ClientListRemove( clientList_t *list, int clientNum ); -char *BG_ClientListString( clientList_t *list ); -void BG_ClientListParse( clientList_t *list, const char *s );  // Friendly Fire Flags  #define FFF_HUMANS         1  #define FFF_ALIENS         2  #define FFF_BUILDABLES     4 +// bg_voice.c +#define MAX_VOICES                8 +#define MAX_VOICE_NAME_LEN        16 +#define MAX_VOICE_CMD_LEN         16 +#define VOICE_ENTHUSIASM_DECAY    0.5f // enthusiasm lost per second + +typedef enum +{ +  VOICE_CHAN_ALL, +  VOICE_CHAN_TEAM , +  VOICE_CHAN_LOCAL, + +  VOICE_CHAN_NUM_CHANS +} voiceChannel_t; + +typedef struct voiceTrack_s +{ +//#ifdef CGAME +  sfxHandle_t            track; +  int                    duration; +//#endif +  char                   *text; +  int                    enthusiasm; +  int                    team; +  int                    klass; +  int                    weapon; +  struct voiceTrack_s    *next; +} voiceTrack_t; + +typedef struct voiceCmd_s +{ +  char              cmd[ MAX_VOICE_CMD_LEN ]; +  voiceTrack_t      *tracks; +  struct voiceCmd_s *next; +} voiceCmd_t; + +typedef struct voice_s +{ +  char             name[ MAX_VOICE_NAME_LEN ]; +  voiceCmd_t       *cmds; +  struct voice_s   *next; +} voice_t; + +voice_t *BG_VoiceInit( void ); +void BG_PrintVoices( voice_t *voices, int debugLevel ); + +voice_t *BG_VoiceByName( voice_t *head, char *name ); +voiceCmd_t *BG_VoiceCmdFind( voiceCmd_t *head, char *name, int *cmdNum ); +voiceCmd_t *BG_VoiceCmdByNum( voiceCmd_t *head, int num); +voiceTrack_t *BG_VoiceTrackByNum( voiceTrack_t *head, int num ); +voiceTrack_t *BG_VoiceTrackFind( voiceTrack_t *head, team_t team, +                                 class_t klass, weapon_t weapon, +                                 int enthusiasm, int *trackNum ); + +int BG_LoadEmoticons( emoticon_t *emoticons, int num ); + +char *BG_TeamName( team_t team ); + +typedef struct +{ +  const char *name; +} dummyCmd_t; +int cmdcmp( const void *a, const void *b ); + +char *G_CopyString( const char *str ); + +#endif diff --git a/src/game/bg_shared.h b/src/game/bg_shared.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/game/bg_shared.h diff --git a/src/game/bg_slidemove.c b/src/game/bg_slidemove.c index aa32a6a..3d6a521 100644 --- a/src/game/bg_slidemove.c +++ b/src/game/bg_slidemove.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,14 +17,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */  // bg_slidemove.c -- part of bg_pmove functionality -#include "../qcommon/q_shared.h" +#include "qcommon/q_shared.h"  #include "bg_public.h"  #include "bg_local.h" @@ -74,8 +75,7 @@ qboolean  PM_SlideMove( qboolean gravity )      if( pml.groundPlane )      {        // slide along the ground plane -      PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, -        pm->ps->velocity, OVERCLIP ); +      PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity );      }    } @@ -166,10 +166,10 @@ qboolean  PM_SlideMove( qboolean gravity )          pml.impactSpeed = -into;        // slide along the plane -      PM_ClipVelocity( pm->ps->velocity, planes[ i ], clipVelocity, OVERCLIP ); +      PM_ClipVelocity( pm->ps->velocity, planes[ i ], clipVelocity );        // slide along the plane -      PM_ClipVelocity( endVelocity, planes[ i ], endClipVelocity, OVERCLIP ); +      PM_ClipVelocity( endVelocity, planes[ i ], endClipVelocity );        // see if there is a second plane that the new move enters        for( j = 0; j < numplanes; j++ ) @@ -181,8 +181,8 @@ qboolean  PM_SlideMove( qboolean gravity )            continue;   // move doesn't interact with the plane          // try clipping the move to the plane -        PM_ClipVelocity( clipVelocity, planes[ j ], clipVelocity, OVERCLIP ); -        PM_ClipVelocity( endClipVelocity, planes[ j ], endClipVelocity, OVERCLIP ); +        PM_ClipVelocity( clipVelocity, planes[ j ], clipVelocity ); +        PM_ClipVelocity( endClipVelocity, planes[ j ], endClipVelocity );          // see if it goes back into the first clip plane          if( DotProduct( clipVelocity, planes[ i ] ) >= 0 ) @@ -290,7 +290,6 @@ PM_StepSlideMove  qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive )  {    vec3_t    start_o, start_v; -  vec3_t    down_o, down_v;    trace_t   trace;    vec3_t    normal;    vec3_t    step_v, step_vNormal; @@ -298,15 +297,7 @@ qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive )    float     stepSize;    qboolean  stepped = qfalse; -  if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) -  { -    if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) -      VectorSet( normal, 0.0f, 0.0f, -1.0f ); -    else -      VectorCopy( pm->ps->grapplePoint, normal ); -  } -  else -    VectorSet( normal, 0.0f, 0.0f, 1.0f ); +  BG_GetClientNormal( pm->ps, normal );    VectorCopy( pm->ps->origin, start_o );    VectorCopy( pm->ps->velocity, start_v ); @@ -339,9 +330,6 @@ qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive )        return stepped;      } -    VectorCopy( pm->ps->origin, down_o ); -    VectorCopy( pm->ps->velocity, down_v ); -      VectorCopy( start_o, up );      VectorMA( up, STEPSIZE, normal, up ); @@ -381,7 +369,7 @@ qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive )        VectorCopy( trace.endpos, pm->ps->origin );      if( trace.fraction < 1.0f ) -      PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); +      PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity );    }    if( !predictive && stepped ) diff --git a/src/game/bg_voice.c b/src/game/bg_voice.c new file mode 100644 index 0000000..06ce939 --- /dev/null +++ b/src/game/bg_voice.c @@ -0,0 +1,653 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2008      Tony J. White +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 3 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, see <https://www.gnu.org/licenses/> + +=========================================================================== +*/ + +// bg_voice.c -- both games voice functions +#include "qcommon/q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +int  trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode  mode ); +int  trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +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 ); + +#ifdef CGAME +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); +int trap_S_SoundDuration( sfxHandle_t handle ); +#endif + + +/* +============ +BG_VoiceParseError +============ +*/ +static void BG_VoiceParseError( fileHandle_t handle, const char *err ) +{ +  int line; +  char filename[ MAX_QPATH ]; + +  trap_Parse_SourceFileAndLine( handle, filename, &line ); +  trap_Parse_FreeSource( handle ); +  Com_Error( ERR_FATAL, "%s on line %d of %s", err, line, filename ); +} + +/* +============ +BG_VoiceList +============ +*/ +static voice_t *BG_VoiceList( void ) +{ +  char fileList[ MAX_VOICES * ( MAX_VOICE_NAME_LEN + 8 ) ] = {""}; +  int numFiles, i, fileLen = 0; +  int count = 0; +  char *filePtr; +  voice_t *voices = NULL; +  voice_t *top = NULL; + +  numFiles = trap_FS_GetFileList( "voice", ".voice", fileList, +    sizeof( fileList ) ); + +  if( numFiles < 1 ) +    return NULL; + +  // special case for default.voice.  this file is REQUIRED and will +  // always be loaded first in the event of overflow of voice definitions +  if( !trap_FS_FOpenFile( "voice/default.voice", NULL, FS_READ ) ) +  { +    Com_Printf( "voice/default.voice missing, voice system disabled." ); +    return NULL; +  } +       +  voices = (voice_t*)BG_Alloc( sizeof( voice_t ) ); +  Q_strncpyz( voices->name, "default", sizeof( voices->name ) ); +  voices->cmds = NULL; +  voices->next = NULL; +  count = 1; + +  top = voices; + +  filePtr = fileList; +  for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) +  { +    fileLen = strlen( filePtr ); + +    // accounted for above +    if( !Q_stricmp( filePtr, "default.voice" ) ) +      continue; + +    if( fileLen > MAX_VOICE_NAME_LEN + 8 ) { +      Com_Printf( S_COLOR_YELLOW "WARNING: MAX_VOICE_NAME_LEN is %d. " +        "skipping \"%s\", filename too long", MAX_VOICE_NAME_LEN, filePtr ); +      continue; +    } + +    // trap_FS_GetFileList() buffer has overflowed +    if( !trap_FS_FOpenFile( va( "voice/%s", filePtr ), NULL, FS_READ ) ) +    { +      Com_Printf( S_COLOR_YELLOW "WARNING: BG_VoiceList(): detected " +        "an invalid .voice file \"%s\" in directory listing.  You have " +        "probably named one or more .voice files with outrageously long " +        "names.  gjbs", filePtr ); +      break; +    } + +    if( count >= MAX_VOICES ) +    { +      Com_Printf( S_COLOR_YELLOW "WARNING: .voice file overflow.  " +        "%d of %d .voice files loaded.  MAX_VOICES is %d", +        count, numFiles, MAX_VOICES ); +      break; +    } +  +    voices->next = (voice_t*)BG_Alloc( sizeof( voice_t ) ); +    voices = voices->next; + +    Q_strncpyz( voices->name, filePtr, sizeof( voices->name ) ); +    // strip extension  +    voices->name[ fileLen - 6 ] = '\0'; +    voices->cmds = NULL; +    voices->next = NULL; +    count++; +  } +  return top; +} + +/* +============ +BG_VoiceParseTrack +============ +*/ +static qboolean BG_VoiceParseTrack( int handle, voiceTrack_t *voiceTrack ) +{ +  pc_token_t token; +  qboolean found = qfalse; +  qboolean foundText = qfalse; +  qboolean foundToken = qfalse; + +  foundToken = trap_Parse_ReadToken( handle, &token ); +  while( foundToken ) +  { +    if( token.string[ 0 ] == '}' ) +    { +      if( foundText ) +        return qtrue; +      else +      { +        BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " +          "missing text attribute for track" ); +      } +    } +    else if( !Q_stricmp( token.string, "team" ) ) +    { +      foundToken = trap_Parse_ReadToken( handle, &token ); +      found = qfalse; +      while( foundToken && token.type == TT_NUMBER ) +      { +        found = qtrue; +        if( voiceTrack->team < 0 ) +          voiceTrack->team = 0; +        voiceTrack->team |= ( 1 << token.intvalue ); +        foundToken = trap_Parse_ReadToken( handle, &token ); +      } +      if( !found ) +      { +        BG_VoiceParseError( handle, +          "BG_VoiceParseTrack(): missing \"team\" value" ); +      } +      continue; +    } +    else if( !Q_stricmp( token.string, "class" ) ) +    { +      foundToken = trap_Parse_ReadToken( handle, &token ); +      found = qfalse; +      while( foundToken && token.type == TT_NUMBER ) +      { +        found = qtrue; +        if( voiceTrack->klass < 0 ) +          voiceTrack->klass = 0; +        voiceTrack->klass |= ( 1 << token.intvalue ); +        foundToken = trap_Parse_ReadToken( handle, &token ); +      } +      if( !found ) +      { +        BG_VoiceParseError( handle,  +          "BG_VoiceParseTrack(): missing \"class\" value" ); +      } +      continue; +    } +    else if( !Q_stricmp( token.string, "weapon" ) ) +    { +      foundToken = trap_Parse_ReadToken( handle, &token ); +      found = qfalse; +      while( foundToken && token.type == TT_NUMBER ) +      { +        found = qtrue; +        if( voiceTrack->weapon < 0 ) +          voiceTrack->weapon = 0; +        voiceTrack->weapon |= ( 1 << token.intvalue ); +        foundToken = trap_Parse_ReadToken( handle, &token ); +      } +      if( !found ) +      { +        BG_VoiceParseError( handle, +          "BG_VoiceParseTrack(): missing \"weapon\" value"); +      } +      continue; +    } +    else if( !Q_stricmp( token.string, "text" ) ) +    { +      if( foundText ) +      { +        BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " +          "duplicate \"text\" definition for track" ); +      } +      foundToken = trap_Parse_ReadToken( handle, &token ); +      if( !foundToken ) +      { +        BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " +          "missing \"text\" value" ); +      } +      foundText = qtrue; +      if( strlen( token.string ) >= MAX_SAY_TEXT ) +      { +        BG_VoiceParseError( handle, va( "BG_VoiceParseTrack(): " +          "\"text\" value " "\"%s\" exceeds MAX_SAY_TEXT length", +          token.string ) ); +      } + +      voiceTrack->text = (char *)BG_Alloc( strlen( token.string ) + 1 ); +      Q_strncpyz( voiceTrack->text, token.string, strlen( token.string ) + 1 ); +      foundToken = trap_Parse_ReadToken( handle, &token ); +      continue; +    } +    else if( !Q_stricmp( token.string, "enthusiasm" ) ) +    { +      foundToken = trap_Parse_ReadToken( handle, &token ); +      if( token.type == TT_NUMBER ) +      { +        voiceTrack->enthusiasm = token.intvalue; +      } +      else +      { +        BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " +          "missing \"enthusiasm\" value" ); +      } +      foundToken = trap_Parse_ReadToken( handle, &token ); +      continue; +    } +    else +    { +        BG_VoiceParseError( handle, va( "BG_VoiceParseTrack():" +          " unknown token \"%s\"", token.string ) ); +    } +  } +  return qfalse; +} + +/* +============ +BG_VoiceParseCommand +============ +*/ +static voiceTrack_t *BG_VoiceParseCommand( int handle ) +{ +  pc_token_t token; +  qboolean parsingTrack = qfalse; +  voiceTrack_t *voiceTracks = NULL; +  voiceTrack_t *top = NULL; +   +  while( trap_Parse_ReadToken( handle, &token ) ) +  { +    if( !parsingTrack && token.string[ 0 ] == '}' ) +      return top; + +    if( parsingTrack ) +    { +      if( token.string[ 0 ] == '{' ) +      { +        BG_VoiceParseTrack( handle, voiceTracks ); +        parsingTrack = qfalse; +        continue; +         +      } +      else +      { +        BG_VoiceParseError( handle, va( "BG_VoiceParseCommand(): " +          "parse error at \"%s\"", token.string ) ); +      } +    } + + +    if( top == NULL ) +    { +      voiceTracks = BG_Alloc( sizeof( voiceTrack_t ) ); +      top = voiceTracks; +    } +    else +    { +      voiceTracks->next = BG_Alloc( sizeof( voiceCmd_t ) ); +      voiceTracks = voiceTracks->next; +    } +     +    if( !trap_FS_FOpenFile( token.string, NULL, FS_READ ) ) +    { +        int line; +        char filename[ MAX_QPATH ]; + +        trap_Parse_SourceFileAndLine( handle, filename, &line ); +        Com_Printf( S_COLOR_YELLOW "WARNING: BG_VoiceParseCommand(): " +          "track \"%s\" referenced on line %d of %s does not exist\n", +          token.string, line, filename ); +    } +    else +    { +#ifdef CGAME +      voiceTracks->track = trap_S_RegisterSound( token.string, qfalse ); +      voiceTracks->duration = trap_S_SoundDuration( voiceTracks->track ); +#endif +    } + +    voiceTracks->team = -1; +    voiceTracks->klass = -1; +    voiceTracks->weapon = -1; +    voiceTracks->enthusiasm = 0; +    voiceTracks->text = NULL; +    voiceTracks->next = NULL; +    parsingTrack = qtrue; + +  } +  return NULL; +} + +/* +============ +BG_VoiceParse +============ +*/ +static voiceCmd_t *BG_VoiceParse( char *name ) +{ +  voiceCmd_t *voiceCmds = NULL; +  voiceCmd_t *top = NULL; +  pc_token_t token; +  qboolean parsingCmd = qfalse; +  int handle; +   +  handle = trap_Parse_LoadSource( va( "voice/%s.voice", name ) ); +  if( !handle ) +    return NULL; + +  while( trap_Parse_ReadToken( handle, &token ) ) +  { +    if( parsingCmd ) +    { +      if( token.string[ 0 ] == '{' ) +      { +        voiceCmds->tracks = BG_VoiceParseCommand( handle ); +        parsingCmd = qfalse; +	continue; +      } +      else +      { +        int line; +        char filename[ MAX_QPATH ]; + +        trap_Parse_SourceFileAndLine( handle, filename, &line ); +        Com_Error( ERR_FATAL, "BG_VoiceParse(): " +          "parse error on line %d of %s", line, filename ); +      } +    } +       +    if( strlen( token.string ) >= MAX_VOICE_CMD_LEN ) +    { +        int line; +        char filename[ MAX_QPATH ]; + +        trap_Parse_SourceFileAndLine( handle, filename, &line ); +        Com_Error( ERR_FATAL, "BG_VoiceParse(): " +          "command \"%s\" exceeds MAX_VOICE_CMD_LEN (%d) on line %d of %s", +          token.string, MAX_VOICE_CMD_LEN, line, filename ); +    } +    +    if( top == NULL ) +    { +      voiceCmds = BG_Alloc( sizeof( voiceCmd_t ) ); +      top = voiceCmds; +    } +    else +    { +      voiceCmds->next = BG_Alloc( sizeof( voiceCmd_t ) ); +      voiceCmds = voiceCmds->next; +    } + +    Q_strncpyz( voiceCmds->cmd, token.string, sizeof( voiceCmds->cmd ) ); +    voiceCmds->next = NULL; +    parsingCmd = qtrue; + +  }  + +  trap_Parse_FreeSource( handle ); +   +  return top; +} + +/* +============ +BG_VoiceInit +============ +*/ +voice_t *BG_VoiceInit( void ) +{ +  voice_t *voices; +  voice_t *voice; + +  voices = BG_VoiceList(); + +  voice = voices; +  while( voice ) +  { +    voice->cmds = BG_VoiceParse( voice->name ); +    voice = voice->next; +  } + +  return voices; +} + + +/* +============ +BG_PrintVoices +============ +*/ +void BG_PrintVoices( voice_t *voices, int debugLevel ) +{ +  voice_t *voice = voices; +  voiceCmd_t *voiceCmd; +  voiceTrack_t *voiceTrack; +   +  int cmdCount; +  int trackCount; + +  if( voice == NULL ) +  { +    Com_Printf( "voice list is empty\n" ); +    return; +  } + +  while( voice != NULL ) +  { +    if( debugLevel > 0 ) +      Com_Printf( "voice \"%s\"\n", voice->name ); +    voiceCmd = voice->cmds; +    cmdCount = 0; +    trackCount = 0; +    while( voiceCmd != NULL ) +    { +      if( debugLevel > 0 ) +        Com_Printf( "  %s\n", voiceCmd->cmd ); +      voiceTrack = voiceCmd->tracks; +      cmdCount++; +      while ( voiceTrack != NULL ) +      { +        if( debugLevel > 1 ) +          Com_Printf( "    text -> %s\n", voiceTrack->text ); +        if( debugLevel > 2 ) +        { +          Com_Printf( "    team -> %d\n", voiceTrack->team ); +          Com_Printf( "    class -> %d\n", voiceTrack->klass ); +          Com_Printf( "    weapon -> %d\n", voiceTrack->weapon ); +          Com_Printf( "    enthusiasm -> %d\n", voiceTrack->enthusiasm ); +#ifdef CGAME +          Com_Printf( "    duration -> %d\n", voiceTrack->duration ); +#endif +        } +        if( debugLevel > 1 ) +          Com_Printf( "\n" ); +        trackCount++;  +        voiceTrack = voiceTrack->next; +      } +      voiceCmd = voiceCmd->next; +    } + +    if( !debugLevel ) +    { +      Com_Printf( "voice \"%s\": %d commands, %d tracks\n", +        voice->name, cmdCount, trackCount ); +    } +    voice = voice->next; +  } +} + +/* +============ +BG_VoiceByName +============ +*/ +voice_t *BG_VoiceByName( voice_t *head, char *name ) +{ +  voice_t *v = head; + +  while( v ) +  { +    if( !Q_stricmp( v->name, name ) ) +      return v; +    v = v->next; +  } +  return NULL; +} + +/* +============ +BG_VoiceCmdFind +============ +*/ +voiceCmd_t *BG_VoiceCmdFind( voiceCmd_t *head, char *name, int *cmdNum ) +{ +  voiceCmd_t *vc = head; +  int i = 0; + +  while( vc ) +  { +    i++; +    if( !Q_stricmp( vc->cmd, name ) ) +    { +      *cmdNum = i; +      return vc; +    } +    vc = vc->next; +  } +  return NULL; +} + +/* +============ +BG_VoiceCmdByNum +============ +*/ +voiceCmd_t *BG_VoiceCmdByNum( voiceCmd_t *head, int num ) +{ +  voiceCmd_t *vc = head; +  int i = 0; + +  while( vc ) +  { +    i++; +    if( i == num ) +      return vc; +    vc = vc->next; +  } +  return NULL; +} + +/* +============ +BG_VoiceTrackByNum +============ +*/ +voiceTrack_t *BG_VoiceTrackByNum( voiceTrack_t *head, int num ) +{ +  voiceTrack_t *vt = head; +  int i = 0; + +  while( vt ) +  { +    i++; +    if( i == num ) +      return vt; +    vt = vt->next; +  } +  return NULL; +} + +/* +============ +BG_VoiceTrackFind +============ +*/ +voiceTrack_t *BG_VoiceTrackFind( voiceTrack_t *head, team_t team, +                                 class_t class, weapon_t weapon, +                                 int enthusiasm, int *trackNum ) +{ +  voiceTrack_t *vt = head; +  int highestMatch = 0; +  int matchCount = 0; +  int selectedMatch = 0; +  int i = 0; +  int j = 0; +   +  // find highest enthusiasm without going over +  while( vt ) +  { +    if( ( vt->team >= 0 && !( vt->team  & ( 1 << team ) ) ) ||  +        ( vt->klass >= 0 && !( vt->klass & ( 1 << class ) ) ) ||  +        ( vt->weapon >= 0 && !( vt->weapon & ( 1 << weapon ) ) ) || +        vt->enthusiasm > enthusiasm ) +    { +      vt = vt->next; +      continue; +    } + +    if( vt->enthusiasm > highestMatch ) +    { +      matchCount = 0; +      highestMatch = vt->enthusiasm;  +    } +    if( vt->enthusiasm == highestMatch ) +      matchCount++; +    vt = vt->next; +  } + +  if( !matchCount ) +    return NULL; + +  // return randomly selected match +  selectedMatch = rand() / ( RAND_MAX / matchCount + 1 ); +  vt = head; +  i = 0; +  j = 0; +  while( vt ) +  { +    j++; +    if( ( vt->team >= 0 && !( vt->team  & ( 1 << team ) ) ) ||  +        ( vt->klass >= 0 && !( vt->klass & ( 1 << class ) ) ) ||  +        ( vt->weapon >= 0 && !( vt->weapon & ( 1 << weapon ) ) ) || +        vt->enthusiasm != highestMatch ) +    { +      vt = vt->next; +      continue; +    } +    if( i == selectedMatch ) +    { +      *trackNum = j; +      return vt;  +    } +    i++; +    vt = vt->next; +  } +  return NULL; +} diff --git a/src/game/g_active.c b/src/game/g_active.c index b5df273..d978a4a 100644 --- a/src/game/g_active.c +++ b/src/game/g_active.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -40,7 +41,7 @@ void P_DamageFeedback( gentity_t *player )    vec3_t    angles;    client = player->client; -  if( client->ps.pm_type == PM_DEAD ) +  if( !PM_Alive( client->ps.pm_type ) )      return;    // total points of damage shot at the player this frame @@ -129,7 +130,7 @@ void P_WorldEffects( gentity_t *ent )          // play a gurp sound instead of a normal pain sound          if( ent->health <= ent->damage )            G_Sound( ent, CHAN_VOICE, G_SoundIndex( "*drown.wav" ) ); -        else if( rand( ) & 1 ) +        else if( rand( ) < RAND_MAX / 2 + 1 )            G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp1.wav" ) );          else            G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp2.wav" ) ); @@ -191,59 +192,74 @@ void G_SetClientSound( gentity_t *ent )  //============================================================== -static void G_ClientShove( gentity_t *ent, gentity_t *victim ) +/* +============== +ClientShove +============== +*/ +static int GetClientMass( gentity_t *ent )  { -  vec3_t  dir, push; -  int entMass = 200, vicMass = 200; - -  // shoving enemies changes gameplay too much -  if( !OnSameTeam( ent, victim ) ) -    return; +  int entMass = 100; -  if ( ( victim->client->ps.weapon >= WP_ABUILD ) && -       ( victim->client->ps.weapon <= WP_HBUILD ) && -       ( victim->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) ) -  return; - -  // alien mass is directly related to their health points -  // human mass is 200, double for bsuit   -  if( ent->client->pers.teamSelection == PTE_ALIENS ) -  { -    entMass = BG_FindHealthForClass( ent->client->pers.classSelection ); -  } -  else if( ent->client->pers.teamSelection == PTE_HUMANS ) +  if( ent->client->pers.teamSelection == TEAM_ALIENS ) +    entMass = BG_Class( ent->client->pers.classSelection )->health; +  else if( ent->client->pers.teamSelection == TEAM_HUMANS )    {      if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) )        entMass *= 2;    }    else +    return 0; +  return entMass; +} + +static void ClientShove( gentity_t *ent, gentity_t *victim ) +{ +  vec3_t dir, push; +  float force; +  int entMass, vicMass; +   +  // Don't push if the entity is not trying to move +  if( !ent->client->pers.cmd.rightmove && !ent->client->pers.cmd.forwardmove && +      !ent->client->pers.cmd.upmove )      return; -  if( victim->client->pers.teamSelection == PTE_ALIENS ) -  { -    vicMass = BG_FindHealthForClass( victim->client->pers.classSelection ); -  } -  else if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, -    victim->client->ps.stats ) ) -  { -    vicMass *= 2; -  } +  // Cannot push enemy players unless they are walking on the player +  if( !OnSameTeam( ent, victim ) && +      victim->client->ps.groundEntityNum != ent - g_entities ) +    return;       +  // Shove force is scaled by relative mass +  entMass = GetClientMass( ent ); +  vicMass = GetClientMass( victim );    if( vicMass <= 0 || entMass <= 0 )      return; +  force = g_shove.value * entMass / vicMass; +  if( force < 0 ) +    force = 0; +  if( force > 150 ) +    force = 150; +  // Give the victim some shove velocity    VectorSubtract( victim->r.currentOrigin, ent->r.currentOrigin, dir );    VectorNormalizeFast( dir ); - -  // don't break the dretch elevator -  if( fabs( dir[ 2 ] ) > fabs( dir[ 0 ] ) && fabs( dir[ 2 ] ) > fabs( dir[ 1 ] ) ) -    return; - -  VectorScale( dir, -    ( g_shove.value * ( ( float )entMass / ( float )vicMass ) ), push ); -  VectorAdd( victim->client->ps.velocity, push, -                victim->client->ps.velocity ); - +  VectorScale( dir, force, push ); +  VectorAdd( victim->client->ps.velocity, push, victim->client->ps.velocity ); +   +  // Set the pmove timer so that the other client can't cancel +  // out the movement immediately +  if( !victim->client->ps.pm_time ) +  { +    int time; + +    time = force * 2 + 0.5f; +    if( time < 50 ) +      time = 50; +    if( time > 200 ) +      time = 200; +    victim->client->ps.pm_time = time; +    victim->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; +  }  }  /* @@ -253,45 +269,38 @@ ClientImpacts  */  void ClientImpacts( gentity_t *ent, pmove_t *pm )  { -  int       i, j; +  int       i;    trace_t   trace;    gentity_t *other; +  // clear a fake trace struct for touch function    memset( &trace, 0, sizeof( trace ) );    for( i = 0; i < pm->numtouch; i++ )    { -    for( j = 0; j < i; j++ ) -    { -      if( pm->touchents[ j ] == pm->touchents[ i ] ) -        break; -    } - -    if( j != i ) -      continue; // duplicated -      other = &g_entities[ pm->touchents[ i ] ];      // see G_UnlaggedDetectCollisions(), this is the inverse of that.      // if our movement is blocked by another player's real position, -    // don't use the unlagged position for them because they are  +    // don't use the unlagged position for them because they are      // blocking or server-side Pmove() from reaching it      if( other->client && other->client->unlaggedCalc.used )        other->client->unlaggedCalc.used = qfalse; -    //charge attack -    if( ent->client->ps.weapon == WP_ALEVEL4 && -        ent->client->ps.stats[ STAT_MISC ] > 0 && -        ent->client->charging ) -      ChargeAttack( ent, other ); +    // tyrant impact attacks +    if( ent->client->ps.weapon == WP_ALEVEL4 ) +    { +      G_ChargeAttack( ent, other ); +      G_CrushAttack( ent, other ); +    } +    // shove players      if( ent->client && other->client ) -      G_ClientShove( ent, other ); - -    if( !other->touch ) -      continue; +      ClientShove( ent, other ); -    other->touch( other, ent, &trace ); +    // touch triggers +    if( other->touch ) +      other->touch( other, ent, &trace );    }  } @@ -316,11 +325,15 @@ void  G_TouchTriggers( gentity_t *ent )    if( !ent->client )      return; +  // noclipping clients don't activate triggers! +  if( ent->client->noclip ) +    return; +    // dead clients don't activate triggers!    if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 )      return; -  BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], +  BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ],                         pmins, pmaxs, NULL, NULL, NULL );    VectorAdd( ent->client->ps.origin, pmins, mins ); @@ -346,9 +359,7 @@ void  G_TouchTriggers( gentity_t *ent )        continue;      // ignore most entities if a spectator -    if( ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) || -        ( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) || -        ( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) ) +    if( ent->client->sess.spectatorState != SPECTATOR_NOT )      {        if( hit->s.eType != ET_TELEPORT_TRIGGER &&            // this is ugly but adding a new ET_? type will @@ -380,136 +391,123 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )  {    pmove_t pm;    gclient_t *client; -  qboolean attack1, attack3; -  qboolean  doPmove = qtrue; +  int clientNum; +  qboolean attack1, following, queued;    client = ent->client;    client->oldbuttons = client->buttons;    client->buttons = ucmd->buttons; -   attack1 = ( ( client->buttons & BUTTON_ATTACK ) && -               !( client->oldbuttons & BUTTON_ATTACK ) ); -   attack3 = ( ( client->buttons & BUTTON_USE_HOLDABLE ) && -               !( client->oldbuttons & BUTTON_USE_HOLDABLE ) ); - -  if( level.mapRotationVoteTime ) +  attack1 = ( client->buttons & BUTTON_ATTACK ) && +            !( client->oldbuttons & BUTTON_ATTACK ); +    +  // We are in following mode only if we are following a non-spectating client            +  following = client->sess.spectatorState == SPECTATOR_FOLLOW; +  if( following )    { -    if( attack1 ) -    { -      G_IntermissionMapVoteCommand( ent, qtrue, qfalse ); -      attack1 = qfalse; -    } -    if( ( client->buttons & BUTTON_ATTACK2 ) && !( client->oldbuttons & BUTTON_ATTACK2 ) ) -      G_IntermissionMapVoteCommand( ent, qfalse, qfalse ); +    clientNum = client->sess.spectatorClient; +    if( clientNum < 0 || clientNum > level.maxclients || +        !g_entities[ clientNum ].client || +        g_entities[ clientNum ].client->sess.spectatorState != SPECTATOR_NOT ) +      following = qfalse;    } - -  if( client->sess.spectatorState == SPECTATOR_LOCKED || client->sess.spectatorState == SPECTATOR_FOLLOW ) -    client->ps.pm_type = PM_FREEZE; +   +  // Check to see if we are in the spawn queue +  if( client->pers.teamSelection == TEAM_ALIENS ) +    queued = G_SearchSpawnQueue( &level.alienSpawnQueue, ent - g_entities ); +  else if( client->pers.teamSelection == TEAM_HUMANS ) +    queued = G_SearchSpawnQueue( &level.humanSpawnQueue, ent - g_entities );    else -    client->ps.pm_type = PM_SPECTATOR; +    queued = qfalse; -  if ( client->sess.spectatorState == SPECTATOR_FOLLOW ) +  // Wants to get out of spawn queue +  if( attack1 && queued )    { -    gclient_t *cl; -    if ( client->sess.spectatorClient >= 0 ) -    { -      cl = &level.clients[ client->sess.spectatorClient ]; -      if ( cl->sess.sessionTeam != TEAM_SPECTATOR ) -        doPmove = qfalse; -    } +    if( client->sess.spectatorState == SPECTATOR_FOLLOW ) +      G_StopFollowing( ent ); +    if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) +      G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); +    else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) +      G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); +    client->pers.classSelection = PCL_NONE; +    client->pers.humanItemSelection = WP_NONE; +    client->ps.stats[ STAT_CLASS ] = PCL_NONE; +    client->ps.pm_flags &= ~PMF_QUEUED; +    queued = qfalse; +  } +  else if( attack1 ) +  { +    // Wants to get into spawn queue +    if( client->sess.spectatorState == SPECTATOR_FOLLOW ) +      G_StopFollowing( ent ); +    if( client->pers.teamSelection == TEAM_NONE ) +      G_TriggerMenu( client->ps.clientNum, MN_TEAM ); +    else if( client->pers.teamSelection == TEAM_ALIENS ) +      G_TriggerMenu( client->ps.clientNum, MN_A_CLASS ); +    else if( client->pers.teamSelection == TEAM_HUMANS ) +      G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN );    } -  if (doPmove) +  // We are either not following anyone or following a spectator +  if( !following )    { -    client->ps.speed = BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] ); - -     // in case the client entered the queue while following a teammate -     if( ( client->pers.teamSelection == PTE_ALIENS && -           G_SearchSpawnQueue( &level.alienSpawnQueue, ent-g_entities ) ) || -         ( client->pers.teamSelection == PTE_HUMANS && -           G_SearchSpawnQueue( &level.humanSpawnQueue, ent-g_entities ) ) ) -     { -       client->ps.pm_flags |= PMF_QUEUED; -     } -  +    if( client->sess.spectatorState == SPECTATOR_LOCKED || +        client->sess.spectatorState == SPECTATOR_FOLLOW ) +      client->ps.pm_type = PM_FREEZE; +    else if( client->noclip ) +      client->ps.pm_type = PM_NOCLIP; +    else +      client->ps.pm_type = PM_SPECTATOR; + +    if( queued ) +      client->ps.pm_flags |= PMF_QUEUED; +    client->ps.speed = client->pers.flySpeed;      client->ps.stats[ STAT_STAMINA ] = 0;      client->ps.stats[ STAT_MISC ] = 0; -    client->ps.stats[ STAT_BUILDABLE ] = 0; -    client->ps.stats[ STAT_PCLASS ] = PCL_NONE; +    client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; +    client->ps.stats[ STAT_CLASS ] = PCL_NONE;      client->ps.weapon = WP_NONE; -    // set up for pmove +    // Set up for pmove      memset( &pm, 0, sizeof( pm ) );      pm.ps = &client->ps; +    pm.pmext = &client->pmext;      pm.cmd = *ucmd; -    pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies +    pm.tracemask = ent->clipmask;      pm.trace = trap_Trace;      pm.pointcontents = trap_PointContents; -    // perform a pmove +    // Perform a pmove      Pmove( &pm ); -    // save results of pmove -    VectorCopy( client->ps.origin, ent->s.origin ); +    // Save results of pmove +    VectorCopy( client->ps.origin, ent->s.pos.trBase ); +    VectorCopy( client->ps.origin, ent->r.currentOrigin ); +    VectorCopy( client->ps.viewangles, ent->r.currentAngles ); +    VectorCopy( client->ps.viewangles, ent->s.pos.trBase );      G_TouchTriggers( ent );      trap_UnlinkEntity( ent ); -    if( ( attack1 ) && ( client->ps.pm_flags & PMF_QUEUED ) ) -    { -      if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -        G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); -      else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -        G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); - -      client->pers.classSelection = PCL_NONE; -      client->ps.stats[ STAT_PCLASS ] = PCL_NONE; -    } -     -    if( attack1 && client->pers.classSelection == PCL_NONE ) -    { -      if( client->pers.teamSelection == PTE_NONE ) -        G_TriggerMenu( client->ps.clientNum, MN_TEAM ); -      else if( client->pers.teamSelection == PTE_ALIENS ) -        G_TriggerMenu( client->ps.clientNum, MN_A_CLASS ); -      else if( client->pers.teamSelection == PTE_HUMANS ) -        G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN ); -    } - -    //set the queue position for the client side +    // Set the queue position and spawn count for the client side      if( client->ps.pm_flags & PMF_QUEUED )      { -      if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) +      if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )        {          client->ps.persistant[ PERS_QUEUEPOS ] =            G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); +        client->ps.persistant[ PERS_SPAWNS ] = level.numAlienSpawns;        } -      else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +      else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )        {          client->ps.persistant[ PERS_QUEUEPOS ] =            G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); +        client->ps.persistant[ PERS_SPAWNS ] = level.numHumanSpawns;        }      }    } - -  else if( attack1 && ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) -  { -    G_StopFollowing( ent ); -    client->pers.classSelection = PCL_NONE; -    if( client->pers.teamSelection == PTE_NONE ) -      G_TriggerMenu( ent-g_entities, MN_TEAM ); -    else if( client->pers.teamSelection == PTE_ALIENS ) -      G_TriggerMenu( ent-g_entities, MN_A_CLASS ); -    else if( client->pers.teamSelection == PTE_HUMANS ) -      G_TriggerMenu( ent-g_entities, MN_H_SPAWN ); -  } -    -  if( attack3 ) -  { -   G_ToggleFollow( ent ); -  }  } @@ -521,8 +519,10 @@ ClientInactivityTimer  Returns qfalse if the client is dropped  =================  */ -qboolean ClientInactivityTimer( gclient_t *client ) +qboolean ClientInactivityTimer( gentity_t *ent )  { +  gclient_t *client = ent->client; +    if( ! g_inactivity.integer )    {      // give everyone some time, so if the operator sets g_inactivity during @@ -540,13 +540,16 @@ qboolean ClientInactivityTimer( gclient_t *client )    }    else if( !client->pers.localClient )    { -    if( level.time > client->inactivityTime ) +    if( level.time > client->inactivityTime && +        !G_admin_permission( ent, ADMF_ACTIVITY ) )      {        trap_DropClient( client - level.clients, "Dropped due to inactivity" );        return qfalse;      } -    if( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) +    if( level.time > client->inactivityTime - 10000 && +        !client->inactivityWarning && +        !G_admin_permission( ent, ADMF_ACTIVITY ) )      {        client->inactivityWarning = qtrue;        trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); @@ -569,203 +572,100 @@ void ClientTimerActions( gentity_t *ent, int msec )    usercmd_t *ucmd;    int       aForward, aRight;    qboolean  walking = qfalse, stopped = qfalse, -            crouched = qfalse, jumping = qfalse, -            strafing = qfalse; +            crouched = qfalse; +  int       i;    ucmd = &ent->client->pers.cmd;    aForward  = abs( ucmd->forwardmove );    aRight    = abs( ucmd->rightmove ); -  client = ent->client; -  client->time100 += msec; -  client->time1000 += msec; -  client->time10000 += msec; -    if( aForward == 0 && aRight == 0 )      stopped = qtrue;    else if( aForward <= 64 && aRight <= 64 )      walking = qtrue; -  if( aRight > 0 ) -    strafing = qtrue; - -  if( ucmd->upmove > 0 ) -    jumping = qtrue; -  else if( ent->client->ps.pm_flags & PMF_DUCKED ) +  if( ucmd->upmove <= 0 && ent->client->ps.pm_flags & PMF_DUCKED )      crouched = qtrue; +  client = ent->client; +  client->time100 += msec; +  client->time1000 += msec; +  client->time10000 += msec; +    while ( client->time100 >= 100 )    { +    weapon_t weapon = BG_GetPlayerWeapon( &client->ps ); +        client->time100 -= 100; -    //if not trying to run then not trying to sprint -    if( walking || stopped ) -      client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; - -    if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) -      client->ps.stats[ STAT_STATE ] &= ~SS_SPEEDBOOST; - -    if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && !crouched ) -    { -      //subtract stamina -      if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) ) -        client->ps.stats[ STAT_STAMINA ] -= STAMINA_LARMOUR_TAKE; -      else -        client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE; - -      if( client->ps.stats[ STAT_STAMINA ] < -MAX_STAMINA ) -        client->ps.stats[ STAT_STAMINA ] = -MAX_STAMINA; -    } - -    if( walking || crouched ) -    { -      //restore stamina -      client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE; - -      if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA ) -        client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; -    } -    else if( stopped ) -    { -      //restore stamina faster +    // Restore or subtract stamina +    if( stopped || client->ps.pm_type == PM_JETPACK )        client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE; - -      if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA ) -        client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; -    } - -    //client is charging up for a pounce -    if( client->ps.weapon == WP_ALEVEL3 || client->ps.weapon == WP_ALEVEL3_UPG ) -    { -      int pounceSpeed = 0; - -      if( client->ps.weapon == WP_ALEVEL3 ) -        pounceSpeed = LEVEL3_POUNCE_SPEED; -      else if( client->ps.weapon == WP_ALEVEL3_UPG ) -        pounceSpeed = LEVEL3_POUNCE_UPG_SPEED; - -      if( client->ps.stats[ STAT_MISC ] < pounceSpeed && ucmd->buttons & BUTTON_ATTACK2 ) -        client->ps.stats[ STAT_MISC ] += ( 100.0f / (float)LEVEL3_POUNCE_CHARGE_TIME ) * pounceSpeed; - -      if( !( ucmd->buttons & BUTTON_ATTACK2 ) ) -      { -        if( client->pmext.pouncePayload > 0 ) -          client->allowedToPounce = qtrue; -      } - -      if( client->ps.stats[ STAT_MISC ] > pounceSpeed ) -        client->ps.stats[ STAT_MISC ] = pounceSpeed; -    } - -    //client is charging up for a... charge -    if( client->ps.weapon == WP_ALEVEL4 ) +    else if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && +             !( client->buttons & BUTTON_WALKING ) ) // walk overrides sprint +      client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE; +    else if( walking || crouched ) +      client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE; +       +    // Check stamina limits +    if( client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX ) +      client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; +    else if( client->ps.stats[ STAT_STAMINA ] < -STAMINA_MAX ) +      client->ps.stats[ STAT_STAMINA ] = -STAMINA_MAX; + +    if( weapon == WP_ABUILD || weapon == WP_ABUILD2 || +        client->ps.stats[ STAT_WEAPON ] == WP_HBUILD )      { -      if( client->ps.stats[ STAT_MISC ] < LEVEL4_CHARGE_TIME && ucmd->buttons & BUTTON_ATTACK2 && -          !client->charging ) -      { -        client->charging = qfalse; //should already be off, just making sure -        client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING; - -        if( ucmd->forwardmove > 0 ) -        { -          //trigger charge sound...is quite annoying -          //if( client->ps.stats[ STAT_MISC ] <= 0 ) -          //  G_AddEvent( ent, EV_LEV4_CHARGE_PREPARE, 0 ); - -          client->ps.stats[ STAT_MISC ] += (int)( 100 * (float)LEVEL4_CHARGE_CHARGE_RATIO ); - -          if( client->ps.stats[ STAT_MISC ] > LEVEL4_CHARGE_TIME ) -            client->ps.stats[ STAT_MISC ] = LEVEL4_CHARGE_TIME; -        } -        else -          client->ps.stats[ STAT_MISC ] = 0; -      } - -      if( !( ucmd->buttons & BUTTON_ATTACK2 ) || client->charging || -          client->ps.stats[ STAT_MISC ] == LEVEL4_CHARGE_TIME ) -      { -        if( client->ps.stats[ STAT_MISC ] > LEVEL4_MIN_CHARGE_TIME ) -        { +        // Update build timer +        if( client->ps.stats[ STAT_MISC ] > 0 )            client->ps.stats[ STAT_MISC ] -= 100; -          if( client->charging == qfalse ) -            G_AddEvent( ent, EV_LEV4_CHARGE_START, 0 ); - -          client->charging = qtrue; -          client->ps.stats[ STAT_STATE ] |= SS_CHARGING; - -          //if the charger has stopped moving take a chunk of charge away -          if( VectorLength( client->ps.velocity ) < 64.0f || aRight ) -            client->ps.stats[ STAT_MISC ] = client->ps.stats[ STAT_MISC ] / 2; - -          //can't charge backwards -          if( ucmd->forwardmove < 0 ) -            client->ps.stats[ STAT_MISC ] = 0; -        } -        else -          client->ps.stats[ STAT_MISC ] = 0; - - -        if( client->ps.stats[ STAT_MISC ] <= 0 ) -        { +        if( client->ps.stats[ STAT_MISC ] < 0 )            client->ps.stats[ STAT_MISC ] = 0; -          client->charging = qfalse; -          client->ps.stats[ STAT_STATE ] &= ~SS_CHARGING; -        } -      } -    } - -    //client is charging up an lcannon -    if( client->ps.weapon == WP_LUCIFER_CANNON ) -    { -      int ammo; - -      ammo = client->ps.ammo; - -      if( client->ps.stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE && ucmd->buttons & BUTTON_ATTACK ) -        client->ps.stats[ STAT_MISC ] += ( 100.0f / LCANNON_CHARGE_TIME ) * LCANNON_TOTAL_CHARGE; - -      if( client->ps.stats[ STAT_MISC ] > LCANNON_TOTAL_CHARGE ) -        client->ps.stats[ STAT_MISC ] = LCANNON_TOTAL_CHARGE; - -      if( client->ps.stats[ STAT_MISC ] > ( ammo * LCANNON_TOTAL_CHARGE ) / 10 ) -        client->ps.stats[ STAT_MISC ] = ammo * LCANNON_TOTAL_CHARGE / 10;      } -    switch( client->ps.weapon ) +    switch( weapon )      {        case WP_ABUILD:        case WP_ABUILD2:        case WP_HBUILD: -      case WP_HBUILD2: -        //set validity bit on buildable +       +        // Set validity bit on buildable          if( ( client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )          { -          int     dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); -          vec3_t  dummy; +          int     dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; +          vec3_t  dummy, dummy2; +          int     dummy3;            if( G_CanBuild( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT, -                          dist, dummy ) == IBE_NONE ) +                          dist, dummy, dummy2, &dummy3 ) == IBE_NONE )              client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT;            else              client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT; -        } -      case WP_BLASTER: -        //update build timer -        if( client->ps.stats[ STAT_MISC ] > 0 ) -          client->ps.stats[ STAT_MISC ] -= 100; - -        if( client->ps.stats[ STAT_MISC ] < 0 ) -          client->ps.stats[ STAT_MISC ] = 0; +          // Let the client know which buildables will be removed by building +          for( i = 0; i < MAX_MISC; i++ ) +          { +            if( i < level.numBuildablesForRemoval ) +              client->ps.misc[ i ] = level.markedBuildables[ i ]->s.number; +            else +              client->ps.misc[ i ] = 0; +          } +        } +        else +        { +          for( i = 0; i < MAX_MISC; i++ ) +            client->ps.misc[ i ] = 0; +        }          break;        default:          break;      } -    if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ) +    if( ent->client->pers.teamSelection == TEAM_HUMANS &&  +        ( client->ps.stats[ STAT_STATE ] & SS_HEALING_2X ) )      {        int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime ); @@ -777,9 +677,10 @@ void ClientTimerActions( gentity_t *ent, int msec )          {            ent->client->medKitHealthToRestore--;            ent->health++; +          ent->client->ps.stats[ STAT_HEALTH ] = ent->health;          }          else -          ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; +          ent->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_2X;        }        else        { @@ -792,13 +693,14 @@ void ClientTimerActions( gentity_t *ent, int msec )            {              ent->client->medKitHealthToRestore--;              ent->health++; +            ent->client->ps.stats[ STAT_HEALTH ] = ent->health;              client->medKitIncrementTime = level.time +                ( remainingStartupTime / MEDKIT_STARTUP_SPEED );            }          }          else -          ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; +          ent->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_2X;        }      }    } @@ -807,20 +709,17 @@ void ClientTimerActions( gentity_t *ent, int msec )    {      client->time1000 -= 1000; -    //client is poison clouded -    if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) -      G_Damage( ent, client->lastPoisonCloudedClient, client->lastPoisonCloudedClient, NULL, NULL, -                LEVEL1_PCLOUD_DMG, 0, MOD_LEVEL1_PCLOUD ); -      //client is poisoned      if( client->ps.stats[ STAT_STATE ] & SS_POISONED )      { -      int damage = ALIEN_POISON_DMG;  -    +      int damage = ALIEN_POISON_DMG; +        if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) )          damage -= BSUIT_POISON_PROTECTION; +        if( BG_InventoryContainsUpgrade( UP_HELMET, client->ps.stats ) )          damage -= HELMET_POISON_PROTECTION; +        if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) )          damage -= LIGHTARMOUR_POISON_PROTECTION; @@ -828,135 +727,37 @@ void ClientTimerActions( gentity_t *ent, int msec )          0, damage, 0, MOD_POISON );      } -    //replenish alien health -    if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && -      level.surrenderTeam != PTE_ALIENS ) -    { -      int       entityList[ MAX_GENTITIES ]; -      vec3_t    range = { LEVEL1_REGEN_RANGE, LEVEL1_REGEN_RANGE, LEVEL1_REGEN_RANGE }; -      vec3_t    mins, maxs; -      int       i, num; -      gentity_t *boostEntity; -      float     modifier = 1.0f; - -      VectorAdd( client->ps.origin, range, maxs ); -      VectorSubtract( client->ps.origin, range, mins ); - -      num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); -      for( i = 0; i < num; i++ ) -      { -        boostEntity = &g_entities[ entityList[ i ] ]; - -        if( boostEntity->client && boostEntity->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && -            boostEntity->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL1_UPG ) -        { -          modifier = LEVEL1_REGEN_MOD; -          break; -        } -        else if( boostEntity->s.eType == ET_BUILDABLE && -            boostEntity->s.modelindex == BA_A_BOOSTER && -            boostEntity->spawned && boostEntity->health > 0 ) -        { -          modifier = BOOSTER_REGEN_MOD; -          break; -        } -      } - -      if( ent->health > 0 && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] && -          !level.paused && -          ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) -      { -        ent->health += BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ) * modifier; - -        // if completely healed, cancel retribution -        if( ent->health >= client->ps.stats[ STAT_MAX_HEALTH ] ) -        { -          for( i = 0; i < MAX_CLIENTS; i++ ) -            ent->client->tkcredits[ i ] = 0; -        } -      } - -      if( ent->health > client->ps.stats[ STAT_MAX_HEALTH ] ) -        ent->health = client->ps.stats[ STAT_MAX_HEALTH ]; -    } -     -     -    if( ent->client->ps.stats[ STAT_HEALTH ] > 0 && ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -    { -      ent->client->pers.statscounters.timealive++; -      level.alienStatsCounters.timealive++; -      if( G_BuildableRange( ent->client->ps.origin, 900, BA_A_OVERMIND ) ) -      { -        ent->client->pers.statscounters.timeinbase++; -        level.alienStatsCounters.timeinbase++; -      } -      if( BG_ClassHasAbility( ent->client->ps.stats[ STAT_PCLASS ], SCA_WALLCLIMBER )  ) -      { -        ent->client->pers.statscounters.dretchbasytime++; -        level.alienStatsCounters.dretchbasytime++; -    if( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING  || ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING)  -    { -      ent->client->pers.statscounters.jetpackusewallwalkusetime++; -      level.alienStatsCounters.jetpackusewallwalkusetime++; -    } -      } -    } -    else if( ent->client->ps.stats[ STAT_HEALTH ] > 0 && ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -    { -      ent->client->pers.statscounters.timealive++; -      level.humanStatsCounters.timealive++; -      if( G_BuildableRange( ent->client->ps.origin, 900, BA_H_REACTOR ) ) -      { -        ent->client->pers.statscounters.timeinbase++; -        level.humanStatsCounters.timeinbase++; -      } -      if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) ) -      { -    if( client->ps.pm_type == PM_JETPACK )  -    { -      ent->client->pers.statscounters.jetpackusewallwalkusetime++; -      level.humanStatsCounters.jetpackusewallwalkusetime++; -    } -      } -    } -    -    // turn off life support when a team admits defeat  -    if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && -      level.surrenderTeam == PTE_ALIENS ) +    // turn off life support when a team admits defeat +    if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS && +        level.surrenderTeam == TEAM_ALIENS )      {        G_Damage( ent, NULL, NULL, NULL, NULL, -        BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ), +        BG_Class( client->ps.stats[ STAT_CLASS ] )->regenRate,          DAMAGE_NO_ARMOR, MOD_SUICIDE );      } -    else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && -      level.surrenderTeam == PTE_HUMANS ) +    else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && +      level.surrenderTeam == TEAM_HUMANS )      {        G_Damage( ent, NULL, NULL, NULL, NULL, 5, DAMAGE_NO_ARMOR, MOD_SUICIDE );      } -    //my new jetpack code -    if( mod_jetpackFuel.value >= 10.0f ) { -      //if we have jetpack and its on -      if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) { -        //check if fuels 0 if so deactivate it if not give a 10 second fuel low warning and take JETPACK_USE_RATE from fuel -        if( client->jetpackfuel <= 0.0f ) { -          BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); -        } else if( client->jetpackfuel < 10.0f && client->jetpackfuel > 0.0f) { -          client->jetpackfuel = client->jetpackfuel - mod_jetpackConsume.value; -          trap_SendServerCommand( client - level.clients, "cp \"^3Fuel ^1Low!!!!!^7\nLand now.\"" ); -        } else { -          client->jetpackfuel = client->jetpackfuel - mod_jetpackConsume.value; -        } +    // lose some voice enthusiasm +    if( client->voiceEnthusiasm > 0.0f ) +      client->voiceEnthusiasm -= VOICE_ENTHUSIASM_DECAY; +    else +      client->voiceEnthusiasm = 0.0f; -        //if jetpack isnt active regenerate fuel and give a message when its full -      } else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && !BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) { -        if( client->jetpackfuel > ( mod_jetpackFuel.value - 10.0f ) && client->jetpackfuel <= mod_jetpackFuel.value  ) { -          client->jetpackfuel = client->jetpackfuel + mod_jetpackRegen.value; -          trap_SendServerCommand( client - level.clients, "cp \"^3Fuel Status: ^2Full!^7\n\"" ); -        } else if( client->jetpackfuel < mod_jetpackFuel.value ) { -          //regenerate some fuel -          client->jetpackfuel = client->jetpackfuel + mod_jetpackRegen.value; -        } +    client->pers.secondsAlive++; +    if( g_freeFundPeriod.integer > 0 && +        client->pers.secondsAlive % g_freeFundPeriod.integer == 0 ) +    { +      // Give clients some credit periodically +      if( G_TimeTilSuddenDeath( ) > 0 ) +      { +        if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) +          G_AddCreditToClient( client, FREEKILL_ALIEN, qtrue ); +        else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) +          G_AddCreditToClient( client, FREEKILL_HUMAN, qtrue );        }      }    } @@ -965,21 +766,42 @@ void ClientTimerActions( gentity_t *ent, int msec )    {      client->time10000 -= 10000; -    if( client->ps.weapon == WP_ALEVEL3_UPG ) +    if( ent->client->ps.weapon == WP_ABUILD || +        ent->client->ps.weapon == WP_ABUILD2 )      { -      int ammo, maxAmmo; +      AddScore( ent, ALIEN_BUILDER_SCOREINC ); +    } +    else if( ent->client->ps.weapon == WP_HBUILD ) +    { +      AddScore( ent, HUMAN_BUILDER_SCOREINC ); +    } -      BG_FindAmmoForWeapon( WP_ALEVEL3_UPG, &maxAmmo, NULL ); -      ammo = client->ps.ammo; +    // Give score to basis that healed other aliens +    if( ent->client->pers.hasHealed ) +    { +      if( client->ps.weapon == WP_ALEVEL1 ) +        AddScore( ent, LEVEL1_REGEN_SCOREINC ); +      else if( client->ps.weapon == WP_ALEVEL1_UPG ) +        AddScore( ent, LEVEL1_UPG_REGEN_SCOREINC ); + +      ent->client->pers.hasHealed = qfalse; +    } +  } -      if( ammo < maxAmmo ) +  // Regenerate Adv. Dragoon barbs +  if( client->ps.weapon == WP_ALEVEL3_UPG ) +  { +    if( client->ps.ammo < BG_Weapon( WP_ALEVEL3_UPG )->maxAmmo ) +    { +      if( ent->timestamp + LEVEL3_BOUNCEBALL_REGEN < level.time )        { -        ammo++; -        client->ps.ammo = ammo; -        client->ps.clips = 0; +        client->ps.ammo++; +        ent->timestamp = level.time;        }      } -  } +    else +      ent->timestamp = level.time; +   }  }  /* @@ -989,7 +811,6 @@ ClientIntermissionThink  */  void ClientIntermissionThink( gclient_t *client )  { -  client->ps.eFlags &= ~EF_TALK;    client->ps.eFlags &= ~EF_FIRING;    client->ps.eFlags &= ~EF_FIRING2; @@ -1020,10 +841,10 @@ void ClientEvents( gentity_t *ent, int oldEventSequence )    vec3_t    dir;    vec3_t    point, mins;    float     fallDistance; -  pClass_t  class; +  class_t   class;    client = ent->client; -  class = client->ps.stats[ STAT_PCLASS ]; +  class = client->ps.stats[ STAT_CLASS ];    if( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS )      oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; @@ -1047,11 +868,11 @@ void ClientEvents( gentity_t *ent, int oldEventSequence )          else if( fallDistance > 1.0f )            fallDistance = 1.0f; -        damage = (int)( (float)BG_FindHealthForClass( class ) * -                 BG_FindFallDamageForClass( class ) * fallDistance ); +        damage = (int)( (float)BG_Class( class )->health * +                 BG_Class( class )->fallDamage * fallDistance );          VectorSet( dir, 0, 0, 1 ); -        BG_FindBBoxForClass( class, mins, NULL, NULL, NULL, NULL ); +        BG_ClassBoundingBox( class, mins, NULL, NULL, NULL, NULL );          mins[ 0 ] = mins[ 1 ] = 0.0f;          VectorAdd( client->ps.origin, mins, point ); @@ -1122,8 +943,8 @@ void SendPendingPredictableEvents( playerState_t *ps )  ==============   G_UnlaggedStore - Called on every server frame.  Stores position data for the client at that  - into client->unlaggedHist[] and the time into level.unlaggedTimes[].   + Called on every server frame.  Stores position data for the client at that + into client->unlaggedHist[] and the time into level.unlaggedTimes[].   This data is used by G_UnlaggedCalc()  ==============  */ @@ -1132,24 +953,24 @@ void G_UnlaggedStore( void )    int i = 0;    gentity_t *ent;    unlagged_t *save; -   +    if( !g_unlagged.integer )      return; -  level.unlaggedIndex++;  +  level.unlaggedIndex++;    if( level.unlaggedIndex >= MAX_UNLAGGED_MARKERS )      level.unlaggedIndex = 0;    level.unlaggedTimes[ level.unlaggedIndex ] = level.time; -  +    for( i = 0; i < level.maxclients; i++ )    {      ent = &g_entities[ i ];      save = &ent->client->unlaggedHist[ level.unlaggedIndex ]; -    save->used = qfalse;  +    save->used = qfalse;      if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) )        continue;      if( ent->client->pers.connected != CON_CONNECTED ) -      continue;  +      continue;      VectorCopy( ent->r.mins, save->mins );      VectorCopy( ent->r.maxs, save->maxs );      VectorCopy( ent->s.pos.trBase, save->origin ); @@ -1160,7 +981,7 @@ void G_UnlaggedStore( void )  /*  ==============   G_UnlaggedClear -  +   Mark all unlaggedHist[] markers for this client invalid.  Useful for   preventing teleporting and death.  ============== @@ -1185,14 +1006,14 @@ void G_UnlaggedCalc( int time, gentity_t *rewindEnt )  {    int i = 0;    gentity_t *ent; -  int startIndex = level.unlaggedIndex; -  int stopIndex = -1; -  int frameMsec = 0; -  float lerp = 0.5f; +  int startIndex; +  int stopIndex; +  int frameMsec; +  float lerp;    if( !g_unlagged.integer )      return; -  +    // clear any calculated values from a previous run    for( i = 0; i < level.maxclients; i++ )    { @@ -1200,13 +1021,18 @@ void G_UnlaggedCalc( int time, gentity_t *rewindEnt )      ent->client->unlaggedCalc.used = qfalse;    } -  for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) +  // client is on the current frame, no need for unlagged +  if( level.unlaggedTimes[ level.unlaggedIndex ] <= time ) +    return; + +  startIndex = level.unlaggedIndex; +  for( i = 1; i < MAX_UNLAGGED_MARKERS; i++ )    { -    if( level.unlaggedTimes[ startIndex ] <= time ) -      break;      stopIndex = startIndex;      if( --startIndex < 0 )        startIndex = MAX_UNLAGGED_MARKERS - 1; +    if( level.unlaggedTimes[ startIndex ] <= time ) +      break;    }    if( i == MAX_UNLAGGED_MARKERS )    { @@ -1214,20 +1040,13 @@ void G_UnlaggedCalc( int time, gentity_t *rewindEnt )      // just use the oldest marker with no lerping      lerp = 0.0f;    } - -  // client is on the current frame, no need for unlagged -  if( stopIndex == -1 ) -    return; - -  // lerp between two markers -  frameMsec = level.unlaggedTimes[ stopIndex ] - -    level.unlaggedTimes[ startIndex ]; -  if( frameMsec > 0 ) +  else    { -    lerp = ( float )( time - level.unlaggedTimes[ startIndex ] ) -      / ( float )frameMsec;  +    // lerp between two markers +    frameMsec = level.unlaggedTimes[ stopIndex ] - level.unlaggedTimes[ startIndex ]; +    lerp = ( float )( time - level.unlaggedTimes[ startIndex ] ) / ( float )frameMsec;    } -   +    for( i = 0; i < level.maxclients; i++ )    {      ent = &g_entities[ i ]; @@ -1243,13 +1062,13 @@ void G_UnlaggedCalc( int time, gentity_t *rewindEnt )        continue;      // between two unlagged markers -    VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].mins, +    VectorLerp2( lerp, ent->client->unlaggedHist[ startIndex ].mins,        ent->client->unlaggedHist[ stopIndex ].mins,        ent->client->unlaggedCalc.mins ); -    VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].maxs, +    VectorLerp2( lerp, ent->client->unlaggedHist[ startIndex ].maxs,        ent->client->unlaggedHist[ stopIndex ].maxs,        ent->client->unlaggedCalc.maxs ); -    VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].origin, +    VectorLerp2( lerp, ent->client->unlaggedHist[ startIndex ].origin,        ent->client->unlaggedHist[ stopIndex ].origin,        ent->client->unlaggedCalc.origin ); @@ -1268,10 +1087,10 @@ void G_UnlaggedOff( void )  {    int i = 0;    gentity_t *ent; -   +    if( !g_unlagged.integer )      return; -   +    for( i = 0; i < level.maxclients; i++ )    {      ent = &g_entities[ i ]; @@ -1304,13 +1123,13 @@ void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range )    int i = 0;    gentity_t *ent;    unlagged_t *calc; -   +    if( !g_unlagged.integer )      return;    if( !attacker->client->pers.useUnlagged )      return; -   +    for( i = 0; i < level.maxclients; i++ )    {      ent = &g_entities[ i ]; @@ -1331,7 +1150,7 @@ void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range )        float maxRadius = ( r1 > r2 ) ? r1 : r2;        if( Distance( muzzle, calc->origin ) > range + maxRadius ) -        continue;  +        continue;      }      // create a backup of the real positions @@ -1355,7 +1174,7 @@ void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range )   the current time, but only updates other player's positions up to the   postition sent in the most recent snapshot. - This allows player X to essentially "move through" the position of player Y  + This allows player X to essentially "move through" the position of player Y   when player X's cmd is processed with Pmove() on the server.  This is because   player Y was clipping player X's Pmove() on his client, but when the same   cmd is processed with Pmove on the server it is not clipped. @@ -1378,6 +1197,7 @@ static void G_UnlaggedDetectCollisions( gentity_t *ent )    if( !g_unlagged.integer )      return; +    if( !ent->client->pers.useUnlagged )      return; @@ -1400,49 +1220,13 @@ static void G_UnlaggedDetectCollisions( gentity_t *ent )    trap_Trace(&tr, ent->client->oldOrigin, ent->r.mins, ent->r.maxs,      ent->client->ps.origin, ent->s.number,  MASK_PLAYERSOLID );    if( tr.entityNum >= 0 && tr.entityNum < MAX_CLIENTS ) -    g_entities[ tr.entityNum ].client->unlaggedCalc.used = qfalse;  +    g_entities[ tr.entityNum ].client->unlaggedCalc.used = qfalse;    G_UnlaggedOff( );  }  /*  ============== -ClientGradualFunds - -g_gradualFreeFunds values: -0 - disabled -1 - vanilla behavior -2 - 1 and counters don't reset on death or evolution -3 - 2 and funds are given even during SD  -============== -*/ -static void ClientGradualFunds( gentity_t *ent ) -{ -  if( !g_gradualFreeFunds.integer ) -    return; - -  if( ent->client->pers.lastFreekillTime + FREEKILL_PERIOD >= level.time ) -    return; - -  if( g_suddenDeath.integer && g_gradualFreeFunds.integer < 3 ) -    return; - -  switch ( ent->client->ps.stats[ STAT_PTEAM ] ) -  { -  case PTE_ALIENS: -    G_AddCreditToClient( ent->client, FREEKILL_ALIEN, qtrue ); -    break; - -  case PTE_HUMANS: -    G_AddCreditToClient( ent->client, FREEKILL_HUMAN, qtrue ); -    break; -  } - -  ent->client->pers.lastFreekillTime += FREEKILL_PERIOD; -} - -/* -==============  ClientThink  This will be called once for each client frame, which will @@ -1459,7 +1243,6 @@ void ClientThink_real( gentity_t *ent )    int       oldEventSequence;    int       msec;    usercmd_t *ucmd; -  int       real_pm_type;    client = ent->client; @@ -1470,34 +1253,12 @@ void ClientThink_real( gentity_t *ent )    // mark the time, so the connection sprite can be removed    ucmd = &ent->client->pers.cmd; -  if( client->pers.paused ) -    ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = ucmd->buttons = 0; -    // sanity check the command time to prevent speedup cheating    if( ucmd->serverTime > level.time + 200 ) -  {      ucmd->serverTime = level.time + 200; -//    G_Printf("serverTime <<<<<\n" ); -  }    if( ucmd->serverTime < level.time - 1000 ) -  {      ucmd->serverTime = level.time - 1000; -//    G_Printf("serverTime >>>>>\n" ); -  } - -  // ucmd->serverTime is a client predicted value, but it works for making a -  // replacement for client->ps.ping when in SPECTATOR_FOLLOW  -  client->pers.ping = level.time - ucmd->serverTime; - -  // account for the one frame of delay on client side -  client->pers.ping -= level.time - level.previousTime; - -  // account for the time that's elapsed since the last ClientEndFrame() -  client->pers.ping += trap_Milliseconds( ) - level.frameMsec; - -  if( client->pers.ping < 0 ) -    client->pers.ping = 0;    msec = ucmd->serverTime - client->ps.commandTime;    // following others may result in bad times, but we still want @@ -1511,9 +1272,15 @@ void ClientThink_real( gentity_t *ent )    client->unlaggedTime = ucmd->serverTime;    if( pmove_msec.integer < 8 ) +  {      trap_Cvar_Set( "pmove_msec", "8" ); +    trap_Cvar_Update(&pmove_msec); +  }    else if( pmove_msec.integer > 33 ) +  {      trap_Cvar_Set( "pmove_msec", "33" ); +    trap_Cvar_Update(&pmove_msec); +  }    if( pmove_fixed.integer || client->pers.pmoveFixed )    { @@ -1527,21 +1294,12 @@ void ClientThink_real( gentity_t *ent )    //    if( level.intermissiontime )    { -    if( level.mapRotationVoteTime ) -    { -      SpectatorThink( ent, ucmd ); -      return; -    } -      ClientIntermissionThink( client );      return;    } -  if( client->pers.teamSelection != PTE_NONE && client->pers.joinedATeam ) -    G_UpdatePTRConnection( client ); -    // spectators don't do much -  if( client->sess.sessionTeam == TEAM_SPECTATOR ) +  if( client->sess.spectatorState != SPECTATOR_NOT )    {      if( client->sess.spectatorState == SPECTATOR_SCOREBOARD )        return; @@ -1550,20 +1308,19 @@ void ClientThink_real( gentity_t *ent )      return;    } +  G_namelog_update_score( client ); +    // check for inactivity timer, but never drop the local client of a non-dedicated server -  if( !ClientInactivityTimer( client ) ) +  if( !ClientInactivityTimer( ent ) )      return; -  // calculate where ent is currently seeing all the other active clients  +  // calculate where ent is currently seeing all the other active clients    G_UnlaggedCalc( ent->client->unlaggedTime, ent );    if( client->noclip )      client->ps.pm_type = PM_NOCLIP;    else if( client->ps.stats[ STAT_HEALTH ] <= 0 )      client->ps.pm_type = PM_DEAD; -  else if( client->ps.stats[ STAT_STATE ] & SS_INFESTING || -           client->ps.stats[ STAT_STATE ] & SS_HOVELING ) -    client->ps.pm_type = PM_FREEZE;    else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ||             client->ps.stats[ STAT_STATE ] & SS_GRABBED )      client->ps.pm_type = PM_GRABBED; @@ -1572,31 +1329,33 @@ void ClientThink_real( gentity_t *ent )    else      client->ps.pm_type = PM_NORMAL; -  // paused -  real_pm_type = client->ps.pm_type; -  if ( level.paused ) client->ps.pm_type = PM_SPECTATOR; - -  if( client->ps.stats[ STAT_STATE ] & SS_GRABBED && +  if( ( client->ps.stats[ STAT_STATE ] & SS_GRABBED ) &&        client->grabExpiryTime < level.time )      client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED; -  if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED && +  if( ( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) &&        client->lastLockTime + LOCKBLOB_LOCKTIME < level.time )      client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED; -  if( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED && +  if( ( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED ) &&        client->lastSlowTime + ABUILDER_BLOB_TIME < level.time )      client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED; -  client->ps.stats[ STAT_BOOSTTIME ] = level.time - client->lastBoostedTime; - -  if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED && -      client->lastBoostedTime + BOOST_TIME < level.time ) -    client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED; +  // Update boosted state flags +  client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTEDWARNING; +  if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) +  { +    if( level.time - client->boostedTime >= BOOST_TIME ) +      client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED; +    else if( level.time - client->boostedTime >= BOOST_WARN_TIME ) +      client->ps.stats[ STAT_STATE ] |= SS_BOOSTEDWARNING; +  } -  if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED && -      client->lastPoisonCloudedTime + LEVEL1_PCLOUD_TIME < level.time ) -    client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED; +  // Check if poison cloud has worn off +  if( ( client->ps.eFlags & EF_POISONCLOUDED ) && +      BG_PlayerPoisonCloudTime( &client->ps ) - level.time + +      client->lastPoisonCloudedTime <= 0 ) +    client->ps.eFlags &= ~EF_POISONCLOUDED;    if( client->ps.stats[ STAT_STATE ] & SS_POISONED &&        client->lastPoisonTime + ALIEN_POISON_TIME < level.time ) @@ -1608,13 +1367,13 @@ void ClientThink_real( gentity_t *ent )        BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) )    {      //if currently using a medkit or have no need for a medkit now -    if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE || +    if( client->ps.stats[ STAT_STATE ] & SS_HEALING_2X ||          ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] &&            !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) )      {        BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats );      } -    else if( client->ps.stats[ STAT_HEALTH ] > 0 && !level.paused ) +    else if( client->ps.stats[ STAT_HEALTH ] > 0 )      {        //remove anti toxin        BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); @@ -1623,7 +1382,7 @@ void ClientThink_real( gentity_t *ent )        client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;        client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME; -      client->ps.stats[ STAT_STATE ] |= SS_MEDKIT_ACTIVE; +      client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X;        client->lastMedKitTime = level.time;        client->medKitHealthToRestore =          client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ]; @@ -1634,6 +1393,102 @@ void ClientThink_real( gentity_t *ent )      }    } +  // Replenish alien health +  if( level.surrenderTeam != client->pers.teamSelection && +      ent->nextRegenTime >= 0 && ent->nextRegenTime < level.time ) +  { +    float regenRate = +        BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->regenRate; + +    if( ent->health <= 0 || ent->nextRegenTime < 0 || regenRate == 0 ) +      ent->nextRegenTime = -1; // no regen +    else +    { +      int       entityList[ MAX_GENTITIES ]; +      int       i, num; +      int       count, interval; +      vec3_t    range, mins, maxs; +      float     modifier = 1.0f; + +      VectorSet( range, REGEN_BOOST_RANGE, REGEN_BOOST_RANGE, +                 REGEN_BOOST_RANGE ); +      VectorAdd( client->ps.origin, range, maxs ); +      VectorSubtract( client->ps.origin, range, mins ); + +      num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); +      for( i = 0; i < num; i++ ) +      { +        gentity_t *boost = &g_entities[ entityList[ i ] ]; + +        if( Distance( client->ps.origin, boost->r.currentOrigin ) > REGEN_BOOST_RANGE ) +          continue; + +        if( modifier < BOOSTER_REGEN_MOD && boost->s.eType == ET_BUILDABLE && +            boost->s.modelindex == BA_A_BOOSTER && boost->spawned && +            boost->health > 0 && boost->powered ) +        { +          modifier = BOOSTER_REGEN_MOD; +          continue; +        } + +        if( boost->s.eType == ET_PLAYER && boost->client && +            boost->client->pers.teamSelection == +              ent->client->pers.teamSelection && boost->health > 0 ) +        { +          class_t class = boost->client->ps.stats[ STAT_CLASS ]; +          qboolean didBoost = qfalse; + +          if( class == PCL_ALIEN_LEVEL1 && modifier < LEVEL1_REGEN_MOD ) +          { +            modifier = LEVEL1_REGEN_MOD; +            didBoost = qtrue; +          } +          else if( class == PCL_ALIEN_LEVEL1_UPG && +                   modifier < LEVEL1_UPG_REGEN_MOD ) +          { +            modifier = LEVEL1_UPG_REGEN_MOD; +            didBoost = qtrue; +          } + +          if( didBoost && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] ) +            boost->client->pers.hasHealed = qtrue; +        } +      } + +      // Transmit heal rate to the client so it can be displayed on the HUD +      client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; +      client->ps.stats[ STAT_STATE ] &= ~( SS_HEALING_2X | SS_HEALING_3X ); +      if( modifier == 1.0f && !G_FindCreep( ent ) ) +      { +        client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE; +        modifier *= ALIEN_REGEN_NOCREEP_MOD; +      } +      else if( modifier >= 3.0f ) +        client->ps.stats[ STAT_STATE ] |= SS_HEALING_3X; +      else if( modifier >= 2.0f ) +        client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X; + +      interval = 1000 / ( regenRate * modifier ); +      // if recovery interval is less than frametime, compensate +      count = 1 + ( level.time - ent->nextRegenTime ) / interval; +      ent->nextRegenTime += count * interval; + +      if( ent->health < client->ps.stats[ STAT_MAX_HEALTH ] ) +      { +        ent->health += count; +        client->ps.stats[ STAT_HEALTH ] = ent->health; + +        // if at max health, clear damage counters +        if( ent->health >= client->ps.stats[ STAT_MAX_HEALTH ] ) +        { +          ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; +          for( i = 0; i < MAX_CLIENTS; i++ ) +            ent->credits[ i ] = 0; +        } +      } +    } +  } +    if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) &&        BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) )    { @@ -1650,10 +1505,11 @@ void ClientThink_real( gentity_t *ent )    }    // set speed -  client->ps.speed = g_speed.value * BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] ); - -  if( client->pers.paused ) -    client->ps.speed = 0; +  if( client->ps.pm_type == PM_NOCLIP ) +    client->ps.speed = client->pers.flySpeed; +  else +    client->ps.speed = g_speed.value * +        BG_Class( client->ps.stats[ STAT_CLASS ] )->speed;    if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time )      client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED; @@ -1669,7 +1525,7 @@ void ClientThink_real( gentity_t *ent )      }      //switch jetpack off if no reactor -    if( !level.reactorPresent ) +    if( !G_Reactor( ) )        BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats );    } @@ -1678,50 +1534,19 @@ void ClientThink_real( gentity_t *ent )    memset( &pm, 0, sizeof( pm ) ); -  if( !( ucmd->buttons & BUTTON_TALK ) && !( client->ps.pm_flags & PMF_RESPAWNED ) ) -  { -    switch( client->ps.weapon ) -    { -      case WP_ALEVEL0: -        if( client->ps.weaponTime <= 0 ) -          pm.autoWeaponHit[ client->ps.weapon ] = CheckVenomAttack( ent ); -        break; - -      case WP_ALEVEL1: -      case WP_ALEVEL1_UPG: -        CheckGrabAttack( ent ); -        break; - -      case WP_ALEVEL3: -      case WP_ALEVEL3_UPG: -        if( client->ps.weaponTime <= 0 ) -          pm.autoWeaponHit[ client->ps.weapon ] = CheckPounceAttack( ent ); -        break; - -      default: -        break; -    } -  } -    if( ent->flags & FL_FORCE_GESTURE )    {      ent->flags &= ~FL_FORCE_GESTURE;      ent->client->pers.cmd.buttons |= BUTTON_GESTURE;    } +   +  // clear fall velocity before every pmove +  client->pmext.fallVelocity = 0.0f;    pm.ps = &client->ps;    pm.pmext = &client->pmext;    pm.cmd = *ucmd; - -  if( pm.ps->pm_type == PM_DEAD ) -    pm.tracemask = MASK_PLAYERSOLID; // & ~CONTENTS_BODY; - -  if( pm.ps->stats[ STAT_STATE ] & SS_INFESTING || -      pm.ps->stats[ STAT_STATE ] & SS_HOVELING ) -    pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; -  else -    pm.tracemask = MASK_PLAYERSOLID; - +  pm.tracemask = ent->clipmask;    pm.trace = trap_Trace;    pm.pointcontents = trap_PointContents;    pm.debugLevel = g_debugMove.integer; @@ -1734,8 +1559,7 @@ void ClientThink_real( gentity_t *ent )    // moved from after Pmove -- potentially the cause of    // future triggering bugs -  if( !ent->client->noclip ) -    G_TouchTriggers( ent ); +  G_TouchTriggers( ent );    Pmove( &pm ); @@ -1745,13 +1569,61 @@ void ClientThink_real( gentity_t *ent )    if( ent->client->ps.eventSequence != oldEventSequence )      ent->eventTime = level.time; -  if ( level.paused ) client->ps.pm_type = real_pm_type; - +  VectorCopy( ent->client->ps.viewangles, ent->r.currentAngles );    if( g_smoothClients.integer )      BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );    else      BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); +  switch( client->ps.weapon ) +  { +    case WP_ALEVEL0: +      if( !CheckVenomAttack( ent ) ) +      { +        client->ps.weaponstate = WEAPON_READY; +      } +      else +      { +        client->ps.generic1 = WPM_PRIMARY; +        G_AddEvent( ent, EV_FIRE_WEAPON, 0 ); +      } +      break; + +    case WP_ALEVEL1: +    case WP_ALEVEL1_UPG: +      CheckGrabAttack( ent ); +      break; + +    case WP_ALEVEL3: +    case WP_ALEVEL3_UPG: +      if( !CheckPounceAttack( ent ) ) +      { +        client->ps.weaponstate = WEAPON_READY; +      } +      else +      { +        client->ps.generic1 = WPM_SECONDARY; +        G_AddEvent( ent, EV_FIRE_WEAPON2, 0 ); +      } +      break; + +    case WP_ALEVEL4: +      // If not currently in a trample, reset the trample bookkeeping data +      if( !( client->ps.pm_flags & PMF_CHARGE ) && client->trampleBuildablesHitPos ) +      { +        ent->client->trampleBuildablesHitPos = 0; +        memset( ent->client->trampleBuildablesHit, 0, sizeof( ent->client->trampleBuildablesHit ) ); +      } +      break; + +    case WP_HBUILD: +      CheckCkitRepair( ent ); +      break; + +    default: +      break; +  } +    SendPendingPredictableEvents( &ent->client->ps );    if( !( ent->client->ps.eFlags & EF_FIRING ) ) @@ -1770,7 +1642,7 @@ void ClientThink_real( gentity_t *ent )    // touch other objects    ClientImpacts( ent, &pm ); -   +    // execute client events    ClientEvents( ent, oldEventSequence ); @@ -1779,12 +1651,12 @@ void ClientThink_real( gentity_t *ent )    // NOTE: now copy the exact origin over otherwise clients can be snapped into solid    VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); -  VectorCopy( ent->client->ps.origin, ent->s.origin ); +  VectorCopy( ent->client->ps.origin, ent->s.pos.trBase );    // save results of triggers and client events    if( ent->client->ps.eventSequence != oldEventSequence )      ent->eventTime = level.time; -   +    // Don't think anymore if dead    if( client->ps.stats[ STAT_HEALTH ] <= 0 )      return; @@ -1794,121 +1666,84 @@ void ClientThink_real( gentity_t *ent )    client->buttons = ucmd->buttons;    client->latched_buttons |= client->buttons & ~client->oldbuttons; -  if( ( client->buttons & BUTTON_GETFLAG ) && !( client->oldbuttons & BUTTON_GETFLAG ) && +  if( ( client->buttons & BUTTON_USE_EVOLVE ) && !( client->oldbuttons & BUTTON_USE_EVOLVE ) &&         client->ps.stats[ STAT_HEALTH ] > 0 )    {      trace_t   trace;      vec3_t    view, point;      gentity_t *traceEnt; -    if( client->ps.stats[ STAT_STATE ] & SS_HOVELING ) -    { -      gentity_t *hovel = client->hovel; +#define USE_OBJECT_RANGE 64 -      //only let the player out if there is room -      if( !AHovel_Blocked( hovel, ent, qtrue ) ) -      { -        //prevent lerping -        client->ps.eFlags ^= EF_TELEPORT_BIT; -        client->ps.eFlags &= ~EF_NODRAW; -        G_UnlaggedClear( ent ); +    int       entityList[ MAX_GENTITIES ]; +    vec3_t    range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; +    vec3_t    mins, maxs; +    int       i, num; -        //client leaves hovel -        client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; +    // look for object infront of player +    AngleVectors( client->ps.viewangles, view, NULL, NULL ); +    VectorMA( client->ps.origin, USE_OBJECT_RANGE, view, point ); +    trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); -        //hovel is empty -        G_SetBuildableAnim( hovel, BANIM_ATTACK2, qfalse ); -        hovel->active = qfalse; -      } -      else -      { -        //exit is blocked -        G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_BLOCKED ); -      } -    } +    traceEnt = &g_entities[ trace.entityNum ]; + +    if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) +      traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context      else      { -#define USE_OBJECT_RANGE 64 - -      int       entityList[ MAX_GENTITIES ]; -      vec3_t    range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; -      vec3_t    mins, maxs; -      int       i, num; +      //no entity in front of player - do a small area search -      //TA: look for object infront of player -      AngleVectors( client->ps.viewangles, view, NULL, NULL ); -      VectorMA( client->ps.origin, USE_OBJECT_RANGE, view, point ); -      trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); - -      traceEnt = &g_entities[ trace.entityNum ]; +      VectorAdd( client->ps.origin, range, maxs ); +      VectorSubtract( client->ps.origin, range, mins ); -      if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use ) -        traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context -      else +      num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); +      for( i = 0; i < num; i++ )        { -        //no entity in front of player - do a small area search - -        VectorAdd( client->ps.origin, range, maxs ); -        VectorSubtract( client->ps.origin, range, mins ); +        traceEnt = &g_entities[ entityList[ i ] ]; -        num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); -        for( i = 0; i < num; i++ ) +        if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use )          { -          traceEnt = &g_entities[ entityList[ i ] ]; - -          if( traceEnt && traceEnt->biteam == client->ps.stats[ STAT_PTEAM ] && traceEnt->use ) -          { -            traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context -            break; -          } +          traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context +          break;          } +      } -        if( i == num && client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) +      if( i == num && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) +      { +        if( BG_AlienCanEvolve( client->ps.stats[ STAT_CLASS ], +                               client->pers.credit, +                               g_alienStage.integer ) )          { -          if( BG_UpgradeClassAvailable( &client->ps ) ) -          { -            //no nearby objects and alien - show class menu -            G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); -          } -          else -          { -            //flash frags -            G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); -          } +          //no nearby objects and alien - show class menu +          G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); +        } +        else +        { +          //flash frags +          G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 );          }        }      }    } -  if( level.framenum > client->retriggerArmouryMenu && client->retriggerArmouryMenu ) -  { -    G_TriggerMenu( client->ps.clientNum, MN_H_ARMOURY ); - -    client->retriggerArmouryMenu = 0; -  } +  client->ps.persistant[ PERS_BP ] = G_GetBuildPoints( client->ps.origin, +    client->ps.stats[ STAT_TEAM ] ); +  client->ps.persistant[ PERS_MARKEDBP ] = G_GetMarkedBuildPoints( client->ps.origin, +    client->ps.stats[ STAT_TEAM ] ); -  // Give clients some credit periodically -  ClientGradualFunds( ent ); +  if( client->ps.persistant[ PERS_BP ] < 0 ) +    client->ps.persistant[ PERS_BP ] = 0;    // perform once-a-second actions    ClientTimerActions( ent, msec ); -   +    if( ent->suicideTime > 0 && ent->suicideTime < level.time )    { -    ent->flags &= ~FL_GODMODE;      ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0;      player_die( ent, ent, ent, 100000, MOD_SUICIDE );      ent->suicideTime = 0;    } -  if( client->pers.bubbleTime && client->pers.bubbleTime < level.time ) -  { -    gentity_t *bubble; - -    client->pers.bubbleTime = level.time + 500; -    bubble = G_TempEntity( client->ps.origin, EV_PLAYER_TELEPORT_OUT ); -    bubble->s.clientNum = ent->s.clientNum; -  }  }  /* @@ -1953,52 +1788,30 @@ SpectatorClientEndFrame  void SpectatorClientEndFrame( gentity_t *ent )  {    gclient_t *cl; -  int       clientNum, flags; +  int       clientNum;    int       score, ping; -  vec3_t   spawn_origin, spawn_angles;    // if we are doing a chase cam or a remote view, grab the latest info    if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )    {      clientNum = ent->client->sess.spectatorClient; - -    if( clientNum >= 0 ) +    if( clientNum >= 0 && clientNum < level.maxclients )      {        cl = &level.clients[ clientNum ]; -        if( cl->pers.connected == CON_CONNECTED )        { -  -    if( cl -> sess.spectatorState != SPECTATOR_FOLLOW )  -    { -          flags = ( cl->ps.eFlags & ~( EF_VOTED | EF_TEAMVOTED ) ) | -            ( ent->client->ps.eFlags & ( EF_VOTED | EF_TEAMVOTED ) ); -          score = ent->client->ps.persistant[ PERS_SCORE ]; -          ping = ent->client->ps.ping; -          ent->client->ps = cl->ps; -          ent->client->ps.persistant[ PERS_SCORE ] = score; -          ent->client->ps.ping = ping; -          ent->client->ps.eFlags = flags; -          ent->client->ps.pm_flags |= PMF_FOLLOW; -          ent->client->ps.pm_flags &= ~PMF_QUEUED; -    } -    else //we are stickyspec-spectating someone who is spectating someone else -    { -      ent->client->ps.clientNum = (g_entities + clientNum)->s.number; -      ent->client->ps.commandTime = cl->ps.commandTime; -      ent->client->ps.weapon = 0; -      ent->client->ps.pm_flags |= PMF_FOLLOW; -      ent->client->ps.stats[ STAT_PCLASS ] = PCL_NONE; - -      if( cl->pers.teamSelection == PTE_ALIENS ) -        G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); -          else if( cl->pers.teamSelection == PTE_HUMANS ) -        G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); - -          G_SetOrigin( ent, spawn_origin ); -          VectorCopy( spawn_origin, ent->client->ps.origin ); -          G_SetClientViewAngle( ent, spawn_angles ); -    } +        score = ent->client->ps.persistant[ PERS_SCORE ]; +        ping = ent->client->ps.ping; + +        // Copy +        ent->client->ps = cl->ps; + +        // Restore +        ent->client->ps.persistant[ PERS_SCORE ] = score; +        ent->client->ps.ping = ping; + +        ent->client->ps.pm_flags |= PMF_FOLLOW; +        ent->client->ps.pm_flags &= ~PMF_QUEUED;        }      }    } @@ -2015,20 +1828,12 @@ while a slow client may have multiple ClientEndFrame between ClientThink.  */  void ClientEndFrame( gentity_t *ent )  { -  clientPersistant_t  *pers; - -  if( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) +  if( ent->client->sess.spectatorState != SPECTATOR_NOT )    {      SpectatorClientEndFrame( ent );      return;    } -  pers = &ent->client->pers; - -  // save a copy of certain playerState values in case of SPECTATOR_FOLLOW  -  pers->score = ent->client->ps.persistant[ PERS_SCORE ]; -  pers->credit = ent->client->ps.persistant[ PERS_CREDIT ]; -    //    // If the end of unit layout is displayed, don't give    // the player any normal movement attributes @@ -2048,8 +1853,6 @@ void ClientEndFrame( gentity_t *ent )    else      ent->s.eFlags &= ~EF_CONNECTION; -  ent->client->ps.stats[ STAT_HEALTH ] = ent->health; // FIXME: get rid of ent->health... -      // respawn if dead    if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 && level.time >= ent->client->respawnTime )      respawn( ent ); @@ -2064,5 +1867,3 @@ void ClientEndFrame( gentity_t *ent )    SendPendingPredictableEvents( &ent->client->ps );  } - - diff --git a/src/game/g_admin.c b/src/game/g_admin.c index dc19530..afc40cb 100644 --- a/src/game/g_admin.c +++ b/src/game/g_admin.c @@ -1,6 +1,7 @@  /*  =========================================================================== -Copyright (C) 2004-2006 Tony J. White +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous. @@ -11,12 +12,12 @@ and Travis Maurer.  The functionality of this code mimics the behaviour of the currently  inactive project shrubet (http://www.etstats.com/shrubet/index.php?ver=2) -by Ryan Mannion.   However, shrubet was a closed-source project and  -none of it's code has been copied, only it's functionality. +by Ryan Mannion.   However, shrubet was a closed-source project and +none of its code has been copied, only its functionality.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -25,8 +26,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -36,420 +37,270 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  static char g_bfb[ 32000 ];  // note: list ordered alphabetically -g_admin_cmd_t g_admin_cmds[ ] =  -  { -    {"adjustban", G_admin_adjustban, "ban", -      "change the duration or reason of a ban.  time is specified as numbers " -      "followed by units 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes)," -      " or seconds if no units are specified. if the duration is" -      " preceded by a + or -, the ban duration will be extended or shortened by" -      " the specified amount", -      "[^3ban#^7] (^5duration^7) (^5reason^7)" +g_admin_cmd_t g_admin_cmds[ ] = +  { +    {"addlayout", G_admin_addlayout, qfalse, "addlayout", +      "place layout elements into the game. the elements are specified by a " +      "union of filtered layouts. the syntax is demonstrated by an example: " +      "^5reactor,telenode|westside+alien|sewers^7 will place only the " +      "reactor and telenodes from the westside layout, and also all alien " +      "layout elements from the sewers layout", +      "[^3layoutelements^7]" +    }, + +    {"adjustban", G_admin_adjustban, qfalse, "ban", +      "change the IP address mask, duration or reason of a ban.  mask is " +      "prefixed with '/'.  duration is specified as numbers followed by units " +      " 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes), or seconds if " +      " no unit is specified.  if the duration is preceded by a + or -, the " +      "ban duration will be extended or shortened by the specified amount", +      "[^3ban#^7] (^5/mask^7) (^5duration^7) (^5reason^7)"      }, -     -    {"adminlog", G_admin_adminlog, "adminlog", -      "list recent admin activity", -      "(^5start id#|name|!command|-skip#^7) (^5search skip#^7)" + +    {"adminhelp", G_admin_adminhelp, qtrue, "adminhelp", +      "display admin commands available to you or help on a specific command", +      "(^5command^7)"      }, -    {"admintest", G_admin_admintest, "admintest", +    {"admintest", G_admin_admintest, qfalse, "admintest",        "display your current admin level",        ""      }, -    {"allowbuild", G_admin_denybuild, "denybuild", +    {"allowbuild", G_admin_denybuild, qfalse, "denybuild",        "restore a player's ability to build",        "[^3name|slot#^7]"      }, -    {"allowweapon", G_admin_denyweapon, "denyweapon", -      "restore a player's ability to use a weapon or class", -      "[^3name|slot#^7] [^3class|weapon|all^7]" -    }, -     -    {"allready", G_admin_allready, "allready", +    {"allready", G_admin_allready, qfalse, "allready",        "makes everyone ready in intermission",        ""      }, -    {"ban", G_admin_ban, "ban", +    {"ban", G_admin_ban, qfalse, "ban",        "ban a player by IP and GUID with an optional expiration time and reason." -      "  time is specified as numbers followed by units 'w' (weeks), 'd' " +      " duration is specified as numbers followed by units 'w' (weeks), 'd' "        "(days), 'h' (hours) or 'm' (minutes), or seconds if no units are "        "specified", -      "[^3name|slot#|IP^7] (^5time^7) (^5reason^7)" -    }, - -    {"buildlog", G_admin_buildlog, "buildlog", -      "display a list of recent builds and deconstructs, optionally specifying" -      " a team", -      "(^5xnum^7) (^5#skip^7) (^5-name|num^7) (^5a|h^7)" -      "\n ^3Example:^7 '!buildlog #10 h' skips 10 events, then shows the previous 10 events affecting human buildables" +      "[^3name|slot#|IP(/mask)^7] (^5duration^7) (^5reason^7)"      }, -    {"cancelvote", G_admin_cancelvote, "cancelvote", -      "cancel a vote taking place", +    {"builder", G_admin_builder, qtrue, "builder", +      "show who built a structure",        ""      }, -     -    {"cp", G_admin_cp, "cp", -      "display a CP message to users, optionally specifying team(s) to send to", -      "(-AHS) [^3message^7]" -    }, -    {"decon", G_admin_decon, "decon", -      "Reverts a decon previously made and ban the user for the time specified in g_deconBanTime", -      "[^3name|slot#^7]" -    }, - -    {"demo", G_admin_demo, "demo", -      "turn admin chat off for the caller so it does not appear in demos. " -      "this is a toggle use !demo again to turn warnings back on", -      "" -    }, - -    {"denybuild", G_admin_denybuild, "denybuild", -      "take away a player's ability to build", -      "[^3name|slot#^7]" +    {"buildlog", G_admin_buildlog, qfalse, "buildlog", +      "show buildable log", +      "(^5name|slot#^7) (^5id^7)"      }, -    {"designate", G_admin_designate, "designate", -      "give the player designated builder privileges", -      "[^3name|slot#^7]" +    {"cancelvote", G_admin_endvote, qfalse, "cancelvote", +      "cancel a vote taking place", +      "(^5a|h^7)"      }, -    {"devmap", G_admin_devmap, "devmap", -      "load a map with cheats (and optionally force layout)", +    {"changemap", G_admin_changemap, qfalse, "changemap", +      "load a map (and optionally force layout)",        "[^3mapname^7] (^5layout^7)"      }, -    {"denyweapon", G_admin_denyweapon, "denyweapon", -      "take away a player's ability to use a weapon or class", -      "[^3name|slot#^7] [^3class|weapon^7]" -    }, - -    {"drop", G_admin_drop, "drop", -      "kick a client from the server without log", -      "[^3name|slot#^7] [^3message^7]" -    }, - -    {"flag", G_admin_flag, "flag", -      "add an admin flag to a player, prefix flag with '-' to disallow the flag. " -      "console can use this command on admin levels by prefacing a '*' to the admin level value.", -      "[^3name|slot#|admin#|*adminlevel^7] (^5+^7|^5-^7)[^3flag^7]" -    }, - -    {"flaglist", G_admin_flaglist, "flag", -      "list all flags understood by this server", -      "" -    }, - -    {"help", G_admin_help, "help", -      "display commands available to you or help on a specific command", -      "(^5command^7)" +    {"denybuild", G_admin_denybuild, qfalse, "denybuild", +      "take away a player's ability to build", +      "[^3name|slot#^7]"      }, -    {"info", G_admin_info, "info", -      "display the contents of server info files", -      "(^5subject^7)" -    }, -     -    {"invisible", G_admin_invisible, "invisible", -      "hides a player so they cannot be seen in playerlists", -      "" -    }, -     -    {"kick", G_admin_kick, "kick", +    {"kick", G_admin_kick, qfalse, "kick",        "kick a player with an optional reason",        "[^3name|slot#^7] (^5reason^7)"      }, -     -    {"L0", G_admin_L0, "l0", -      "Sets a level 1 to level 0", -      "[^3name|slot#^7]" -    }, -     -    {"L1", G_admin_L1, "l1", -      "Sets a level 0 to level 1", -      "[^3name|slot#^7]" -    }, -     -    {"layoutsave", G_admin_layoutsave, "layoutsave", -      "save a map layout", -      "[^3mapname^7]" -    }, -     -    {"listadmins", G_admin_listadmins, "listadmins", + +    {"listadmins", G_admin_listadmins, qtrue, "listadmins",        "display a list of all server admins and their levels", -      "(^5name|start admin#^7) (^5minimum level to display^7)" +      "(^5name^7) (^5start admin#^7)"      }, -     -    {"listlayouts", G_admin_listlayouts, "listlayouts", + +    {"listlayouts", G_admin_listlayouts, qtrue, "listlayouts",        "display a list of all available layouts for a map",        "(^5mapname^7)"      }, -    {"listplayers", G_admin_listplayers, "listplayers", +    {"listplayers", G_admin_listplayers, qtrue, "listplayers",        "display a list of players, their client numbers and their levels",        ""      }, -     -    {"listmaps", G_admin_listmaps, "listmaps", -      "display a list of available maps on the server", -      "(^5map name^7)" -    }, -     -    {"lock", G_admin_lock, "lock", + +    {"lock", G_admin_lock, qfalse, "lock",        "lock a team to prevent anyone from joining it",        "[^3a|h^7]"      }, -     -    {"map", G_admin_map, "map", -      "load a map (and optionally force layout)", -      "[^3mapname^7] (^5layout^7)" -    }, -    {"maplog", G_admin_maplog, "maplog", -      "show recently played maps", -      "" -    }, - -    {"mute", G_admin_mute, "mute", +    {"mute", G_admin_mute, qfalse, "mute",        "mute a player", -      "[^3name|slot#^7] (Duration)" +      "[^3name|slot#^7]"      }, -     -    {"namelog", G_admin_namelog, "namelog", + +    {"namelog", G_admin_namelog, qtrue, "namelog",        "display a list of names used by recently connected players", -      "(^5name^7)" +      "(^5name|IP(/mask)^7) (start namelog#)"      }, -    {"nextmap", G_admin_nextmap, "nextmap", +    {"nextmap", G_admin_nextmap, qfalse, "nextmap",        "go to the next map in the cycle",        ""      }, -    {"nobuild", G_admin_nobuild, "nobuild", -      "set nobuild markers to prevent players from building in an area", -      "(^5area^7) (^5height^7)" +    {"passvote", G_admin_endvote, qfalse, "passvote", +      "pass a vote currently taking place", +      "(^5a|h^7)"      }, -    {"passvote", G_admin_passvote, "passvote", -      "pass a vote currently taking place", +    {"pause", G_admin_pause, qfalse, "pause", +      "Pause (or unpause) the game.",        ""      }, -     -    {"pause", G_admin_pause, "pause", -      "prevent a player from interacting with the game." -      "  * will pause all players, using no argument will pause game clock", -      "(^5name|slot|*^7)" -    }, - -    {"putteam", G_admin_putteam, "putteam", +    {"putteam", G_admin_putteam, qfalse, "putteam",        "move a player to a specified team", -      "[^3name|slot#^7] [^3h|a|s^7] (^3duration^7)" +      "[^3name|slot#^7] [^3h|a|s^7]"      }, -    {"readconfig", G_admin_readconfig, "readconfig", +    {"readconfig", G_admin_readconfig, qfalse, "readconfig",        "reloads the admin config file and refreshes permission flags",        ""      }, -     -    {"register", G_admin_register, "register", -      "Registers your name to protect it from being used by others or updates your admin name to your current name.", -      "" -    }, -    {"rename", G_admin_rename, "rename", +    {"rename", G_admin_rename, qfalse, "rename",        "rename a player",        "[^3name|slot#^7] [^3new name^7]"      }, -    {"restart", G_admin_restart, "restart", +    {"restart", G_admin_restart, qfalse, "restart",        "restart the current map (optionally using named layout or keeping/switching teams)",        "(^5layout^7) (^5keepteams|switchteams|keepteamslock|switchteamslock^7)"      }, -    {"revert", G_admin_revert, "revert", -      "revert one or more buildlog events, optionally of only one team", -      "(^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)" -      "\n ^3Example:^7 '!revert x5 h' reverts the last 5 events affecting human buildables" +    {"revert", G_admin_revert, qfalse, "revert", +      "revert buildables to a given time", +      "[^3id^7]"      }, -    {"rotation", G_admin_listrotation, "rotation", -       "display a list of maps that are in the active map rotation", -       "" +    {"setdevmode", G_admin_setdevmode, qfalse, "setdevmode", +      "switch developer mode on or off", +      "[^3on|off^7]"      }, -    {"seen", G_admin_seen, "seen", -      "find the last time a player was on the server", -      "[^3name|admin#^7]" +    {"setivo", G_admin_setivo, qfalse, "setivo", +      "set an intermission view override", +      "[^3s|a|h^7]"      }, -    {"setlevel", G_admin_setlevel, "setlevel", +    {"setlevel", G_admin_setlevel, qfalse, "setlevel",        "sets the admin level of a player",        "[^3name|slot#|admin#^7] [^3level^7]"      }, -    {"showbans", G_admin_showbans, "showbans", -      "display a (partial) list of active bans", -      "(^5start at ban#^7) (^5name|IP|'-subnet'^7)" +    {"setnextmap", G_admin_setnextmap, qfalse, "setnextmap", +      "set the next map (and, optionally, a forced layout)", +      "[^3mapname^7] (^5layout^7)"      }, -    {"slap", G_admin_slap, "slap", -      "Do damage to a player, and send them flying", -      "[^3name|slot^7] (damage)" +    {"showbans", G_admin_showbans, qtrue, "showbans", +      "display a (partial) list of active bans", +      "(^5name|IP(/mask)^7) (^5start at ban#^7)"      }, -    {"spec999", G_admin_spec999, "spec999", +    {"spec999", G_admin_spec999, qfalse, "spec999",        "move 999 pingers to the spectator team", -      "" -    }, - -    {"specme", G_admin_putmespec, "specme", -      "moves you to the spectators", -      "" -    }, - -    {"subnetban", G_admin_subnetban, "subnetban", -      "Add or change a subnet mask on a ban", -      "[^3ban#^7] [^5CIDR mask^7]" -      "\n ^3Example:^7 '!subnetban 10 16' changes ban #10 to be a ban on XXX.XXX.*.*" -      "\n ^3Example:^7 '!subnetban 10 24' changes ban #10 to be a ban on XXX.XXX.XXX.*" -      "\n ^3Example:^7 '!subnetban 10 32' changes ban #10 to be a regular (non-subnet) ban" -      "\n ^1WARNING:^7 Use of this command may make your admin.dat incompatible with other game.qvms" -    }, - -    {"suspendban", G_admin_suspendban, "ban", -      "suspend a ban for a length of time. time is specified as numbers " -      "followed by units 'w' (weeks), 'd' (days), 'h' (hours) or 'm' (minutes)," -      " or seconds if no units are specified", -      "[^5ban #^7] [^5length^7]" -    }, +      ""}, -    {"time", G_admin_time, "time", +    {"time", G_admin_time, qtrue, "time",        "show the current local server time", -      "" +      ""}, +    {"transform", G_admin_transform, qfalse, "magic", +      "change a human player to a different player model", +      "[^3name|slot#^7] [^3player model^7]"      }, -    {"unban", G_admin_unban, "ban", +    {"unban", G_admin_unban, qfalse, "ban",        "unbans a player specified by the slot as seen in showbans",        "[^3ban#^7]"      }, -     -    {"undesignate", G_admin_designate, "designate", -      "revoke designated builder privileges", -      "[^3name|slot#^7]" -    }, -     -    {"unflag", G_admin_flag, "flag", -      "clears an admin flag from a player. " -      "console can use this command on admin levels by prefacing a '*' to the admin level value.", -      "[^3name|slot#|admin#|*adminlevel^7] (^5+^7|^5-^7)[^3flag^7]" -    }, -    {"unlock", G_admin_unlock, "lock", +    {"unlock", G_admin_lock, qfalse, "lock",        "unlock a locked team",        "[^3a|h^7]"      }, -     -    {"unmute", G_admin_mute, "mute", + +    {"unmute", G_admin_mute, qfalse, "mute",        "unmute a muted player",        "[^3name|slot#^7]"      }, -    {"unpause", G_admin_pause, "pause", -      "allow a player to interact with the game." -      "  * will unpause all players, using no argument will unpause game clock", -      "(^5name|slot|*^7)" -    }, - -    { -     "warn", G_admin_warn, "warn", -      "Warn a player to cease or face admin intervention", -      "[^3name|slot#^7] [reason]" -    }, - -    {"setdevmode", G_admin_setdevmode, "setdevmode", -      "switch developer mode on or off", -      "[^3on|off^7]" -    }, +    {"sm", G_admin_sm, qfalse, "schachtmeister", +      "Schachtmeister", +      "..." +    } +  }; -    {"hstage", G_admin_hstage, "stage", -      "change the stage for humans", -      "[^3#^7]" -    }, +static size_t adminNumCmds = ARRAY_LEN( g_admin_cmds ); -    {"astage", G_admin_astage, "stage", -      "change the stage for aliens", -      "[^3#^7]" -    }, +static int admin_level_maxname = 0; +g_admin_level_t *g_admin_levels = NULL; +g_admin_admin_t *g_admin_admins = NULL; +g_admin_ban_t *g_admin_bans = NULL; +g_admin_command_t *g_admin_commands = NULL; -    {"bubble", G_admin_bubble, "bubble", -      "continuously spawn bubbles around a player", -      "[^3name|slot#^7]" -    }, +void G_admin_register_cmds( void ) +{ +  int i; -    {"scrim", G_admin_scrim, "scrim", -       "toggles scrim mode", -       "[on|off]", -    }, +  for( i = 0; i < adminNumCmds; i++ ) +    trap_AddCommand( g_admin_cmds[ i ].keyword ); +} -    {"give", G_admin_give, "give", -      "give funds to a player", -      "[^3name|slot#^7] [^3amount^7]" -    }, +void G_admin_unregister_cmds( void ) +{ +  int i; -    {"setrotation", G_admin_setrotation, "setrotation", -      "sets the map rotation", -      "[^3rotation^7]" -    }, +  for( i = 0; i < adminNumCmds; i++ ) +    trap_RemoveCommand( g_admin_cmds[ i ].keyword ); +} -    {"versions", G_admin_versions, "namelog", -     "Check what versions of Tremulous players are running.", -     "" -    }, +void G_admin_cmdlist( gentity_t *ent ) +{ +  int   i; +  char  out[ MAX_STRING_CHARS ] = ""; +  int   len, outlen; -    {"showff", G_admin_showff, "showff", -      "shows how much friendly damage a player has done this game" -      "\nno arguments will list all connected players", -      "(^3name|slot^7)" -      "\n ^3Example:^7 ^120% ^7means 1/5th of the damage dealt this game was dealt to the team" -    }, +  outlen = 0; -    {"tklog", G_admin_tklog, "tklog", -      "list recent teamkill activity", -      "(^5start id#|name|-skip#^7) (^5search skip#^7)" -    }, +  for( i = 0; i < adminNumCmds; i++ ) +  { +    if( !G_admin_permission( ent, g_admin_cmds[ i ].flag ) ) +      continue; -    {"sm", G_admin_sm, "schachtmeister", -      "Schachtmeister", -      "..." +    len = strlen( g_admin_cmds[ i ].keyword ) + 1; +    if( len + outlen >= sizeof( out ) - 1 ) +    { +      trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); +      outlen = 0;      } -  }; - -static int adminNumCmds = sizeof( g_admin_cmds ) / sizeof( g_admin_cmds[ 0 ] ); - -static int admin_level_maxname = 0; -g_admin_level_t *g_admin_levels[ MAX_ADMIN_LEVELS ]; -g_admin_admin_t *g_admin_admins[ MAX_ADMIN_ADMINS ]; -g_admin_ban_t *g_admin_bans[ MAX_ADMIN_BANS ]; -g_admin_command_t *g_admin_commands[ MAX_ADMIN_COMMANDS ]; -g_admin_namelog_t *g_admin_namelog[ MAX_ADMIN_NAMELOGS ]; +    strcpy( out + outlen, va( " %s", g_admin_cmds[ i ].keyword ) ); +    outlen += len; +  } -int G_admin_parse_time( const char *time ); +  trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); +}  // match a certain flag within these flags -// return state of whether flag was found or not,  -// set *perm to indicate whether found flag was + or -  static qboolean admin_permission( char *flags, const char *flag, qboolean *perm )  {    char *token, *token_p = flags; -  qboolean all_found = qfalse; -  qboolean base_perm = qfalse; - +  qboolean allflags = qfalse; +  qboolean p = qfalse; +  *perm = qfalse;    while( *( token = COM_Parse( &token_p ) ) )    {      *perm = qtrue; @@ -459,63 +310,84 @@ static qboolean admin_permission( char *flags, const char *flag, qboolean *perm        return qtrue;      if( !strcmp( token, ADMF_ALLFLAGS ) )      { -      all_found = qtrue; -      base_perm = *perm; +      allflags = qtrue; +      p = *perm;      }    } +  if( allflags ) +    *perm = p; +  return allflags; +} + +g_admin_cmd_t *G_admin_cmd( const char *cmd ) +{ +  return bsearch( cmd, g_admin_cmds, adminNumCmds, sizeof( g_admin_cmd_t ), +    cmdcmp ); +} + +g_admin_level_t *G_admin_level( const int l ) +{ +  g_admin_level_t *level; -  if( all_found && flag[ 0 ] != '.' ) +  for( level = g_admin_levels; level; level = level->next )    { -    *perm = base_perm; -    return qtrue; +    if( level->level == l ) +      return level;    } -  return qfalse; +  return NULL;  } -static int admin_adminlog_index = 0; -g_admin_adminlog_t *g_admin_adminlog[ MAX_ADMIN_ADMINLOGS ]; - -static int admin_tklog_index = 0; -g_admin_tklog_t *g_admin_tklog[ MAX_ADMIN_TKLOGS ]; - -// This function should only be used directly when the client is connecting and thus has no GUID. -// Else, use G_admin_permission()  -qboolean G_admin_permission_guid( char *guid, const char* flag ) +g_admin_admin_t *G_admin_admin( const char *guid )  { -  int i; -  int l = 0; -  qboolean perm = qfalse; +  g_admin_admin_t *admin; -  // Does the admin specifically have this flag granted/denied to them,  -  // irrespective of their admin level? -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) +  for( admin = g_admin_admins; admin; admin = admin->next )    { -    if( !Q_stricmp( guid, g_admin_admins[ i ]->guid ) ) -    { -      if( admin_permission( g_admin_admins[ i ]->flags, flag, &perm ) ) -        return perm; -      l = g_admin_admins[ i ]->level; -      break; -    } +    if( !Q_stricmp( admin->guid, guid ) ) +      return admin;    } -  // If not, is this flag granted/denied for their admin level? -  for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) +  return NULL; +} + +g_admin_command_t *G_admin_command( const char *cmd ) +{ +  g_admin_command_t *c; + +  for( c = g_admin_commands; c; c = c->next )    { -    if( g_admin_levels[ i ]->level == l ) -      return admin_permission( g_admin_levels[ i ]->flags, flag, &perm ) && -        perm; +    if( !Q_stricmp( c->command, cmd ) ) +      return c;    } -  return qfalse; -} +  return NULL; +}  qboolean G_admin_permission( gentity_t *ent, const char *flag )  { -  if(!ent) return qtrue; //console always wins +  qboolean perm; +  g_admin_admin_t *a; +  g_admin_level_t *l; + +  // console always wins +  if( !ent ) +    return qtrue; + +  if( ( a = ent->client->pers.admin ) ) +  { +    if( admin_permission( a->flags, flag, &perm ) ) +      return perm; + +    l = G_admin_level( a->level ); +  } +  else +    l = G_admin_level( 0 ); + +  if( l ) +    return admin_permission( l->flags, flag, &perm ) && perm; -  return G_admin_permission_guid(ent->client->pers.guid, flag); +  return qfalse;  }  qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len ) @@ -524,125 +396,94 @@ qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len )    gclient_t *client;    char testName[ MAX_NAME_LENGTH ] = {""};    char name2[ MAX_NAME_LENGTH ] = {""}; +  g_admin_admin_t *admin;    int alphaCount = 0; -  G_SanitiseString( name, name2, sizeof( name2) ); +  G_SanitiseString( name, name2, sizeof( name2 ) ); -  if( !Q_stricmp( name2, "UnnamedPlayer" ) )  +  if( !strcmp( name2, "unnamedplayer" ) )      return qtrue; -  if( !Q_stricmp( name2, "console" ) ) +  if( !strcmp( name2, "console" ) ) +  { +    if( err && len > 0 ) +      Q_strncpyz( err, "The name 'console' is not allowed.", len ); +    return qfalse; +  } + +  G_DecolorString( name, testName, sizeof( testName ) ); +  if( isdigit( testName[ 0 ] ) )    { -    Q_strncpyz( err, va( "The name '%s^7' is invalid here", name2 ), -      len ); +    if( err && len > 0 ) +      Q_strncpyz( err, "Names cannot begin with numbers", len ); +    return qfalse; +  } + +  for( i = 0; testName[ i ]; i++) +  { +    if( isalpha( testName[ i ] ) ) +     alphaCount++; +  } + +  if( alphaCount == 0 ) +  { +    if( err && len > 0 ) +      Q_strncpyz( err, "Names must contain letters", len );      return qfalse;    }    for( i = 0; i < level.maxclients; i++ )    {      client = &level.clients[ i ]; -    if( client->pers.connected != CON_CONNECTING -      && client->pers.connected != CON_CONNECTED )  -    { +    if( client->pers.connected == CON_DISCONNECTED )        continue; -    }      // can rename ones self to the same name using different colors      if( i == ( ent - g_entities ) )        continue; -    G_SanitiseString( client->pers.netname, testName, sizeof( testName) ); -    if( !Q_stricmp( name2, testName ) ) +    G_SanitiseString( client->pers.netname, testName, sizeof( testName ) ); +    if( !strcmp( name2, testName ) )      { -      Q_strncpyz( err, va( "The name '%s^7' is already in use", name ), -        len ); +      if( err && len > 0 ) +        Com_sprintf( err, len, "The name '%s^7' is already in use", name );        return qfalse;      }    } -    -  if( Q_isdigit( name2[ 0 ] ) || name2[ 0 ] == '-' ) -  { -    Q_strncpyz( err, "Names cannot begin with a number or with a dash. Please choose another.", len ); -    return qfalse; -  } -   -  for( i = 0; name2[ i ] !='\0'; i++) -  { -    if( Q_isalpha( name2[ i ] ) ) -     alphaCount++; -    if( name2[ i ] == ' ' ) -    { -      if( name2[ i + 1 ] == '-' ) -      { -        Q_strncpyz( err, "Names cannot contain a - preceded by a space. Please choose another.", len ); -        return qfalse; -      } -    } -  } -   -  if( alphaCount == 0 )  +  for( admin = g_admin_admins; admin; admin = admin->next )    { -    Q_strncpyz( err, va( "The name '%s^7' does not include at least one letter. Please choose another.", name ), len ); -    return qfalse; -  } - -  if( !g_adminNameProtect.string[ 0 ] ) -    return qtrue; - -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) -  { -    if( g_admin_admins[ i ]->level < 1 ) +    if( admin->level < 1 )        continue; -    G_SanitiseString( g_admin_admins[ i ]->name, testName, sizeof( testName) ); -    if( !Q_stricmp( name2, testName ) && -      Q_stricmp( ent->client->pers.guid, g_admin_admins[ i ]->guid ) ) +    G_SanitiseString( admin->name, testName, sizeof( testName ) ); +    if( !strcmp( name2, testName ) && ent->client->pers.admin != admin )      { -      Q_strncpyz( err, va( "The name '%s^7' belongs to an admin. " -        "Please choose another.", name ), len ); +      if( err && len > 0 ) +        Com_sprintf( err, len, "The name '%s^7' belongs to an admin, " +                     "please use another name", name );        return qfalse;      }    }    return qtrue;  } -static qboolean admin_higher_guid( char *admin_guid, char *victim_guid ) +static qboolean admin_higher_admin( g_admin_admin_t *a, g_admin_admin_t *b )  { -  int i; -  int alevel = 0; -  int alevel2 = 0; - -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) -  { -    if( !Q_stricmp( admin_guid, g_admin_admins[ i ]->guid ) ) -    { -      alevel = g_admin_admins[ i ]->level; - -      // Novelty Levels should be equivelant to level 1 -      if( alevel > 9 ) -        alevel = 1; +  qboolean perm; -      break; -    } -  } -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) -  { -    if( !Q_stricmp( victim_guid, g_admin_admins[ i ]->guid ) ) -    { -      alevel2 = g_admin_admins[ i ]->level; +  if( !b ) +    return qtrue; -      // Novelty Levels should be equivelant to level 1 -      if( alevel2 > 9 ) -        alevel2 = 1; +  if( admin_permission( b->flags, ADMF_IMMUTABLE, &perm ) ) +    return !perm; -      if( alevel < alevel2 ) -        return qfalse; +  return b->level <= ( a ? a->level : 0 ); +} -      if( strstr( g_admin_admins[ i ]->flags, va( "%s", ADMF_IMMUTABLE ) ) ) -        return qfalse; -    } -  } -  return qtrue; +static qboolean admin_higher_guid( char *admin_guid, char *victim_guid ) +{ +  return admin_higher_admin( G_admin_admin( admin_guid ), +    G_admin_admin( victim_guid ) );  }  static qboolean admin_higher( gentity_t *admin, gentity_t *victim ) @@ -651,25 +492,15 @@ static qboolean admin_higher( gentity_t *admin, gentity_t *victim )    // console always wins    if( !admin )      return qtrue; -  // just in case -  if( !victim ) -    return qtrue; -  return admin_higher_guid( admin->client->pers.guid, -    victim->client->pers.guid ); +  return admin_higher_admin( admin->client->pers.admin, +    victim->client->pers.admin );  }  static void admin_writeconfig_string( char *s, fileHandle_t f )  { -  char buf[ MAX_STRING_CHARS ]; - -  buf[ 0 ] = '\0';    if( s[ 0 ] ) -  { -    //Q_strcat(buf, sizeof(buf), s); -    Q_strncpyz( buf, s, sizeof( buf ) ); -    trap_FS_Write( buf, strlen( buf ), f ); -  } +    trap_FS_Write( s, strlen( s ), f );    trap_FS_Write( "\n", 1, f );  } @@ -677,18 +508,18 @@ static void admin_writeconfig_int( int v, fileHandle_t f )  {    char buf[ 32 ]; -  Com_sprintf( buf, sizeof(buf), "%d", v ); -  if( buf[ 0 ] ) -    trap_FS_Write( buf, strlen( buf ), f ); -  trap_FS_Write( "\n", 1, f ); +  Com_sprintf( buf, sizeof( buf ), "%d\n", v ); +  trap_FS_Write( buf, strlen( buf ), f );  } -void admin_writeconfig( void ) +static void admin_writeconfig( void )  {    fileHandle_t f; -  int len, i; -  int t, expiretime; -  char levels[ MAX_STRING_CHARS ] = {""}; +  int t; +  g_admin_admin_t *a; +  g_admin_level_t *l; +  g_admin_ban_t *b; +  g_admin_command_t *c;    if( !g_admin.string[ 0 ] )    { @@ -697,96 +528,75 @@ void admin_writeconfig( void )      return;    }    t = trap_RealTime( NULL ); -  len = trap_FS_FOpenFile( g_admin.string, &f, FS_WRITE ); -  if( len < 0 ) +  if( trap_FS_FOpenFile( g_admin.string, &f, FS_WRITE ) < 0 )    {      G_Printf( "admin_writeconfig: could not open g_admin file \"%s\"\n",                g_admin.string );      return;    } -  for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) +  for( l = g_admin_levels; l; l = l->next )    {      trap_FS_Write( "[level]\n", 8, f );      trap_FS_Write( "level   = ", 10, f ); -    admin_writeconfig_int( g_admin_levels[ i ]->level, f ); +    admin_writeconfig_int( l->level, f );      trap_FS_Write( "name    = ", 10, f ); -    admin_writeconfig_string( g_admin_levels[ i ]->name, f ); +    admin_writeconfig_string( l->name, f );      trap_FS_Write( "flags   = ", 10, f ); -    admin_writeconfig_string( g_admin_levels[ i ]->flags, f ); +    admin_writeconfig_string( l->flags, f );      trap_FS_Write( "\n", 1, f );    } -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) +  for( a = g_admin_admins; a; a = a->next )    {      // don't write level 0 users -    if( g_admin_admins[ i ]->level < 1 ) +    if( a->level == 0 )        continue; -    //if set dont write admins that havent been seen in a while -    expiretime = G_admin_parse_time( g_adminExpireTime.string ); -    if( expiretime > 0 ) { -      //only expire level 1 people -      if( t - expiretime > g_admin_admins[ i ]->seen && g_admin_admins[ i ]->level == 1 ) { -        G_Printf("Admin %s has been expired.\n", g_admin_admins[ i ]->name ); -        continue; -      } -    } -          trap_FS_Write( "[admin]\n", 8, f );      trap_FS_Write( "name    = ", 10, f ); -    admin_writeconfig_string( g_admin_admins[ i ]->name, f ); +    admin_writeconfig_string( a->name, f );      trap_FS_Write( "guid    = ", 10, f ); -    admin_writeconfig_string( g_admin_admins[ i ]->guid, f ); +    admin_writeconfig_string( a->guid, f );      trap_FS_Write( "level   = ", 10, f ); -    admin_writeconfig_int( g_admin_admins[ i ]->level, f ); +    admin_writeconfig_int( a->level, f );      trap_FS_Write( "flags   = ", 10, f ); -    admin_writeconfig_string( g_admin_admins[ i ]->flags, f ); -    trap_FS_Write( "seen    = ", 10, f ); -    admin_writeconfig_int( g_admin_admins[ i ]->seen, f ); +    admin_writeconfig_string( a->flags, f );      trap_FS_Write( "\n", 1, f );    } -  for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) +  for( b = g_admin_bans; b; b = b->next )    {      // don't write expired bans      // if expires is 0, then it's a perm ban -    if( g_admin_bans[ i ]->expires != 0 && -         ( g_admin_bans[ i ]->expires - t ) < 1 ) +    if( b->expires != 0 && b->expires <= t )        continue;      trap_FS_Write( "[ban]\n", 6, f );      trap_FS_Write( "name    = ", 10, f ); -    admin_writeconfig_string( g_admin_bans[ i ]->name, f ); +    admin_writeconfig_string( b->name, f );      trap_FS_Write( "guid    = ", 10, f ); -    admin_writeconfig_string( g_admin_bans[ i ]->guid, f ); +    admin_writeconfig_string( b->guid, f );      trap_FS_Write( "ip      = ", 10, f ); -    admin_writeconfig_string( g_admin_bans[ i ]->ip, f ); +    admin_writeconfig_string( b->ip.str, f );      trap_FS_Write( "reason  = ", 10, f ); -    admin_writeconfig_string( g_admin_bans[ i ]->reason, f ); +    admin_writeconfig_string( b->reason, f );      trap_FS_Write( "made    = ", 10, f ); -    admin_writeconfig_string( g_admin_bans[ i ]->made, f ); +    admin_writeconfig_string( b->made, f );      trap_FS_Write( "expires = ", 10, f ); -    admin_writeconfig_int( g_admin_bans[ i ]->expires, f ); -    if( g_admin_bans[ i ]->suspend > t ) { -      trap_FS_Write( "suspend = ", 10, f ); -      admin_writeconfig_int( g_admin_bans[ i ]->suspend, f ); -    } +    admin_writeconfig_int( b->expires, f );      trap_FS_Write( "banner  = ", 10, f ); -    admin_writeconfig_string( g_admin_bans[ i ]->banner, f ); -    trap_FS_Write( "blevel  = ", 10, f ); -    admin_writeconfig_int( g_admin_bans[ i ]->bannerlevel, f ); +    admin_writeconfig_string( b->banner, f );      trap_FS_Write( "\n", 1, f );    } -  for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ ) +  for( c = g_admin_commands; c; c = c->next )    { -    levels[ 0 ] = '\0';      trap_FS_Write( "[command]\n", 10, f );      trap_FS_Write( "command = ", 10, f ); -    admin_writeconfig_string( g_admin_commands[ i ]->command, f ); +    admin_writeconfig_string( c->command, f );      trap_FS_Write( "exec    = ", 10, f ); -    admin_writeconfig_string( g_admin_commands[ i ]->exec, f ); +    admin_writeconfig_string( c->exec, f );      trap_FS_Write( "desc    = ", 10, f ); -    admin_writeconfig_string( g_admin_commands[ i ]->desc, f ); +    admin_writeconfig_string( c->desc, f );      trap_FS_Write( "flag    = ", 10, f ); -    admin_writeconfig_string( g_admin_commands[ i ]->flag, f ); +    admin_writeconfig_string( c->flag, f );      trap_FS_Write( "\n", 1, f );    }    trap_FS_FCloseFile( f ); @@ -794,41 +604,32 @@ void admin_writeconfig( void )  static void admin_readconfig_string( char **cnf, char *s, int size )  { -  char * t; +  char *t;    //COM_MatchToken(cnf, "="); +  s[ 0 ] = '\0';    t = COM_ParseExt( cnf, qfalse ); -  if( !strcmp( t, "=" ) ) -  { -    t = COM_ParseExt( cnf, qfalse ); -  } -  else +  if( strcmp( t, "=" ) )    { -    G_Printf( "readconfig: warning missing = before " -              "\"%s\" on line %d\n", -              t, -              COM_GetCurrentParseLine() ); +    COM_ParseWarning( "expected '=' before \"%s\"", t ); +    Q_strncpyz( s, t, size );    } -  s[ 0 ] = '\0'; -  while( t[ 0 ] ) +  while( 1 )    { -    if( ( s[ 0 ] == '\0' && strlen( t ) <= size )  -      || ( strlen( t ) + strlen( s ) < size ) ) -    { - -      Q_strcat( s, size, t ); -      Q_strcat( s, size, " " ); -    }      t = COM_ParseExt( cnf, qfalse ); +    if( !*t ) +      break; +    if( strlen( t ) + strlen( s ) >= size ) +      break; +    if( *s ) +      Q_strcat( s, size, " " ); +    Q_strcat( s, size, t );    } -  // trim the trailing space -  if( strlen( s ) > 0 && s[ strlen( s ) - 1 ] == ' ' ) -    s[ strlen( s ) - 1 ] = '\0';  }  static void admin_readconfig_int( char **cnf, int *v )  { -  char * t; +  char *t;    //COM_MatchToken(cnf, "=");    t = COM_ParseExt( cnf, qfalse ); @@ -838,10 +639,7 @@ static void admin_readconfig_int( char **cnf, int *v )    }    else    { -    G_Printf( "readconfig: warning missing = before " -              "\"%s\" on line %d\n", -              t, -              COM_GetCurrentParseLine() ); +    COM_ParseWarning( "expected '=' before \"%s\"", t );    }    *v = atoi( t );  } @@ -850,413 +648,217 @@ static void admin_readconfig_int( char **cnf, int *v )  // ones to make new installs easier for admins  static void admin_default_levels( void )  { -  g_admin_level_t * l; -  int i; - -  for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) -  { -    G_Free( g_admin_levels[ i ] ); -    g_admin_levels[ i ] = NULL; -  } -  for( i = 0; i <= 5; i++ ) -  { -    l = G_Alloc( sizeof( g_admin_level_t ) ); -    l->level = i; -    *l->name = '\0'; -    *l->flags = '\0'; -    g_admin_levels[ i ] = l; -  } +  g_admin_level_t *l; +  int             level = 0; -  Q_strncpyz( g_admin_levels[ 0 ]->name, "^4Unknown Player", -    sizeof( l->name ) ); -  Q_strncpyz( g_admin_levels[ 0 ]->flags,  -    "listplayers admintest help specme time",  +  l = g_admin_levels = BG_Alloc( sizeof( g_admin_level_t ) ); +  l->level = level++; +  Q_strncpyz( l->name, "^4Unknown Player", sizeof( l->name ) ); +  Q_strncpyz( l->flags, +    "listplayers admintest adminhelp time",      sizeof( l->flags ) ); -  Q_strncpyz( g_admin_levels[ 1 ]->name, "^5Server Regular", -    sizeof( l->name ) ); -  Q_strncpyz( g_admin_levels[ 1 ]->flags,  -    "listplayers admintest help specme time",  +  l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); +  l->level = level++; +  Q_strncpyz( l->name, "^5Server Regular", sizeof( l->name ) ); +  Q_strncpyz( l->flags, +    "listplayers admintest adminhelp time",      sizeof( l->flags ) ); -  Q_strncpyz( g_admin_levels[ 2 ]->name, "^6Team Manager", -    sizeof( l->name ) ); -  Q_strncpyz( g_admin_levels[ 2 ]->flags,  -    "listplayers admintest help specme time putteam spec999 warn denybuild", +  l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); +  l->level = level++; +  Q_strncpyz( l->name, "^6Team Manager", sizeof( l->name ) ); +  Q_strncpyz( l->flags, +    "listplayers admintest adminhelp time putteam spec999",      sizeof( l->flags ) ); -  Q_strncpyz( g_admin_levels[ 3 ]->name, "^2Junior Admin", -    sizeof( l->name ) ); -  Q_strncpyz( g_admin_levels[ 3 ]->flags,  -    "listplayers admintest help specme time putteam spec999 kick mute warn " -    "denybuild ADMINCHAT SEESFULLLISTPLAYERS", +  l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); +  l->level = level++; +  Q_strncpyz( l->name, "^2Junior Admin", sizeof( l->name ) ); +  Q_strncpyz( l->flags, +    "listplayers admintest adminhelp time putteam spec999 kick mute ADMINCHAT",      sizeof( l->flags ) ); -  Q_strncpyz( g_admin_levels[ 4 ]->name, "^3Senior Admin", -    sizeof( l->name ) ); -  Q_strncpyz( g_admin_levels[ 4 ]->flags,  -    "listplayers admintest help specme time putteam spec999 kick mute showbans " -    "ban namelog warn denybuild decon ADMINCHAT SEESFULLLISTPLAYERS", +  l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); +  l->level = level++; +  Q_strncpyz( l->name, "^3Senior Admin", sizeof( l->name ) ); +  Q_strncpyz( l->flags, +    "listplayers admintest adminhelp time putteam spec999 kick mute showbans ban " +    "namelog ADMINCHAT",      sizeof( l->flags ) ); -  Q_strncpyz( g_admin_levels[ 5 ]->name, "^1Server Operator", -    sizeof( l->name ) ); -  Q_strncpyz( g_admin_levels[ 5 ]->flags,  -    "ALLFLAGS -INCOGNITO -IMMUTABLE -DBUILDER -BANIMMUNITY", +  l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); +  l->level = level++; +  Q_strncpyz( l->name, "^1Server Operator", sizeof( l->name ) ); +  Q_strncpyz( l->flags, +    "ALLFLAGS -IMMUTABLE -INCOGNITO",      sizeof( l->flags ) ); +  admin_level_maxname = 15;  } -//  return a level for a player entity. -int G_admin_level( gentity_t *ent ) +void G_admin_authlog( gentity_t *ent )  { -  int i; -  qboolean found = qfalse; +  char            aflags[ MAX_ADMIN_FLAGS * 2 ]; +  g_admin_level_t *level; +  int             levelNum = 0;    if( !ent ) -  { -    return MAX_ADMIN_LEVELS; -  } +    return; -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) -  { -    if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) ) -    { +  if( ent->client->pers.admin ) +    levelNum = ent->client->pers.admin->level; -      found = qtrue; -      break; -    } -  } +  level = G_admin_level( levelNum ); -  if( found ) -  { -    return g_admin_admins[ i ]->level; -  } +  Com_sprintf( aflags, sizeof( aflags ), "%s %s", +               ent->client->pers.admin->flags, +               ( level ) ? level->flags : "" ); -  return 0; +  G_LogPrintf( "AdminAuth: %i \"%s" S_COLOR_WHITE "\" \"%s" S_COLOR_WHITE +               "\" [%d] (%s): %s\n", +               (int)( ent - g_entities ), ent->client->pers.netname, +               ent->client->pers.admin->name, ent->client->pers.admin->level, +               ent->client->pers.guid, aflags );  } -//  set a player's adminname -void G_admin_set_adminname( gentity_t *ent ) +static char adminLog[ MAX_STRING_CHARS ]; +static int  adminLogLen; +static void admin_log_start( gentity_t *admin, const char *cmd )  { -  int i; -  qboolean found = qfalse; +  const char *name = admin ? admin->client->pers.netname : "console"; -  if( !ent ) -  { -    return; -  } - -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) -  { -    if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) ) -    { -      found = qtrue; -      break; -    } -  } -  -  if( found ) -  { -     Q_strncpyz( ent->client->pers.adminName, g_admin_admins[ i ]->name, sizeof( ent->client->pers.adminName ) ); -  } -  else -  { -     Q_strncpyz( ent->client->pers.adminName, "", sizeof( ent->client->pers.adminName ) ); -  } +  adminLogLen = Q_snprintf( adminLog, sizeof( adminLog ), +    "%d \"%s" S_COLOR_WHITE "\" \"%s" S_COLOR_WHITE "\" [%d] (%s): %s", +    admin ? admin->s.clientNum : -1, +    name, +    admin && admin->client->pers.admin ? admin->client->pers.admin->name : name, +    admin && admin->client->pers.admin ? admin->client->pers.admin->level : 0, +    admin ? admin->client->pers.guid : "", +    cmd );  } -//  Get an admin's registered name -const char *G_admin_get_adminname( gentity_t *ent ) +static void admin_log( const char *str )  { -  int i; - -  if( !ent ) -    return "console"; - -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) -  { -    if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) ) -      return g_admin_admins[ i ]->name; -  } - -  return ent->client->pers.netname; +  if( adminLog[ 0 ] ) +    adminLogLen += Q_snprintf( adminLog + adminLogLen, +      sizeof( adminLog ) - adminLogLen, ": %s", str );  } -// Get an admin's name to print as the user of various commands,  -// obeying admin stealth settings when necessary -char* G_admin_adminPrintName( gentity_t *ent ) +static void admin_log_abort( void )  { -  char *out; - -  if( !ent->client->pers.adminLevel ) -  { -    out = ""; -    return out; -  } -   -  if( G_admin_permission( ent, ADMF_ADMINSTEALTH ) ) -  { -     out = ent->client->pers.adminName; -  } -  else -  { -     out = ent->client->pers.netname; -  }   - - -  return out; +  adminLog[ 0 ] = '\0'; +  adminLogLen = 0;  } -static void admin_log( gentity_t *admin, char *cmd, int skiparg ) +static void admin_log_end( const qboolean ok )  { -  fileHandle_t f; -  int len, i, j; -  char string[ MAX_STRING_CHARS ], decoloured[ MAX_STRING_CHARS ]; -  int min, tens, sec; -  g_admin_admin_t *a; -  g_admin_level_t *l; -  char flags[ MAX_ADMIN_FLAGS * 2 ]; -  gentity_t *victim = NULL; -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ]; - -  if( !g_adminLog.string[ 0 ] ) -    return ; - - -  len = trap_FS_FOpenFile( g_adminLog.string, &f, FS_APPEND ); -  if( len < 0 ) -  { -    G_Printf( "admin_log: error could not open %s\n", g_adminLog.string ); -    return ; -  } +  if( adminLog[ 0 ] ) +    G_LogPrintf( "AdminExec: %s: %s\n", ok ? "ok" : "fail", adminLog ); +  admin_log_abort( ); +} -  sec = (level.time - level.startTime) / 1000; -  min = sec / 60; -  sec -= min * 60; -  tens = sec / 10; -  sec -= tens * 10; +struct llist +{ +  struct llist *next; +}; +static int admin_search( gentity_t *ent, +  const char *cmd, +  const char *noun, +  qboolean ( *match )( void *, const void * ), +  void ( *out )( void *, char * ), +  const void *list, +  const void *arg, /* this will be used as char* later */ +  int start, +  const int offset, +  const int limit ) +{ +  int i; +  int count = 0; +  int found = 0; +  int total; +  int next = 0, end = 0; +  char str[ MAX_STRING_CHARS ]; +  struct llist *l = (struct llist *)list; + +  for( total = 0; l; total++, l = l->next ) ; +  if( start < 0 ) +    start += total; +  else +    start -= offset; +  if( start < 0 || start > total ) +    start = 0; -  *flags = '\0'; -  if( admin ) +  ADMBP_begin(); +  for( i = 0, l = (struct llist *)list; l; i++, l = l->next )    { -    for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) +    if( match( l, arg ) )      { -      if( !Q_stricmp( g_admin_admins[ i ]->guid , admin->client->pers.guid ) ) +      if( i >= start && ( limit < 1 || count < limit ) )        { - -        a = g_admin_admins[ i ]; -        Q_strncpyz( flags, a->flags, sizeof( flags ) ); -        for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ ) -        { -          if( g_admin_levels[ j ]->level == a->level ) -          { -            l = g_admin_levels[ j ]; -            Q_strcat( flags, sizeof( flags ), l->flags ); -            break; -          } -        } -        break; +        out( l, str ); +        ADMBP( va( "%-3d %s\n", i + offset, str ) ); +        count++; +        end = i; +      } +      else if( count == limit ) +      { +        if( next == 0 ) +          next = i;        } -    } -  } -  if( G_SayArgc() > 1 + skiparg ) -  { -    G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -    if( G_ClientNumbersFromString( name, pids ) == 1 ) -    { -      victim = &g_entities[ pids[ 0 ] ]; +      found++;      }    } -  if( victim && Q_stricmp( cmd, "attempted" ) ) -  { -    Com_sprintf( string, sizeof( string ), -                 "%3i:%i%i: %i: %s: %s (%s): %s: %s: %s: %s: \"%s\"\n", -                 min, -                 tens, -                 sec, -                 ( admin ) ? admin->s.clientNum : -1, -                 ( admin ) ? admin->client->pers.guid -                 : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", -                 ( admin ) ? admin->client->pers.netname : "console", -                 ( admin ) ? admin->client->pers.adminName : "console", -                 flags, -                 cmd, -                 victim->client->pers.guid, -                 victim->client->pers.netname, -                 G_SayConcatArgs( 2 + skiparg ) ); -  } -  else +  if( limit > 0 )    { -    Com_sprintf( string, sizeof( string ), -                 "%3i:%i%i: %i: %s: %s (%s): %s: %s: \"%s\"\n", -                 min, -                 tens, -                 sec, -                 ( admin ) ? admin->s.clientNum : -1, -                 ( admin ) ? admin->client->pers.guid -                 : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", -                 ( admin ) ? admin->client->pers.netname : "console", -                 ( admin ) ? admin->client->pers.adminName : "console", -                 flags, -                 cmd, -                 G_SayConcatArgs( 1 + skiparg ) ); +    ADMBP( va( "^3%s: ^7showing %d of %d %s %d-%d%s%s.", +      cmd, count, found, noun, start + offset, end + offset, +      *(char *)arg ? " matching " : "", (char *)arg ) ); +    if( next ) +      ADMBP( va( "  use '%s%s%s %d' to see more", cmd, +        *(char *)arg ? " " : "", +        (char *)arg, +        next + offset ) );    } +  ADMBP( "\n" ); +  ADMBP_end(); +  return next + offset; +} -  if( g_decolourLogfiles.integer ) -  { -    G_DecolorString( string, decoloured ); -    trap_FS_Write( decoloured, strlen( decoloured ), f ); -  } -  else +static qboolean admin_match( void *admin, const void *match ) +{ +  char n1[ MAX_NAME_LENGTH ], n2[ MAX_NAME_LENGTH ]; +  G_SanitiseString( (char *)match, n2, sizeof( n2 ) ); +  if( !n2[ 0 ] ) +    return qtrue; +  G_SanitiseString( ( (g_admin_admin_t *)admin )->name, n1, sizeof( n1 ) ); +  return strstr( n1, n2 ) ? qtrue : qfalse; +} +static void admin_out( void *admin, char *str ) +{ +  g_admin_admin_t *a = (g_admin_admin_t *)admin; +  g_admin_level_t *l = G_admin_level( a->level ); +  int lncol = 0, i; +  for( i = 0; l && l->name[ i ]; i++ )    { -     trap_FS_Write( string, strlen( string ), f ); +    if( Q_IsColorString( l->name + i ) ) +      lncol += 2;    } -  trap_FS_FCloseFile( f ); -   -  if ( !Q_stricmp( cmd, "attempted" ) ) -  { -    Com_sprintf( string, sizeof( string ), -                 "%s^7 (%i) %s: %s", -                 ( admin ) ? admin->client->pers.netname : "console", -                 ( admin ) ? admin->s.clientNum : -1, -                 cmd, -                 G_SayConcatArgs( 1 + skiparg ) ); -    G_AdminsPrintf("%s\n",string); -  } -   -    G_LogPrintf("Admin Command: %s^7 (%s): %s %s\n",( admin ) ? admin->client->pers.netname : "console", ( admin ) ? admin->client->pers.adminName : "console", cmd, G_SayConcatArgs( 1 + skiparg )); +  Com_sprintf( str, MAX_STRING_CHARS, "%-6d %*s^7 %s", +    a->level, admin_level_maxname + lncol - 1, l ? l->name : "(null)", +    a->name );  } - -static int admin_listadmins( gentity_t *ent, int start, char *search, int minlevel ) +static int admin_listadmins( gentity_t *ent, int start, char *search )  { -  int drawn = 0; -  char guid_stub[9]; -  char name[ MAX_NAME_LENGTH ] = {""}; -  char name2[ MAX_NAME_LENGTH ] = {""}; -  char lname[ MAX_NAME_LENGTH ] = {""}; -  char lname_fmt[ 5 ]; -  int i,j; -  gentity_t *vic; -  int l = 0; -  qboolean dup = qfalse; -   -  ADMBP_begin(); - -  // print out all connected players regardless of level if name searching -  for( i = 0; i < level.maxclients && search[ 0 ]; i++ ) -  { -    vic = &g_entities[ i ]; - -    if( vic->client && vic->client->pers.connected != CON_CONNECTED ) -      continue; -     -    l = vic->client->pers.adminLevel; - -    G_SanitiseString( vic->client->pers.netname, name, sizeof( name ) ); -    if( !strstr( name, search ) ) -      continue; - -    for( j = 0; j < 8; j++ ) -      guid_stub[ j ] = vic->client->pers.guid[ j + 24 ]; -    guid_stub[ j ] = '\0'; -   -    lname[ 0 ] = '\0';  -    Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) ); -    for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ ) -    { -      if( g_admin_levels[ j ]->level == l ) -      { -        G_DecolorString( g_admin_levels[ j ]->name, lname ); -        Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is", -          ( admin_level_maxname + strlen( g_admin_levels[ j ]->name ) -            - strlen( lname ) ) ); -        Com_sprintf( lname, sizeof( lname ), lname_fmt, -           g_admin_levels[ j ]->name ); -        break; -      } -    } -    ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n", -      i, -      l, -      lname, -      guid_stub, -      vic->client->pers.netname ) ); -    drawn++; -  } - -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] -    && drawn < MAX_ADMIN_LISTITEMS; i++ ) -   if( g_admin_admins[ i ]->level >= minlevel )  -   { - -     if( start ) -     { -       start--; -       continue; -     } - -     if( search[ 0 ] ) -     { -       G_SanitiseString( g_admin_admins[ i ]->name, name, sizeof( name ) ); -       if( !strstr( name, search ) ) -         continue; -       -       // verify we don't have the same guid/name pair in connected players -       // since we don't want to draw the same player twice -       dup = qfalse; -       for( j = 0; j < level.maxclients; j++ ) -       { -         vic = &g_entities[ j ]; -         if( !vic->client || vic->client->pers.connected != CON_CONNECTED ) -           continue; -         G_SanitiseString( vic->client->pers.netname, name2, sizeof( name2) ); -         if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid ) -           && strstr( name2, search ) )  -         { -           dup = qtrue; -           break; -         } -       } -       if( dup ) -         continue; -     } -     for( j = 0; j < 8; j++ ) -       guid_stub[ j ] = g_admin_admins[ i ]->guid[ j + 24 ]; -     guid_stub[ j ] = '\0'; -     -     lname[ 0 ] = '\0';  -     Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) ); -     for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ ) -     { -       if( g_admin_levels[ j ]->level == g_admin_admins[ i ]->level ) -       { -         G_DecolorString( g_admin_levels[ j ]->name, lname ); -         Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is", -           ( admin_level_maxname + strlen( g_admin_levels[ j ]->name ) -             - strlen( lname ) ) ); -         Com_sprintf( lname, sizeof( lname ), lname_fmt, -            g_admin_levels[ j ]->name ); -         break; -       } -     } -     ADMBP( va( "%4i %4i %s^7 (*%s) %s^7\n", -       ( i + MAX_CLIENTS ), -       g_admin_admins[ i ]->level, -       lname, -       guid_stub, -       g_admin_admins[ i ]->name ) ); -     drawn++; -   } -  ADMBP_end(); -  return drawn; +  return admin_search( ent, "listadmins", "admins", admin_match, admin_out, +    g_admin_admins, search, start, MAX_CLIENTS, MAX_ADMIN_LISTITEMS );  } +#define MAX_DURATION_LENGTH 13  void G_admin_duration( int secs, char *duration, int dursize )  { - +  // sizeof("12.5 minutes") == 13    if( secs > ( 60 * 60 * 24 * 365 * 50 ) || secs < 0 )      Q_strncpyz( duration, "PERMANENT", dursize );    else if( secs >= ( 60 * 60 * 24 * 365 ) ) @@ -1278,440 +880,200 @@ void G_admin_duration( int secs, char *duration, int dursize )      Com_sprintf( duration, dursize, "%i seconds", secs );  } -qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen ) +static void G_admin_ban_message( +  gentity_t     *ent, +  g_admin_ban_t *ban, +  char          *creason, +  int           clen, +  char          *areason, +  int           alen ) +{ +  if( creason ) +  { +    char duration[ MAX_DURATION_LENGTH ]; +    G_admin_duration( ban->expires - trap_RealTime( NULL ), duration, +      sizeof( duration ) ); +    // part of this might get cut off on the connect screen +    Com_sprintf( creason, clen, +      "You have been banned by %s" S_COLOR_WHITE " duration: %s" +      " reason: %s", +      ban->banner, +      duration, +      ban->reason ); +  } + +  if( areason && ent ) +  { +    // we just want the ban number +    int n = 1; +    g_admin_ban_t *b = g_admin_bans; +    for( ; b && b != ban; b = b->next, n++ ) +      ; +    Com_sprintf( areason, alen, +      S_COLOR_YELLOW "Banned player %s" S_COLOR_YELLOW +      " tried to connect from %s (ban #%d)", +      ent->client->pers.netname[ 0 ] ? ent->client->pers.netname : ban->name, +      ent->client->pers.ip.str, +      n ); +  } +} + +static qboolean G_admin_ban_matches( g_admin_ban_t *ban, gentity_t *ent ) +{ +  return !Q_stricmp( ban->guid, ent->client->pers.guid ) || +         ( !G_admin_permission( ent, ADMF_IMMUNITY ) && +           G_AddressCompare( &ban->ip, &ent->client->pers.ip ) ); +} + +static g_admin_ban_t *G_admin_match_ban( gentity_t *ent )  { -  static char lastConnectIP[ 16 ] = {""}; -  static int lastConnectTime = 0; -  char guid[ 33 ]; -  char ip[ 16 ]; -  char *value; -  int i; -  unsigned int userIP = 0, intIP = 0, tempIP; -  int IP[5], k, mask, ipscanfcount;    int t; -  char notice[51]; -  qboolean ignoreIP = qfalse; -   -  trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) ); -  -  *reason = '\0';  -   -  if( !*userinfo ) -    return qfalse; -   -  value = Info_ValueForKey( userinfo, "ip" ); -  Q_strncpyz( ip, value, sizeof( ip ) ); -  // strip port -  value = strchr( ip, ':' ); -  if ( value ) -    *value = '\0'; -   -  if( !*ip ) -    return qfalse; -   -  value = Info_ValueForKey( userinfo, "cl_guid" ); -  Q_strncpyz( guid, value, sizeof( guid ) ); -   +  g_admin_ban_t *ban; +    t = trap_RealTime( NULL ); -  memset( IP, 0, sizeof( IP )); -  sscanf(ip, "%i.%i.%i.%i", &IP[4], &IP[3], &IP[2], &IP[1]); -  for(k = 4; k >= 1; k--) -  { -    if(!IP[k]) continue; -    userIP |= IP[k] << 8*(k-1); -  } -  ignoreIP = G_admin_permission_guid( guid , ADMF_BAN_IMMUNITY ); -  for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) +  if( ent->client->pers.localClient ) +    return NULL; + +  for( ban = g_admin_bans; ban; ban = ban->next )    {      // 0 is for perm ban -    if( g_admin_bans[ i ]->expires != 0 && -         ( g_admin_bans[ i ]->expires - t ) < 1 ) -      continue; -    //if suspend time is over continue -    if( g_admin_bans[ i ]->suspend >= t ) +    if( ban->expires != 0 && ban->expires <= t )        continue; -    if( !ignoreIP ) -    { -      tempIP = userIP; -      intIP = 0; -      mask = -1; - -      memset( IP, 0, sizeof( IP )); -      ipscanfcount = sscanf(g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &IP[4], &IP[3], &IP[2], &IP[1], &IP[0]); - -      if( ipscanfcount == 4 ) -        mask = -1; -      else if( ipscanfcount == 5 ) -        mask = IP[0]; -      else if( ipscanfcount > 0 && ipscanfcount < 4 ) -        mask = 8 * ipscanfcount; -      else -        continue; +    if( G_admin_ban_matches( ban, ent ) ) +      return ban; +  } -      for(k = 4; k >= 1; k--) -      { -        if(!IP[k]) continue; -        intIP |= IP[k] << 8*(k-1); -      } +  return NULL; +} -      if(mask > 0 && mask <= 32)  -      { -        tempIP &= ~((1 << (32-mask)) - 1); -        intIP &= ~((1 << (32-mask)) - 1); -      } +qboolean G_admin_ban_check( gentity_t *ent, char *reason, int rlen ) +{ +  g_admin_ban_t *ban; +  char warningMessage[ MAX_STRING_CHARS ]; -      if( intIP == tempIP || mask == 0 ) -      { -        char duration[ 32 ]; -        G_admin_duration( ( g_admin_bans[ i ]->expires - t ), -          duration, sizeof( duration ) ); - -        // flood protected -        if( t - lastConnectTime >= 300 || -            Q_stricmp( lastConnectIP, ip ) ) -        { -          lastConnectTime = t; -          Q_strncpyz( lastConnectIP, ip, sizeof( lastConnectIP ) ); - -          G_WarningsPrintf( -            "ban", -            "Banned player %s^7 (%s^7) tried to connect (ban #%i on %s by %s^7 expires %s reason: %s^7 )\n", -            Info_ValueForKey( userinfo, "name" ), -            g_admin_bans[ i ]->name, -            i+1, -            ip,  -            g_admin_bans[ i ]->banner, -            duration, -            g_admin_bans[ i ]->reason ); -        } -             -        Com_sprintf( -          reason, -          rlen, -          "You have been banned by %s^7 reason: %s^7 expires: %s       %s", -          g_admin_bans[ i ]->banner, -          g_admin_bans[ i ]->reason, -          duration, -          notice -          ); -        G_LogPrintf("Banned player tried to connect from IP %s\n", ip); -        return qtrue; -      } -    } -    if( *guid && !Q_stricmp( g_admin_bans[ i ]->guid, guid ) ) -    { -      char duration[ 32 ]; -      G_admin_duration( ( g_admin_bans[ i ]->expires - t ), -        duration, sizeof( duration ) ); -      Com_sprintf( -        reason, -        rlen, -        "You have been banned by %s^7 reason: %s^7 expires: %s", -        g_admin_bans[ i ]->banner, -        g_admin_bans[ i ]->reason, -        duration -      ); -      G_Printf("Banned player tried to connect with GUID %s\n", guid); -      return qtrue; -    } -  } -  if ( *guid ) +  if( ent->client->pers.localClient ) +    return qfalse; + +  if( ( ban = G_admin_match_ban( ent ) ) )    { -    int count = 0; -    qboolean valid = qtrue; +    G_admin_ban_message( ent, ban, reason, rlen, +      warningMessage, sizeof( warningMessage ) ); -    while( guid[ count ] != '\0' && valid ) -    { -      if( (guid[ count ] < '0' || guid[ count ] > '9') && -          (guid[ count ] < 'A' || guid[ count ] > 'F') ) -      { -        valid = qfalse; -      } -      count++; -    } -    if( !valid || count != 32 ) -    { -      Com_sprintf( reason, rlen, "Invalid client data" ); -      G_Printf("Player with invalid GUID [%s] connect from IP %s\n", guid, ip); -      return qtrue; -    } +    // don't spam admins +    if( ban->warnCount++ < 5 ) +      G_AdminMessage( NULL, warningMessage ); +    // and don't fill the console +    else if( ban->warnCount < 10 ) +      trap_Print( va( "%s%s\n", warningMessage, +        ban->warnCount + 1 == 10 ? +          S_COLOR_WHITE " - future messages for this ban will be suppressed" : +          "" ) ); +    return qtrue;    } +    return qfalse;  } -qboolean G_admin_cmd_check( gentity_t *ent, qboolean say ) +qboolean G_admin_cmd_check( gentity_t *ent )  { -  int i;    char command[ MAX_ADMIN_CMD_LEN ]; -  char *cmd; -  int skip = 0; +  g_admin_cmd_t *admincmd; +  g_admin_command_t *c; +  qboolean success;    command[ 0 ] = '\0'; -  G_SayArgv( 0, command, sizeof( command ) ); -  if( !Q_stricmp( command, "say" ) || -       ( G_admin_permission( ent, ADMF_TEAMCHAT_CMD ) && -         ( !Q_stricmp( command, "say_team" ) ) ) ) -  { -    skip = 1; -    G_SayArgv( 1, command, sizeof( command ) ); -  } +  trap_Argv( 0, command, sizeof( command ) );    if( !command[ 0 ] )      return qfalse; -  if( command[ 0 ] == '!' ) -  { -    cmd = &command[ 1 ]; -  } -  else -  { -    return qfalse; -  } -   -       // Flood limit.  If they're talking too fast, determine that and return. -  if( g_floodMinTime.integer ) -   if ( G_Flood_Limited( ent ) ) -   { -    trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); -    return qtrue; -   } +  Q_strlwr( command ); +  admin_log_start( ent, command ); -  if( G_admin_is_restricted( ent, qtrue ) ) -    return qtrue; - -  for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ ) +  if( ( c = G_admin_command( command ) ) )    { -    if( Q_stricmp( cmd, g_admin_commands[ i ]->command ) ) -      continue; - -    if( G_admin_permission( ent, g_admin_commands[ i ]->flag ) ) +    admin_log( ConcatArgsPrintable( 1 ) ); +    if( ( success = G_admin_permission( ent, c->flag ) ) )      { -      trap_SendConsoleCommand( EXEC_APPEND, g_admin_commands[ i ]->exec ); -      admin_log( ent, cmd, skip ); -      G_admin_adminlog_log( ent, cmd, NULL, skip, qtrue ); +      if( G_FloodLimited( ent ) ) +        return qtrue; +      trap_SendConsoleCommand( EXEC_APPEND, c->exec );      }      else      { -      ADMP( va( "^3!%s: ^7permission denied\n", g_admin_commands[ i ]->command ) ); -      admin_log( ent, "attempted", skip - 1 ); -      G_admin_adminlog_log( ent, cmd, NULL, skip, qfalse ); +      ADMP( va( "^3%s: ^7permission denied\n", c->command ) );      } +    admin_log_end( success );      return qtrue;    } -  for( i = 0; i < adminNumCmds; i++ ) +  if( ( admincmd = G_admin_cmd( command ) ) )    { -    if( Q_stricmp( cmd, g_admin_cmds[ i ].keyword ) ) -      continue; - -    if( G_admin_permission( ent, g_admin_cmds[ i ].flag ) ) +    if( ( success = G_admin_permission( ent, admincmd->flag ) ) )      { -      g_admin_cmds[ i ].handler( ent, skip ); -      admin_log( ent, cmd, skip ); -      G_admin_adminlog_log( ent, cmd, NULL, skip, qtrue ); +      if( G_FloodLimited( ent ) ) +        return qtrue; +      if( admincmd->silent ) +        admin_log_abort( ); +      if( !( success = admincmd->handler( ent ) ) ) +        admin_log( ConcatArgsPrintable( 1 ) );      }      else      { -      ADMP( va( "^3!%s: ^7permission denied\n", g_admin_cmds[ i ].keyword ) ); -      admin_log( ent, "attempted", skip - 1 ); -      G_admin_adminlog_log( ent, cmd, NULL, skip, qfalse ); +      ADMP( va( "^3%s: ^7permission denied\n", admincmd->keyword ) ); +      admin_log( ConcatArgsPrintable( 1 ) );      } +    admin_log_end( success );      return qtrue;    }    return qfalse;  } -void G_admin_namelog_cleanup( ) +static void llsort( struct llist **head, int compar( const void *, const void * ) )  { -  int i; +  struct llist *a, *b, *t, *l; +  int i, c = 1, ns, as, bs; -  for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) -  { -    if( g_admin_namelog[ i ]->smj.comment ) -      G_Free( g_admin_namelog[ i ]->smj.comment ); -    G_Free( g_admin_namelog[ i ] ); -    g_admin_namelog[ i ] = NULL; -  } -} - -static void dispatchSchachtmeisterIPAQuery( const char *ipa ) -{ -  trap_SendConsoleCommand( EXEC_APPEND, va( "smq ipa \"%s\"\n", ipa ) ); -} - -static void schachtmeisterProcess( g_admin_namelog_t *namelog ) -{ -  schachtmeisterJudgement_t *j = &namelog->smj; - -  if( !j->ratingTime || level.time - j->ratingTime >= 600000 ) -  { -    if( j->queryTime ) -      return; - -    j->queryTime = level.time; -    goto dispatch; -  } - -  if( !j->queryTime || level.time - j->queryTime >= 60000 ) -    return; - -  if( j->dispatchTime && level.time - j->dispatchTime < 5000 ) +  if( !*head )      return; -  dispatch: -  j->dispatchTime = level.time; -  dispatchSchachtmeisterIPAQuery( namelog->ip ); -} - -void G_admin_schachtmeisterFrame( void ) -{ -  int i; -  for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) -    schachtmeisterProcess( g_admin_namelog[ i ] ); -} - -void G_admin_namelog_update( gclient_t *client, qboolean disconnect ) -{ -  int i, j; -  g_admin_namelog_t *namelog; -  char n1[ MAX_NAME_LENGTH ]; -  char n2[ MAX_NAME_LENGTH ]; -  int clientNum = ( client - level.clients ); - -  if ( client->sess.invisible == qfalse ) -  { -    G_admin_seen_update( client->pers.guid ); -  } - -  G_SanitiseString( client->pers.netname, n1, sizeof( n1 ) ); -  for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) +  do    { -    if( disconnect && g_admin_namelog[ i ]->slot != clientNum ) -      continue; - -    if( !disconnect && !( g_admin_namelog[ i ]->slot == clientNum || -                          g_admin_namelog[ i ]->slot == -1 ) ) +    a = *head, l = *head = NULL; +    for( ns = 0; a; ns++, a = b )      { -      continue; -    } - -    if( !Q_stricmp( client->pers.ip, g_admin_namelog[ i ]->ip ) -      && !Q_stricmp( client->pers.guid, g_admin_namelog[ i ]->guid ) ) -    { -      for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES -        && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) +      b = a; +      for( i = as = 0; i < c; i++ )        { -        G_SanitiseString( g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) ); -        if( !Q_stricmp( n1, n2 ) )  +        as++; +        if( !( b = b->next ) )            break;        } -      if( j == MAX_ADMIN_NAMELOG_NAMES ) -        j = MAX_ADMIN_NAMELOG_NAMES - 1; -      Q_strncpyz( g_admin_namelog[ i ]->name[ j ], client->pers.netname, -        sizeof( g_admin_namelog[ i ]->name[ j ] ) ); -      g_admin_namelog[ i ]->slot = ( disconnect ) ? -1 : clientNum; -  -      // if this player is connecting, they are no longer banned -      if( !disconnect ) -        g_admin_namelog[ i ]->banned = qfalse; - -      //check other things like if user was denybuild or muted or denyweapon and restore them -      if( !disconnect ) -      { -        if( g_admin_namelog[ i ]->muted ) -        { -          client->pers.muted = qtrue; -          client->pers.muteExpires = g_admin_namelog[ i ]->muteExpires; -          G_AdminsPrintf( "^7%s^7's mute has been restored\n", client->pers.netname ); -          g_admin_namelog[ i ]->muted = qfalse; -        } -        if( g_admin_namelog[ i ]->denyBuild ) -        { -          client->pers.denyBuild = qtrue; -          G_AdminsPrintf( "^7%s^7's Denybuild has been restored\n", client->pers.netname ); -          g_admin_namelog[ i ]->denyBuild = qfalse; -        } -        if( g_admin_namelog[ i ]->denyHumanWeapons > 0 || g_admin_namelog[ i ]->denyAlienClasses > 0 ) -        { -          if( g_admin_namelog[ i ]->denyHumanWeapons > 0 ) -            client->pers.denyHumanWeapons =  g_admin_namelog[ i ]->denyHumanWeapons; -          if( g_admin_namelog[ i ]->denyAlienClasses > 0 ) -            client->pers.denyAlienClasses =  g_admin_namelog[ i ]->denyAlienClasses; - -          G_AdminsPrintf( "^7%s^7's Denyweapon has been restored\n", client->pers.netname ); -          g_admin_namelog[ i ]->denyHumanWeapons = 0; -          g_admin_namelog[ i ]->denyAlienClasses = 0; -        } -        if( g_admin_namelog[ i ]->specExpires > 0 ) -        { -          client->pers.specExpires = g_admin_namelog[ i ]->specExpires; -          G_AdminsPrintf( "^7%s^7's Putteam spectator has been restored\n", client->pers.netname ); -          g_admin_namelog[ i ]->specExpires = 0; -        } -        if( g_admin_namelog[ i ]->voteCount > 0 ) -        { -          client->pers.voteCount = g_admin_namelog[ i ]->voteCount; -          g_admin_namelog[ i ]->voteCount = 0; -        } -      } -      else +      for( bs = c; ( b && bs ) || as; l = t )        { -        //for mute -        if( G_IsMuted( client ) ) -        { -          g_admin_namelog[ i ]->muted = qtrue; -          g_admin_namelog[ i ]->muteExpires = client->pers.muteExpires; -        } -        //denybuild -        if( client->pers.denyBuild ) -        { -          g_admin_namelog[ i ]->denyBuild = qtrue; -        } -        //denyweapon humans -        if( client->pers.denyHumanWeapons > 0 ) -        { -          g_admin_namelog[ i ]->denyHumanWeapons = client->pers.denyHumanWeapons; -        } -        //denyweapon aliens -        if( client->pers.denyAlienClasses > 0 ) -        { -          g_admin_namelog[ i ]->denyAlienClasses = client->pers.denyAlienClasses; -        } -        //putteam spec -        if( client->pers.specExpires > 0 ) -        { -          g_admin_namelog[ i ]->specExpires = client->pers.specExpires; -        } -        if( client->pers.voteCount > 0 ) -        { -          g_admin_namelog[ i ]->voteCount = client->pers.voteCount; -        } +        if( as && ( !bs || !b || compar( a, b ) <= 0 ) ) +          t = a, a = a->next, as--; +        else +          t = b, b = b->next, bs--; +        if( l ) +          l->next = t; +        else +          *head = t;        } - -      return;      } -  } -  if( i >= MAX_ADMIN_NAMELOGS ) -  { -    G_Printf( "G_admin_namelog_update: warning, g_admin_namelogs overflow\n" ); -    return; -  } -  namelog = G_Alloc( sizeof( g_admin_namelog_t ) ); -  memset( namelog, 0, sizeof( *namelog ) ); -  for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES ; j++ ) -    namelog->name[ j ][ 0 ] = '\0'; -  Q_strncpyz( namelog->ip, client->pers.ip, sizeof( namelog->ip ) ); -  Q_strncpyz( namelog->guid, client->pers.guid, sizeof( namelog->guid ) ); -  Q_strncpyz( namelog->name[ 0 ], client->pers.netname, -    sizeof( namelog->name[ 0 ] ) ); -  namelog->slot = ( disconnect ) ? -1 : clientNum; -  schachtmeisterProcess( namelog ); -  g_admin_namelog[ i ] = namelog; +    l->next = NULL; +    c *= 2; +  } while( ns > 1 ); +} + +static int cmplevel( const void *a, const void *b ) +{ +  return ((g_admin_level_t *)b)->level - ((g_admin_level_t *)a)->level;  } -qboolean G_admin_readconfig( gentity_t *ent, int skiparg ) +qboolean G_admin_readconfig( gentity_t *ent )  { -  g_admin_level_t * l = NULL; +  g_admin_level_t *l = NULL;    g_admin_admin_t *a = NULL;    g_admin_ban_t *b = NULL;    g_admin_command_t *c = NULL; @@ -1722,54 +1084,82 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg )    char *t;    qboolean level_open, admin_open, ban_open, command_open;    int i; +  char ip[ 44 ];    G_admin_cleanup();    if( !g_admin.string[ 0 ] )    { -    ADMP( "^3!readconfig: g_admin is not set, not loading configuration " +    ADMP( "^3readconfig: g_admin is not set, not loading configuration "        "from a file\n" ); -    admin_default_levels();      return qfalse;    } -  len = trap_FS_FOpenFile( g_admin.string, &f, FS_READ ) ; +  len = trap_FS_FOpenFile( g_admin.string, &f, FS_READ );    if( len < 0 )    { -    ADMP( va( "^3!readconfig: ^7could not open admin config file %s\n", -            g_admin.string ) ); +    G_Printf( "^3readconfig: ^7could not open admin config file %s\n", +            g_admin.string );      admin_default_levels();      return qfalse;    } -  cnf = G_Alloc( len + 1 ); +  cnf = BG_Alloc( len + 1 );    cnf2 = cnf;    trap_FS_Read( cnf, len, f );    *( cnf + len ) = '\0';    trap_FS_FCloseFile( f ); -  t = COM_Parse( &cnf ); +  admin_level_maxname = 0; +    level_open = admin_open = ban_open = command_open = qfalse; -  while( *t ) +  COM_BeginParseSession( g_admin.string ); +  while( 1 )    { -    if( !Q_stricmp( t, "[level]" ) || -         !Q_stricmp( t, "[admin]" ) || -         !Q_stricmp( t, "[ban]" ) || -         !Q_stricmp( t, "[command]" ) ) -    { +    t = COM_Parse( &cnf ); +    if( !*t ) +      break; -      if( level_open ) -        g_admin_levels[ lc++ ] = l; -      else if( admin_open ) -        g_admin_admins[ ac++ ] = a; -      else if( ban_open ) -        g_admin_bans[ bc++ ] = b; -      else if( command_open ) -        g_admin_commands[ cc++ ] = c; -      level_open = admin_open = -                     ban_open = command_open = qfalse; +    if( !Q_stricmp( t, "[level]" ) ) +    { +      if( l ) +        l = l->next = BG_Alloc( sizeof( g_admin_level_t ) ); +      else +        l = g_admin_levels = BG_Alloc( sizeof( g_admin_level_t ) ); +      level_open = qtrue; +      admin_open = ban_open = command_open = qfalse; +      lc++;      } - -    if( level_open ) +    else if( !Q_stricmp( t, "[admin]" ) ) +    { +      if( a ) +        a = a->next = BG_Alloc( sizeof( g_admin_admin_t ) ); +      else +        a = g_admin_admins = BG_Alloc( sizeof( g_admin_admin_t ) ); +      admin_open = qtrue; +      level_open = ban_open = command_open = qfalse; +      ac++; +    } +    else if( !Q_stricmp( t, "[ban]" ) ) +    { +      if( b ) +        b = b->next = BG_Alloc( sizeof( g_admin_ban_t ) ); +      else +        b = g_admin_bans = BG_Alloc( sizeof( g_admin_ban_t ) ); +      ban_open = qtrue; +      level_open = admin_open = command_open = qfalse; +      bc++; +    } +    else if( !Q_stricmp( t, "[command]" ) ) +    { +      if( c ) +        c = c->next = BG_Alloc( sizeof( g_admin_command_t ) ); +      else +        c = g_admin_commands = BG_Alloc( sizeof( g_admin_command_t ) ); +      command_open = qtrue; +      level_open = admin_open = ban_open = qfalse; +      cc++; +    } +    else if( level_open )      {        if( !Q_stricmp( t, "level" ) )        { @@ -1778,6 +1168,10 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg )        else if( !Q_stricmp( t, "name" ) )        {          admin_readconfig_string( &cnf, l->name, sizeof( l->name ) ); +        // max printable name length for formatting +        len = Q_PrintStrlen( l->name ); +        if( len > admin_level_maxname ) +          admin_level_maxname = len;        }        else if( !Q_stricmp( t, "flags" ) )        { @@ -1785,9 +1179,7 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg )        }        else        { -        ADMP( va( "^3!readconfig: ^7[level] parse error near %s on line %d\n", -                t, -                COM_GetCurrentParseLine() ) ); +        COM_ParseError( "[level] unrecognized token \"%s\"", t );        }      }      else if( admin_open ) @@ -1808,15 +1200,9 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg )        {          admin_readconfig_string( &cnf, a->flags, sizeof( a->flags ) );        } -      else if( !Q_stricmp( t, "seen" ) ) -      { -        admin_readconfig_int( &cnf, &a->seen ); -      }        else        { -        ADMP( va( "^3!readconfig: ^7[admin] parse error near %s on line %d\n", -                t, -                COM_GetCurrentParseLine() ) ); +        COM_ParseError( "[admin] unrecognized token \"%s\"", t );        }      } @@ -1832,7 +1218,8 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg )        }        else if( !Q_stricmp( t, "ip" ) )        { -        admin_readconfig_string( &cnf, b->ip, sizeof( b->ip ) ); +        admin_readconfig_string( &cnf, ip, sizeof( ip ) ); +        G_AddressParse( ip, &b->ip );        }        else if( !Q_stricmp( t, "reason" ) )        { @@ -1846,23 +1233,13 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg )        {          admin_readconfig_int( &cnf, &b->expires );        } -      else if( !Q_stricmp( t, "suspend" ) ) -      { -        admin_readconfig_int( &cnf, &b->suspend ); -      }        else if( !Q_stricmp( t, "banner" ) )        {          admin_readconfig_string( &cnf, b->banner, sizeof( b->banner ) );        } -      else if( !Q_stricmp( t, "blevel" ) ) -      { -        admin_readconfig_int( &cnf, &b->bannerlevel ); -      }        else        { -        ADMP( va( "^3!readconfig: ^7[ban] parse error near %s on line %d\n", -                t, -                COM_GetCurrentParseLine() ) ); +        COM_ParseError( "[ban] unrecognized token \"%s\"", t );        }      }      else if( command_open ) @@ -1885,761 +1262,305 @@ qboolean G_admin_readconfig( gentity_t *ent, int skiparg )        }        else        { -        ADMP( va( "^3!readconfig: ^7[command] parse error near %s on line %d\n", -                t, -                COM_GetCurrentParseLine() ) ); +        COM_ParseError( "[command] unrecognized token \"%s\"", t );        }      } - -    if( !Q_stricmp( t, "[level]" ) ) -    { -      if( lc >= MAX_ADMIN_LEVELS ) -        return qfalse; -      l = G_Alloc( sizeof( g_admin_level_t ) ); -      l->level = 0; -      *l->name = '\0'; -      *l->flags = '\0'; -      level_open = qtrue; -    } -    else if( !Q_stricmp( t, "[admin]" ) ) -    { -      if( ac >= MAX_ADMIN_ADMINS ) -        return qfalse; -      a = G_Alloc( sizeof( g_admin_admin_t ) ); -      *a->name = '\0'; -      *a->guid = '\0'; -      a->level = 0; -      *a->flags = '\0'; -      a->seen = 0; -      admin_open = qtrue; -    } -    else if( !Q_stricmp( t, "[ban]" ) ) -    { -      if( bc >= MAX_ADMIN_BANS ) -        return qfalse; -      b = G_Alloc( sizeof( g_admin_ban_t ) ); -      *b->name = '\0'; -      *b->guid = '\0'; -      *b->ip = '\0'; -      *b->made = '\0'; -      b->expires = 0; -      b->suspend = 0; -      *b->reason = '\0'; -      b->bannerlevel = 0; -      ban_open = qtrue; -    } -    else if( !Q_stricmp( t, "[command]" ) ) +    else      { -      if( cc >= MAX_ADMIN_COMMANDS ) -        return qfalse; -      c = G_Alloc( sizeof( g_admin_command_t ) ); -      *c->command = '\0'; -      *c->exec = '\0'; -      *c->desc = '\0'; -      *c->flag = '\0'; -      command_open = qtrue; +      COM_ParseError( "unexpected token \"%s\"", t );      } -    t = COM_Parse( &cnf ); -  } -  if( level_open ) -  { - -    g_admin_levels[ lc++ ] = l;    } -  if( admin_open ) -    g_admin_admins[ ac++ ] = a; -  if( ban_open ) -    g_admin_bans[ bc++ ] = b; -  if( command_open ) -    g_admin_commands[ cc++ ] = c; -  G_Free( cnf2 ); -  ADMP( va( "^3!readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands\n", +  BG_Free( cnf2 ); +  ADMP( va( "^3readconfig: ^7loaded %d levels, %d admins, %d bans, %d commands\n",            lc, ac, bc, cc ) );    if( lc == 0 )      admin_default_levels();    else    { -    char n[ MAX_NAME_LENGTH ] = {""}; -  -    // max printable name length for formatting  -    for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) -    { -      G_DecolorString( l->name, n ); -      if( strlen( n ) > admin_level_maxname ) -        admin_level_maxname = strlen( n ); -    } +    llsort( (struct llist **)&g_admin_levels, cmplevel ); +    llsort( (struct llist **)&g_admin_admins, cmplevel );    } -  // reset adminLevel + +  // restore admin mapping    for( i = 0; i < level.maxclients; i++ ) +  {      if( level.clients[ i ].pers.connected != CON_DISCONNECTED ) -      level.clients[ i ].pers.adminLevel = G_admin_level( &g_entities[ i ] ); +    { +      level.clients[ i ].pers.admin = +        G_admin_admin( level.clients[ i ].pers.guid ); +      if( level.clients[ i ].pers.admin ) +        G_admin_authlog( &g_entities[ i ] ); +      G_admin_cmdlist( &g_entities[ i ] ); +    } +  } +    return qtrue;  } -qboolean G_admin_time( gentity_t *ent, int skiparg ) +qboolean G_admin_time( gentity_t *ent )  {    qtime_t qt; -  int t; -  t = trap_RealTime( &qt ); -  ADMP( va( "^3!time: ^7local time is %02i:%02i:%02i\n", +  trap_RealTime( &qt ); +  ADMP( va( "^3time: ^7local time is %02i:%02i:%02i\n",      qt.tm_hour, qt.tm_min, qt.tm_sec ) ); -        return qtrue;  } -static int G_admin_find_slot( gentity_t *ent, char *namearg, const char *command ) -{ -  char name[ MAX_NAME_LENGTH ]; -  char testname[ MAX_NAME_LENGTH ]; -  char *guid = NULL; -  int matches = 0; -  int id = -1; -  int i; -  qboolean numeric = qtrue; - -  G_SanitiseString( namearg, name, sizeof( name ) ); -  for( i = 0; i < sizeof( name ) && name[ i ] ; i++ ) -  { -    if( !isdigit( name[ i ] ) ) -    { -      numeric = qfalse; -      break; -    } -  } -  if( numeric ) -  { -    id = atoi( name ); - -    if( id >= 0 && id < level.maxclients ) -    { -      gentity_t *vic; - -      vic = &g_entities[ id ]; -      if( vic && vic->client && vic->client->pers.connected != CON_DISCONNECTED ) -        return id; - -      ADMP( va( "^3!%s:^7 no one connected by that slot number\n", command ) ); -      return -1; -    } -    if( id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS && -        g_admin_admins[ id - MAX_CLIENTS ] ) -    { -      return id; -    } - -    ADMP( va( "^3!%s:^7 no match for slot or admin number %d\n", command, id ) ); -    return -1; -  } - -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && matches < 2; i++ ) -  { -    G_SanitiseString( g_admin_admins[ i ]->name, testname, sizeof( testname ) ); -    if( strstr( testname, name ) ) -    { -      id = i + MAX_CLIENTS; -      guid = g_admin_admins[ i ]->guid; -      matches++; -    } -  } -  for( i = 0; i < level.maxclients && matches < 2; i++ ) -  { -    if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) -      continue; - -    if( matches && guid && !Q_stricmp( level.clients[ i ].pers.guid, guid ) ) -      continue; - -    G_SanitiseString( level.clients[ i ].pers.netname, testname, sizeof( testname ) ); -    if( strstr( testname, name ) ) -    { -      id = i; -      matches++; -    } -  } - -  if( matches == 0 ) -  { -    ADMP( va( "^3!%s:^7 no match, use !listplayers or !listadmins to " -      "find an appropriate number to use instead of name.\n", command ) ); -    return -1;  -  } - -  if( matches == 1 ) -    return id; - -  ADMP( va( "^3!%s:^7 multiple matches, use the admin number instead:\n", command ) ); -  admin_listadmins( ent, 0, name, 0 ); +// this should be in one of the headers, but it is only used here for now +namelog_t *G_NamelogFromString( gentity_t *ent, char *s ); -  return -1; -} - -static int G_admin_find_admin_slot( gentity_t *ent, char *namearg, char *command, char *nick, int nick_len ) +/* +for consistency, we should be able to target a disconnected player with setlevel +but we can't use namelog and remain consistent, so the solution would be to make +everyone a real level 0 admin so they can be targeted until the next level +but that seems kind of stupid +*/ +qboolean G_admin_setlevel( gentity_t *ent )  { -  gentity_t *vic; -  char *guid; -  int id; +  char name[ MAX_NAME_LENGTH ] = {""}; +  char lstr[ 12 ]; // 11 is max strlen() for 32-bit (signed) int +  char testname[ MAX_NAME_LENGTH ] = {""};    int i; +  gentity_t *vic = NULL; +  g_admin_admin_t *a = NULL; +  g_admin_level_t *l = NULL; +  int na; -  if ( nick ) -    nick[ 0 ] = '\0'; - -  id = G_admin_find_slot( ent, namearg, command ); -  if( id < 0 ) -    return -1; - -  if( id < MAX_CLIENTS ) +  if( trap_Argc() < 3 )    { -    vic = &g_entities[ id ]; -    guid = vic->client->pers.guid; -    for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) -    { -      if( !Q_stricmp( guid, g_admin_admins[ i ]->guid ) ) -      { -        id = i + MAX_CLIENTS; -        if( nick ) -          Q_strncpyz( nick, vic->client->pers.netname, nick_len ); -        break; -      } -    } -    if( id < MAX_CLIENTS ) -    { -      ADMP( va( "^3!%s:^7 player is not !registered\n", command ) ); -      return -1; -    } +    ADMP( "^3setlevel: ^7usage: setlevel [name|slot#] [level]\n" ); +    return qfalse;    } -  id -= MAX_CLIENTS; -  if( nick && !nick[ 0 ] ) -    Q_strncpyz( nick, g_admin_admins[ id ]->name, nick_len ); - -  return id; -} - -qboolean G_admin_setlevel( gentity_t *ent, int skiparg ) -{ -  char lstr[ 11 ]; // 10 is max strlen() for 32-bit int -  char adminname[ MAX_NAME_LENGTH ] = {""}; -  char testname[ MAX_NAME_LENGTH ] = {""}; -  char guid[ 33 ]; -  int l, i; -  gentity_t *vic = NULL; -  qboolean updated = qfalse; -  g_admin_admin_t *a; -  qboolean found = qfalse; -  int id = -1; +  trap_Argv( 1, testname, sizeof( testname ) ); +  trap_Argv( 2, lstr, sizeof( lstr ) ); -  if( G_SayArgc() < 3 + skiparg ) +  if( !( l = G_admin_level( atoi( lstr ) ) ) )    { -    ADMP( "^3!setlevel: ^7usage: !setlevel [name|slot#] [level]\n" ); +    ADMP( "^3setlevel: ^7level is not defined\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, testname, sizeof( testname ) ); -  G_SayArgv( 2 + skiparg, lstr, sizeof( lstr ) ); -  l = atoi( lstr ); -  if( ent && l > ent->client->pers.adminLevel ) +  if( ent && l->level > +    ( ent->client->pers.admin ? ent->client->pers.admin->level : 0 ) )    { -    ADMP( "^3!setlevel: ^7you may not use !setlevel to set a level higher " +    ADMP( "^3setlevel: ^7you may not use setlevel to set a level higher "        "than your current level\n" );      return qfalse;    } -  // if admin is activated for the first time on a running server, we need -  // to ensure at least the default levels get created -  if( !ent && !g_admin_levels[ 0 ] ) -    G_admin_readconfig(NULL, 0); +  for( na = 0, a = g_admin_admins; a; na++, a = a->next ); -  for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) +  for( i = 0; testname[ i ] && isdigit( testname[ i ] ); i++ ); +  if( !testname[ i ] )    { -    if( g_admin_levels[ i ]->level == l ) -    { -      found = qtrue; -      break; -    } -  } -  if( !found ) -  { -    ADMP( "^3!setlevel: ^7level is not defined\n" ); -    return qfalse; -  } - -  id = G_admin_find_slot( ent, testname, "setlevel" ); -  if( id >=0 && id < MAX_CLIENTS ) -  { -    vic = &g_entities[ id ]; -    Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) ); -    Q_strncpyz( guid, vic->client->pers.guid, sizeof( guid ) ); -  } -  else if( id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS && -    g_admin_admins[ id - MAX_CLIENTS ] ) -  { -    Q_strncpyz( adminname, g_admin_admins[ id - MAX_CLIENTS ]->name, -      sizeof( adminname ) ); -    Q_strncpyz( guid, g_admin_admins[ id - MAX_CLIENTS ]->guid, -      sizeof( guid ) ); -    for( i = 0; i < level.maxclients; i++ ) +    int id = atoi( testname ); +    if( id < MAX_CLIENTS )      { -      if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) -        continue; -      if( !Q_stricmp( level.clients[ i ].pers.guid, guid ) ) +      vic = &g_entities[ id ]; +      if( !vic || !vic->client || vic->client->pers.connected == CON_DISCONNECTED )        { -        vic = &g_entities[ i ]; -        Q_strncpyz( adminname, vic->client->pers.netname, sizeof( adminname ) ); +        ADMP( va( "^3setlevel: ^7no player connected in slot %d\n", id ) ); +        return qfalse;        }      } -  } -  else -  { -    return qfalse; -  } - -  if( !Q_stricmp( guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ) -  { -    ADMP( va( "^3!setlevel: ^7%s does not have a valid GUID\n", adminname ) ); -    return qfalse; -  } -  if( ent && !admin_higher_guid( ent->client->pers.guid, guid ) ) -  { -    ADMP( "^3!setlevel: ^7sorry, but your intended victim has a higher" -        " admin level than you\n" ); -    return qfalse; -  } - -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ];i++ ) -  { -    if( !Q_stricmp( g_admin_admins[ i ]->guid, guid ) ) -    { -      g_admin_admins[ i ]->level = l; -      Q_strncpyz( g_admin_admins[ i ]->name, adminname, -                  sizeof( g_admin_admins[ i ]->name ) ); -      updated = qtrue; -    } -  } -  if( !updated ) -  { -    if( i == MAX_ADMIN_ADMINS ) +    else if( id < na + MAX_CLIENTS ) +      for( i = 0, a = g_admin_admins; i < id - MAX_CLIENTS; i++, a = a->next ); +    else      { -      ADMP( "^3!setlevel: ^7too many admins\n" ); +      ADMP( va( "^3setlevel: ^7%s not in range 1-%d\n", +                testname, na + MAX_CLIENTS - 1 ) );        return qfalse;      } -    a = G_Alloc( sizeof( g_admin_admin_t ) ); -    a->level = l; -    Q_strncpyz( a->name, adminname, sizeof( a->name ) ); -    Q_strncpyz( a->guid, guid, sizeof( a->guid ) ); -    *a->flags = '\0'; -    g_admin_admins[ i ] = a;    } - -  AP( va(  -    "print \"^3!setlevel: ^7%s^7 was given level %d admin rights by %s\n\"", -    adminname, l, ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -  if( vic ) -  { -    vic->client->pers.adminLevel = l; -    G_admin_set_adminname( vic ); -  } -   -  if( !g_admin.string[ 0 ] ) -    ADMP( "^3!setlevel: ^7WARNING g_admin not set, not saving admin record " -      "to a file\n" );    else -    admin_writeconfig(); -  return qtrue; -} - -static int SortFlags( const void *pa, const void *pb ) -{ -  char *a = (char *)pa; -  char *b = (char *)pb; - -  if( *a == '-' || *a == '+' ) -    a++; -  if( *b == '-' || *b == '+' ) -    b++; -  return strcmp(a, b); -} - -#define MAX_USER_FLAGS 200 -const char *G_admin_user_flag( char *oldflags, char *flag, qboolean add, qboolean clear, -                               char *newflags, int size ) -{ -  char *token, *token_p; -  char *key; -  char head_flags[ MAX_USER_FLAGS ][ MAX_ADMIN_FLAG_LEN ]; -  char tail_flags[ MAX_USER_FLAGS ][ MAX_ADMIN_FLAG_LEN ]; -  char allflag[ MAX_ADMIN_FLAG_LEN ]; -  char newflag[ MAX_ADMIN_FLAG_LEN ]; -  int head_count = 0; -  int tail_count = 0; -  qboolean flagset = qfalse; -  int i; +    G_SanitiseString( testname, name, sizeof( name ) ); -  if( !flag[ 0 ] ) -  { -    return "invalid admin flag"; -  } - -  allflag[ 0 ] = '\0'; -  token_p = oldflags; -  while( *( token = COM_Parse( &token_p ) ) ) +  if( vic ) +    a = vic->client->pers.admin; +  else if( !a )    { -    key = token; -    if( *key == '-' || *key == '+' ) -      key++; +    g_admin_admin_t *wa; +    int             matches = 0; -    if( !strcmp( key, flag ) ) +    for( wa = g_admin_admins; wa && matches < 2; wa = wa->next )      { -      if( flagset ) -        continue; -      flagset = qtrue; -      if( clear ) +      G_SanitiseString( wa->name, testname, sizeof( testname ) ); +      if( strstr( testname, name ) )        { -        // clearing ALLFLAGS will result in clearing any following flags -        if( !strcmp( key, ADMF_ALLFLAGS ) ) -          break; -        else -          continue; +        a = wa; +        matches++;        } -      Com_sprintf( newflag, sizeof( newflag ), "%s%s", -        ( add ) ? "+" : "-", key ); -    } -    else -    { -      Q_strncpyz( newflag, token, sizeof( newflag ) );      } -    if( !strcmp( key, ADMF_ALLFLAGS ) ) +    for( i = 0; i < level.maxclients && matches < 2; i++ )      { -      if( !allflag[ 0 ] ) -        Q_strncpyz( allflag, newflag, sizeof( allflag ) ); -      continue; -    } +      if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) +        continue; -    if( !allflag[ 0 ] ) -    { -      if( head_count < MAX_USER_FLAGS ) +      if( matches && level.clients[ i ].pers.admin && +          level.clients[ i ].pers.admin == a )        { -        Q_strncpyz( head_flags[ head_count ], newflag, -                    sizeof( head_flags[ head_count ] ) ); -        head_count++; +        vic = &g_entities[ i ]; +        continue;        } -    } -    else -    { -      if( tail_count < MAX_USER_FLAGS ) + +      G_SanitiseString( level.clients[ i ].pers.netname, testname, +        sizeof( testname ) ); +      if( strstr( testname, name ) )        { -        Q_strncpyz( tail_flags[ tail_count ], newflag, -                    sizeof( tail_flags[ tail_count ] ) ); -        tail_count++; +        vic = &g_entities[ i ]; +        a = vic->client->pers.admin; +        matches++;        }      } -  } -  if( !flagset && !clear ) -  { -    if( !strcmp( flag, ADMF_ALLFLAGS ) ) +    if( matches == 0 )      { -      Com_sprintf( allflag, sizeof( allflag ), "%s%s", -        ( add ) ? "" : "-", ADMF_ALLFLAGS ); +      ADMP( "^3setlevel:^7 no match.  use listplayers or listadmins to " +        "find an appropriate number to use instead of name.\n" ); +      return qfalse;      } -    else if( !allflag[ 0 ] ) +    if( matches > 1 )      { -      if( head_count < MAX_USER_FLAGS ) -      { -        Com_sprintf( head_flags[ head_count ], sizeof( head_flags[ head_count ] ), -          "%s%s", ( add ) ? "" : "-", flag ); -        head_count++; -      } -    } -    else -    { -      if( tail_count < MAX_USER_FLAGS ) -      { -        Com_sprintf( tail_flags[ tail_count ], sizeof( tail_flags[ tail_count ] ), -          "%s%s", ( add ) ? "+" : "-", flag ); -        tail_count++; -      } +      ADMP( "^3setlevel:^7 more than one match.  Use the admin number " +        "instead:\n" ); +      admin_listadmins( ent, 0, name ); +      return qfalse;      }    } -  qsort( head_flags, head_count, sizeof( head_flags[ 0 ] ), SortFlags ); -  qsort( tail_flags, tail_count, sizeof( tail_flags[ 0 ] ), SortFlags ); - -  // rebuild flags -  newflags[ 0 ] = '\0'; -  for( i = 0; i < head_count; i++ ) +  if( ent && !admin_higher_admin( ent->client->pers.admin, a ) )    { -    Q_strcat( newflags, size, -              va( "%s%s", ( i ) ? " ": "", head_flags[ i ] ) ); -  } -  if( allflag[ 0 ] ) -  { -    Q_strcat( newflags, size, -      va( "%s%s", ( newflags[ 0 ] ) ? " ": "", allflag ) ); - -    for( i = 0; i < tail_count; i++ ) -    { -      Q_strcat( newflags, size, -                va( " %s", tail_flags[ i ] ) ); -    } +    ADMP( "^3setlevel: ^7sorry, but your intended victim has a higher" +        " admin level than you\n" ); +    return qfalse;    } -  return NULL; -} - -typedef struct { -  char *flag; -  char *description; -} AdminFlagListEntry_t; -static AdminFlagListEntry_t adminFlagList[] = -{ -  { ADMF_ACTIVITY,             "inactivity rules do not apply" }, -  { ADMF_ADMINCHAT,            "can see and use admin chat" }, -  { ADMF_HIGHADMINCHAT,        "can see and use high admin chat" }, -  { ADMF_ALLFLAGS,             "has all flags and can use any command" }, -  { ADMF_BAN_IMMUNITY,         "immune from IP bans" }, -  { ADMF_CAN_PERM_BAN,         "can permanently ban players" }, -  { ADMF_DBUILDER,             "permanent designated builder" }, -  { ADMF_FORCETEAMCHANGE,      "team balance rules do not apply" }, -  { ADMF_INCOGNITO,            "does not show as admin in !listplayers" }, -  { ADMF_SEESINCOGNITO,        "sees registered name of players flagged with INCOGNITO" }, -  { ADMF_IMMUNITY,             "cannot be vote kicked or muted" }, -  { ADMF_IMMUTABLE,            "admin commands cannot be used on them" }, -  { ADMF_NOCENSORFLOOD,        "no flood protection" }, -  { ADMF_NO_VOTE_LIMIT,        "vote limitations do not apply" }, -  { ADMF_SEESFULLLISTPLAYERS,  "sees all info in !listplayers" }, -  { ADMF_SPEC_ALLCHAT,         "can see team chat as spectator" }, -  { ADMF_ADMINSTEALTH,         "uses admin stealth" }, -  { ADMF_TEAMCHANGEFREE,       "keeps credits on team switch" }, -  { ADMF_TEAMCHAT_CMD,         "can run commands from team chat" }, -  { ADMF_UNACCOUNTABLE,        "does not need to specify reason for kick/ban" }, -  { ADMF_NOSCRIMRESTRICTION,   "team joining, vote and chat restrictions during scrims do not apply" }, -  { ADMF_NO_BUILD,             "can not build" }, -  { ADMF_NO_CHAT,              "can not talk" }, -  { ADMF_NO_VOTE,              "can not call votes" }, -  { ADMF_NOAUTOBAHN,           "ignored by the Autobahn system" } -}; -static int adminNumFlags= sizeof( adminFlagList ) / sizeof( adminFlagList[ 0 ] ); - -#define MAX_LISTCOMMANDS 128 -qboolean G_admin_flaglist( gentity_t *ent, int skiparg ) -{ -  qboolean shown[ MAX_LISTCOMMANDS ]; -  int i, j; -  int count = 0; - -  ADMBP_begin(); - -  ADMBP( "^3Ability flags:\n" ); - -  for( i = 0; i < adminNumFlags; i++ ) +  if( vic && vic->client->pers.guidless )    { -    ADMBP( va( "  %s%-20s ^7%s\n", -      ( adminFlagList[ i ].flag[ 0 ] != '.' ) ? "^5" : "^1", -      adminFlagList[ i ].flag, -      adminFlagList[ i ].description ) ); +    ADMP( va( "^3setlevel: ^7%s^7 has no GUID\n", vic->client->pers.netname ) ); +    return qfalse;    } -  ADMBP( "^3Command flags:\n" ); - -  memset( shown, 0, sizeof( shown ) ); -  for( i = 0; i < adminNumCmds; i++ ) +  if( !a && vic )    { -    if( i < MAX_LISTCOMMANDS && shown[ i ] ) -      continue; -    ADMBP( va( "  ^5%-20s^7", g_admin_cmds[ i ].flag ) ); -    for( j = i; j < adminNumCmds; j++ ) -    { -      if( !strcmp( g_admin_cmds[ j ].flag, g_admin_cmds[ i ].flag ) ) -      { -        ADMBP( va( " %s", g_admin_cmds[ j ].keyword ) ); -        if( j < MAX_LISTCOMMANDS ) -          shown[ j ] = qtrue; -      } -    } -    ADMBP( "\n" ); -    count++; +    for( a = g_admin_admins; a && a->next; a = a->next ); +    if( a ) +      a = a->next = BG_Alloc( sizeof( g_admin_admin_t ) ); +    else +      a = g_admin_admins = BG_Alloc( sizeof( g_admin_admin_t ) ); +    vic->client->pers.admin = a; +    Q_strncpyz( a->guid, vic->client->pers.guid, sizeof( a->guid ) );    } -  ADMBP( va( "^3!flaglist: ^7listed %d abilities and %d command flags\n", -    adminNumFlags, count ) ); +  a->level = l->level; +  if( vic ) +    Q_strncpyz( a->name, vic->client->pers.netname, sizeof( a->name ) ); -  ADMBP_end(); +  admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", a->level, a->guid, +    a->name ) ); + +  AP( va( +    "print \"^3setlevel: ^7%s^7 was given level %d admin rights by %s\n\"", +    a->name, a->level, ( ent ) ? ent->client->pers.netname : "console" ) ); +  admin_writeconfig(); +  if( vic ) +  { +    G_admin_authlog( vic ); +    G_admin_cmdlist( vic ); +  }    return qtrue;  } -qboolean G_admin_flag( gentity_t *ent, int skiparg ) +static void admin_create_ban( gentity_t *ent, +  char *netname, +  char *guid, +  addr_t *ip, +  int seconds, +  char *reason )  { -  char command[ MAX_ADMIN_CMD_LEN ], *cmd; -  char name[ MAX_NAME_LENGTH ]; -  char flagbuf[ MAX_ADMIN_FLAG_LEN ]; -  char *flag; -  int id; -  char adminname[ MAX_NAME_LENGTH ] = {""}; -  const char *result; -  qboolean add = qtrue; -  qboolean clear = qfalse; -  int admin_level = -1; -  int i, level; - -  G_SayArgv( skiparg, command, sizeof( command ) ); -  cmd = command; -  if( *cmd == '!' ) -    cmd++; - -  if( G_SayArgc() < 2 + skiparg ) -  { -    ADMP( va( "^3!%s: ^7usage: !%s slot# flag\n", cmd, cmd ) ); -    return qfalse; -  } - -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  if( name[ 0 ] == '*' ) -  { -    if( ent ) -    { -      ADMP( va( "^3!%s: only console can change admin level flags\n", cmd ) ); -      return qfalse; -    } -    id = atoi( name + 1 ); -    for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) -    { -      if( g_admin_levels[ i ]->level == id ) -      { -        admin_level = i; -        break; -      } -    } -    if( admin_level < 0 ) -    { -      ADMP( va( "^3!%s: admin level %d does not exist\n", cmd, id ) ); -      return qfalse; -    } -    Com_sprintf( adminname, sizeof( adminname ), "admin level %d", id ); -  } -  else -  { -    id = G_admin_find_admin_slot( ent, name, cmd, adminname, sizeof( adminname ) ); -    if( id < 0 ) -      return qfalse; +  g_admin_ban_t *b = NULL; +  qtime_t       qt; +  int           t; +  int           i; +  char          *name; +  char          disconnect[ MAX_STRING_CHARS ]; -    if( ent && !admin_higher_guid( ent->client->pers.guid, g_admin_admins[ id ]->guid ) ) -    { -      ADMP( va( "^3%s:^7 your intended victim has a higher admin level than you\n", cmd ) ); -      return qfalse; -    } -  } +  t = trap_RealTime( &qt ); -  if( G_SayArgc() < 3 + skiparg ) +  for( b = g_admin_bans; b; b = b->next )    { -    flag = ""; -    level = 0; -    if( admin_level < 0 ) -    { -      for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) -      { -        if( g_admin_admins[ id ]->level == g_admin_levels[ i ]->level ) -        { -          flag = g_admin_levels[ i ]->flags; -          level = g_admin_admins[ id ]->level; -          break; -        } -      } -      ADMP( va( "^3%s:^7 flags for %s^7 are '^3%s^7'\n", -        cmd, adminname, g_admin_admins[ id ]->flags) ); -    } -    else -    { -      flag = g_admin_levels[ admin_level ]->flags; -      level = g_admin_levels[ admin_level ]->level; -    } -    ADMP( va( "^3%s:^7  level %d flags are '%s'\n", -      cmd, level, flag ) ); - -    return qtrue; +    if( !b->next ) +      break;    } -  G_SayArgv( 2 + skiparg, flagbuf, sizeof( flagbuf ) ); -  flag = flagbuf; -  if( flag[ 0 ] == '-' || flag[ 0 ] == '+' ) -  { -    add = ( flag[ 0 ] == '+' ); -    flag++; -  } -  if( ent && !Q_stricmp( ent->client->pers.guid, g_admin_admins[ id ]->guid ) ) +  if( b )    { -    ADMP( va( "^3%s:^7 you may not change your own flags (use rcon)\n", cmd ) ); -    return qfalse; -  } -  if( flag[ 0 ] != '.' && !G_admin_permission( ent, flag ) ) -  { -    ADMP( va( "^3%s:^7 you can only change flags that you also have\n", cmd ) ); -    return qfalse; +    if( !b->next ) +      b = b->next = BG_Alloc( sizeof( g_admin_ban_t ) );    } +  else +    b = g_admin_bans = BG_Alloc( sizeof( g_admin_ban_t ) ); -  if( !Q_stricmp( cmd, "unflag" ) ) -  { -    clear = qtrue; -  } +  Q_strncpyz( b->name, netname, sizeof( b->name ) ); +  Q_strncpyz( b->guid, guid, sizeof( b->guid ) ); +  memcpy( &b->ip, ip, sizeof( b->ip ) ); -  if( admin_level < 0 ) -  { -    result = G_admin_user_flag( g_admin_admins[ id ]->flags, flag, add, clear, -                                g_admin_admins[ id ]->flags, sizeof( g_admin_admins[ id ]->flags ) ); -  } -  else -  { -    result = G_admin_user_flag( g_admin_levels[ admin_level ]->flags, flag, add, clear, -                                g_admin_levels[ admin_level ]->flags, -                                sizeof( g_admin_levels[ admin_level ]->flags ) ); -  } -  if( result ) -  { -    ADMP( va( "^3!flag: ^7an error occured setting flag '^3%s^7', %s\n", -      flag, result ) ); -    return qfalse; -  } +  Com_sprintf( b->made, sizeof( b->made ), "%04i-%02i-%02i %02i:%02i:%02i", +    qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday, +    qt.tm_hour, qt.tm_min, qt.tm_sec ); -  if( !Q_stricmp( cmd, "flag" ) ) -  { -    G_AdminsPrintf( "^3!%s: ^7%s^7 was %s admin flag '%s' by %s\n", -      cmd, adminname, -      ( add ) ? "given" : "denied", -      flag, -      ( ent ) ? ent->client->pers.netname : "console" ); -  } +  if( ent && ent->client->pers.admin ) +    name = ent->client->pers.admin->name; +  else if( ent ) +    name = ent->client->pers.netname;    else -  { -    G_AdminsPrintf( "^3!%s: ^7admin flag '%s' for %s^7 cleared by %s\n", -      cmd, flag, adminname, -      ( ent ) ? ent->client->pers.netname : "console" ); -  } +    name = "console"; -  if( !g_admin.string[ 0 ] ) -    ADMP( va( "^3!%s: ^7WARNING g_admin not set, not saving admin record " -      "to a file\n", cmd ) ); +  Q_strncpyz( b->banner, name, sizeof( b->banner ) ); +  if( !seconds ) +    b->expires = 0;    else -    admin_writeconfig(); +    b->expires = t + seconds; +  if( !*reason ) +    Q_strncpyz( b->reason, "banned by admin", sizeof( b->reason ) ); +  else +    Q_strncpyz( b->reason, reason, sizeof( b->reason ) ); -  return qtrue; +  G_admin_ban_message( NULL, b, disconnect, sizeof( disconnect ), NULL, 0 ); + +  for( i = 0; i < level.maxclients; i++ ) +  { +    if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) +      continue; +    if( G_admin_ban_matches( b, &g_entities[ i ] ) ) +    { +      trap_SendServerCommand( i, va( "disconnect \"%s\"", disconnect ) ); + +      trap_DropClient( i, va( "has been kicked by %s^7. reason: %s", +        b->banner, b->reason ) ); +    } +  }  }  int G_admin_parse_time( const char *time )  {    int seconds = 0, num = 0; -  int i; -  for( i = 0; time[ i ]; i++ ) +  if( !*time ) +    return -1; +  while( *time )    { -    if( isdigit( time[ i ] ) ) -    { -      num = num * 10 + time[ i ] - '0'; -      continue; -    } -    if( i == 0 || !isdigit( time[ i - 1 ] ) ) +    if( !isdigit( *time ) )        return -1; -    switch( time[ i ] ) +    while( isdigit( *time ) ) +      num = num * 10 + *time++ - '0'; + +    if( !*time ) +      break; +    switch( *time++ )      {        case 'w': num *= 7;        case 'd': num *= 24; @@ -2653,2093 +1574,776 @@ int G_admin_parse_time( const char *time )    }    if( num )      seconds += num; -  // overflow -  if( seconds < 0 ) -    seconds = 0;    return seconds;  } -static qboolean admin_create_ban( gentity_t *ent, -  char *netname, -  char *guid, -  char *ip, -  int seconds, -  char *reason )  +qboolean G_admin_setdevmode( gentity_t *ent )  { -  g_admin_ban_t *b = NULL; -  qtime_t qt; -  int t; -  int i; - -  t = trap_RealTime( &qt ); -  b = G_Alloc( sizeof( g_admin_ban_t ) ); - -  if( !b ) +  char str[ 5 ]; +  if( trap_Argc() != 2 ) +  { +    ADMP( "^3setdevmode: ^7usage: setdevmode [on|off]\n" );      return qfalse; - -  Q_strncpyz( b->name, netname, sizeof( b->name ) ); -  Q_strncpyz( b->guid, guid, sizeof( b->guid ) ); -  Q_strncpyz( b->ip, ip, sizeof( b->ip ) ); -  b->suspend = 0; - -  //strftime( b->made, sizeof( b->made ), "%m/%d/%y %H:%M:%S", lt ); -  Q_strncpyz( b->made, va( "%02i/%02i/%02i %02i:%02i:%02i", -    (qt.tm_mon + 1), qt.tm_mday, (qt.tm_year - 100), -    qt.tm_hour, qt.tm_min, qt.tm_sec ), -    sizeof( b->made ) ); - -  Q_strncpyz( b->banner, G_admin_get_adminname( ent ), sizeof( b->banner ) ); - -  if( ent ) -    b->bannerlevel = G_admin_level( ent ); -  else -    b->bannerlevel = 0; - -  if( !seconds ) -    b->expires = 0; -  else -    b->expires = t + seconds; -  if( !*reason ) -    Q_strncpyz( b->reason, "banned by admin", sizeof( b->reason ) ); +  } +  trap_Argv( 1, str, sizeof( str ) ); +  if( !Q_stricmp( str, "on" ) ) +  { +    if( g_cheats.integer ) +    { +      ADMP( "^3setdevmode: ^7developer mode is already on\n" ); +      return qfalse; +    } +    trap_Cvar_Set( "sv_cheats", "1" ); +    trap_Cvar_Update( &g_cheats ); +    AP( va( "print \"^3setdevmode: ^7%s ^7has switched developer mode on\n\"", +            ent ? ent->client->pers.netname : "console" ) ); +    return qtrue; +  } +  else if( !Q_stricmp( str, "off" ) ) +  { +    if( !g_cheats.integer ) +    { +      ADMP( "^3setdevmode: ^7developer mode is already off\n" ); +      return qfalse; +    } +    trap_Cvar_Set( "sv_cheats", "0" ); +    trap_Cvar_Update( &g_cheats ); +    AP( va( "print \"^3setdevmode: ^7%s ^7has switched developer mode off\n\"", +            ent ? ent->client->pers.netname : "console" ) ); +    return qtrue; +  }    else -    Q_strncpyz( b->reason, reason, sizeof( b->reason ) ); -  for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) -    ; -  if( i == MAX_ADMIN_BANS )    { -    ADMP( "^3!ban: ^7too many bans\n" ); -    G_Free( b ); +    ADMP( "^3setdevmode: ^7usage: setdevmode [on|off]\n" );      return qfalse;    } -  g_admin_bans[ i ] = b; -  return qtrue;  } -qboolean G_admin_kick( gentity_t *ent, int skiparg ) +qboolean G_admin_kick( gentity_t *ent )  { -  int pids[ MAX_CLIENTS ]; +  int pid;    char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ];    int minargc;    gentity_t *vic; -  char notice[51]; -   -  trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) ); -  minargc = 3 + skiparg; +  minargc = 3;    if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) -    minargc = 2 + skiparg; +    minargc = 2; -  if( G_SayArgc() < minargc ) +  if( trap_Argc() < minargc )    { -    ADMP( "^3!kick: ^7usage: !kick [name] [reason]\n" ); +    ADMP( "^3kick: ^7usage: kick [name] [reason]\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  reason = G_SayConcatArgs( 2 + skiparg ); -  if( G_ClientNumbersFromString( name, pids ) != 1 ) +  trap_Argv( 1, name, sizeof( name ) ); +  reason = ConcatArgs( 2 ); +  if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 )    { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!kick: ^7%s\n", err ) ); +    ADMP( va( "^3kick: ^7%s", err ) );      return qfalse;    } -  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) +  vic = &g_entities[ pid ]; +  if( !admin_higher( ent, vic ) )    { -    ADMP( "^3!kick: ^7sorry, but your intended victim has a higher admin" +    ADMP( "^3kick: ^7sorry, but your intended victim has a higher admin"          " level than you\n" );      return qfalse;    } -  vic = &g_entities[ pids[ 0 ] ]; +  if( vic->client->pers.localClient ) +  { +    ADMP( "^3kick: ^7disconnecting the host would end the game\n" ); +    return qfalse; +  } +  admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\"", +    pid, +    vic->client->pers.guid, +    vic->client->pers.netname, +    reason ) );    admin_create_ban( ent,      vic->client->pers.netname, -    vic->client->pers.guid, -    vic->client->pers.ip, G_admin_parse_time( g_adminTempBan.string ), +    vic->client->pers.guidless ? "" : vic->client->pers.guid, +    &vic->client->pers.ip, +    MAX( 1, G_admin_parse_time( g_adminTempBan.string ) ),      ( *reason ) ? reason : "kicked by admin" ); -  if( g_admin.string[ 0 ] ) -    admin_writeconfig(); - - trap_SendServerCommand( pids[ 0 ], -  va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\n%s\"", -    ( ent ) ? va( "admin:\n%s", G_admin_adminPrintName( ent ) ) : "admin\nconsole", -    ( *reason ) ? reason : "kicked by admin", notice ) ); -   -  trap_DropClient( pids[ 0 ], va( "kicked%s^7, reason: %s", -    ( ent ) ? va( " by %s", G_admin_adminPrintName( ent ) ) : " by console", -    ( *reason ) ? reason : "kicked by admin" ) ); +  admin_writeconfig();    return qtrue;  } -qboolean G_admin_ban( gentity_t *ent, int skiparg ) +qboolean G_admin_setivo( gentity_t* ent )  { -  int seconds; -  char search[ MAX_NAME_LENGTH ]; -  char secs[ 7 ]; -  char *reason; -  int minargc; -  char duration[ 32 ]; -  int logmatch = -1, logmatches = 0; -  int i, j; -  qboolean exactmatch = qfalse; -  char n2[ MAX_NAME_LENGTH ]; -  char s2[ MAX_NAME_LENGTH ]; -  char guid_stub[ 9 ]; -  char notice[51]; -   -  trap_Cvar_VariableStringBuffer( "g_banNotice", notice, sizeof( notice ) ); -   -  if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && -       G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) -  { -    minargc = 2 + skiparg; -  } -  else if( ( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) || g_adminMaxBan.integer ) || -            G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) -  { -    minargc = 3 + skiparg; -  } -  else -  { -    minargc = 4 + skiparg; -  } -  if( G_SayArgc() < minargc ) +  char arg[ 3 ]; +  const char *cn; +  gentity_t *spot; + +  if( !ent )    { -    ADMP( "^3!ban: ^7usage: !ban [name|slot|ip] [time] [reason]\n" ); +    ADMP( "^3setivo: ^7the console can't position intermission view overrides\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, search, sizeof( search ) ); -  G_SanitiseString( search, s2, sizeof( s2 ) ); -  G_SayArgv( 2 + skiparg, secs, sizeof( secs ) ); -  seconds = G_admin_parse_time( secs ); -  if( seconds <= 0 ) +  if( trap_Argc() != 2 )    { -    if( g_adminMaxBan.integer && !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) -    { -      ADMP( va( "^3!ban: ^7using your admin level's maximum ban length of %s\n", -        g_adminMaxBan.string ) ); -      seconds = G_admin_parse_time( g_adminMaxBan.string ); -    } -    else if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) -    { -      seconds = 0; -    } -    else -    { -      ADMP( "^3!ban: ^7ban time must be positive\n" ); -      return qfalse; -    } -    reason = G_SayConcatArgs( 2 + skiparg ); +    ADMP( "^3setivo: ^7usage: setivo {s|a|h}\n" ); +    return qfalse;    } +  trap_Argv( 1, arg, sizeof( arg ) ); +  if( !Q_stricmp( arg, "s" ) ) +    cn = "info_player_intermission"; +  else if( !Q_stricmp( arg, "a" ) ) +    cn = "info_alien_intermission"; +  else if( !Q_stricmp( arg, "h" ) ) +    cn = "info_human_intermission";    else    { -    reason = G_SayConcatArgs( 3 + skiparg ); - -    if( g_adminMaxBan.integer && -        seconds > G_admin_parse_time( g_adminMaxBan.string ) && -        !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) -    { -      seconds = G_admin_parse_time( g_adminMaxBan.string ); -      ADMP( va( "^3!ban: ^7ban length limited to %s for your admin level\n", -        g_adminMaxBan.string ) ); -    } -  } - -  for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) -  { -    // skip players in the namelog who have already been banned -    if( g_admin_namelog[ i ]->banned ) -      continue; - -    // skip disconnected players when banning on slot number -    if( g_admin_namelog[ i ]->slot == -1 ) -      continue; - -    if( !Q_stricmp( va( "%d", g_admin_namelog[ i ]->slot ), s2 ) ) -    { -      logmatches = 1; -      logmatch = i; -      exactmatch = qtrue; -      break; -    } -  }  - -  for( i = 0; -       !exactmatch && i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; -       i++ ) -  { -    // skip players in the namelog who have already been banned -    if( g_admin_namelog[ i ]->banned ) -      continue; - -    if( !Q_stricmp( g_admin_namelog[ i ]->ip, s2 ) ) -    { -      logmatches = 1; -      logmatch = i; -      exactmatch = qtrue; -      break; -    } -    for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES -      && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) -    { -      G_SanitiseString(g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) ); -      if( strstr( n2, s2 ) ) -      { -        if( logmatch != i ) -          logmatches++; -        logmatch = i;  -      } -    } -  } -   -  if( !logmatches )  -  { -    ADMP( "^3!ban: ^7no player found by that name, IP, or slot number\n" ); -    return qfalse; -  }  -  else if( logmatches > 1 ) -  { -    ADMBP_begin(); -    ADMBP( "^3!ban: ^7multiple recent clients match name, use IP or slot#:\n" ); -    for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) -    { -      for( j = 0; j < 8; j++ ) -        guid_stub[ j ] = g_admin_namelog[ i ]->guid[ j + 24 ]; -      guid_stub[ j ] = '\0'; -      for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES -        && g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) -      { -        G_SanitiseString(g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) ); -        if( strstr( n2, s2 ) ) -        { -          if( g_admin_namelog[ i ]->slot > -1 ) -            ADMBP( "^3" ); -          ADMBP( va( "%-2s (*%s) %15s ^7'%s^7'\n", -           (g_admin_namelog[ i ]->slot > -1) ? -             va( "%d", g_admin_namelog[ i ]->slot ) : "-", -           guid_stub, -           g_admin_namelog[ i ]->ip, -           g_admin_namelog[ i ]->name[ j ] ) ); -        } -      } -    } -    ADMBP_end(); +    ADMP( "^3setivo: ^7the argument must be either s, a or h\n" );      return qfalse;    } -   -  G_admin_duration( ( seconds ) ? seconds : -1, -    duration, sizeof( duration ) ); -  if( ent && !admin_higher_guid( ent->client->pers.guid, -    g_admin_namelog[ logmatch ]->guid ) ) +  spot = G_Find( NULL, FOFS( classname ), cn ); +  if( !spot )    { - -    ADMP( "^3!ban: ^7sorry, but your intended victim has a higher admin" -      " level than you\n" ); -    return qfalse; +    spot = G_Spawn(); +    spot->classname = (char *)cn;    } +  spot->count = 1; -  admin_create_ban( ent, -    g_admin_namelog[ logmatch ]->name[ 0 ], -    g_admin_namelog[ logmatch ]->guid, -    g_admin_namelog[ logmatch ]->ip, -    seconds, reason );  - -  g_admin_namelog[ logmatch ]->banned = qtrue; +  BG_GetClientViewOrigin( &ent->client->ps, spot->r.currentOrigin ); +  VectorCopy( ent->client->ps.viewangles, spot->r.currentAngles ); -  if( !g_admin.string[ 0 ] ) -    ADMP( "^3!ban: ^7WARNING g_admin not set, not saving ban to a file\n" ); -  else -    admin_writeconfig(); - -  if( g_admin_namelog[ logmatch ]->slot == -1 )  -  { -    // client is already disconnected so stop here -    AP( va( "print \"^3!ban:^7 %s^7 has been banned by %s^7 " -      "duration: %s, reason: %s\n\"", -      g_admin_namelog[ logmatch ]->name[ 0 ], -      ( ent ) ? G_admin_adminPrintName( ent ) : "console", -      duration, -      ( *reason ) ? reason : "banned by admin" ) ); -    return qtrue; -  } -   -  trap_SendServerCommand( g_admin_namelog[ logmatch ]->slot, -    va( "disconnect \"You have been banned.\n" -      "admin:\n%s^7\nduration:\n%s\nreason:\n%s\n%s\"", -      ( ent ) ? G_admin_adminPrintName( ent ) : "console", -      duration, -      ( *reason ) ? reason : "banned by admin", notice ) ); - -  trap_DropClient(  g_admin_namelog[ logmatch ]->slot, -    va( "banned by %s^7, duration: %s, reason: %s", -      ( ent ) ? G_admin_adminPrintName( ent ) : "console", -      duration, -      ( *reason ) ? reason : "banned by admin" ) ); +  ADMP( "^3setivo: ^7intermission view override positioned\n" );    return qtrue;  } -// If true then don't let the player join a team, use the chat or run commands. -qboolean G_admin_is_restricted(gentity_t *ent, qboolean sendMessage) +qboolean G_admin_ban( gentity_t *ent )  { -	schachtmeisterJudgement_t *j = NULL; -	int i; - -	// Never restrict admins or whitelisted players. -	if (G_admin_permission(ent, ADMF_NOAUTOBAHN) || -	    G_admin_permission(ent, ADMF_IMMUNITY)) -		return qfalse; - -	// Find the relevant namelog. -	// FIXME: this shouldn't require looping over *all* namelogs. -	for (i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[i]; i++) { -		if (g_admin_namelog[i]->slot == ent - g_entities) { -			j = &g_admin_namelog[i]->smj; -			break; -		} -	} - -	// A missing namelog shouldn't happen. -	if (!j) -		return qfalse; - -	// Restrictions concern only unrated players. -	if (j->ratingTime) -		return qfalse; - -	// Don't wait forever, allow up to 15 seconds. -	if (level.time - j->queryTime >= 15000) -		return qfalse; - -	if (sendMessage) -		trap_SendServerCommand(ent - g_entities, "print \"Please wait a moment before doing anything.\n\""); - -	return qtrue; -} - -static void admin_autobahn(gentity_t *ent, int rating) -{ -	// Allow per-GUID exceptions and never autoban admins. -	if (G_admin_permission(ent, ADMF_NOAUTOBAHN) || -	    G_admin_permission(ent, ADMF_IMMUNITY)) -		return; - -	// Don't do anything if the rating is clear. -	if (rating >= g_schachtmeisterClearThreshold.integer) -		return; - -	// Ban only if the rating is low enough. -	if (rating > g_schachtmeisterAutobahnThreshold.integer) { -		G_AdminsPrintf("%s^7 (#%d) has rating %d\n", -		               ent->client->pers.netname, ent - g_entities, -		               rating); -		return; -	} - -	G_LogAutobahn(ent, NULL, rating, qfalse); - -	trap_SendServerCommand(ent - g_entities, va("disconnect \"%s\"\n", -	                       g_schachtmeisterAutobahnMessage.string)); -	trap_DropClient(ent - g_entities, "dropped by the Autobahn"); -} - -void G_admin_IPA_judgement( const char *ipa, int rating, const char *comment ) -{ -  int i; -  for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) -  { -    if( !strcmp( g_admin_namelog[ i ]->ip, ipa ) ) -    { -      schachtmeisterJudgement_t *j = &g_admin_namelog[ i ]->smj; - -      j->ratingTime = level.time; -      j->queryTime = 0; -      j->dispatchTime = 0; - -      j->rating = rating; - -      if( j->comment ) -        G_Free( j->comment ); - -      if( comment ) -      { -        j->comment = G_Alloc( strlen( comment ) + 1 ); -        strcpy( j->comment, comment ); -      } -      else -        j->comment = NULL; - -      if( g_admin_namelog[ i ]->slot != -1 ) -        admin_autobahn( g_entities + g_admin_namelog[ i ]->slot, j->rating ); -    } -  } -} - -qboolean G_admin_adjustban( gentity_t *ent, int skiparg ) -{ -  int bnum; -  int length; -  int expires; -  int time = trap_RealTime( NULL ); -  char duration[ 32 ] = {""}; -  char *reason; -  char bs[ 5 ]; +  int seconds; +  char search[ MAX_NAME_LENGTH ];    char secs[ MAX_TOKEN_CHARS ]; -  char mode = '\0'; +  char *reason; +  char duration[ MAX_DURATION_LENGTH ]; +  int i; +  addr_t ip; +  qboolean ipmatch = qfalse; +  namelog_t *match = NULL; +  qboolean cidr = qfalse; -  if( G_SayArgc() < 3 + skiparg ) +  if( trap_Argc() < 2 )    { -    ADMP( "^3!adjustban: ^7usage: !adjustban [ban#] [time] [reason]\n" ); +    ADMP( "^3ban: ^7usage: ban [name|slot|IP(/mask)] [duration] [reason]\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, bs, sizeof( bs ) ); -  bnum = atoi( bs ); -  if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1] ) +  trap_Argv( 1, search, sizeof( search ) ); +  trap_Argv( 2, secs, sizeof( secs ) ); + +  seconds = G_admin_parse_time( secs ); +  if( seconds <= 0 )    { -    ADMP( "^3!adjustban: ^7invalid ban#\n" ); -    return qfalse; +    seconds = 0; +    reason = ConcatArgs( 2 );    } -  if( g_admin_bans[ bnum - 1 ]->expires == 0 && -      !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) +  else    { -    ADMP( "^3!adjustban: ^7you cannot modify permanent bans\n" ); -    return qfalse; +    reason = ConcatArgs( 3 );    } -  if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) ) +  if( !*reason && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) )    { -    ADMP( "^3!adjustban: ^7you cannot modify Bans made by admins higher than you\n" ); +    ADMP( "^3ban: ^7you must specify a reason\n" );      return qfalse;    } -  if( g_adminMaxBan.integer && -      !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && -      g_admin_bans[ bnum - 1 ]->expires - time > G_admin_parse_time( g_adminMaxBan.string ) ) +  if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) )    { -    ADMP( va( "^3!adjustban: ^7your admin level cannot modify bans longer than %s\n", -              g_adminMaxBan.string ) ); -    return qfalse; -  } -  G_SayArgv( 2 + skiparg, secs, sizeof( secs ) ); -  if( secs[ 0 ] == '+' || secs[ 0 ] == '-' ) -    mode = secs[ 0 ]; -  length = G_admin_parse_time( &secs[ mode ? 1 : 0 ] ); -  if( length < 0 ) -    skiparg--; -  else -  { -    if( length ) +    int maximum = MAX( 1, G_admin_parse_time( g_adminMaxBan.string ) ); +    if( seconds == 0 || seconds > maximum )      { -      if( g_admin_bans[ bnum - 1 ]->expires == 0 && mode ) -      { -        ADMP( "^3!adjustban: ^7new duration must be explicit\n" ); -        return qfalse; -      } -      if( mode == '+' ) -        expires = g_admin_bans[ bnum - 1 ]->expires + length; -      else if( mode == '-' ) -        expires = g_admin_bans[ bnum - 1 ]->expires - length; -      else -        expires = time + length; -      if( expires <= time ) -      { -        ADMP( "^3!adjustban: ^7ban time must be positive\n" ); -        return qfalse; -      } -      if( g_adminMaxBan.integer && -          !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && -          expires - time > G_admin_parse_time( g_adminMaxBan.string ) ) -      { -        ADMP( va( "^3!adjustban: ^7ban length is limited to %s for your admin level\n", -                  g_adminMaxBan.string ) ); -        length = G_admin_parse_time( g_adminMaxBan.string ); -        expires = time + length; -      } +      ADMP( "^3ban: ^7you may not issue permanent bans\n" ); +      seconds = maximum;      } -    else if( G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) -      expires = 0; +  } + +  if( G_AddressParse( search, &ip ) ) +  { +    int max = ip.type == IPv4 ? 32 : 128; +    int min; + +    cidr = G_admin_permission( ent, ADMF_CAN_IP_BAN ); +    if( cidr ) +      min = ent ? max / 2 : 1;      else +      min = max; + +    if( ip.mask < min || ip.mask > max )      { -      ADMP( "^3!adjustban: ^7ban time must be positive\n" ); +      ADMP( va( "^3ban: ^7invalid netmask (%d is not in the range %d-%d)\n", +        ip.mask, min, max ) );        return qfalse;      } +    ipmatch = qtrue; -    g_admin_bans[ bnum - 1 ]->expires = expires; -    G_admin_duration( ( expires ) ? expires - time : -1, -      duration, sizeof( duration ) ); -  } -  reason = G_SayConcatArgs( 3 + skiparg ); -  if( *reason ) -    Q_strncpyz( g_admin_bans[ bnum - 1 ]->reason, reason, -      sizeof( g_admin_bans[ bnum - 1 ]->reason ) ); -  AP( va( "print \"^3!adjustban: ^7ban #%d for %s^7 has been updated by %s^7 " -    "%s%s%s%s%s\n\"", -    bnum, -    g_admin_bans[ bnum - 1 ]->name, -    ( ent ) ? G_admin_adminPrintName( ent ) : "console", -    ( length >= 0 ) ? "duration: " : "", -    duration, -    ( length >= 0 && *reason ) ? ", " : "", -    ( *reason ) ? "reason: " : "", -    reason ) ); -  if( ent ) { -    Q_strncpyz( g_admin_bans[ bnum - 1 ]->banner, G_admin_get_adminname( ent ), -      sizeof( g_admin_bans[ bnum - 1 ]->banner ) ); -    g_admin_bans[ bnum - 1 ]->bannerlevel = G_admin_level( ent ); -  } - -  if( g_admin.string[ 0 ] ) -    admin_writeconfig(); -  return qtrue; -} - +    for( match = level.namelogs; match; match = match->next ) +    { +      // skip players in the namelog who have already been banned +      if( match->banned ) +        continue; -qboolean G_admin_subnetban( gentity_t *ent, int skiparg ) -{ -  int bnum; -  int mask; -  unsigned int IPRlow = 0, IPRhigh = 0; -  char cIPRlow[ 32 ], cIPRhigh[ 32 ]; -  char bs[ 5 ]; -  char strmask[ 5 ]; -  char exl[2]; -  int k, IP[5]; -   -  if( G_SayArgc() < 3 + skiparg ) -  { -    ADMP( "^3!subnetban: ^7usage: !subnetban [ban#] [mask]\n" ); -    return qfalse; -  } -  G_SayArgv( 1 + skiparg, bs, sizeof( bs ) ); -  bnum = atoi( bs ); -  if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1] ) -  { -    ADMP( "^3!subnetban: ^7invalid ban#\n" ); -    return qfalse; -  } -  if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) ) -  { -    ADMP( "^3!subnetban: ^7you cannot subnetban Bans on bans made by admins higher than you\n" ); -    return qfalse; -  } +      for( i = 0; i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ]; i++ ) +      { +        if( G_AddressCompare( &ip, &match->ip[ i ] ) ) +          break; +      } +      if( i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ] ) +        break; +    } -  G_SayArgv( 2 + skiparg, strmask, sizeof( strmask ) ); -  mask = atoi( strmask ); -   -  if( mask >= 0 && mask <= 32) -  { -    G_SayArgv( 3 + skiparg, exl, sizeof( exl ) ); -    if( mask >= 0 && mask < 16 ) +    if( !match )      { -      if( ent ) +      if( cidr )        { -        ADMP( "^3!subnetban: ^7Only console may ban such a large network. Regular admins may only ban >=16.\n" ); -        return qfalse; +        ADMP( "^3ban: ^7no player found by that IP address; banning anyway\n" );        } -      if( strcmp(exl, "!") ) +      else        { -        ADMP( "^3!subnetban: ^1WARNING:^7 you are about to ban a large network, use !subnetban [ban] [mask] ! to force^7\n" ); +        ADMP( "^3ban: ^7no player found by that IP address\n" );          return qfalse;        }      } - -    memset( IP, 0, sizeof( IP ) ); -    sscanf(g_admin_bans[ bnum - 1 ]->ip, "%d.%d.%d.%d/%d", &IP[4], &IP[3], &IP[2], &IP[1], &IP[0]); -    for(k = 4; k >= 1; k--) -    { -      if( IP[k] ) -        IPRlow |= IP[k] << 8*(k-1); -    } -    IPRhigh = IPRlow; -    if( mask == 32 ) -    { -      Q_strncpyz(  -        g_admin_bans[ bnum - 1 ]->ip,  -        va("%i.%i.%i.%i", IP[4], IP[3], IP[2], IP[1]),  -        sizeof( g_admin_bans[ bnum - 1 ]->ip )  -      ); -    } -    else -    { -      Q_strncpyz(  -        g_admin_bans[ bnum - 1 ]->ip,  -        va("%i.%i.%i.%i/%i", IP[4], IP[3], IP[2], IP[1], mask ),  -        sizeof( g_admin_bans[ bnum - 1 ]->ip ) -      ); -      IPRlow &= ~((1 << (32-mask)) - 1); -      IPRhigh |= ((1 << (32-mask)) - 1); -    }    } -  else +  else if( !( match = G_NamelogFromString( ent, search ) ) || match->banned )    { -    ADMP( "^3!subnetban: ^7mask is out of range, please use 0-32 inclusive\n" ); +    ADMP( "^3ban: ^7no match\n" );      return qfalse;    } -  if( mask > 0 ) -  { -    Q_strncpyz(  -      cIPRlow,  -      va("%u.%u.%u.%u", (IPRlow & (255 << 24)) >> 24, (IPRlow & (255 << 16)) >> 16, (IPRlow & (255 << 8)) >> 8, IPRlow & 255),  -      sizeof( cIPRlow )  -    ); -    Q_strncpyz(  -      cIPRhigh,  -      va("%u.%u.%u.%u", (IPRhigh & (255 << 24)) >> 24, (IPRhigh & (255 << 16)) >> 16, (IPRhigh & (255 << 8)) >> 8, IPRhigh & 255),  -      sizeof( cIPRhigh )  -    ); -  } -  else -  { -    Q_strncpyz( cIPRlow, "0.0.0.0", sizeof( cIPRlow ) ); -    Q_strncpyz( cIPRhigh, "255.255.255.255", sizeof( cIPRhigh ) ); -     -  } -   -  AP( va( "print \"^3!subnetban: ^7ban #%d for %s^7 has been updated by %s^7 " -    "%s (%s - %s)\n\"", -    bnum, -    g_admin_bans[ bnum - 1 ]->name, -    ( ent ) ? G_admin_adminPrintName( ent ) : "console", -    g_admin_bans[ bnum - 1 ]->ip, -    cIPRlow, -    cIPRhigh) ); -  if( ent ) -    Q_strncpyz( g_admin_bans[ bnum - 1 ]->banner, G_admin_get_adminname( ent ), -      sizeof( g_admin_bans[ bnum - 1 ]->banner ) ); -  if( g_admin.string[ 0 ] ) -    admin_writeconfig(); -  return qtrue; -} - -qboolean G_admin_suspendban( gentity_t *ent, int skiparg ) -{ -  int bnum; -  int length; -  int timenow = 0; -  int expires = 0; -  char *arg; -  char bs[ 5 ]; -  char duration[ 32 ]; -  qtime_t qt; -  if( G_SayArgc() < 3 + skiparg ) -  { -    ADMP( "^3!suspendban: ^7usage: !suspendban [ban #] [length]\n" ); -    return qfalse; -  } -  G_SayArgv( 1 + skiparg, bs, sizeof( bs ) ); -  bnum = atoi( bs ); -  if( bnum < 1 || !g_admin_bans[ bnum - 1] ) +  if( ent && match && !admin_higher_guid( ent->client->pers.guid, match->guid ) )    { -    ADMP( "^3!suspendban: ^7invalid ban #\n" ); + +    ADMP( "^3ban: ^7sorry, but your intended victim has a higher admin" +      " level than you\n" );      return qfalse;    } -  if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) ) +  if( match && match->slot > -1 && level.clients[ match->slot ].pers.localClient )    { -    ADMP( "^3!suspendban: ^7you cannot suspend Bans made by admins higher than you\n" ); +    ADMP( "^3ban: ^7disconnecting the host would end the game\n" );      return qfalse;    } -  arg = G_SayConcatArgs( 2 + skiparg ); -  timenow = trap_RealTime( &qt ); -  length = G_admin_parse_time( arg ); - -  if( length < 0 ) -  { -    ADMP( "^3!suspendban: ^7invalid length\n" ); -    return qfalse; -  } -  if( length > MAX_ADMIN_BANSUSPEND_DAYS * 24 * 60 * 60 ) -  { -    length = MAX_ADMIN_BANSUSPEND_DAYS * 24 * 60 * 60; -    ADMP( va( "^3!suspendban: ^7maximum ban suspension is %d days\n", -      MAX_ADMIN_BANSUSPEND_DAYS ) ); -  } else if( g_admin_bans[ bnum - 1 ]->expires > 0 && length + timenow > g_admin_bans[ bnum - 1 ]->expires ) { -    length = g_admin_bans[ bnum - 1 ]->expires - timenow; -    G_admin_duration( length , duration, sizeof( duration ) ); -    ADMP( va( "^3!suspendban: ^7Suspension Duration trimmed to Ban duration: %s\n", -          duration ) ); -  } +  G_admin_duration( ( seconds ) ? seconds : -1, +    duration, sizeof( duration ) ); -  if ( length > 0 ) -  { -    expires = timenow + length; -  } -  if( g_admin_bans[ bnum - 1 ]->suspend == expires ) -  { -    ADMP( "^3!suspendban: ^7no change\n" ); -    return qfalse; -  } +  AP( va( "print \"^3ban:^7 %s^7 has been banned by %s^7 " +    "duration: %s, reason: %s\n\"", +    match ? match->name[ match->nameOffset ] : "an IP address", +    ( ent ) ? ent->client->pers.netname : "console", +    duration, +    ( *reason ) ? reason : "banned by admin" ) ); -  g_admin_bans[ bnum - 1 ]->suspend = expires; -  if ( length > 0 ) +  admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\"", +    seconds, +    match ? match->guid : "", +    match ? match->name[ match->nameOffset ] : "IP ban", +    reason ) ); +  if( ipmatch )    { -    G_admin_duration( length , duration, sizeof( duration ) ); -    AP( va( "print \"^3!suspendban: ^7ban #%d suspended for %s\n\"", -      bnum, duration ) ); +    if( match ) +    { +      admin_create_ban( ent, +        match->slot == -1 ? +          match->name[ match->nameOffset ] : +          level.clients[ match->slot ].pers.netname, +        match->guidless ? "" : match->guid, +        &ip, +        seconds, reason ); +    } +    else +    { +      admin_create_ban( ent, "IP ban", "", &ip, seconds, reason ); +    } +    admin_log( va( "[%s]", ip.str ) );    }    else    { -    AP( va( "print \"^3!suspendban: ^7ban #%d suspension removed\n\"", -      bnum ) ); +    // ban all IP addresses used by this player +    for( i = 0; i < MAX_NAMELOG_ADDRS && match->ip[ i ].str[ 0 ]; i++ ) +    { +      admin_create_ban( ent, +        match->slot == -1 ? +          match->name[ match->nameOffset ] : +          level.clients[ match->slot ].pers.netname, +        match->guidless ? "" : match->guid, +        &match->ip[ i ], +        seconds, reason ); +      admin_log( va( "[%s]", match->ip[ i ].str ) ); +    }    } +  if( match ) +    match->banned = qtrue; +    if( !g_admin.string[ 0 ] ) -    ADMP( "^3!adjustban: ^7WARNING g_admin not set, not saving ban to a file\n" ); +    ADMP( "^3ban: ^7WARNING g_admin not set, not saving ban to a file\n" );    else      admin_writeconfig(); +    return qtrue;  } -qboolean G_admin_unban( gentity_t *ent, int skiparg ) +qboolean G_admin_unban( gentity_t *ent )  {    int bnum; +  int time = trap_RealTime( NULL );    char bs[ 5 ]; -  int t; +  int i; +  g_admin_ban_t *ban; -  t = trap_RealTime( NULL ); -  if( G_SayArgc() < 2 + skiparg ) +  if( trap_Argc() < 2 )    { -    ADMP( "^3!unban: ^7usage: !unban [ban#]\n" ); +    ADMP( "^3unban: ^7usage: unban [ban#]\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, bs, sizeof( bs ) ); +  trap_Argv( 1, bs, sizeof( bs ) );    bnum = atoi( bs ); -  if( bnum < 1 || bnum > MAX_ADMIN_BANS || !g_admin_bans[ bnum - 1 ] ) -  { -    ADMP( "^3!unban: ^7invalid ban#\n" ); -    return qfalse; -  } -  if( g_admin_bans[ bnum - 1 ]->expires == 0 && -      !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) ) +  for( ban = g_admin_bans, i = 1; ban && i < bnum; ban = ban->next, i++ ) +    ; +  if( i != bnum || !ban )    { -    ADMP( "^3!unban: ^7you cannot remove permanent bans\n" ); +    ADMP( "^3unban: ^7invalid ban#\n" );      return qfalse;    } -  if( g_admin_bans[ bnum - 1 ]->bannerlevel > G_admin_level( ent ) ) +  if( ban->expires > 0 && ban->expires - time <= 0 )    { -    ADMP( "^3!unban: ^7you cannot Remove Bans made by admins higher than you\n" ); +    ADMP( "^3unban: ^7ban already expired\n" );      return qfalse;    } -  if( g_adminMaxBan.integer && -      !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && -      g_admin_bans[ bnum - 1 ]->expires - t > G_admin_parse_time( g_adminMaxBan.string ) ) +  if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && +    ( ban->expires == 0 || ( ban->expires - time > MAX( 1, +      G_admin_parse_time( g_adminMaxBan.string ) ) ) ) )    { -    ADMP( va( "^3!unban: ^7your admin level cannot remove bans longer than %s\n", -              g_adminMaxBan.string ) ); +    ADMP( "^3unban: ^7you cannot remove permanent bans\n" );      return qfalse;    } -  g_admin_bans[ bnum -1 ]->expires = t; -  AP( va( "print \"^3!unban: ^7ban #%d for %s^7 has been removed by %s\n\"", +  admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\": [%s]", +    ban->expires ? ban->expires - time : 0, ban->guid, ban->name, ban->reason, +    ban->ip.str ) ); +  AP( va( "print \"^3unban: ^7ban #%d for %s^7 has been removed by %s\n\"",            bnum, -          g_admin_bans[ bnum - 1 ]->name, -          ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -  if( g_admin.string[ 0 ] ) -    admin_writeconfig(); +          ban->name, +          ( ent ) ? ent->client->pers.netname : "console" ) ); +  ban->expires = time; +  admin_writeconfig();    return qtrue;  } -qboolean G_admin_putteam( gentity_t *ent, int skiparg ) +qboolean G_admin_addlayout( gentity_t *ent )  { -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], team[ 7 ], err[ MAX_STRING_CHARS ]; -  gentity_t *vic; -  pTeam_t teamnum = PTE_NONE; -  char teamdesc[ 32 ] = {"spectators"}; -  char secs[ 7 ]; -  int seconds = 0; -  qboolean useDuration = qfalse; - -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  G_SayArgv( 2 + skiparg, team, sizeof( team ) ); -  if( G_SayArgc() < 3 + skiparg ) -  { -    ADMP( "^3!putteam: ^7usage: !putteam [name] [h|a|s] (duration)\n" ); -    return qfalse; -  } - -  if( G_ClientNumbersFromString( name, pids ) != 1 ) -  { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!putteam: ^7%s\n", err ) ); -    return qfalse; -  } -  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) -  { -    ADMP( "^3!putteam: ^7sorry, but your intended victim has a higher " -        " admin level than you\n" ); -    return qfalse; -  } -  vic = &g_entities[ pids[ 0 ] ]; +  char layout[ MAX_QPATH ]; -  if ( vic->client->sess.invisible == qtrue ) +  if( trap_Argc( ) != 2 )    { -    ADMP( "^3!putteam: ^7invisible players cannot join a team\n" ); +    ADMP( "^3addlayout: ^7usage: addlayout <layoutelements>\n" );      return qfalse;    } -  switch( team[ 0 ] ) -  { -  case 'a': -    teamnum = PTE_ALIENS; -    Q_strncpyz( teamdesc, "aliens", sizeof( teamdesc ) ); -    break; -  case 'h': -    teamnum = PTE_HUMANS; -    Q_strncpyz( teamdesc, "humans", sizeof( teamdesc ) ); -    break; -  case 's': -    teamnum = PTE_NONE; -    break; -  default: -    ADMP( va( "^3!putteam: ^7unknown team %c\n", team[ 0 ] ) ); -    return qfalse; -  } -  //duration code -  if( G_SayArgc() > 3 + skiparg ) { -    //can only lock players in spectator -    if ( teamnum != PTE_NONE ) -    { -      ADMP( "^3!putteam: ^7You can only lock a player into the spectators team\n" ); -      return qfalse; -    } -    G_SayArgv( 3 + skiparg, secs, sizeof( secs ) ); -    seconds = G_admin_parse_time( secs ); -    useDuration = qtrue; -  } +  trap_Argv( 1, layout, sizeof( layout ) ); -  if( vic->client->pers.teamSelection == teamnum && teamnum != PTE_NONE ) -  { -    ADMP( va( "^3!putteam: ^7%s ^7is already on the %s team\n", vic->client->pers.netname, teamdesc ) ); -    return qfalse; -  } +  G_LayoutLoad( layout ); -  if( useDuration == qtrue && seconds > 0 ) { -    vic->client->pers.specExpires = level.time + ( seconds * 1000 ); -  } -  G_ChangeTeam( vic, teamnum ); - -  AP( va( "print \"^3!putteam: ^7%s^7 put %s^7 on to the %s team%s\n\"", -          ( ent ) ? G_admin_adminPrintName( ent ) : "console", -          vic->client->pers.netname, teamdesc, -          ( seconds ) ? va( " for %i seconds", seconds ) : "" ) ); +  AP( va( "print \"^3addlayout: ^7some layout elements have been placed by %s\n\"", +          ent ? ent->client->pers.netname : "console" ) );    return qtrue;  } -qboolean G_admin_seen(gentity_t *ent, int skiparg ) +qboolean G_admin_adjustban( gentity_t *ent )  { -  char name[ MAX_NAME_LENGTH ]; -  char search[ MAX_NAME_LENGTH ]; -  char sduration[ 32 ]; -  qboolean numeric = qtrue; -  int i, j; -  int id = -1; -  int count = 0; -  int t; -  qtime_t qt; -  gentity_t *vic; -  qboolean ison; +  int bnum; +  int length, maximum; +  int expires; +  int time = trap_RealTime( NULL ); +  char duration[ MAX_DURATION_LENGTH ] = {""}; +  char *reason; +  char bs[ 5 ]; +  char secs[ MAX_TOKEN_CHARS ]; +  char mode = '\0'; +  g_admin_ban_t *ban; +  int mask = 0; +  int i; +  int skiparg = 0; -  if( G_SayArgc() < 2 + skiparg ) +  if( trap_Argc() < 3 )    { -    ADMP( "^3!seen: ^7usage: !seen [name|admin#]\n" ); +    ADMP( "^3adjustban: ^7usage: adjustban [ban#] [/mask] [duration] [reason]" +      "\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  G_SanitiseString( name, search, sizeof( search ) ); -  for( i = 0; i < sizeof( search ) && search[ i ] ; i++ ) +  trap_Argv( 1, bs, sizeof( bs ) ); +  bnum = atoi( bs ); +  for( ban = g_admin_bans, i = 1; ban && i < bnum; ban = ban->next, i++ ); +  if( i != bnum || !ban )    { -    if( search[ i ] < '0' || search[ i ] > '9' ) -    { -      numeric = qfalse; -      break; -    } +    ADMP( "^3adjustban: ^7invalid ban#\n" ); +    return qfalse;    } - -  if( numeric ) +  maximum = MAX( 1, G_admin_parse_time( g_adminMaxBan.string ) ); +  if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && +    ( ban->expires == 0 || ban->expires - time > maximum ) )    { -    id = atoi( name ); -    search[ 0 ] = '\0'; +    ADMP( "^3adjustban: ^7you cannot modify permanent bans\n" ); +    return qfalse;    } - -  ADMBP_begin(); -  t = trap_RealTime( &qt ); - -  for( i = 0; i < level.maxclients && count < 10; i ++ ) +  trap_Argv( 2, secs, sizeof( secs ) ); +  if( secs[ 0 ] == '/' )    { -    vic = &g_entities[ i ]; +    int max = ban->ip.type == IPv6 ? 128 : 32; +    int min; -    if( !vic->client || vic->client->pers.connected != CON_CONNECTED ) -      continue; - -    G_SanitiseString( vic->client->pers.netname, name, sizeof( name ) ); +    if( G_admin_permission( ent, ADMF_CAN_IP_BAN ) ) +      min = ent ? max / 2 : 1; +    else +      min = max; -    if( i == id || (search[ 0 ] && strstr( name, search ) ) ) +    mask = atoi( secs + 1 ); +    if( mask < min || mask > max )      { -      if ( vic->client->sess.invisible == qfalse ) -      { -        ADMBP( va( "^3%4d ^7%s^7 is currently playing\n", i, vic->client->pers.netname ) ); -        count++; -      } +      ADMP( va( "^3adjustban: ^7invalid netmask (%d is not in range %d-%d)\n", +        mask, min, max ) ); +      return qfalse;      } +    trap_Argv( 3 + skiparg++, secs, sizeof( secs ) );    } -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] && count < 10; i++ ) +  if( secs[ 0 ] == '+' || secs[ 0 ] == '-' ) +    mode = secs[ 0 ]; +  length = G_admin_parse_time( &secs[ mode ? 1 : 0 ] ); +  if( length < 0 ) +    skiparg--; +  else    { -    G_SanitiseString( g_admin_admins[ i ]->name, name, sizeof( name ) ); -    if( i + MAX_CLIENTS == id || (search[ 0 ] && strstr( name, search ) ) ) +    if( length )      { -      ison = qfalse; -      for( j = 0; j < level.maxclients; j++ ) +      if( ban->expires == 0 && mode )        { -        vic = &g_entities[ j ]; -        if( !vic->client || vic->client->pers.connected != CON_CONNECTED ) -          continue; -        G_SanitiseString( vic->client->pers.netname, name, sizeof( name ) ); -        if( !Q_stricmp( vic->client->pers.guid, g_admin_admins[ i ]->guid ) -          && strstr( name, search ) ) -        { -          if ( vic->client->sess.invisible == qfalse ) -          { -            ison = qtrue; -            break; -          } -        } -      } - -      if( ison ) -      { -        if( id == -1 ) -          continue; -        ADMBP( va( "^3%4d ^7%s^7 is currently playing\n", -          i + MAX_CLIENTS, g_admin_admins[ i ]->name ) ); +        ADMP( "^3adjustban: ^7new duration must be explicit\n" ); +        return qfalse;        } +      if( mode == '+' ) +        expires = ban->expires + length; +      else if( mode == '-' ) +        expires = ban->expires - length;        else +        expires = time + length; +      if( expires <= time )        { -        G_admin_duration( t - g_admin_admins[ i ]->seen, -          sduration, sizeof( sduration ) ); -        ADMBP( va( "%4d %s^7 last seen %s%s\n", -          i + MAX_CLIENTS, g_admin_admins[ i ]->name , -          ( g_admin_admins[ i ]->seen ) ? sduration : "", -          ( g_admin_admins[ i ]->seen ) ? " ago" : "time is unknown" ) ); +        ADMP( "^3adjustban: ^7ban duration must be positive\n" ); +        return qfalse;        } -      count++; -    } -  } - -  if( search[ 0 ] ) -    ADMBP( va( "^3!seen:^7 found %d player%s matching '%s'\n", -      count, (count == 1) ? "" : "s", search ) ); -  else if ( !count ) -    ADMBP( "^3!seen:^7 no one connectd by that slot number\n" ); - -  ADMBP_end(); -  return qtrue; -} - -void G_admin_seen_update( char *guid ) -{ -  int i; - -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ] ; i++ ) -  { -    if( !Q_stricmp( g_admin_admins[ i ]->guid, guid ) ) -    { -      qtime_t qt; - -      g_admin_admins[ i ]->seen = trap_RealTime( &qt ); -      return;      } -  } -} - -void G_admin_adminlog_cleanup( void ) -{ -  int i; - -  for( i = 0; i < MAX_ADMIN_ADMINLOGS && g_admin_adminlog[ i ]; i++ ) -  { -    G_Free( g_admin_adminlog[ i ] ); -    g_admin_adminlog[ i ] = NULL; -  } - -  admin_adminlog_index = 0; -} - -void G_admin_adminlog_log( gentity_t *ent, char *command, char *args, int skiparg, qboolean success ) -{ -  g_admin_adminlog_t *adminlog; -  int previous; -  int count = 1; -  int i; - -  if( !command ) -    return; - -  if( !Q_stricmp( command, "adminlog" ) || -      !Q_stricmp( command, "admintest" ) || -      !Q_stricmp( command, "help" ) || -      !Q_stricmp( command, "info" ) || -      !Q_stricmp( command, "listadmins" ) || -      !Q_stricmp( command, "listplayers" ) || -      !Q_stricmp( command, "namelog" ) || -      !Q_stricmp( command, "showbans" ) || -      !Q_stricmp( command, "seen" ) || -      !Q_stricmp( command, "time" ) ) -    return; - -  previous = admin_adminlog_index - 1; -  if( previous < 0 ) -    previous = MAX_ADMIN_ADMINLOGS - 1; - -  if( g_admin_adminlog[ previous ] ) -    count = g_admin_adminlog[ previous ]->id + 1; - -  if( g_admin_adminlog[ admin_adminlog_index ] ) -    adminlog = g_admin_adminlog[ admin_adminlog_index ]; -  else -    adminlog = G_Alloc( sizeof( g_admin_adminlog_t ) ); - -  memset( adminlog, 0, sizeof( *adminlog ) ); -  adminlog->id = count; -  adminlog->time = level.time - level.startTime; -  adminlog->success = success; -  Q_strncpyz( adminlog->command, command, sizeof( adminlog->command ) ); - -  if ( args ) -    Q_strncpyz( adminlog->args, args, sizeof( adminlog->args ) ); -  else -    Q_strncpyz( adminlog->args, G_SayConcatArgs( 1 + skiparg ), sizeof( adminlog->args ) ); - -  if( ent ) -  { -    qboolean found = qfalse; -    // real admin name -    for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) +    else +      length = expires = 0; +    if( !G_admin_permission( ent, ADMF_CAN_PERM_BAN ) && +      ( length == 0 || length > maximum ) )      { -      if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) ) -      { -        Q_strncpyz( adminlog->name, g_admin_admins[ i ]->name, sizeof( adminlog->name ) ); -        found = qtrue; -        break; -      } +      ADMP( "^3adjustban: ^7you may not issue permanent bans\n" ); +      expires = time + maximum;      } -    if( !found ) -      Q_strncpyz( adminlog->name, ent->client->pers.netname, sizeof( adminlog->name ) ); -    adminlog->level = ent->client->pers.adminLevel; +    ban->expires = expires; +    G_admin_duration( ( expires ) ? expires - time : -1, duration, +      sizeof( duration ) );    } -  else +  if( mask )    { -    Q_strncpyz( adminlog->name, "console", sizeof( adminlog->name ) ); -    adminlog->level = 10000; +    char *p = strchr( ban->ip.str, '/' ); +    if( !p ) +      p = ban->ip.str + strlen( ban->ip.str ); +    if( mask == ( ban->ip.type == IPv6 ? 128 : 32 ) ) +      *p = '\0'; +    else +      Com_sprintf( p, sizeof( ban->ip.str ) - ( p - ban->ip.str ), "/%d", mask ); +    ban->ip.mask = mask;    } - -  g_admin_adminlog[ admin_adminlog_index ] = adminlog; -  admin_adminlog_index++; -  if( admin_adminlog_index >= MAX_ADMIN_ADMINLOGS ) -    admin_adminlog_index = 0; +  reason = ConcatArgs( 3 + skiparg ); +  if( *reason ) +    Q_strncpyz( ban->reason, reason, sizeof( ban->reason ) ); +  admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\": \"%s" S_COLOR_WHITE "\": [%s]", +    ban->expires ? ban->expires - time : 0, ban->guid, ban->name, ban->reason, +    ban->ip.str ) ); +  AP( va( "print \"^3adjustban: ^7ban #%d for %s^7 has been updated by %s^7 " +    "%s%s%s%s%s%s\n\"", +    bnum, +    ban->name, +    ( ent ) ? ent->client->pers.netname : "console", +    ( mask ) ? +      va( "netmask: /%d%s", mask, +        ( length >= 0 || *reason ) ? ", " : "" ) : "", +    ( length >= 0 ) ? "duration: " : "", +    duration, +    ( length >= 0 && *reason ) ? ", " : "", +    ( *reason ) ? "reason: " : "", +    reason ) ); +  if( ent ) +    Q_strncpyz( ban->banner, ent->client->pers.netname, sizeof( ban->banner ) ); +  admin_writeconfig(); +  return qtrue;  } -qboolean G_admin_adminlog( gentity_t *ent, int skiparg ) +qboolean G_admin_putteam( gentity_t *ent )  { -  g_admin_adminlog_t *results[ 10 ]; -  int result_index = 0; -  char *search_cmd = NULL; -  char *search_name = NULL; -  int index; -  int skip = 0; -  int skipped = 0; -  int checked = 0; -  char n1[ MAX_NAME_LENGTH ]; -  char fmt_name[ 16 ]; -  char argbuf[ 32 ]; -  int name_length = 12; -  int max_id = 0; -  int i; -  qboolean match; - -  memset( results, 0, sizeof( results ) ); - -  index = admin_adminlog_index; -  for( i = 0; i < 10; i++ ) -  { -    int prev; - -    prev = index - 1; -    if( prev < 0 ) -      prev = MAX_ADMIN_ADMINLOGS - 1; -    if( !g_admin_adminlog[ prev ] ) -      break; -    if( g_admin_adminlog[ prev ]->id > max_id ) -      max_id = g_admin_adminlog[ prev ]->id; -    index = prev; -  } - -  if( G_SayArgc() > 1 + skiparg ) -  { -    G_SayArgv( 1 + skiparg, argbuf, sizeof( argbuf ) ); -    if( ( *argbuf >= '0' && *argbuf <= '9' ) || *argbuf == '-' ) -    { -      int id; - -      id = atoi( argbuf ); -      if( id < 0 ) -        id += ( max_id - 9 ); -      else if( id <= max_id - MAX_ADMIN_ADMINLOGS ) -        id = max_id - MAX_ADMIN_ADMINLOGS + 1; - -      if( id + 9 >= max_id ) -        id = max_id - 9; -      if( id < 1 ) -        id = 1; -      for( i = 0; i < MAX_ADMIN_ADMINLOGS; i++ ) -      { -        if( g_admin_adminlog[i] && g_admin_adminlog[ i ]->id == id ) -        { -          index = i; -          break; -        } -      } -    } -    else if ( *argbuf == '!' ) -    { -      search_cmd = argbuf + 1; -    } -    else -    { -      search_name = argbuf; -    } - -    if( G_SayArgc() > 2 + skiparg && ( search_cmd || search_name ) ) -    { -      char skipbuf[ 4 ]; -      G_SayArgv( 2 + skiparg, skipbuf, sizeof( skipbuf ) ); -      skip = atoi( skipbuf ); -    } -  } - -  if( search_cmd || search_name ) -  { -    g_admin_adminlog_t *result_swap[ 10 ]; - -    memset( result_swap, 0, sizeof( result_swap ) ); - -    index = admin_adminlog_index - 1; -    if( index < 0 ) -      index = MAX_ADMIN_ADMINLOGS - 1; - -    while( g_admin_adminlog[ index ] && -      checked < MAX_ADMIN_ADMINLOGS && -      result_index < 10 ) -    { -      match = qfalse; -      if( search_cmd ) -      { -        if( !Q_stricmp( search_cmd, g_admin_adminlog[ index ]->command ) ) -          match = qtrue; -      } -      if( search_name ) -      { -        G_SanitiseString( g_admin_adminlog[ index ]->name, n1, sizeof( n1 ) ); -        if( strstr( n1, search_name ) ) -          match = qtrue; -      } - -      if( match && skip > 0 ) -      { -        match = qfalse; -        skip--; -        skipped++; -      } -      if( match ) -      { -        result_swap[ result_index ] = g_admin_adminlog[ index ]; -        result_index++; -      } - -      checked++; -      index--; -      if( index < 0 ) -        index = MAX_ADMIN_ADMINLOGS - 1; -    } -    // search runs backwards, turn it around -    for( i = 0; i < result_index; i++ ) -      results[ i ] = result_swap[ result_index - i - 1 ]; -  } -  else -  { -    while( g_admin_adminlog[ index ] && result_index < 10 ) -    { -      results[ result_index ] = g_admin_adminlog[ index ]; -      result_index++; -      index++; -      if( index >= MAX_ADMIN_ADMINLOGS ) -        index = 0; -    } -  } +  int pid; +  char name[ MAX_NAME_LENGTH ], team[ sizeof( "spectators" ) ], +       err[ MAX_STRING_CHARS ]; +  gentity_t *vic; +  team_t teamnum = TEAM_NONE; -  for( i = 0; results[ i ] && i < 10; i++ ) +  trap_Argv( 1, name, sizeof( name ) ); +  trap_Argv( 2, team, sizeof( team ) ); +  if( trap_Argc() < 3 )    { -    int l; - -    G_DecolorString( results[ i ]->name, n1 ); -    l = strlen( n1 ); -    if( l > name_length ) -      name_length = l; -  } -  ADMBP_begin( ); -  for( i = 0; results[ i ] && i < 10; i++ ) -  { -    char levelbuf[ 8 ]; -    int t; - -    t = results[ i ]->time / 1000; -    G_DecolorString( results[ i ]->name, n1 ); -    Com_sprintf( fmt_name, sizeof( fmt_name ), "%%%ds",  -      ( name_length + strlen( results[ i ]->name ) - strlen( n1 ) ) ); -    Com_sprintf( n1, sizeof( n1 ), fmt_name, results[ i ]->name ); -    Com_sprintf( levelbuf, sizeof( levelbuf ), "%2d", results[ i ]->level ); -    ADMBP( va( "%s%3d %3d:%02d %2s ^7%s^7 %s!%s ^7%s^7\n", -      ( results[ i ]->success ) ? "^7" : "^1", -      results[ i ]->id, -      t / 60, t % 60, -      ( results[ i ]->level ) < 10000 ? levelbuf : " -", -      n1, -      ( results[ i ]->success ) ? "^3" : "^1", -      results[ i ]->command, -      results[ i ]->args ) ); -  } -  if( search_cmd || search_name ) -  { -    ADMBP( va( "^3!adminlog:^7 Showing %d matches for '%s^7'.", -      result_index, -      argbuf ) ); -    if( checked < MAX_ADMIN_ADMINLOGS && g_admin_adminlog[ checked ] ) -      ADMBP( va( " run '!adminlog %s^7 %d' to see more", -       argbuf, -       skipped + result_index ) ); -    ADMBP( "\n" ); -  } -  else if ( results[ 0 ] ) -  { -    ADMBP( va( "^3!adminlog:^7 Showing %d - %d of %d.\n", -      results[ 0 ]->id, -      results[ result_index - 1 ]->id, -      max_id ) ); -  } -  else -  { -    ADMBP( "^3!adminlog:^7 log is empty.\n" ); +    ADMP( "^3putteam: ^7usage: putteam [name] [h|a|s]\n" ); +    return qfalse;    } -  ADMBP_end( ); -  return qtrue; -} - -qboolean G_admin_map( gentity_t *ent, int skiparg ) -{ -  char map[ MAX_QPATH ]; -  char layout[ MAX_QPATH ] = { "" }; -   -  if( G_SayArgc( ) < 2 + skiparg ) +  if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 )    { -    ADMP( "^3!map: ^7usage: !map [map] (layout)\n" ); +    ADMP( va( "^3putteam: ^7%s", err ) );      return qfalse;    } - -  G_SayArgv( skiparg + 1, map, sizeof( map ) ); - -  if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) ) +  vic = &g_entities[ pid ]; +  if( !admin_higher( ent, vic ) )    { -    ADMP( va( "^3!map: ^7invalid map name '%s'\n", map ) ); +    ADMP( "^3putteam: ^7sorry, but your intended victim has a higher " +        " admin level than you\n" );      return qfalse;    } - -  if( G_SayArgc( ) > 2 + skiparg ) +  teamnum = G_TeamFromString( team ); +  if( teamnum == NUM_TEAMS )    { -    G_SayArgv( skiparg + 2, layout, sizeof( layout ) ); -    if( !Q_stricmp( layout, "*BUILTIN*" ) || -      trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), -        NULL, FS_READ ) > 0 ) -    { -      trap_Cvar_Set( "g_layouts", layout ); -    } -    else -    { -      ADMP( va( "^3!map: ^7invalid layout name '%s'\n", layout ) ); -      return qfalse; -    } +    ADMP( va( "^3putteam: ^7unknown team %s\n", team ) ); +    return qfalse;    } +  if( vic->client->pers.teamSelection == teamnum ) +    return qfalse; +  admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", pid, vic->client->pers.guid, +    vic->client->pers.netname ) ); +  G_ChangeTeam( vic, teamnum ); -  trap_SendConsoleCommand( EXEC_APPEND, va( "map %s", map ) ); -  level.restarted = qtrue; -  AP( va( "print \"^3!map: ^7map '%s' started by %s^7 %s\n\"", map, -          ( ent ) ? G_admin_adminPrintName( ent ) : "console", -          ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) ); -  G_admin_maplog_result( "M" ); +  AP( va( "print \"^3putteam: ^7%s^7 put %s^7 on to the %s team\n\"", +          ( ent ) ? ent->client->pers.netname : "console", +          vic->client->pers.netname, BG_TeamName( teamnum ) ) );    return qtrue;  } -qboolean G_admin_devmap( gentity_t *ent, int skiparg ) +qboolean G_admin_changemap( gentity_t *ent )  {    char map[ MAX_QPATH ];    char layout[ MAX_QPATH ] = { "" }; -  if( G_SayArgc( ) < 2 + skiparg ) +  if( trap_Argc( ) < 2 )    { -    ADMP( "^3!devmap: ^7usage: !devmap [map] (layout)\n" ); +    ADMP( "^3changemap: ^7usage: changemap [map] (layout)\n" );      return qfalse;    } -  G_SayArgv( skiparg + 1, map, sizeof( map ) ); +  trap_Argv( 1, map, sizeof( map ) ); -  if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) ) +  if( !G_MapExists( map ) )    { -    ADMP( va( "^3!devmap: ^7invalid map name '%s'\n", map ) ); +    ADMP( va( "^3changemap: ^7invalid map name '%s'\n", map ) );      return qfalse;    } -  if( G_SayArgc( ) > 2 + skiparg ) +  if( trap_Argc( ) > 2 )    { -    G_SayArgv( skiparg + 2, layout, sizeof( layout ) ); -    if( !Q_stricmp( layout, "*BUILTIN*" ) || -      trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), -        NULL, FS_READ ) > 0 ) +    trap_Argv( 2, layout, sizeof( layout ) ); +    if( G_LayoutExists( map, layout ) )      { -      trap_Cvar_Set( "g_layouts", layout ); +      trap_Cvar_Set( "g_nextLayout", layout );      }      else      { -      ADMP( va( "^3!devmap: ^7invalid layout name '%s'\n", layout ) ); +      ADMP( va( "^3changemap: ^7invalid layout name '%s'\n", layout ) );        return qfalse;      }    } +  admin_log( map ); +  admin_log( layout ); -  trap_SendConsoleCommand( EXEC_APPEND, va( "devmap %s", map ) ); +  G_MapConfigs( map ); +  trap_SendConsoleCommand( EXEC_APPEND, va( "%smap \"%s\"\n", +                             ( g_cheats.integer ? "dev" : "" ), map ) );    level.restarted = qtrue; -  AP( va( "print \"^3!devmap: ^7map '%s' started by %s^7 with cheats %s\n\"", map, -          ( ent ) ? G_admin_adminPrintName( ent ) : "console", +  AP( va( "print \"^3changemap: ^7map '%s' started by %s^7 %s\n\"", map, +          ( ent ) ? ent->client->pers.netname : "console",            ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) ); -  G_admin_maplog_result( "D" );    return qtrue;  } -void G_admin_maplog_update( void ) +qboolean G_admin_mute( gentity_t *ent )  { -  char map[ 64 ]; -  char maplog[ MAX_CVAR_VALUE_STRING ]; -  char *ptr; -  int count = 0; - -  trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - -  Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) ); -  ptr = maplog; -  while( *ptr && count < MAX_ADMIN_MAPLOG_LENGTH )  -  { -    while( *ptr != ' ' && *ptr != '\0' ) ptr++; - -    count++; -    if( count >= MAX_ADMIN_MAPLOG_LENGTH ) -    { -      *ptr = '\0'; -    } - -    if( *ptr == ' ' ) ptr++; -  } - -  trap_Cvar_Set( "g_adminMapLog", va( "%s%s%s", -    map, -    ( maplog[0] != '\0'  ) ? " " : "", -    maplog ) ); -} - -void G_admin_maplog_result( char *flag ) -{ -  static int lastTime = 0; -  char maplog[ MAX_CVAR_VALUE_STRING ]; -  int t; - -  if( !flag ) -    return; - -  // avoid race when called in same frame -  if( level.time == lastTime ) -    return; - -  lastTime = level.time; - -  if( g_adminMapLog.string[ 0 ] && -    g_adminMapLog.string[ 1 ] == ';' ) -  { -    // only one result allowed -    return; -  } -   -  if ( level.surrenderTeam != PTE_NONE ) -  { -    if( flag[ 0 ] == 'a' ) -    { -      if( level.surrenderTeam == PTE_HUMANS ) -        flag = "A"; -    } -    else if( flag[ 0 ] == 'h' ) -    { -      if( level.surrenderTeam == PTE_ALIENS ) -        flag = "H"; -    } -  } - -  t = ( level.time - level.startTime ) / 1000; -  Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) ); -  trap_Cvar_Set( "g_adminMapLog", va( "%1s;%03d:%02d;%s", -    flag, -    t / 60, t % 60, -    maplog ) ); -} - - -qboolean G_admin_maplog( gentity_t *ent, int skiparg ) -{ -  char maplog[ MAX_CVAR_VALUE_STRING ]; -  char *ptr; -  int count = 0; - -  Q_strncpyz( maplog, g_adminMapLog.string, sizeof( maplog ) ); - -  ADMBP_begin( ); -  ptr = maplog; -  while( *ptr != '\0' && count < MAX_ADMIN_MAPLOG_LENGTH + 1 ) -  { -    char *end; -    const char *result = NULL; -    char *clock = NULL; -    char *colon; - -    end = ptr; -    while( *end != ' ' && *end != '\0' ) end++; -    if( *end == ' ' ) -    { -      *end = '\0'; -      end++; -    } - -     if( ptr[ 0 ] && ptr[ 1 ] == ';' ) -     { -       switch( ptr[ 0 ] ) -       { -         case 't': -           result = "^7tie"; -           break; -         case 'a': -           result = "^1Alien win"; -           break; -         case 'A': -           result = "^1Alien win ^7/ Humans admitted defeat"; -           break; -         case 'h': -           result = "^4Human win"; -           break; -         case 'H': -           result = "^4Human win ^7/ Aliens admitted defeat"; -           break; -         case 'd': -           result = "^5draw vote"; -           break; -         case 'N': -           result = "^6admin loaded next map"; -           break; -         case 'r': -           result = "^2restart vote"; -           break; -         case 'R': -           result = "^6admin restarted map"; -           break; -         case 'm': -           result = "^2map vote"; -           break; -         case 'M': -           result = "^6admin changed map"; -           break; -         case 'D': -           result = "^6admin loaded devmap"; -           break; -         default: -           result = ""; -           break; -       } -       ptr += 2; -       colon = strchr( ptr, ';' ); -       if ( colon ) -       { -         clock = ptr; -         ptr = colon + 1; -         *colon = '\0'; -  -         // right justification with -6%s doesnt work.. -         if( clock[ 0 ] == '0' && clock[ 1 ] != ':' ) -         { -           if( clock[ 1 ] == '0' && clock[ 2 ] != ':' ) -             clock[ 1 ] = ' '; -           clock[ 0 ] = ' '; -         } -       } -     } -     else if( count == 0 ) -     { -       result = "^3current map"; -       clock = "  -:--"; -     } -  -     ADMBP( va( "%s%20s %-6s %s^7\n", -       ( count == 0 ) ? "^3" : "^7", -       ptr, -       ( clock ) ? clock : "", -       ( result ) ? result : "" ) ); - -    ptr = end; -    count++; -  } -  ADMBP_end( ); - -  return qtrue; -} - -qboolean G_admin_layoutsave( gentity_t *ent, int skiparg ) -{ -  char layout[ MAX_QPATH ]; - -  if( G_SayArgc( ) < 2 + skiparg ) -  { -    ADMP( "^3!layoutsave: ^7usage: !layoutsave [layout]\n" ); -    return qfalse; -  } - -  G_SayArgv( skiparg + 1, layout, sizeof( layout ) ); - -  trap_SendConsoleCommand( EXEC_APPEND, va( "layoutsave %s", layout ) ); -  AP( va( "print \"^3!layoutsave: ^7layout saved as '%s' by %s\n\"", layout, -          ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -  return qtrue; -} - -qboolean G_admin_demo( gentity_t *ent, int skiparg ) -{ -  if( !ent ) -  { -    ADMP( "!demo: console can not use demo.\n" ); -    return qfalse; -  } - -  ent->client->pers.ignoreAdminWarnings = !( ent->client->pers.ignoreAdminWarnings ); - -  ADMP( va( "^3!demo: ^7your visibility of admin chat is now %s\n", -    ( ent->client->pers.ignoreAdminWarnings ) ? "^1disabled" : "^2enabled" ) ); - -  return qtrue; -} - -qboolean G_admin_mute( gentity_t *ent, int skiparg ) -{ -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; -  char command[ MAX_ADMIN_CMD_LEN ], *cmd; -  gentity_t *vic; -  char secs[ 7 ]; -  int seconds = 0; - -  G_SayArgv( skiparg, command, sizeof( command ) ); -  cmd = command; - -  if( cmd && *cmd == '!' ) -    cmd++; - -  if( G_SayArgc() < 2 + skiparg ) -  { -    ADMP( va( "^3!%s: ^7usage: !%s [name|slot#] (duration)\n", cmd, cmd ) ); -    return qfalse; -  } - -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); - -  if( G_ClientNumbersFromString( name, pids ) != 1 ) -  { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!%s: ^7%s\n", cmd, err ) ); -    return qfalse; -  } - -  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) -  { -    ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin" -        " level than you\n", cmd ) ); -    return qfalse; -  } - -  vic = &g_entities[ pids[ 0 ] ]; -  if( !Q_stricmp( cmd, "unmute" ) ) -  { -    if( vic->client->pers.muted == qfalse ) -    { -      ADMP( "^3!unmute: ^7player is not currently muted\n" ); -      return qtrue; -    } - -    vic->client->pers.muteExpires = 0; -    vic->client->pers.muted = qfalse; - -    CPx( pids[ 0 ], "cp \"^1You have been unmuted\"" ); -    AP( va( "print \"^3!unmute: ^7%s^7 has been unmuted by %s\n\"", -        vic->client->pers.netname, -        ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -  } else { -    // Duration -    if( G_SayArgc() > 2 + skiparg ) -    { -      G_SayArgv( 2 + skiparg, secs, sizeof( secs ) ); -      seconds = G_admin_parse_time( secs ); -      vic->client->pers.muteExpires = level.time + ( seconds * 1000 ); -    } - -    vic->client->pers.muted = qtrue; - -    CPx( pids[ 0 ], "cp \"^1You've been muted\"" ); -    AP( va( "print \"^3!mute: ^7%s^7 has been muted by ^7%s%s\n\"", -        vic->client->pers.netname, -        ( ent ) ? G_admin_adminPrintName( ent ) : "console", -          ( seconds ) ? va( " ^7for %i seconds", seconds ) : "" ) ); -  } - -  return qtrue; -} - -qboolean G_admin_cp( gentity_t *ent, int skiparg ) -{ -  int minargc; -  char *s; - -  minargc = 2 + skiparg; - -  if( G_SayArgc() < minargc ) -  { -    ADMP( "^3!cp: ^7usage: !cp [message]\n" ); -    return qfalse; -  } - -  s = G_SayConcatArgs( 1 + skiparg ); -  G_CP(ent); -  return qtrue; -} - -qboolean G_admin_denybuild( gentity_t *ent, int skiparg ) -{ -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; -  char command[ MAX_ADMIN_CMD_LEN ], *cmd; -  gentity_t *vic; +  char name[ MAX_NAME_LENGTH ]; +  char command[ MAX_ADMIN_CMD_LEN ]; +  namelog_t *vic; -  G_SayArgv( skiparg, command, sizeof( command ) ); -  cmd = command; -  if( cmd && *cmd == '!' ) -    cmd++; -  if( G_SayArgc() < 2 + skiparg ) +  trap_Argv( 0, command, sizeof( command ) ); +  if( trap_Argc() < 2 )    { -    ADMP( va( "^3!%s: ^7usage: !%s [name|slot#]\n", cmd, cmd ) ); +    ADMP( va( "^3%s: ^7usage: %s [name|slot#]\n", command, command ) );      return qfalse;    } -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  if( G_ClientNumbersFromString( name, pids ) != 1 ) +  trap_Argv( 1, name, sizeof( name ) ); +  if( !( vic = G_NamelogFromString( ent, name ) ) )    { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!%s: ^7%s\n", cmd, err ) ); +    ADMP( va( "^3%s: ^7no match\n", command ) );      return qfalse;    } -  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) +  if( ent && !admin_higher_admin( ent->client->pers.admin, +      G_admin_admin( vic->guid ) ) )    { -    ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin" -              " level than you\n", cmd ) ); +    ADMP( va( "^3%s: ^7sorry, but your intended victim has a higher admin" +        " level than you\n", command ) );      return qfalse;    } -  vic = &g_entities[ pids[ 0 ] ]; -  if( vic->client->pers.denyBuild ) +  if( vic->muted )    { -    if( !Q_stricmp( cmd, "denybuild" ) ) +    if( !Q_stricmp( command, "mute" ) )      { -      ADMP( "^3!denybuild: ^7player already has no building rights\n" ); -      return qtrue; +      ADMP( "^3mute: ^7player is already muted\n" ); +      return qfalse;      } -    vic->client->pers.denyBuild = qfalse; -    CPx( pids[ 0 ], "cp \"^1You've regained your building rights\"" ); -    AP( va( -      "print \"^3!allowbuild: ^7building rights for ^7%s^7 restored by %s\n\"", -      vic->client->pers.netname, -      ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); +    vic->muted = qfalse; +    if( vic->slot > -1 ) +      CPx( vic->slot, "cp \"^1You have been unmuted\"" ); +    AP( va( "print \"^3unmute: ^7%s^7 has been unmuted by %s\n\"", +            vic->name[ vic->nameOffset ], +            ( ent ) ? ent->client->pers.netname : "console" ) );    }    else    { -    if( !Q_stricmp( cmd, "allowbuild" ) ) -    { -      ADMP( "^3!allowbuild: ^7player already has building rights\n" ); -      return qtrue; -    } -    vic->client->pers.denyBuild = qtrue; -    vic->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; -    if( vic->client->ps.stats[ STAT_PCLASS ]== PCL_ALIEN_BUILDER0 || vic->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) +    if( !Q_stricmp( command, "unmute" ) )      { -      vic->suicideTime = level.time + 1000; +      ADMP( "^3unmute: ^7player is not currently muted\n" ); +      return qfalse;      } -    CPx( pids[ 0 ], "cp \"^1You've lost your building rights\"" ); -    AP( va( -      "print \"^3!denybuild: ^7building rights for ^7%s^7 revoked by ^7%s\n\"", -      vic->client->pers.netname, -      ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); +    vic->muted = qtrue; +    if( vic->slot > -1 ) +      CPx( vic->slot, "cp \"^1You've been muted\"" ); +    AP( va( "print \"^3mute: ^7%s^7 has been muted by ^7%s\n\"", +            vic->name[ vic->nameOffset ], +            ( ent ) ? ent->client->pers.netname : "console" ) );    } +  admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", vic->slot, vic->guid, +    vic->name[ vic->nameOffset ] ) );    return qtrue;  } -qboolean G_admin_denyweapon( gentity_t *ent, int skiparg ) +qboolean G_admin_denybuild( gentity_t *ent )  { -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; -  char command[ MAX_ADMIN_CMD_LEN ], *cmd; -  char buffer[ 32 ]; -  int weapon = WP_NONE; -  int class = PCL_NONE; -  char *realname; -  gentity_t *vic; -  int flag; -  qboolean all = qfalse; +  char name[ MAX_NAME_LENGTH ]; +  char command[ MAX_ADMIN_CMD_LEN ]; +  namelog_t *vic; -  G_SayArgv( skiparg, command, sizeof( command ) ); -  cmd = command; -  if( cmd && *cmd == '!' ) -    cmd++; -  if( G_SayArgc() < 3 + skiparg ) +  trap_Argv( 0, command, sizeof( command ) ); +  if( trap_Argc() < 2 )    { -    ADMP( va( "^3!%s: ^7usage: !%s [name|slot#] [class|weapon]\n", cmd, cmd ) ); +    ADMP( va( "^3%s: ^7usage: %s [name|slot#]\n", command, command ) );      return qfalse;    } -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  if( G_ClientNumbersFromString( name, pids ) != 1 ) +  trap_Argv( 1, name, sizeof( name ) ); +  if( !( vic = G_NamelogFromString( ent, name ) ) )    { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!%s: ^7%s\n", cmd, err ) ); +    ADMP( va( "^3%s: ^7no match\n", command ) );      return qfalse;    } -  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) +  if( ent && !admin_higher_admin( ent->client->pers.admin, +      G_admin_admin( vic->guid ) ) )    { -    ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin" -              " level than you\n", cmd ) ); +    ADMP( va( "^3%s: ^7sorry, but your intended victim has a higher admin" +              " level than you\n", command ) );      return qfalse;    } -  vic = &g_entities[ pids[ 0 ] ]; - -  if( vic == ent && -      !Q_stricmp( cmd, "denyweapon" ) ) -  { -    ADMP( va( "^3!%s: ^7sorry, you cannot %s yourself\n", cmd, cmd ) ); -    return qfalse; -  } - -  G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) ); - -  if( !Q_stricmp( buffer, "all" ) && -      !Q_stricmp( cmd, "allowweapon" ) ) -  { -    if( vic->client->pers.denyHumanWeapons || -        vic->client->pers.denyAlienClasses ) -    { -      vic->client->pers.denyHumanWeapons = 0; -      vic->client->pers.denyAlienClasses = 0; - -      CPx( pids[ 0 ], "cp \"^1You've regained all weapon and class rights\"" ); -      AP( va( "print \"^3!%s: ^7all weapon and class rights for ^7%s^7 restored by %s\n\"", -        cmd, -        vic->client->pers.netname, -        ( ent ) ? ent->client->pers.netname : "console" ) ); -    } -    else -    { -      ADMP( va( "^3!%s: ^7player already has all rights\n", cmd ) ); -    } -    return qtrue; -  } - -  if( !Q_stricmp( buffer, "all" ) && -      !Q_stricmp( cmd, "denyweapon" ) ) -  { -    all = qtrue; -    weapon = WP_NONE; -    class = PCL_NONE; - -    if( vic->client->pers.denyHumanWeapons == 0xFFFFFF && -        vic->client->pers.denyAlienClasses == 0xFFFFFF ) -    { -      ADMP( va( "^3!%s: ^7player already has no weapon or class rights\n", cmd ) ); -      return qtrue; -    } - -    if( vic->client->pers.teamSelection == PTE_HUMANS ) -    { -      weapon = vic->client->ps.weapon; -      if( weapon < WP_PAIN_SAW || weapon > WP_GRENADE ) -        weapon = WP_NONE; -    } -    if( vic->client->pers.teamSelection == PTE_ALIENS ) -    { -      class = vic->client->pers.classSelection; -      if( class < PCL_ALIEN_LEVEL1 || class > PCL_ALIEN_LEVEL4 ) -        class = PCL_NONE; -    } - -    vic->client->pers.denyHumanWeapons = 0xFFFFFF; -    vic->client->pers.denyAlienClasses = 0xFFFFFF; -  } -  else -  { -    weapon = BG_FindWeaponNumForName( buffer ); -    if( weapon < WP_PAIN_SAW || weapon > WP_GRENADE ) -      class = BG_FindClassNumForName( buffer ); -    if( ( weapon < WP_PAIN_SAW || weapon > WP_GRENADE ) && -        ( class < PCL_ALIEN_LEVEL1 || class > PCL_ALIEN_LEVEL4 ) ) -    { -      { -        ADMP( va( "^3!%s: ^7unknown weapon or class\n", cmd ) ); -        return qfalse; -      } -    } -  } - -  if( class == PCL_NONE ) +  if( vic->denyBuild )    { -    realname = BG_FindHumanNameForWeapon( weapon ); -    flag = 1 << (weapon - WP_BLASTER); -    if( !Q_stricmp( cmd, "denyweapon" ) ) +    if( !Q_stricmp( command, "denybuild" ) )      { -      if( ( vic->client->pers.denyHumanWeapons & flag ) && !all ) -      { -        ADMP( va( "^3!%s: ^7player already has no %s rights\n", cmd, realname ) ); -        return qtrue; -      } -      vic->client->pers.denyHumanWeapons |= flag; -      if( vic->client->pers.teamSelection == PTE_HUMANS ) -      { -        if( ( weapon == WP_GRENADE || all ) && -            BG_InventoryContainsUpgrade( UP_GRENADE, vic->client->ps.stats ) ) -        { -          BG_RemoveUpgradeFromInventory( UP_GRENADE, vic->client->ps.stats ); -          G_AddCreditToClient( vic->client, (short)BG_FindPriceForUpgrade( UP_GRENADE ), qfalse ); -        } -        if( BG_InventoryContainsWeapon( weapon, vic->client->ps.stats ) ) -        { -          int maxAmmo, maxClips; - -          BG_RemoveWeaponFromInventory( weapon, vic->client->ps.stats ); -          G_AddCreditToClient( vic->client, (short)BG_FindPriceForWeapon( weapon ), qfalse ); - -          BG_AddWeaponToInventory( WP_MACHINEGUN, vic->client->ps.stats ); -          BG_FindAmmoForWeapon( WP_MACHINEGUN, &maxAmmo, &maxClips ); -          vic->client->ps.ammo = maxAmmo; -          vic->client->ps.clips = maxClips; -          G_ForceWeaponChange( vic, WP_MACHINEGUN ); -          vic->client->ps.stats[ STAT_MISC ] = 0; -          ClientUserinfoChanged( pids[ 0 ], qfalse ); -        } -      } -    } -    else -    { -      if( !( vic->client->pers.denyHumanWeapons & flag ) ) -      { -        ADMP( va( "^3!%s: ^7player already has %s rights\n", cmd, realname ) ); -        return qtrue; -      } -      vic->client->pers.denyHumanWeapons &= ~flag; +      ADMP( "^3denybuild: ^7player already has no building rights\n" ); +      return qfalse;      } +    vic->denyBuild = qfalse; +    if( vic->slot > -1 ) +      CPx( vic->slot, "cp \"^1You've regained your building rights\"" ); +    AP( va( +      "print \"^3allowbuild: ^7building rights for ^7%s^7 restored by %s\n\"", +      vic->name[ vic->nameOffset ], +      ( ent ) ? ent->client->pers.netname : "console" ) );    }    else    { -    realname = BG_FindHumanNameForClassNum( class ); -    flag = 1 << class; -    if( !Q_stricmp( cmd, "denyweapon" ) ) +    if( !Q_stricmp( command, "allowbuild" ) )      { -      if( ( vic->client->pers.denyAlienClasses & flag ) && !all ) -      { -        ADMP( va( "^3!%s: ^7player already has no %s rights\n", cmd, realname ) ); -        return qtrue; -      } -      vic->client->pers.denyAlienClasses |= flag; -      if( vic->client->pers.teamSelection == PTE_ALIENS && -          vic->client->pers.classSelection == class ) -      { -        vec3_t infestOrigin; -        short cost; - -        G_RoomForClassChange( vic, PCL_ALIEN_LEVEL0, infestOrigin ); - -        vic->client->pers.evolveHealthFraction = (float)vic->client->ps.stats[ STAT_HEALTH ] / -            (float)BG_FindHealthForClass( class ); -        if( vic->client->pers.evolveHealthFraction < 0.0f ) -          vic->client->pers.evolveHealthFraction = 0.0f; -        else if( vic->client->pers.evolveHealthFraction > 1.0f ) -          vic->client->pers.evolveHealthFraction = 1.0f; - -        vic->client->pers.classSelection = PCL_ALIEN_LEVEL0; -        cost = BG_ClassCanEvolveFromTo( PCL_ALIEN_LEVEL0, class, 9, 0 ); -        if( cost < 0 ) cost = 0; -        G_AddCreditToClient( vic->client, cost, qfalse ); -        ClientUserinfoChanged( pids[ 0 ], qfalse ); -        VectorCopy( infestOrigin, vic->s.pos.trBase ); -        ClientSpawn( vic, vic, vic->s.pos.trBase, vic->s.apos.trBase ); -      } +      ADMP( "^3allowbuild: ^7player already has building rights\n" ); +      return qfalse;      } -    else +    vic->denyBuild = qtrue; +    if( vic->slot > -1 )      { -      if( !( vic->client->pers.denyAlienClasses & flag ) ) -      { -        ADMP( va( "^3!%s: ^7player already has %s rights\n", cmd, realname ) ); -        return qtrue; -      } -      vic->client->pers.denyAlienClasses &= ~flag; +      level.clients[ vic->slot ].ps.stats[ STAT_BUILDABLE ] = BA_NONE; +      CPx( vic->slot, "cp \"^1You've lost your building rights\"" );      } +    AP( va( +      "print \"^3denybuild: ^7building rights for ^7%s^7 revoked by ^7%s\n\"", +      vic->name[ vic->nameOffset ], +      ( ent ) ? ent->client->pers.netname : "console" ) );    } - -  if( all ) -    realname = "weapon and class"; - -  CPx( pids[ 0 ], va( "cp \"^1You've %s your %s rights\"", -    ( !Q_stricmp( cmd, "denyweapon" ) ) ? "lost" : "regained", -    realname ) ); -  AP( va( -    "print \"^3!%s: ^7%s rights for ^7%s^7 %s by %s\n\"", -    cmd, realname, -    vic->client->pers.netname, -    ( !Q_stricmp( cmd, "denyweapon" ) ) ? "revoked" : "restored", -    ( ent ) ? ent->client->pers.netname : "console" ) ); -   +  admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", vic->slot, vic->guid, +    vic->name[ vic->nameOffset ] ) );    return qtrue;  } -qboolean G_admin_listadmins( gentity_t *ent, int skiparg ) +qboolean G_admin_listadmins( gentity_t *ent )  { -  int i, found = 0; +  int i;    char search[ MAX_NAME_LENGTH ] = {""};    char s[ MAX_NAME_LENGTH ] = {""}; -  int start = 0; -  qboolean numeric = qtrue; -  int drawn = 0; -  int minlevel = 1; +  int start = MAX_CLIENTS; -  if( G_SayArgc() == 3 + skiparg ) +  if( trap_Argc() == 3 )    { -    G_SayArgv( 2 + skiparg, s, sizeof( s ) ); -    if( s[ 0 ] >= '0' && s[ 0] <= '9' ) -    { -      minlevel = atoi( s ); -      if( minlevel < 1 )  -       minlevel = 1; -    } -  } - -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) -  { -    if( g_admin_admins[ i ]->level >= minlevel ) -      found++; -  } -  if( !found ) -  { -    if( minlevel > 1 ) -    { -      ADMP( va( "^3!listadmins: ^7no admins level %i or greater found\n", minlevel ) ); -    } -    else -    { -       ADMP( "^3!listadmins: ^7no admins defined\n" ); -    } -    return qfalse; +    trap_Argv( 2, s, sizeof( s ) ); +    start = atoi( s );    } - -  if( G_SayArgc() >= 2 + skiparg ) +  if( trap_Argc() > 1 )    { -    G_SayArgv( 1 + skiparg, s, sizeof( s ) ); -    for( i = 0; i < sizeof( s ) && s[ i ]; i++ ) +    trap_Argv( 1, s, sizeof( s ) ); +    i = 0; +    if( trap_Argc() == 2 )      { -      if( s[ i ] >= '0' && s[ i ] <= '9' ) -        continue; -      numeric = qfalse;  +      i = s[ 0 ] == '-'; +      for( ; isdigit( s[ i ] ); i++ );      } -    if( numeric )  -    { +    if( i && !s[ i ] )        start = atoi( s ); -      if( start > 0 ) -        start -= 1; -      else if( start < 0 ) -        start = found + start; -    }      else        G_SanitiseString( s, search, sizeof( search ) );    } -  if( start >= found || start < 0 ) -    start = 0; - -  drawn = admin_listadmins( ent, start, search, minlevel ); - -  if( search[ 0 ] ) -  { -    if( drawn <= 20 ) -    { -      ADMP( va( "^3!listadmins:^7 found %d admins level %i or greater matching '%s^7'\n", -        drawn, minlevel, search ) ); -    } -    else -    { -      ADMP( va( "^3!listadmins:^7 found >20 admins level %i or greater matching '%s^7. Try a more narrow search.'\n", -        minlevel, search ) ); -    } -  } -  else -  { -    ADMBP_begin(); -    ADMBP( va( "^3!listadmins:^7 showing admins level %i or greater %d - %d of %d.  ", -      minlevel, -      ( found ) ? ( start + 1 ) : 0, -      ( ( start + MAX_ADMIN_LISTITEMS ) > found ) ? -       found : ( start + MAX_ADMIN_LISTITEMS ), -      found ) ); -    if( ( start + MAX_ADMIN_LISTITEMS ) < found ) -    { -      if( minlevel > 1) -      { -        ADMBP( va( "run '!listadmins %d %d' to see more", -          ( start + MAX_ADMIN_LISTITEMS + 1 ), minlevel ) ); -      } -      else -      { -        ADMBP( va( "run '!listadmins %d' to see more", -          ( start + MAX_ADMIN_LISTITEMS + 1 ) ) ); -      } -    } -    ADMBP( "\n" ); -    ADMBP_end(); -  } +  admin_listadmins( ent, start, search );    return qtrue;  } -qboolean G_admin_listlayouts( gentity_t *ent, int skiparg ) +qboolean G_admin_listlayouts( gentity_t *ent )  {    char list[ MAX_CVAR_VALUE_STRING ];    char map[ MAX_QPATH ];    int count = 0;    char *s;    char layout[ MAX_QPATH ] = { "" }; -  int i = 0; -   -  if( G_SayArgc( ) == 2 + skiparg )  -    G_SayArgv( 1 +skiparg, map, sizeof( map ) ); +  size_t i = 0; + +  if( trap_Argc( ) == 2 ) +    trap_Argv( 1, map, sizeof( map ) );    else      trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); -   +    count = G_LayoutList( map, list, sizeof( list ) );    ADMBP_begin( ); -  ADMBP( va( "^3!listlayouts:^7 %d layouts found for '%s':\n", count, map ) ); +  ADMBP( va( "^3listlayouts:^7 %d layouts found for '%s':\n", count, map ) );    s = &list[ 0 ];    while( *s )    { @@ -4762,690 +2366,193 @@ qboolean G_admin_listlayouts( gentity_t *ent, int skiparg )    return qtrue;  } -qboolean G_admin_listplayers( gentity_t *ent, int skiparg ) +qboolean G_admin_listplayers( gentity_t *ent )  { -  int i, j; -  int invisiblePlayers = 0; -  gclient_t *p; -  char c[ 3 ], t[ 2 ]; // color and team letter -  char n[ MAX_NAME_LENGTH ] = {""}; -  char n2[ MAX_NAME_LENGTH ] = {""}; -  char n3[ MAX_NAME_LENGTH ] = {""}; -  char lname[ MAX_NAME_LENGTH ]; -  char lname2[ MAX_NAME_LENGTH ]; -  char muted[ 2 ], denied[ 2 ], dbuilder[ 2 ], immune[ 2 ], guidless[ 2 ]; -  int l; -  char lname_fmt[ 5 ]; - -  //get amount of invisible players -  for( i = 0; i < level.maxclients; i++ ) { -    p = &level.clients[ i ]; -    if ( p->sess.invisible == qtrue ) -      invisiblePlayers++; -  } +  int             i, j; +  gclient_t       *p; +  char            c, t; // color and team letter +  char            *registeredname; +  char            lname[ MAX_NAME_LENGTH ]; +  char            muted, denied; +  int             colorlen; +  char            namecleaned[ MAX_NAME_LENGTH ]; +  char            name2cleaned[ MAX_NAME_LENGTH ]; +  g_admin_level_t *l; +  g_admin_level_t *d = G_admin_level( 0 ); +  qboolean        hint; +  qboolean        canset = G_admin_permission( ent, "setlevel" );    ADMBP_begin(); -  ADMBP( va( "^3!listplayers^7: %d players connected:\n", -    level.numConnectedClients - invisiblePlayers ) ); +  ADMBP( va( "^3listplayers: ^7%d players connected:\n", +    level.numConnectedClients ) );    for( i = 0; i < level.maxclients; i++ )    {      p = &level.clients[ i ]; - -    // Ignore invisible players -    if ( p->sess.invisible == qtrue ) +    if( p->pers.connected == CON_DISCONNECTED )        continue; - -    Q_strncpyz( t, "S", sizeof( t ) ); -    Q_strncpyz( c, S_COLOR_YELLOW, sizeof( c ) ); -    if( p->pers.teamSelection == PTE_HUMANS ) -    { -      Q_strncpyz( t, "H", sizeof( t ) ); -      Q_strncpyz( c, S_COLOR_BLUE, sizeof( c ) ); -    } -    else if( p->pers.teamSelection == PTE_ALIENS ) -    { -      Q_strncpyz( t, "A", sizeof( t ) ); -      Q_strncpyz( c, S_COLOR_RED, sizeof( c ) ); -    } -      if( p->pers.connected == CON_CONNECTING )      { -      Q_strncpyz( t, "C", sizeof( t ) ); -      Q_strncpyz( c, S_COLOR_CYAN, sizeof( c ) ); +      t = 'C'; +      c = COLOR_YELLOW;      } -    else if( p->pers.connected != CON_CONNECTED ) +    else      { -      continue; +      t = toupper( *( BG_TeamName( p->pers.teamSelection ) ) ); +      if( p->pers.teamSelection == TEAM_HUMANS ) +        c = COLOR_CYAN; +      else if( p->pers.teamSelection == TEAM_ALIENS ) +        c = COLOR_RED; +      else +        c = COLOR_WHITE;      } -    guidless[ 0 ] = '\0'; -    if( !Q_stricmp( p->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ) -    { -      Q_strncpyz( guidless, "G", sizeof( guidless ) ); -    } -    muted[ 0 ] = '\0'; -    if( G_admin_permission( &g_entities[ i ], ADMF_NO_VOTE ) || G_admin_permission( &g_entities[ i ], ADMF_FAKE_NO_VOTE )  ) -    { -      Q_strncpyz( muted, "V", sizeof( muted ) ); -    } -    if( p->pers.muted ) -    { -      Q_strncpyz( muted, "M", sizeof( muted ) ); -    } -    denied[ 0 ] = '\0'; -    if( p->pers.denyBuild ) -    { -      Q_strncpyz( denied, "B", sizeof( denied ) ); -    } -    if( p->pers.denyHumanWeapons || p->pers.denyAlienClasses ) -    { -      Q_strncpyz( denied, "W", sizeof( denied ) ); -    } +    muted = p->pers.namelog->muted ? 'M' : ' '; +    denied = p->pers.namelog->denyBuild ? 'B' : ' '; -    dbuilder[ 0 ] = '\0'; -    if( p->pers.designatedBuilder ) +    l = d; +    registeredname = NULL; +    hint = canset; +    if( p->pers.admin )      { -      if( !G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) && -          G_admin_permission( &g_entities[ i ], ADMF_DBUILDER ) &&  -          G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) ) -      { -        Q_strncpyz( dbuilder, "P", sizeof( dbuilder ) ); -      } -      else +      if( hint ) +        hint = admin_higher( ent, &g_entities[ i ] ); +      if( hint || !G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) )        { -        Q_strncpyz( dbuilder, "D", sizeof( dbuilder ) ); +        l = G_admin_level( p->pers.admin->level ); +        G_SanitiseString( p->pers.netname, namecleaned, +                          sizeof( namecleaned ) ); +        G_SanitiseString( p->pers.admin->name, +                          name2cleaned, sizeof( name2cleaned ) ); +        if( Q_stricmp( namecleaned, name2cleaned ) ) +          registeredname = p->pers.admin->name;        }      } -    immune[ 0 ] = '\0'; -    if( G_admin_permission( &g_entities[ i ], ADMF_BAN_IMMUNITY ) && -        G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) ) -    { -      Q_strncpyz( immune, "I", sizeof( immune ) ); -    } -    if( p->pers.paused ) -    { -      // use immunity slot for paused player status -      Q_strncpyz( immune, "L", sizeof( immune ) ); -    } +    if( l ) +      Q_strncpyz( lname, l->name, sizeof( lname ) ); -    l = 0; -    G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) ); -    n[ 0 ] = '\0'; -    for( j = 0; j < MAX_ADMIN_ADMINS && g_admin_admins[ j ]; j++ ) +    for( colorlen = j = 0; lname[ j ]; j++ )      { -      if( !Q_stricmp( g_admin_admins[ j ]->guid, p->pers.guid ) ) -      { - -        // don't gather aka or level info if the admin is incognito -        if( ent && G_admin_permission( &g_entities[ i ], ADMF_INCOGNITO ) && !G_admin_permission(ent, ADMF_SEESINCOGNITO) ) -        { -          break; -        } -        l = g_admin_admins[ j ]->level; -        G_SanitiseString( g_admin_admins[ j ]->name, n3, sizeof( n3 ) ); -        if( Q_stricmp( n2, n3 ) ) -        { -          Q_strncpyz( n, g_admin_admins[ j ]->name, sizeof( n ) ); -        } -        break; -      } +      if( Q_IsColorString( &lname[ j ] ) ) +        colorlen += 2;      } -    lname[ 0 ] = '\0'; -    Q_strncpyz( lname_fmt, "%s", sizeof( lname_fmt ) ); -    for( j = 0; j < MAX_ADMIN_LEVELS && g_admin_levels[ j ]; j++ ) -    { -      if( g_admin_levels[ j ]->level == l ) -      { -        Q_strncpyz( lname, g_admin_levels[ j ]->name, sizeof( lname ) ); -        if( *lname ) -        { -          G_DecolorString( lname, lname2 ); -          Com_sprintf( lname_fmt, sizeof( lname_fmt ), "%%%is", -            ( admin_level_maxname + (int)( strlen( lname ) - strlen( lname2 ) ) ) ); -          Com_sprintf( lname2, sizeof( lname2 ), lname_fmt, lname ); -        } -        break; -      } -    } +    ADMBP( va( "%2i ^%c%c %s %s^7 %*s^7 ^1%c%c^7 %s^7 %s%s%s\n", +              i, +              c, +              t, +              p->pers.guidless ? "^1---" : va( "^7%-2i^2%c", l ? l->level : 0, hint ? '*' : ' ' ), +              p->pers.alternateProtocol == 2 ? "^11" : p->pers.alternateProtocol == 1 ? "^3G" : " ", +              admin_level_maxname + colorlen, +              lname, +              muted, +              denied, +              p->pers.netname, +              ( registeredname ) ? "(a.k.a. " : "", +              ( registeredname ) ? registeredname : "", +              ( registeredname ) ? S_COLOR_WHITE ")" : "" ) ); -    if ( G_admin_permission(ent, ADMF_SEESFULLLISTPLAYERS ) ) { -      ADMBP( va( "%2i %s%s^7 %-2i %s^7 ^1%1s%1s%1s%1s%1s^7 %s^7 %s%s^7%s\n", -               i, -               c, -               t, -               l, -               ( *lname ) ? lname2 : "",  -               immune, -               muted, -               dbuilder, -               denied, -               guidless, -               p->pers.netname, -               ( *n ) ? "(a.k.a. " : "", -               n, -               ( *n ) ? ")" : "" -             ) ); -    } else { -      ADMBP( va( "%2i %s%s^7 %-2i %s^7 ^1%1s%1s%1s%1s^7 %s^7 %s%s^7%s\n", -               i, -               c, -               t, -               l, -               ( *lname ) ? lname2 : "",  -               immune, -               muted, -               dbuilder, -               denied, -               p->pers.netname, -               ( *n ) ? "(a.k.a. " : "", -               n, -               ( *n ) ? ")" : "" -             ) ); -    }    }    ADMBP_end();    return qtrue;  } -#define MAX_LISTMAPS_MAPS 256 - -static int SortMaps(const void *a, const void *b) +static qboolean ban_matchip( void *ban, const void *ip )  { -  return strcmp(*(char **)a, *(char **)b); +  return G_AddressCompare( &((g_admin_ban_t *)ban)->ip, (addr_t *)ip ) || +    G_AddressCompare( (addr_t *)ip, &((g_admin_ban_t *)ban)->ip );  } - -qboolean G_admin_listmaps( gentity_t *ent, int skiparg ) +static qboolean ban_matchname( void *ban, const void *name )  { -  char fileList[ 4096 ] = {""}; -  char *fileSort[ MAX_LISTMAPS_MAPS ]; -  char search[ 16 ] = {""}; -  int numFiles; -  int i; -  int fileLen = 0; -  int  count = 0; -  char *filePtr; -  int rows; +  char match[ MAX_NAME_LENGTH ]; -  if( G_SayArgc( ) > 1 + skiparg ) -  { -    G_SayArgv( skiparg + 1, search, sizeof( search ) ); -  } - -  numFiles = trap_FS_GetFileList( "maps/", ".bsp", -    fileList, sizeof( fileList ) ); -  filePtr = fileList; -  for( i = 0; i < numFiles && count < MAX_LISTMAPS_MAPS; i++, filePtr += fileLen + 1 ) -  { -    fileLen = strlen( filePtr ); -    if (fileLen < 5) -      continue; - -    filePtr[ fileLen - 4 ] = '\0'; - -    if( search[ 0 ] && !strstr( filePtr, search ) ) -      continue; - -    fileSort[ count ] = filePtr; -    count++; -  } - -  qsort(fileSort, count, sizeof(fileSort[ 0 ]), SortMaps); - -  rows = count / 3; -  if ( rows * 3 < count ) rows++; - -  ADMBP_begin(); -  for( i = 0; i < rows; i++ ) -  { -    ADMBP( va( "^7%20s %20s %20s\n", -      fileSort[ i ], -      ( rows + i < count ) ? fileSort[ rows + i ] : "", -      ( rows * 2 + i < count ) ? fileSort[ rows * 2 + i ] : "" ) ); -  } -  if ( search[ 0 ] ) -    ADMBP( va( "^3!listmaps: ^7found %d maps matching '%s^7'.\n", count, search ) ); -  else -    ADMBP( va( "^3!listmaps: ^7listing %d maps.\n", count ) ); - -  ADMBP_end(); - -  return qtrue; +  G_SanitiseString( ( (g_admin_ban_t *)ban )->name, match, sizeof( match ) ); +  return strstr( match, (const char *)name ) != NULL;  } - -qboolean G_admin_listrotation( gentity_t *ent, int skiparg ) +static void ban_out( void *ban, char *str )  { -  int i, j, statusColor; -  char mapnames[ MAX_STRING_CHARS ]; -  char *status = NULL; - -  extern mapRotations_t mapRotations; +  size_t i; +  int colorlen1 = 0; +  char duration[ MAX_DURATION_LENGTH ]; +  char *d_color = S_COLOR_WHITE; +  char date[ 11 ]; +  g_admin_ban_t *b = ( g_admin_ban_t * )ban; +  int t = trap_RealTime( NULL ); +  char *made = b->made; -  // Check for an active map rotation -  if ( !G_MapRotationActive() || -       g_currentMapRotation.integer == NOT_ROTATING ) +  for( i = 0; b->name[ i ]; i++ )    { -    trap_SendServerCommand( ent-g_entities, "print \"^3!rotation: ^7There is no active map rotation on this server\n\"" ); -    return qfalse; +    if( Q_IsColorString( &b->name[ i ] ) ) +      colorlen1 += 2;    } -  // Locate the active map rotation and output its contents -  for( i = 0; i < mapRotations.numRotations; i++ ) -  { -    if ( i == g_currentMapRotation.integer ) -    { -      int currentMap = G_GetCurrentMap( i ); - -      ADMBP_begin(); -      ADMBP( va( "^3!rotation: ^7%s\n", mapRotations.rotations[ i ].name ) ); - -      for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ ) -      { -        Q_strncpyz( mapnames, mapRotations.rotations[ i ].maps[ j ].name, sizeof( mapnames ) ); - -        if( !Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*VOTE*" ) ) -        { -          char slotMap[ 64 ]; -          int lineLen = 0; -          int k; - -          trap_Cvar_VariableStringBuffer( "mapname", slotMap, sizeof( slotMap ) ); -          mapnames[ 0 ] = '\0'; -          for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ ) -          { -            char *thisMap; -            int mc = 7; - -            if( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].lhs != MCV_VOTE ) -              continue; - -            thisMap = mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest; -            lineLen += strlen( thisMap ) + 1; - -            if( currentMap == j && !Q_stricmp( thisMap, slotMap ) ) -              mc = 3; -            Q_strcat( mapnames, sizeof( mapnames ), va( "^7%s%s^%i%s", -              ( k ) ? ", " : "", -              ( lineLen > 50 ) ? "\n                  " : "", -              mc, thisMap ) ); -            if( lineLen > 50 ) -              lineLen = strlen( thisMap ) + 2; -            else -              lineLen++; -          } - -          if( currentMap == j ) -          { -            statusColor = 3; -            status = "current slot"; -          } -          else if( !k ) -          { -            statusColor = 1; -            status = "empty vote"; -          } -          else -          { -            statusColor = 7; -            status = "vote"; -          } -        } else if( !Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*RANDOM*" ) ) -        { -          char slotMap[ 64 ]; -          int lineLen = 0; -          int k; - -          trap_Cvar_VariableStringBuffer( "mapname", slotMap, sizeof( slotMap ) ); -          mapnames[ 0 ] = '\0'; -          for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ ) -          { -            char *thisMap; -            int mc = 7; - -            if( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].lhs != MCV_SELECTEDRANDOM ) -              continue; - -            thisMap = mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest; -            lineLen += strlen( thisMap ) + 1; - -            if( currentMap == j && !Q_stricmp( thisMap, slotMap ) ) -              mc = 3; -            Q_strcat( mapnames, sizeof( mapnames ), va( "^7%s%s^%i%s", -                      ( k ) ? ", " : "", -                        ( lineLen > 50 ) ? "\n                  " : "", -                          mc, thisMap ) ); -            if( lineLen > 50 ) -              lineLen = strlen( thisMap ) + 2; -            else -              lineLen++; -          } - -          if( currentMap == j ) -          { -            statusColor = 3; -            status = "current slot"; -          } -          else if( !k ) -          { -            statusColor = 1; -            status = "empty Random"; -          } -          else -          { -            statusColor = 7; -            status = "Random"; -          } -        } -        else if ( currentMap == j ) -        { -          statusColor = 3; -          status = "current slot"; -        } -        else if ( !G_MapExists( mapRotations.rotations[ i ].maps[ j ].name ) ) -        { -          statusColor = 1; -          status = "missing"; -        } -        else -        { -          statusColor = 7; -          status = ""; -        } -        ADMBP( va( " ^%i%-12s %3i %s\n", statusColor, status, j + 1, mapnames ) ); -      } - -      ADMBP_end(); - -      // No maps were found in the active map rotation -      if ( mapRotations.rotations[ i ].numMaps < 1 ) -      { -        trap_SendServerCommand( ent-g_entities, "print \"  - ^7Empty!\n\"" ); -        return qfalse; -      } -    } -  } +  // only print out the the date part of made +  date[ 0 ] = '\0'; +  for( i = 0; *made && *made != ' ' && i < sizeof( date ) - 1; i++ ) +    date[ i ] = *made++; +  date[ i ] = 0; -  if( g_nextMap.string[0] ) +  if( !b->expires || b->expires - t > 0 ) +    G_admin_duration( b->expires ? b->expires - t : - 1, +                      duration, sizeof( duration ) ); +  else    { -    ADMP( va ("^5 Next map overriden by g_nextMap to: %s\n", g_nextMap.string ) ); +    Q_strncpyz( duration, "expired", sizeof( duration ) ); +    d_color = S_COLOR_CYAN;    } -   -  return qtrue; -} - -qboolean G_admin_showbans( gentity_t *ent, int skiparg ) +  Com_sprintf( str, MAX_STRING_CHARS, "%-*s %s%-15s " S_COLOR_WHITE "%-8s %s" +    "\n     \\__ %s%-*s " S_COLOR_WHITE "%s", +    MAX_NAME_LENGTH + colorlen1 - 1, b->name, +    ( strchr( b->ip.str, '/' ) ) ? S_COLOR_RED : S_COLOR_WHITE, +    b->ip.str, +    date, +    b->banner, +    d_color, +    MAX_DURATION_LENGTH - 1, +    duration, +    b->reason ); +} +qboolean G_admin_showbans( gentity_t *ent )  { -  int i, found = 0; -  int t; -  char duration[ 32 ]; -  char sduration[ 32 ]; -  char suspended[ 64 ] = { "" }; -  char name_fmt[ 32 ] = { "%s" }; -  char banner_fmt[ 32 ] = { "%s" }; -  int max_name = 1, max_banner = 1; -  int secs; -  int start = 0; +  int i; +  int start = 1;    char filter[ MAX_NAME_LENGTH ] = {""}; -  char date[ 11 ]; -  char *made; -  int j; -  char n1[ MAX_NAME_LENGTH * 2 ] = {""}; -  char n2[ MAX_NAME_LENGTH * 2 ] = {""}; -  int bannerslevel = 0; -  qboolean numeric = qtrue; -  char *ip_match = NULL; -  int ip_match_len = 0;    char name_match[ MAX_NAME_LENGTH ] = {""}; -  int show_count = 0; -  qboolean subnetfilter = qfalse; +  qboolean ipmatch = qfalse; +  addr_t ip; -  t = trap_RealTime( NULL ); - -  for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) +  if( trap_Argc() == 3 )    { -    if( g_admin_bans[ i ]->expires != 0 -      && ( g_admin_bans[ i ]->expires - t ) < 1 ) -    { -      continue; -    } -    found++; +    trap_Argv( 2, filter, sizeof( filter ) ); +    start = atoi( filter );    } - -  if( G_SayArgc() >= 2 + skiparg ) +  if( trap_Argc() > 1 )    { -    G_SayArgv( 1 + skiparg, filter, sizeof( filter ) ); -    if( G_SayArgc() >= 3 + skiparg ) -    { +    trap_Argv( 1, filter, sizeof( filter ) ); +    i = 0; +    if( trap_Argc() == 2 ) +      for( i = filter[ 0 ] == '-'; isdigit( filter[ i ] ); i++ ); +    if( !filter[ i ] )        start = atoi( filter ); -      G_SayArgv( 2 + skiparg, filter, sizeof( filter ) ); -    } -    for( i = 0; i < sizeof( filter ) && filter[ i ] ; i++ ) -    { -      if( ( filter[ i ] < '0' || filter[ i ] > '9' ) -        && filter[ i ] != '.' && filter[ i ] != '-' ) -      { -        numeric = qfalse; -        break; -      } -    } - -    if (!numeric) -    { -      if( filter[ 0 ] != '-' ) -      { -        G_SanitiseString( filter, name_match, sizeof( name_match) ); - -      } -      else -      { -        if( !Q_strncmp( filter, "-sub", 4 ) ) -        { -          subnetfilter = qtrue; -        } -        else -        { -          ADMP( va( "^3!showbans: ^7invalid argument %s\n", filter ) ); -          return qfalse; -        } -      } -    } -    else if( strchr( filter, '.' ) != NULL ) -    { -      ip_match = filter; -      ip_match_len = strlen(ip_match); -    } -    else -    { -      start = atoi( filter ); -      filter[0] = '\0'; -    } -    // showbans 1 means start with ban 0 -    if( start > 0 ) -      start -= 1; -    else if( start < 0 ) -      start = found + start; -  } -   -  if( start >= MAX_ADMIN_BANS || start < 0 ) -    start = 0; - -  for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ]  -    && show_count < MAX_ADMIN_SHOWBANS; i++ ) -  { -    qboolean match = qfalse; - -    if (!numeric) -      { -        if( !subnetfilter ) -        { -          G_SanitiseString( g_admin_bans[ i ]->name, n1, sizeof( n1 ) ); -          if (strstr( n1, name_match) ) -            match = qtrue; -        } -        else -        { -          int mask = -1; -          int dummy; -          int scanflen = 0; -          scanflen = sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask ); -          if( scanflen == 5 && mask < 32 ) -          { -            match = qtrue; -          } -        } -      } - -    if ( ( match ) || !ip_match -      || Q_strncmp( ip_match, g_admin_bans[ i ]->ip, ip_match_len) == 0 ) -    { -      G_DecolorString( g_admin_bans[ i ]->name, n1 ); -      G_DecolorString( g_admin_bans[ i ]->banner, n2 ); -      if( strlen( n1 ) > max_name ) -      { -        max_name = strlen( n1 ); -      } -      if( strlen( n2 ) > max_banner ) -        max_banner = strlen( n2 ); -  -      show_count++; -    } +    else if( !( ipmatch = G_AddressParse( filter, &ip ) ) ) +      G_SanitiseString( filter, name_match, sizeof( name_match ) );    } -  if( start >= found ) -  { -    ADMP( va( "^3!showbans: ^7there are %d active bans\n", found ) ); -    return qfalse; -  } -  ADMBP_begin(); -  show_count = 0; -  for( i = start; i < MAX_ADMIN_BANS && g_admin_bans[ i ] -    && show_count < MAX_ADMIN_SHOWBANS; i++ ) -  { -    if (!numeric) -    { -      if( !subnetfilter ) -      { -        G_SanitiseString( g_admin_bans[ i ]->name, n1, sizeof( n1 ) ); -        if ( strstr ( n1, name_match ) == NULL ) -          continue; -      } -      else -      { -        int mask = -1; -        int dummy; -        int scanflen = 0; -        scanflen = sscanf( g_admin_bans[ i ]->ip, "%d.%d.%d.%d/%d", &dummy, &dummy, &dummy, &dummy, &mask ); -        if( scanflen != 5 || mask >= 32 ) -        { -          continue; -        } -      } -    } -    else if( ip_match != NULL -      && Q_strncmp( ip_match, g_admin_bans[ i ]->ip, ip_match_len ) != 0) -      continue; - -    // only print out the the date part of made -    date[ 0 ] = '\0'; -    made = g_admin_bans[ i ]->made; -    for( j = 0; made && *made; j++ ) -    { -      if( ( j + 1 ) >= sizeof( date ) ) -        break; -      if( *made == ' ' ) -        break; -      date[ j ] = *made; -      date[ j + 1 ] = '\0'; -      made++; -    } - -    if( g_admin_bans[ i ]->expires != 0 -        && ( g_admin_bans[ i ]->expires - t ) < 1 ) -    { -      Com_sprintf( duration, sizeof( duration ), "^1*EXPIRED*^7" ); -    } else { -      secs = ( g_admin_bans[ i ]->expires - t ); -      G_admin_duration( secs, duration, sizeof( duration ) ); -    } - -    suspended[ 0 ] = '\0'; -    if( g_admin_bans[ i ]->suspend > t ) -    { -      G_admin_duration( g_admin_bans[ i ]->suspend - t, sduration, sizeof( sduration ) ); -      Com_sprintf( suspended, sizeof( suspended ), "^3*SUSPENDED*^7 for %s^7", -        sduration ); -    } - -    G_DecolorString( g_admin_bans[ i ]->name, n1 ); -    Com_sprintf( name_fmt, sizeof( name_fmt ), "%%%is", -      ( max_name + strlen( g_admin_bans[ i ]->name ) - strlen( n1 ) ) ); -    Com_sprintf( n1, sizeof( n1 ), name_fmt, g_admin_bans[ i ]->name );  - -    G_DecolorString( g_admin_bans[ i ]->banner, n2 ); -    Com_sprintf( banner_fmt, sizeof( banner_fmt ), "%%%is", -      ( max_banner + strlen( g_admin_bans[ i ]->banner ) - strlen( n2 ) ) ); -    Com_sprintf( n2, sizeof( n2 ), banner_fmt, g_admin_bans[ i ]->banner ); -    bannerslevel = g_admin_bans[ i ]->bannerlevel; - -    ADMBP( va( "%4i %s^7 %-15s %-8s %-10s\n     |  %-15s^7 Level:%2i\n     |  %s\n     \\__ %s\n", -             ( i + 1 ), -             n1, -             g_admin_bans[ i ]->ip, -             date, -             duration, -             n2, -             bannerslevel, -             suspended, -             g_admin_bans[ i ]->reason ) ); - -    show_count++; -  } - -  if (!numeric || ip_match) -  { -    char matchmethod[50]; -    if( numeric )  -      Com_sprintf( matchmethod, sizeof(matchmethod), "IP" ); -    else if( !subnetfilter ) -      Com_sprintf( matchmethod, sizeof(matchmethod), "name" ); -    else -      Com_sprintf( matchmethod, sizeof(matchmethod), "ip range size" ); - - -    ADMBP( va( "^3!showbans:^7 found %d matching bans by %s.  ", -             show_count, -             matchmethod ) ); -  } -  else -  { -    ADMBP( va( "^3!showbans:^7 showing bans %d - %d of %d.  ", -             ( found ) ? ( start + 1 ) : 0, -             ( ( start + MAX_ADMIN_SHOWBANS ) > found ) ? -             found : ( start + MAX_ADMIN_SHOWBANS ), -             found ) ); -  } - -  if( ( start + MAX_ADMIN_SHOWBANS ) < found ) -  { -    ADMBP( va( "run !showbans %d %s to see more", -             ( start + MAX_ADMIN_SHOWBANS + 1 ), -             (filter[0]) ? filter : "" ) ); -  } -  ADMBP( "\n" ); -  ADMBP_end(); +  admin_search( ent, "showbans", "bans", +    ipmatch ? ban_matchip : ban_matchname, +    ban_out, g_admin_bans, +    ipmatch ? (void * )&ip : (void *)name_match, +    start, 1, MAX_ADMIN_SHOWBANS );    return qtrue;  } -qboolean G_admin_help( gentity_t *ent, int skiparg ) +qboolean G_admin_adminhelp( gentity_t *ent )  { -  int i; -  int commandsPerLine = 6; - -  if( G_SayArgc() < 2 + skiparg ) +  g_admin_command_t *c; +  if( trap_Argc() < 2 )    { -    int j = 0; +    size_t i;      int count = 0;      ADMBP_begin(); @@ -5453,235 +2560,104 @@ qboolean G_admin_help( gentity_t *ent, int skiparg )      {        if( G_admin_permission( ent, g_admin_cmds[ i ].flag ) )        { -        ADMBP( va( "^3!%-12s", g_admin_cmds[ i ].keyword ) ); -        j++; +        ADMBP( va( "^3%-12s", g_admin_cmds[ i ].keyword ) );          count++; -      } -      // show 6 commands per line -      if( j == 6 ) -      { -        ADMBP( "\n" ); -        j = 0; +        // show 6 commands per line +        if( count % 6 == 0 ) +          ADMBP( "\n" );        }      } -    for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ ) +    for( c = g_admin_commands; c; c = c->next )      { -      if( !G_admin_permission( ent, g_admin_commands[ i ]->flag ) ) +      if( !G_admin_permission( ent, c->flag ) )          continue; -      ADMBP( va( "^3!%-12s", g_admin_commands[ i ]->command ) ); -      j++; +      ADMBP( va( "^3%-12s", c->command ) );        count++;        // show 6 commands per line -      if( j == 6 ) -      { +      if( count % 6 == 0 )          ADMBP( "\n" ); -        j = 0; -      }      } - -    if( count ) +    if( count % 6 )        ADMBP( "\n" ); -    ADMBP( va( "^3!help: ^7%i available commands\n", count ) ); -    ADMBP( "run !help [^3command^7] for help with a specific command.\n" ); -    ADMBP( "The following non-standard /commands may also be available to you: \n" ); -    count = 1; - -    if( ent && g_AllStats.integer ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "allstats" ) ); -      count++; -    } -    if ( ent ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "builder" ) ); -      count++; -    } -    if( ent && g_allowVote.integer && G_admin_permission( ent, ADMF_NO_VOTE ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "callvote" ) ); -      count++; -    } -    if( ent && g_allowVote.integer && G_admin_permission( ent, ADMF_NO_VOTE ) && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "callteamvote" ) ); -      count++; -    } -    if( ent && g_allowShare.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "donate" ) ); -      count++; -    } -    if( g_privateMessages.integer ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "m" ) ); -      count++; -    } -    if( ent && g_markDeconstruct.integer == 2 && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "mark" ) ); -      count++; -    } -    if( ent && g_actionPrefix.string[0] ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "me" ) ); -      count++; -    } -    if( ent && g_actionPrefix.string[0] ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "me_team" ) ); -      count++; -    } -    if( ent && g_actionPrefix.string[0] && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "mt" ) ); -      count++; -    } -    if( ent && g_myStats.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "mystats" ) ); -      count++; -    } -    if( ent && ent->client->pers.designatedBuilder && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "protect" ) ); -      count++; -    } -    if( ent && ent->client->pers.designatedBuilder && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "resign" ) ); -      count++; -    } -    if( g_publicSayadmins.integer || G_admin_permission( ent, ADMF_ADMINCHAT ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "say_admins" ) ); -      count++; -    } -    if( ent && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "say_area" ) ); -      count++; -    } -    if( ent && g_allowShare.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "share" ) ); -      count++; -    } -    if( ent && g_teamStatus.integer && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ) { -      if( count > commandsPerLine && ( count % commandsPerLine ) == 1 ) ADMBP( "\n" ); -      ADMBP( va( "^5/%-12s", "teamstatus" ) ); -      count++; -    } -    ADMBP( "\n" ); +    ADMBP( va( "^3adminhelp: ^7%i available commands\n", count ) ); +    ADMBP( "run adminhelp [^3command^7] for adminhelp with a specific command.\n" );      ADMBP_end();      return qtrue;    }    else    { -    //!help param +    // adminhelp param      char param[ MAX_ADMIN_CMD_LEN ]; -    char *cmd; +    g_admin_cmd_t *admincmd; +    qboolean denied = qfalse; -    G_SayArgv( 1 + skiparg, param, sizeof( param ) ); -    cmd = ( param[0] == '!' ) ? ¶m[1] : ¶m[0]; +    trap_Argv( 1, param, sizeof( param ) );      ADMBP_begin(); -    for( i = 0; i < adminNumCmds; i++ ) +    if( ( c = G_admin_command( param ) ) )      { -      if( !Q_stricmp( cmd, g_admin_cmds[ i ].keyword ) ) +      if( G_admin_permission( ent, c->flag ) )        { -        if( !G_admin_permission( ent, g_admin_cmds[ i ].flag ) ) -        { -          ADMBP( va( "^3!help: ^7you have no permission to use '%s'\n", -                   g_admin_cmds[ i ].keyword ) ); -          ADMBP_end(); -          return qfalse; -        } -        ADMBP( va( "^3!help: ^7help for '!%s':\n", -          g_admin_cmds[ i ].keyword ) ); -        ADMBP( va( " ^3Function: ^7%s\n", g_admin_cmds[ i ].function ) ); -        ADMBP( va( " ^3Syntax: ^7!%s %s\n", g_admin_cmds[ i ].keyword, -                 g_admin_cmds[ i ].syntax ) ); -        ADMBP( va( " ^3Flag: ^7'%s'\n", g_admin_cmds[ i ].flag ) ); -        ADMBP_end(); +        ADMBP( va( "^3adminhelp: ^7help for '%s':\n", c->command ) ); +        ADMBP( va( " ^3Description: ^7%s\n", c->desc ) ); +        ADMBP( va( " ^3Syntax: ^7%s\n", c->command ) ); +        ADMBP( va( " ^3Flag: ^7'%s'\n", c->flag ) ); +        ADMBP_end( );          return qtrue;        } +      denied = qtrue;      } -    for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ ) +    if( ( admincmd = G_admin_cmd( param ) ) )      { -      if( !Q_stricmp( cmd, g_admin_commands[ i ]->command ) ) +      if( G_admin_permission( ent, admincmd->flag ) )        { -        if( !G_admin_permission( ent, g_admin_commands[ i ]->flag ) ) -        { -          ADMBP( va( "^3!help: ^7you have no permission to use '%s'\n", -                   g_admin_commands[ i ]->command ) ); -          ADMBP_end(); -          return qfalse; -        } -        ADMBP( va( "^3!help: ^7help for '%s':\n", -          g_admin_commands[ i ]->command ) ); -        ADMBP( va( " ^3Description: ^7%s\n", g_admin_commands[ i ]->desc ) ); -        ADMBP( va( " ^3Syntax: ^7!%s\n", g_admin_commands[ i ]->command ) ); +        ADMBP( va( "^3adminhelp: ^7help for '%s':\n", admincmd->keyword ) ); +        ADMBP( va( " ^3Description: ^7%s\n", admincmd->function ) ); +        ADMBP( va( " ^3Syntax: ^7%s %s\n", admincmd->keyword, +                 admincmd->syntax ) ); +        ADMBP( va( " ^3Flag: ^7'%s'\n", admincmd->flag ) );          ADMBP_end();          return qtrue;        } +      denied = qtrue;      } -    ADMBP( va( "^3!help: ^7no help found for '%s'\n", cmd ) ); -    ADMBP_end(); +    ADMBP( va( "^3adminhelp: ^7%s '%s'\n", +      denied ? "you do not have permission to use" : "no help found for", +      param ) ); +    ADMBP_end( );      return qfalse;    }  } -qboolean G_admin_admintest( gentity_t *ent, int skiparg ) +qboolean G_admin_admintest( gentity_t *ent )  { -  int i, l = 0; -  qboolean found = qfalse; -  qboolean lname = qfalse; +  g_admin_level_t *l;    if( !ent )    { -    ADMP( "^3!admintest: ^7you are on the console.\n" ); +    ADMP( "^3admintest: ^7you are on the console.\n" );      return qtrue;    } -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) -  { -    if( !Q_stricmp( g_admin_admins[ i ]->guid, ent->client->pers.guid ) ) -    { -      found = qtrue; -      break; -    } -  } -  if( found ) -  { -    l = g_admin_admins[ i ]->level; -    for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) -    { -      if( g_admin_levels[ i ]->level != l ) -        continue; -      if( *g_admin_levels[ i ]->name ) -      { -        lname = qtrue; -        break; -      } -    } -  } -  AP( va( "print \"^3!admintest: ^7%s^7 is a level %d admin %s%s^7%s\n\"", +  l = G_admin_level( ent->client->pers.admin ? ent->client->pers.admin->level : 0 ); + +  AP( va( "print \"^3admintest: ^7%s^7 is a level %d admin %s%s^7%s\n\"",            ent->client->pers.netname, -          l, -          ( lname ) ? "(" : "", -          ( lname ) ? g_admin_levels[ i ]->name : "", -          ( lname ) ? ")" : "" ) ); +          l ? l->level : 0, +          l ? "(" : "", +          l ? l->name : "", +          l ? ")" : "" ) );    return qtrue;  } -qboolean G_admin_allready( gentity_t *ent, int skiparg ) +qboolean G_admin_allready( gentity_t *ent )  {    int i = 0;    gclient_t *cl;    if( !level.intermissiontime )    { -    ADMP( "^3!allready: ^7this command is only valid during intermission\n" ); +    ADMP( "^3allready: ^7this command is only valid during intermission\n" );      return qfalse;    } @@ -5691,233 +2667,54 @@ qboolean G_admin_allready( gentity_t *ent, int skiparg )      if( cl->pers.connected != CON_CONNECTED )        continue; -    if( cl->pers.teamSelection == PTE_NONE ) +    if( cl->pers.teamSelection == TEAM_NONE )        continue; -    cl->readyToExit = 1; -  } -  AP( va( "print \"^3!allready:^7 %s^7 says everyone is READY now\n\"", -     ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );  -  return qtrue; -} - -qboolean G_admin_cancelvote( gentity_t *ent, int skiparg ) -{ - -  if(!level.voteTime && !level.teamVoteTime[ 0 ] && !level.teamVoteTime[ 1 ] ) -  { -    ADMP( "^3!cancelvote^7: no vote in progress\n" ); -    return qfalse; -  } -  level.voteNo = level.numConnectedClients; -  level.voteYes = 0; -  CheckVote( ); -  level.teamVoteNo[ 0 ] = level.numConnectedClients; -  level.teamVoteYes[ 0 ] = 0; -  CheckTeamVote( PTE_HUMANS ); -  level.teamVoteNo[ 1 ] = level.numConnectedClients; -  level.teamVoteYes[ 1 ] = 0; -  CheckTeamVote( PTE_ALIENS ); -  AP( va( "print \"^3!cancelvote: ^7%s^7 decided that everyone voted No\n\"", -          ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -  return qtrue; -} - -qboolean G_admin_passvote( gentity_t *ent, int skiparg ) -{ -  if(!level.voteTime && !level.teamVoteTime[ 0 ] && !level.teamVoteTime[ 1 ] ) -  { -    ADMP( "^3!passvote^7: no vote in progress\n" ); -    return qfalse; +    cl->readyToExit = qtrue;    } -  level.voteYes = level.numConnectedClients; -  level.voteNo = 0; -  CheckVote( ); -  level.teamVoteYes[ 0 ] = level.numConnectedClients; -  level.teamVoteNo[ 0 ] = 0; -  CheckTeamVote( PTE_HUMANS ); -  level.teamVoteYes[ 1 ] = level.numConnectedClients; -  level.teamVoteNo[ 1 ] = 0; -  CheckTeamVote( PTE_ALIENS ); -  AP( va( "print \"^3!passvote: ^7%s^7 decided that everyone voted Yes\n\"", -          ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); +  AP( va( "print \"^3allready:^7 %s^7 says everyone is READY now\n\"", +     ( ent ) ? ent->client->pers.netname : "console" ) );    return qtrue;  } -static qboolean G_admin_global_pause( gentity_t *ent, int skiparg ) +qboolean G_admin_endvote( gentity_t *ent )  { -  if( level.paused ) -  { -    level.pauseTime = level.time; -    ADMP( "^3!pause: ^7Game is already paused, unpause timer reset\n" ); -    return qfalse; -  } - -  level.paused = qtrue; -  level.pauseTime = level.time; - -  level.pause_speed = g_speed.value; -  level.pause_gravity = g_gravity.value; -  level.pause_knockback = g_knockback.value; -  level.pause_ff = g_friendlyFire.integer; -  level.pause_ffb = g_friendlyBuildableFire.integer; - -  g_speed.value = 0; -  g_gravity.value = 0; -  g_knockback.value = 0; -  g_friendlyFire.integer = 0; -  g_friendlyBuildableFire.integer = 0; - -  CP( "cp \"^1Game is PAUSED\"" ); -  AP( va( "print \"^3!pause: ^7The game has been paused by %s\n\"", -    ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - -  return qtrue; -} +  char teamName[ sizeof( "spectators" ) ] = {"s"}; +  char command[ MAX_ADMIN_CMD_LEN ]; +  team_t team; +  qboolean cancel; +  const char *msg; -static qboolean G_admin_global_unpause( gentity_t *ent, int skiparg ) -{ -  if( !level.paused ) +  trap_Argv( 0, command, sizeof( command ) ); +  cancel = !Q_stricmp( command, "cancelvote" ); +  if( trap_Argc() == 2 ) +    trap_Argv( 1, teamName, sizeof( teamName ) ); +  team = G_TeamFromString( teamName ); +  if( team == NUM_TEAMS )    { -    ADMP( "^3!unpause: ^7Game is not paused\n" ); +    ADMP( va( "^3%s: ^7invalid team '%s'\n", command, teamName ) );      return qfalse;    } - -  level.paused = qfalse; - -  g_speed.value = level.pause_speed; -  g_gravity.value = level.pause_gravity; -  g_knockback.value = level.pause_knockback; -  g_friendlyFire.integer = level.pause_ff; -  g_friendlyBuildableFire.integer = level.pause_ffb; - -  CP( "cp \"^2Game is RESUMED\"" ); -  AP( va( "print \"^3!unpause: ^7The game has been resumed by %s\n\"", -    ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); - -  return qtrue; -} - -static qboolean is_numeric(const char *str) -{ -	const char *p; - -	if (!*str) -		return qfalse; - -	for (p = str; *p; p++) -		if (*p < '0' || *p > '9') -			return qfalse; - -	return qtrue; -} - -qboolean G_admin_pause( gentity_t *ent, int skiparg ) -{ -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; -  char command[ MAX_ADMIN_CMD_LEN ], *cmd; -  int i; -  int count = 0; -  gentity_t *vic; - -  G_SayArgv( skiparg, command, sizeof( command ) ); -  cmd = command; -  if( cmd && *cmd == '!' ) -    cmd++; - -  if( G_SayArgc() < 2 + skiparg ) -  { -    // global pause -    if( !Q_stricmp( cmd, "pause" ) ) -      return G_admin_global_pause( ent, skiparg ); -    else -      return G_admin_global_unpause( ent, skiparg ); -  } -  if( G_SayArgc() != 2 + skiparg ) +  msg = va( "print \"^3%s: ^7%s^7 decided that everyone voted %s\n\"", +    command, ( ent ) ? ent->client->pers.netname : "console", +    cancel ? "No" : "Yes" ); +  if( !level.voteTime[ team ] )    { -    ADMP( va( "^3!%s: ^7usage: !%s (^5name|slot|*^7)\n", cmd, cmd ) ); +    ADMP( va( "^3%s: ^7no vote in progress\n", command ) );      return qfalse;    } - -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  if( !Q_stricmp( name, "*" ) ) -  { -    for( i = 0; i < MAX_CLIENTS; i++ ) -    { -      vic = &g_entities[ i ]; -      if( vic && vic->client && -          vic->client->pers.connected == CON_CONNECTED ) -      { -        pids[ count ] = i; -        count++; -      } -    } -  } +  admin_log( BG_TeamName( team ) ); +  level.voteNo[ team ] = cancel ? level.numVotingClients[ team ] : 0; +  level.voteYes[ team ] = cancel ? 0 : level.numVotingClients[ team ]; +  G_CheckVote( team ); +  if( team == TEAM_NONE ) +    AP( msg );    else -  { -    if( G_ClientNumbersFromString( name, pids ) != 1 ) -    { -      G_MatchOnePlayer( pids, err, sizeof( err ) ); -      ADMP( va( "^3!%s: ^7%s\n", cmd, err ) ); -      return qfalse; -    } -    count = 1; -  } - -  for( i = 0; i < count; i++ ) -  { -    vic = &g_entities[ pids[ i ] ]; -    if ( !vic || !vic->client ) continue; -    if( !admin_higher( ent, vic ) ) -    { -      if( count == 1 ) -        ADMP( va( "^3!%s: ^7sorry, but your intended victim has a higher admin" -                    " level than you\n", cmd ) ); -      continue; -    } -    if( vic->client->pers.paused ) -    { -      if( !Q_stricmp( cmd, "pause" ) ) -      { -        if( count == 1 ) -          ADMP( "^3!pause: ^7player is already paused\n" ); -        continue; -      } -      vic->client->pers.paused = qfalse; -      CPx( pids[ i ], "cp \"^2You've been unpaused\"" ); -      if( count == 1 ) -        AP( va( "print \"^3!unpause: ^7%s^7 unpaused by %s\n\"", -            vic->client->pers.netname, -            ( ent ) ? ent->client->pers.netname : "console" ) ); -    } -    else -    { -      if( !Q_stricmp( cmd, "unpause" ) ) -      { -        if( count == 1 ) -          ADMP( "^3!unpause: ^7player is already unpaused\n" ); -        continue; -      } -      if( count == 1 && ent == vic) -      { -        ADMP( "^3!pause: ^7you can not pause yourself\n" ); -        continue; -      } -      vic->client->pers.paused = qtrue; -      CPx( pids[ i ], va( "cp \"^1You've been paused by ^7%s\"", -          ( ent ) ? ent->client->pers.netname : "console" ) ); -      if( count == 1 ) -        AP( va( "print \"^3!pause: ^7%s^7 paused by %s\n\"", -            vic->client->pers.netname, -            ( ent ) ? ent->client->pers.netname : "console" ) ); -    } -    ClientUserinfoChanged( pids[ i ], qfalse ); -  } +    G_TeamCommand( team, msg );    return qtrue;  } -qboolean G_admin_spec999( gentity_t *ent, int skiparg ) +qboolean G_admin_spec999( gentity_t *ent )  {    int i;    gentity_t *vic; @@ -5929,146 +2726,186 @@ qboolean G_admin_spec999( gentity_t *ent, int skiparg )        continue;      if( vic->client->pers.connected != CON_CONNECTED )        continue; -    if( vic->client->pers.teamSelection == PTE_NONE ) +    if( vic->client->pers.teamSelection == TEAM_NONE )        continue;      if( vic->client->ps.ping == 999 )      { -      G_ChangeTeam( vic, PTE_NONE ); -      AP( va( "print \"^3!spec999: ^7%s^7 moved ^7%s^7 to spectators\n\"", -        ( ent ) ? G_admin_adminPrintName( ent ) : "console",  +      G_ChangeTeam( vic, TEAM_NONE ); +      AP( va( "print \"^3spec999: ^7%s^7 moved %s^7 to spectators\n\"", +        ( ent ) ? ent->client->pers.netname : "console",          vic->client->pers.netname ) );      }    }    return qtrue;  } +  +qboolean G_admin_transform( gentity_t *ent ) +{ +  int pid; +  char name[ MAX_NAME_LENGTH ]; +  char modelname[ MAX_NAME_LENGTH ]; +  char skin[ MAX_NAME_LENGTH ]; +  char err[ MAX_STRING_CHARS ]; +  char userinfo[ MAX_INFO_STRING ]; +  gentity_t *victim = NULL; +  int i; +  qboolean found = qfalse; + +  if (trap_Argc() < 3) +  { +    ADMP("^3transform: ^7usage: transform [name|slot#] [model] <skin>\n"); +    return qfalse; +  } -qboolean G_admin_register(gentity_t *ent, int skiparg ){ -  int level = 0; +  trap_Argv(1, name, sizeof(name)); +  trap_Argv(2, modelname, sizeof(modelname)); -  if( !ent ) return qtrue; +  strcpy(skin, "default"); +  if (trap_Argc() >= 4) +  { +      trap_Argv(1, skin, sizeof(skin)); +  } -  level = G_admin_level(ent); +  pid = G_ClientNumberFromString(name, err, sizeof(err)); +  if (pid == -1) +  { +    ADMP(va("^3transform: ^7%s", err)); +    return qfalse; +  } -  if( level == 0 ) -   level = 1; -   -  if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) ) +  victim = &g_entities[ pid ]; +  if (victim->client->pers.connected != CON_CONNECTED)    { -    ADMP( va( "^3!register: ^7 You cannot register for name protection until you update your client. Please replace your client executable with the one at http://trem.tjw.org/backport/ and reconnect. Updating your client will also allow you to have faster map downloads.\n" ) ); +    ADMP("^3transform: ^7sorry, but your intended victim is still connecting\n");      return qfalse;    } -  if( g_newbieNumbering.integer -    && g_newbieNamePrefix.string[ 0 ] -    && !Q_stricmpn ( ent->client->pers.netname, g_newbieNamePrefix.string, strlen(g_newbieNamePrefix.string ) ) ) +  for ( i = 0; i < level.playerModelCount; i++ )    { -    ADMP( va( "^3!register: ^7 You cannot register names that begin with '%s^7'.\n", -      g_newbieNamePrefix.string ) ); +    if ( !strcmp(modelname, level.playerModel[i]) ) +    { +      found = qtrue; +      break; +    } +  } + +  if (!found) +  { +    ADMP(va("^3transform: ^7no matching model %s\n", modelname));      return qfalse;    } -  trap_SendConsoleCommand( EXEC_APPEND,va( "!setlevel %d %d;",ent - g_entities, level) ); -   -  AP( va( "print \"^3!register: ^7%s^7 is now a protected nickname.\n\"", ent->client->pers.netname) ); -   +  trap_GetUserinfo(pid, userinfo, sizeof(userinfo)); +  AP( va("print \"^3transform: ^7%s^7 has been changed into %s^7 by %s\n\"", +         victim->client->pers.netname, modelname, +         (ent ? ent->client->pers.netname : "console")) ); + +  Info_SetValueForKey( userinfo, "model", modelname ); +  Info_SetValueForKey( userinfo, "skin", GetSkin(modelname, skin)); +  Info_SetValueForKey( userinfo, "voice", modelname ); +  trap_SetUserinfo( pid, userinfo ); +  ClientUserinfoChanged( pid, qtrue ); +    return qtrue;  } -qboolean G_admin_rename( gentity_t *ent, int skiparg ) +qboolean G_admin_rename( gentity_t *ent )  { -  int pids[ MAX_CLIENTS ]; +  int pid;    char name[ MAX_NAME_LENGTH ];    char newname[ MAX_NAME_LENGTH ]; -  char oldname[ MAX_NAME_LENGTH ];    char err[ MAX_STRING_CHARS ];    char userinfo[ MAX_INFO_STRING ]; -  char *s;    gentity_t *victim = NULL; -  if( G_SayArgc() < 3 + skiparg ) +  if( trap_Argc() < 3 )    { -    ADMP( "^3!rename: ^7usage: !rename [name] [newname]\n" ); +    ADMP( "^3rename: ^7usage: rename [name] [newname]\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  s = G_SayConcatArgs( 2 + skiparg ); -  Q_strncpyz( newname, s, sizeof( newname ) ); -  if( G_ClientNumbersFromString( name, pids ) != 1 ) +  trap_Argv( 1, name, sizeof( name ) ); +  Q_strncpyz( newname, ConcatArgs( 2 ), sizeof( newname ) ); +  if( ( pid = G_ClientNumberFromString( name, err, sizeof( err ) ) ) == -1 )    { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!rename: ^7%s\n", err ) ); +    ADMP( va( "^3rename: ^7%s", err ) );      return qfalse;    } -  victim = &g_entities[ pids[ 0 ] ] ; +  victim = &g_entities[ pid ];    if( !admin_higher( ent, victim ) )    { -    ADMP( "^3!rename: ^7sorry, but your intended victim has a higher admin" +    ADMP( "^3rename: ^7sorry, but your intended victim has a higher admin"          " level than you\n" );      return qfalse;    }    if( !G_admin_name_check( victim, newname, err, sizeof( err ) ) )    { -    G_admin_print( ent, va( "^3!rename: Invalid name: ^7%s\n", err ) ); +    ADMP( va( "^3rename: ^7%s\n", err ) ); +    return qfalse; +  } +  if( victim->client->pers.connected != CON_CONNECTED ) +  { +    ADMP( "^3rename: ^7sorry, but your intended victim is still connecting\n" );      return qfalse;    } -  level.clients[ pids[ 0 ] ].pers.nameChanges--; -  level.clients[ pids[ 0 ] ].pers.nameChangeTime = 0; -  trap_GetUserinfo( pids[ 0 ], userinfo, sizeof( userinfo ) ); -  s = Info_ValueForKey( userinfo, "name" ); -  Q_strncpyz( oldname, s, sizeof( oldname ) ); +  admin_log( va( "%d (%s) \"%s" S_COLOR_WHITE "\"", pid, +    victim->client->pers.guid, victim->client->pers.netname ) ); +  admin_log( newname ); +  trap_GetUserinfo( pid, userinfo, sizeof( userinfo ) ); +  AP( va( "print \"^3rename: ^7%s^7 has been renamed to %s^7 by %s\n\"", +          victim->client->pers.netname, +          newname, +          ( ent ) ? ent->client->pers.netname : "console" ) );    Info_SetValueForKey( userinfo, "name", newname ); -  trap_SetUserinfo( pids[ 0 ], userinfo ); -  ClientUserinfoChanged( pids[ 0 ], qtrue ); -  if( strcmp( oldname, level.clients[ pids[ 0 ] ].pers.netname ) ) -    AP( va( "print \"^3!rename: ^7%s^7 has been renamed to %s^7 by %s\n\"", -        oldname, -        level.clients[ pids[ 0 ] ].pers.netname, -        ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); +  trap_SetUserinfo( pid, userinfo ); +  ClientUserinfoChanged( pid, qtrue );    return qtrue;  } -qboolean G_admin_restart( gentity_t *ent, int skiparg ) +qboolean G_admin_restart( gentity_t *ent )  { -  char layout[ MAX_CVAR_VALUE_STRING ] = { "" }; -  char teampref[ MAX_CVAR_VALUE_STRING ] = { "" }; -  int i; +  char      layout[ MAX_CVAR_VALUE_STRING ] = { "" }; +  char      teampref[ MAX_STRING_CHARS ] = { "" }; +  char      map[ MAX_CVAR_VALUE_STRING ]; +  int       i;    gclient_t *cl; -  if( G_SayArgc( ) > 1 + skiparg ) +  if( trap_Argc( ) > 1 )    {      char map[ MAX_QPATH ];      trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); -    G_SayArgv( skiparg + 1, layout, sizeof( layout ) ); +    trap_Argv( 1, layout, sizeof( layout ) ); -    if( Q_stricmp( layout, "keepteams" ) && Q_stricmp( layout, "keepteamslock" ) && Q_stricmp( layout, "switchteams" ) && Q_stricmp( layout, "switchteamslock" ) ) +    // Figure out which argument is which +    if( Q_stricmp( layout, "keepteams" ) && +        Q_stricmp( layout, "keepteamslock" ) && +        Q_stricmp( layout, "switchteams" ) && +        Q_stricmp( layout, "switchteamslock" ) )      { -      if( !Q_stricmp( layout, "*BUILTIN*" ) || -        trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), -          NULL, FS_READ ) > 0 ) +      if( G_LayoutExists( map, layout ) )        { -        trap_Cvar_Set( "g_layouts", layout ); +        trap_Cvar_Set( "g_nextLayout", layout );        }        else        { -        ADMP( va( "^3!restart: ^7layout '%s' does not exist\n", layout ) ); +        ADMP( va( "^3restart: ^7layout '%s' does not exist\n", layout ) );          return qfalse;        }      } -    else  +    else      { -      strcpy(layout,""); -      G_SayArgv( skiparg + 1, teampref, sizeof( teampref ) );     +      layout[ 0 ] = '\0'; +      trap_Argv( 1, teampref, sizeof( teampref ) );      }    } -   -  if( G_SayArgc( ) > 2 + skiparg )  -  { -    G_SayArgv( skiparg + 2, teampref, sizeof( teampref ) );       -  } -   -   -  if( !Q_stricmp( teampref, "keepteams" ) || !Q_stricmp( teampref, "keepteamslock" ) ) + +  if( trap_Argc( ) > 2 ) +    trap_Argv( 2, teampref, sizeof( teampref ) ); + +  admin_log( layout ); +  admin_log( teampref ); + +  if( !Q_stricmpn( teampref, "keepteams", 9 ) )    {      for( i = 0; i < g_maxclients.integer; i++ )      { @@ -6076,2349 +2913,716 @@ qboolean G_admin_restart( gentity_t *ent, int skiparg )        if( cl->pers.connected != CON_CONNECTED )          continue; -      if( cl->pers.teamSelection == PTE_NONE ) +      if( cl->pers.teamSelection == TEAM_NONE )          continue;        cl->sess.restartTeam = cl->pers.teamSelection;      } -  }  -  else if(!Q_stricmp( teampref, "switchteams" ) ||  !Q_stricmp( teampref, "switchteamslock" )) +  } +  else if( !Q_stricmpn( teampref, "switchteams", 11 ) )    {      for( i = 0; i < g_maxclients.integer; i++ )      {        cl = level.clients + i; -      if( cl->pers.connected != CON_CONNECTED ) -        continue; -      if( cl->pers.teamSelection == PTE_NONE ) +      if( cl->pers.connected != CON_CONNECTED )          continue; -      if( cl->pers.teamSelection == PTE_ALIENS ) -        cl->sess.restartTeam = PTE_HUMANS; -      else if(cl->pers.teamSelection == PTE_HUMANS ) -        cl->sess.restartTeam = PTE_ALIENS; -    }     +      if( cl->pers.teamSelection == TEAM_HUMANS ) +        cl->sess.restartTeam = TEAM_ALIENS; +      else if(cl->pers.teamSelection == TEAM_ALIENS ) +	    cl->sess.restartTeam = TEAM_HUMANS; +    }    } -   -  if( !Q_stricmp( teampref, "switchteamslock" ) || !Q_stricmp( teampref, "keepteamslock" ) ) -  { + +  if( !Q_stricmp( teampref, "switchteamslock" ) || +      !Q_stricmp( teampref, "keepteamslock" ) )      trap_Cvar_Set( "g_lockTeamsAtStart", "1" ); -  } -  trap_SendConsoleCommand( EXEC_APPEND, "map_restart" ); -   -  if(teampref[ 0 ]) -    strcpy(teampref,va( "^7(with teams option: '%s^7')", teampref )); -   -  G_admin_maplog_result( "R" ); -   -  AP( va( "print \"^3!restart: ^7map restarted by %s %s %s\n\"", -          ( ent ) ? G_admin_adminPrintName( ent ) : "console", +  trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); +  G_MapConfigs( map ); +  trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); + +  AP( va( "print \"^3restart: ^7map restarted by %s %s %s\n\"", +          ( ent ) ? ent->client->pers.netname : "console",            ( layout[ 0 ] ) ? va( "^7(forcing layout '%s^7')", layout ) : "", -          teampref ) ); +          ( teampref[ 0 ] ) ? va( "^7(with teams option: '%s^7')", teampref ) : "" ) );    return qtrue;  } -qboolean G_admin_nobuild( gentity_t *ent, int skiparg ) +qboolean G_admin_nextmap( gentity_t *ent )  { -  char buffer[ MAX_STRING_CHARS ]; -  int  i, tmp; -   -  if( G_SayArgc() < 2 + skiparg ) -  { -    ADMP( "^3!nobuild: ^7usage: !nobuild (^5enable / disable / log / remove / save^7)\n" ); +  if( level.exited )      return qfalse; -  } -   -  G_SayArgv( 1 + skiparg, buffer, sizeof( buffer ) ); -   -  if( !Q_stricmp( buffer, "enable" ) || !Q_stricmp( buffer, "Enable" ) ) -  { -  	if( G_SayArgc() < 4 + skiparg ) -  	{ -		ADMP( "^3!nobuild: ^7usage: !nobuild enable (^5area^7) (^5height^7)\n" ); -		return qfalse; -  	} -	 -  	if( !level.noBuilding ) -  	{ -	 -  		level.noBuilding = qtrue; -		 -		// Grab and set the area -		G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) ); -		tmp = atoi( buffer ); -		level.nbArea = tmp; -		 -		// Grab and set the height -		G_SayArgv( 3 + skiparg, buffer, sizeof( buffer ) ); -		tmp = atoi( buffer ); -		level.nbHeight = tmp; -		 -		ADMP( "^3!nobuild: ^7nobuild is now enabled, please place a buildable to spawn a marker\n" ); -		return qtrue; -  	} -	else -	{ -		ADMP( "^3!nobuild: ^7nobuild is already enabled. type !nobuild disable to disable nobuild mode.\n" ); -		return qfalse; -	} -  } -   -  if( !Q_stricmp( buffer, "disable" ) || !Q_stricmp( buffer, "Disable" ) ) -  { -  	if( level.noBuilding ) -  	{ -  		level.noBuilding = qfalse; -		level.nbArea = 0.0f; -		level.nbHeight = 0.0f; -		ADMP( "^3!nobuild: ^7nobuild is now disabled\n" ); -		return qtrue; -  	} -	else -	{ -		ADMP( "^3!nobuild: ^7nobuild is disabled. type !nobuild enable (^5area^7) (^5height^7) to enable nobuild mode.\n" ); -		return qfalse; -	} -  } -   -  if( !Q_stricmp( buffer, "log" ) || !Q_stricmp( buffer, "Log" ) ) -  { - 	ADMBP_begin(); -	 -	tmp = 0; -  	for( i = 0; i < MAX_GENTITIES; i++ ) -  	{ -  		if( level.nbMarkers[ i ].Marker == NULL ) -  		continue; -		 -		// This will display a start at 1, and not 0. This is to stop confusion by server ops -		ADMBP( va( "^7#%i at origin (^5%f %f %f^7)^7\n", i + 1, level.nbMarkers[ i ].Origin[0], level.nbMarkers[ i ].Origin[1], level.nbMarkers[ i ].Origin[2] ) ); -		tmp++; - 	} -	ADMBP( va( "^3!nobuild:^7 displaying %i marker(s)\n", tmp ) ); -	 -  	ADMBP_end(); -	return qtrue; -  } -   -  if( !Q_stricmp( buffer, "remove" ) || !Q_stricmp( buffer, "Remove" ) ) -  { - 	if( G_SayArgc() < 3 + skiparg ) -  	{ -		ADMP( "^3!nobuild: ^7usage: !nobuild remove (^5marker #^7)\n" ); -		return qfalse; -  	} -	 -	G_SayArgv( 2 + skiparg, buffer, sizeof( buffer ) ); -	tmp = atoi( buffer ) - 1; -	// ^ that was to work with the display number... -	 -	if( level.nbMarkers[ tmp ].Marker == NULL ) -	{ -		ADMP( "^3!nobuild: ^7that is not a valid marker number\n" ); -		return qfalse; -	} -	// Donno why im doing this, but lets clear this... -	level.nbMarkers[ tmp ].Marker->noBuild.isNB = qfalse; -	level.nbMarkers[ tmp ].Marker->noBuild.Area = 0.0f; -	level.nbMarkers[ tmp ].Marker->noBuild.Height = 0.0f; -	 -	// Free the entitiy and null it out... -	G_FreeEntity( level.nbMarkers[ tmp ].Marker ); -	level.nbMarkers[ tmp ].Marker = NULL; -	 -	// That is to work with the display number... -	ADMP( va( "^3!nobuild:^7 marker %i has been removed\n", tmp + 1 ) ); -	return qtrue; -  } -   -  if( !Q_stricmp( buffer, "save" ) || !Q_stricmp( buffer, "Save" ) ) -  { -  	int  i, tmp; -	 - 	G_NobuildSave( ); -	 -	tmp = 0; -  	for( i = 0; i < MAX_GENTITIES; i++ ) -  	{ -  		if( level.nbMarkers[ i ].Marker == NULL ) -  		continue; -		 -		tmp++; - 	} -	ADMP( va( "^3!nobuild:^7 %i nobuild markers have been saved\n", tmp ) ); -	return qtrue; -  } -  return qfalse; -} - -qboolean G_admin_nextmap( gentity_t *ent, int skiparg ) -{ -  AP( va( "print \"^3!nextmap: ^7%s^7 decided to load the next map\n\"", -    ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -  level.lastWin = PTE_NONE; +  AP( va( "print \"^3nextmap: ^7%s^7 decided to load the next map\n\"", +    ( ent ) ? ent->client->pers.netname : "console" ) ); +  level.lastWin = TEAM_NONE;    trap_SetConfigstring( CS_WINNER, "Evacuation" );    LogExit( va( "nextmap was run by %s", -    ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -  G_admin_maplog_result( "N" ); +    ( ent ) ? ent->client->pers.netname : "console" ) );    return qtrue;  } -static const char *displaySchachtmeisterJudgement(const schachtmeisterJudgement_t *j, -                                                  g_admin_namelog_t *namelog) -{ -	static char buffer[20]; - -    if (G_admin_permission_guid(namelog->guid, ADMF_INCOGNITO)) { -	    Com_sprintf(buffer, sizeof(buffer), "^2   +0"); -    } else if (strcmp(namelog->guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX") && -	    (G_admin_permission_guid(namelog->guid, ADMF_NOAUTOBAHN) || -	     G_admin_permission_guid(namelog->guid, ADMF_IMMUNITY))) { -		Com_sprintf(buffer, sizeof(buffer), "^7 N/A "); -	} else if (!j->ratingTime) { -		Com_sprintf(buffer, sizeof(buffer), "^5 wait"); -	} else { -		int color; - -		if (j->rating >= g_schachtmeisterClearThreshold.integer) -			color = 2; -		else if (j->rating <= g_schachtmeisterAutobahnThreshold.integer) -			color = 1; -		else -			color = 3; - -		Com_sprintf(buffer, sizeof(buffer), "^%i%+5i", color, j->rating); -	} - -	return buffer; -} - -qboolean G_admin_namelog( gentity_t *ent, int skiparg ) +qboolean G_admin_setnextmap( gentity_t *ent )  { -  int i, j; -  char search[ MAX_NAME_LENGTH ] = {""}; -  char s2[ MAX_NAME_LENGTH ] = {""}; -  char n2[ MAX_NAME_LENGTH ] = {""}; -  char guid_stub[ 9 ]; -  qboolean found = qfalse; -  int printed = 0; +  int argc = trap_Argc(); +  char map[ MAX_QPATH ]; +  char layout[ MAX_QPATH ]; -  if( G_SayArgc() > 1 + skiparg ) +  if( argc < 2 || argc > 3 )    { -    G_SayArgv( 1 + skiparg, search, sizeof( search ) ); -    G_SanitiseString( search, s2, sizeof( s2 ) ); +    ADMP( "^3setnextmap: ^7usage: setnextmap [map] (layout)\n" ); +    return qfalse;    } -  ADMBP_begin(); -  for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) -  { -    if( search[0] ) -    { -      found = qfalse; -      for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES &&  -        g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) -      { -        G_SanitiseString( g_admin_namelog[ i ]->name[ j ], n2, sizeof( n2 ) ); -        if( strstr( n2, s2 ) ) -        { -          found = qtrue; -          break; -        } -      } -      if( !found ) -        continue; -    } -    printed++; -    for( j = 0; j < 8; j++ ) -      guid_stub[ j ] = g_admin_namelog[ i ]->guid[ j + 24 ]; -    guid_stub[ j ] = '\0'; -    if( g_admin_namelog[ i ]->slot > -1 ) -       ADMBP( "^3" ); -    ADMBP( va( "%-2s (*%s) %15s %s^7",  -      (g_admin_namelog[ i ]->slot > -1 ) ? -        va( "%d", g_admin_namelog[ i ]->slot ) : "-", -      guid_stub, g_admin_namelog[ i ]->ip, -      displaySchachtmeisterJudgement( &g_admin_namelog[ i ]->smj, g_admin_namelog[ i ] ) ) ); -    for( j = 0; j < MAX_ADMIN_NAMELOG_NAMES &&  -      g_admin_namelog[ i ]->name[ j ][ 0 ]; j++ ) -    { -      ADMBP( va( " '%s^7'", g_admin_namelog[ i ]->name[ j ] ) ); -    } -    ADMBP( "\n" );  -  }  -  ADMBP( va( "^3!namelog:^7 %d recent clients found\n", printed ) ); -  ADMBP_end(); -  return qtrue; -} -qboolean G_admin_lock( gentity_t *ent, int skiparg ) -{ -  char teamName[2] = {""}; -  pTeam_t team; +  trap_Argv( 1, map, sizeof( map ) ); -  if( G_SayArgc() < 2 + skiparg ) +  if( !G_MapExists( map ) )    { -    ADMP( "^3!lock: ^7usage: !lock [a|h]\n" ); -    return qfalse; -  } -  G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) ); -  if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' ) -    team = PTE_ALIENS; -  else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' ) -    team = PTE_HUMANS; -  else -  { -    ADMP( va( "^3!lock: ^7invalid team\"%c\"\n", teamName[0] ) ); +    ADMP( va( "^3setnextmap: ^7map '%s' does not exist\n", map ) );      return qfalse;    } -  if( team == PTE_ALIENS ) +  if( argc > 2 )    { -    if( level.alienTeamLocked ) -    { -      ADMP( "^3!lock: ^7Alien team is already locked\n" ); -      return qfalse; -    } -    else -      level.alienTeamLocked = qtrue; -  } -  else if( team == PTE_HUMANS ) { -    if( level.humanTeamLocked ) +    trap_Argv( 2, layout, sizeof( layout ) ); + +    if( !G_LayoutExists( map, layout ) )      { -      ADMP( "^3!lock: ^7Human team is already locked\n" ); +      ADMP( va( "^3setnextmap: ^7layout '%s' does not exist for map '%s'\n", layout, map ) );        return qfalse;      } -    else -      level.humanTeamLocked = qtrue; -  } -  AP( va( "print \"^3!lock: ^7%s team has been locked by %s\n\"", -    ( team == PTE_ALIENS ) ? "Alien" : "Human", -    ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -  return qtrue; -}  - -qboolean G_admin_unlock( gentity_t *ent, int skiparg ) -{ -  char teamName[2] = {""}; -  pTeam_t team; - -  if( G_SayArgc() < 2 + skiparg ) -  { -    ADMP( "^3!unlock: ^7usage: !unlock [a|h]\n" ); -    return qfalse; +    trap_Cvar_Set( "g_nextLayout", layout );    } -  G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) ); -  if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' ) -    team = PTE_ALIENS; -  else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' ) -    team = PTE_HUMANS;    else -  { -    ADMP( va( "^3!unlock: ^7invalid team\"%c\"\n", teamName[0] ) ); -    return qfalse; -  } -   -  if( team == PTE_ALIENS ) -  { -    if( !level.alienTeamLocked ) -    { -      ADMP( "^3!unlock: ^7Alien team is not currently locked\n" ); -      return qfalse; -    } -    else -      level.alienTeamLocked = qfalse; -  } -  else if( team == PTE_HUMANS ) { -    if( !level.humanTeamLocked ) -    { -      ADMP( "^3!unlock: ^7Human team is not currently locked\n" ); -      return qfalse; -    } -    else -      level.humanTeamLocked = qfalse; -  } +    trap_Cvar_Set( "g_nextLayout", "" ); + +  trap_Cvar_Set( "g_nextMap", map ); -  AP( va( "print \"^3!unlock: ^7%s team has been unlocked by %s\n\"", -    ( team == PTE_ALIENS ) ? "Alien" : "Human", -    ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); +  AP( va( "print \"^3setnextmap: ^7%s^7 has set the next map to '%s'%s\n\"", +          ( ent ) ? ent->client->pers.netname : "console", map, +          argc > 2 ? va( " with layout '%s'", layout ) : "" ) );    return qtrue; -}  +} -qboolean G_admin_designate( gentity_t *ent, int skiparg ) +static qboolean namelog_matchip( void *namelog, const void *ip )  { -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; -  char command[ MAX_ADMIN_CMD_LEN ], *cmd; -  gentity_t *vic; - -  if( G_SayArgc() < 2 + skiparg ) -  { -    ADMP( "^3!designate: ^7usage: designate [name|slot#]\n" ); -    return qfalse; -  } -  G_SayArgv( skiparg, command, sizeof( command ) ); -  cmd = command; -  if( cmd && *cmd == '!' ) -    cmd++; -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  if( G_ClientNumbersFromString( name, pids ) != 1 ) -  { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!designate: ^7%s\n", err ) ); -    return qfalse; -  } -  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) && -    !Q_stricmp( cmd, "undesignate" ) ) -  { -    ADMP( "^3!designate: ^7sorry, but your intended victim has a higher admin" -        " level than you\n" ); -    return qfalse; -  } -  vic = &g_entities[ pids[ 0 ] ]; -  if( vic->client->pers.designatedBuilder == qtrue ) +  int i; +  namelog_t *n = (namelog_t *)namelog; +  for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ )    { -    if( !Q_stricmp( cmd, "designate" ) ) -    { -      ADMP( "^3!designate: ^7player is already designated builder\n" ); +    if( G_AddressCompare( &n->ip[ i ], (addr_t *)ip ) || +        G_AddressCompare( (addr_t *)ip, &n->ip[ i ] ) )        return qtrue; -    } -    vic->client->pers.designatedBuilder = qfalse; -    CPx( pids[ 0 ], "cp \"^1Your designation has been revoked\"" ); -    AP( va( -      "print \"^3!designate: ^7%s^7's designation has been revoked by %s\n\"", -       vic->client->pers.netname, -       ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -    G_CheckDBProtection( );    } -  else +  return qfalse; +} +static qboolean namelog_matchname( void *namelog, const void *name ) +{ +  char match[ MAX_NAME_LENGTH ]; +  int i; +  namelog_t *n = (namelog_t *)namelog; +  for( i = 0; i < MAX_NAMELOG_NAMES && n->name[ i ][ 0 ]; i++ )    { -    if( !Q_stricmp( cmd, "undesignate" ) ) -    { -      ADMP( "^3!undesignate: ^7player is not currently designated builder\n" ); +    G_SanitiseString( n->name[ i ], match, sizeof( match ) ); +    if( strstr( match, (const char *)name ) )        return qtrue; -    } -    vic->client->pers.designatedBuilder = qtrue; -    CPx( pids[ 0 ], "cp \"^1You've been designated\"" ); -    AP( va( "print \"^3!designate: ^7%s^7 has been designated by ^7%s\n\"", -      vic->client->pers.netname, -      ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );    } -  return qtrue; +  return qfalse;  } +static void namelog_out( void *namelog, char *str ) +{ +  namelog_t *n = (namelog_t *)namelog; +  char *p = str; +  int l, l2 = MAX_STRING_CHARS, i; +  const char *scolor; - //!Warn by Gate (Daniel Evans)  -qboolean G_admin_warn( gentity_t *ent, int skiparg ) -{//mostly copy and paste with the proper lines altered from !mute and !kick -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ]; -  int minargc; -  gentity_t *vic; - -  minargc = 3 + skiparg; -  if( G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) -    minargc = 2 + skiparg; - -  if( G_SayArgc() < minargc ) +  if( n->slot > -1 )    { -    ADMP( "^3!warn: ^7usage: warn [name] [reason]\n" ); -    return qfalse; +    scolor = S_COLOR_YELLOW; +    l = Q_snprintf( p, l2, "%s%-2d", scolor, n->slot ); +    p += l; +    l2 -= l;    } -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  reason = G_SayConcatArgs( 2 + skiparg ); -  if( G_ClientNumbersFromString( name, pids ) != 1 ) -  { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!warn: ^7%s\n", err ) ); -    return qfalse; -  } -  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) +  else    { -    ADMP( "^3!warn: ^7sorry, but your intended victim has a higher admin" -        " level than you.\n" ); -    return qfalse; +    *p++ = '-'; +    *p++ = ' '; +    *p = '\0'; +    l2 -= 2; +    scolor = S_COLOR_WHITE;    } -  -  vic = &g_entities[ pids[ 0 ] ]; -  //next line is the onscreen warning -  CPx( pids[ 0 ],va("cp \"^1You have been warned by an administrator.\n ^3Cease immediately or face admin action!\n^1 %s%s\"",(*reason)? "REASON: " : "" ,(*reason)? reason : "") ); -  AP( va( "print \"^3!warn: ^7%s^7 has been warned to cease and desist: %s by %s \n\"", -            vic->client->pers.netname, (*reason) ? reason : "his current activity", -            ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) );//console announcement -  return qtrue; -} -  -qboolean G_admin_putmespec( gentity_t *ent, int skiparg ) -{ -  if( !ent ) + +  for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ )    { -    ADMP( "!specme: sorry, but console isn't allowed on the spectators team\n"); -    return qfalse; +    l = Q_snprintf( p, l2, " %s", n->ip[ i ].str ); +    p += l; +    l2 -= l;    } -  if( level.paused ) +  for( i = 0; i < MAX_NAMELOG_NAMES && n->name[ i ][ 0 ]; i++ )    { -    ADMP("!specme: disabled when game is paused\n"); -    return qfalse; -  } -   -  if(ent->client->pers.teamSelection == PTE_NONE) -    return qfalse; -   -    //guard against build timer exploit -  if( ent->client->pers.teamSelection != PTE_NONE && ent->client->sess.sessionTeam != TEAM_SPECTATOR &&  -     ( ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || -       ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG || -       BG_InventoryContainsWeapon( WP_HBUILD, ent->client->ps.stats ) || -       BG_InventoryContainsWeapon( WP_HBUILD2, ent->client->ps.stats ) ) && -      ent->client->ps.stats[ STAT_MISC ] > 0 ) -  { -    ADMP("!specme: You cannot leave your team until the build timer expires"); -    return qfalse; +    l = Q_snprintf( p, l2, " '" S_COLOR_WHITE "%s%s'%s", n->name[ i ], scolor, +                    i == n->nameOffset ? "*" : "" ); +    p += l; +    l2 -= l;    } -   -  G_ChangeTeam( ent, PTE_NONE ); -  AP( va("print \"^3!specme: ^7%s^7 decided to join the spectators\n\"", ent->client->pers.netname ) ); -  return qtrue;  } - -qboolean G_admin_slap( gentity_t *ent, int skiparg ) +qboolean G_admin_namelog( gentity_t *ent )  { -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; -  gentity_t *vic; -  vec3_t dir; - -  if( level.intermissiontime ) return qfalse; +  char search[ MAX_NAME_LENGTH ] = {""}; +  char s2[ MAX_NAME_LENGTH ] = {""}; +  addr_t ip; +  qboolean ipmatch = qfalse; +  int start = MAX_CLIENTS, i; -  if( G_SayArgc() < 2 + skiparg ) +  if( trap_Argc() == 3 )    { -    ADMP( "^3!slap: ^7usage: !slap [name|slot#]\n" ); -    return qfalse; +    trap_Argv( 2, search, sizeof( search ) ); +    start = atoi( search );    } - -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  if( G_ClientNumbersFromString( name, pids ) != 1 ) +  if( trap_Argc() > 1 )    { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!slap: ^7%s\n", err ) ); -    return qfalse; +    trap_Argv( 1, search, sizeof( search ) ); +    i = 0; +    if( trap_Argc() == 2 ) +      for( i = search[ 0 ] == '-'; isdigit( search[ i ] ); i++ ); +    if( !search[ i ] ) +      start = atoi( search ); +    else if( !( ipmatch = G_AddressParse( search, &ip ) ) ) +      G_SanitiseString( search, s2, sizeof( s2 ) );    } -  vic = &g_entities[ pids[ 0 ] ]; -  if( !vic ) -  { -    ADMP( "^3!slap: ^7bad target\n" ); -    return qfalse; -  } -  if( vic == ent ) -  { -    ADMP( "^3!slap: ^7sorry, you cannot slap yourself\n" ); -    return qfalse; -  } -  if( !admin_higher( ent, vic ) ) -  { -    ADMP( "^3!slap: ^7sorry, but your intended victim has a higher admin" -          " level than you\n" ); -    return qfalse; -  } -  if( vic->client->pers.teamSelection == PTE_NONE || -      vic->client->pers.classSelection == PCL_NONE ) -  { -    ADMP( "^3!slap: ^7can't slap spectators\n" ); -    return qfalse; -  } +  admin_search( ent, "namelog", "recent players", +    ipmatch ? namelog_matchip : namelog_matchname, namelog_out, level.namelogs, +    ipmatch ? (void *)&ip : s2, start, MAX_CLIENTS, MAX_ADMIN_LISTITEMS ); +  return qtrue; +} -  // knockback in a random direction -  dir[0] = crandom(); -  dir[1] = crandom(); -  dir[2] = random(); -  G_Knockback( vic, dir, g_slapKnockback.integer ); +/* +================== +G_NamelogFromString -  trap_SendServerCommand( vic-g_entities, -    va( "cp \"%s^7 is not amused\n\"", -        ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); +This is similar to G_ClientNumberFromString but for namelog instead +Returns NULL if not exactly 1 match +================== +*/ +namelog_t *G_NamelogFromString( gentity_t *ent, char *s ) +{ +  namelog_t *p, *m = NULL; +  int       i, found = 0; +  char      n2[ MAX_NAME_LENGTH ] = {""}; +  char      s2[ MAX_NAME_LENGTH ] = {""}; -  if( g_slapDamage.integer > 0 ) +  if( !s[ 0 ] ) +    return NULL; + +  // if a number is provided, it is a clientnum or namelog id +  for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); +  if( !s[ i ] )    { -    int damage; +    i = atoi( s ); -    if( G_SayArgc() > 2 + skiparg ) +    if( i >= 0 && i < level.maxclients )      { -      char dmg_str[ MAX_STRING_CHARS ]; -      G_SayArgv( 2 + skiparg, dmg_str, sizeof( dmg_str ) ); -      damage = atoi(dmg_str); -      if( damage < 0 ) damage = 0; +      if( level.clients[ i ].pers.connected != CON_DISCONNECTED ) +        return level.clients[ i ].pers.namelog;      } -    else +    else if( i >= MAX_CLIENTS )      { -      if( g_slapDamage.integer > 100 ) g_slapDamage.integer = 100; -      damage = BG_FindHealthForClass( vic->client->ps.stats[ STAT_PCLASS ] ) * -        g_slapDamage.integer / 100; -      if( damage < 1 ) damage = 1; +      for( p = level.namelogs; p; p = p->next ) +      { +        if( p->id == i ) +          break; +      } +      if( p ) +        return p;      } -    vic->health -= damage; -    vic->client->ps.stats[ STAT_HEALTH ] = vic->health; -    vic->lastDamageTime = level.time; -    if( vic->health <= 0 ) -    { -      vic->flags |= FL_NO_KNOCKBACK; -      vic->enemy = &g_entities[ pids[ 0 ] ]; -      vic->die( vic, ent, ent, damage, MOD_SLAP ); -    } -    else if( vic->pain ) -    { -      vic->pain( vic, &g_entities[ pids[ 0 ] ], damage ); -    } +    return NULL;    } -  return qtrue; -} -qboolean G_admin_drop( gentity_t *ent, int skiparg ) -{ -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; +  // check for a name match +  G_SanitiseString( s, s2, sizeof( s2 ) ); -  if( G_SayArgc() < 2 + skiparg ) +  for( p = level.namelogs; p; p = p->next )    { -    ADMP( "^3!drop: ^7usage: !drop [name|slot#] [message]\n" ); -    return qfalse; -  } +    for( i = 0; i < MAX_NAMELOG_NAMES && p->name[ i ][ 0 ]; i++ ) +    { +      G_SanitiseString( p->name[ i ], n2, sizeof( n2 ) ); -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  if( G_ClientNumbersFromString( name, pids ) != 1 ) -  { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!drop: ^7%s\n", err ) ); -    return qfalse; -  } +      // if this is an exact match to a current player +      if( i == p->nameOffset && p->slot > -1 && !strcmp( s2, n2 ) ) +        return p; -  if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) -  { -    ADMP( "^3!drop: ^7sorry, but your intended victim has a higher admin" -          " level than you\n" ); -    return qfalse; +      if( strstr( n2, s2 ) ) +        m = p; +    } + +    if( m == p ) +      found++;    } -  // victim's message -  if( G_SayArgc() > 2 + skiparg ) -    trap_SendServerCommand( pids[ 0 ], -      va( "disconnect \"You have been dropped.\n%s^7\n\"", -      G_SayConcatArgs( 2 + skiparg ) ) ); -  else -    trap_SendServerCommand( pids[ 0 ], va( "disconnect" ) ); +  if( found == 1 ) +    return m; -  // server message -  trap_DropClient( pids[ 0 ], va( "disconnected" ) ); +  if( found > 1 ) +    admin_search( ent, "namelog", "recent players", namelog_matchname, +      namelog_out, level.namelogs, s2, 0, MAX_CLIENTS, -1 ); -  return qtrue; +  return NULL;  } -qboolean G_admin_buildlog( gentity_t *ent, int skiparg ) +qboolean G_admin_lock( gentity_t *ent )  { -#define LOG_DISPLAY_LENGTH 10 -  buildHistory_t *ptr; -  gentity_t *builder = NULL; -  int skip = 0, start = 0, lastID = -1, firstID = -1, i, len, matchlen = 0; -  pTeam_t team = PTE_NONE; -  char message[ MAX_STRING_CHARS ], *teamchar;  -  char *name, *action, *buildablename, markstring[ MAX_STRING_CHARS ];  -  if( !g_buildLogMaxLength.integer ) -  { -    ADMP( "^3!buildlog: ^7build logging is disabled" ); -    return qfalse; -  } -  if( G_SayArgc( ) >= 2 + skiparg ) -  { -    for( i = 1; i + skiparg < G_SayArgc( ); i++ ) -    { -      char argbuf[ 64 ], err[ MAX_STRING_CHARS ]; -      int x = 0, pids[ MAX_CLIENTS ]; -      G_SayArgv( i + skiparg, argbuf, sizeof argbuf ); -      switch( argbuf[ 0 ]) -      { -        case 'x': -          x = 1; -        default: -          skip = atoi( argbuf + x ); -          start = 0; -          break; -        case '#': -          start = atoi( argbuf + 1 ); -          skip = 0; -          break; -        case '-': -          if(G_ClientNumbersFromString(argbuf + 1, pids) != 1) -          { -           G_MatchOnePlayer(pids, err, sizeof(err)); -           ADMP(va("^3!revert: ^7%s\n", err)); -           return qfalse; -          } -          builder = g_entities + *pids; -          break; -        case 'A': -        case 'a': -          team = PTE_ALIENS; -          break; -        case 'H': -        case 'h': -          team = PTE_HUMANS; -          break; -      } -    } -  } -  // !buildlog can be abused, so let everyone know when it is used -  AP( va( "print \"^3!buildlog: ^7%s^7 requested a log of recent building" -      " activity\n\"", ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -  len = G_CountBuildLog( ); // also clips the log if too long -  if( !len ) -  { -    ADMP( "^3!buildlog: ^7no build log found\n" ); -    return qfalse; -  } -  if( start ) -  { -    // set skip based on start -    for( ptr = level.buildHistory; ptr && ptr->ID != start;  -        ptr = ptr->next, skip++ ); -    if( !ptr ) -    { -      ADMP( "^3!buildlog: ^7log ID not found\n" ); -      skip = 0; -    } -  } -  // ensure skip is a useful value -  if( skip > len - LOG_DISPLAY_LENGTH ) -    skip = len - LOG_DISPLAY_LENGTH; -  *message = '\0'; -  // skip to start entry -  for( ptr = level.buildHistory, i = len; ptr && i > len - skip;  -      ptr = ptr->next ) -  { -    // these checks could perhaps be done more efficiently but they are cheap -    // in processor time so I'm not worrying -    if( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) ) -      continue; -    if( builder && builder != ptr->ent ) -      continue; -    matchlen++; -    i--; -  } -  for( ; i + LOG_DISPLAY_LENGTH > len - skip && i > 0; i--, ptr = ptr->next ) -  { -    if( !ptr )  -      break; // run out of log -    *markstring = '\0'; // reinit markstring -    // check team -    if( ( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) ) -        || ( builder && builder != ptr->ent ) ) -    { -      skip++; // loop an extra time because we skipped one -      continue;  -    } -    if( lastID < 0 ) -      lastID = ptr->ID; -    firstID = ptr->ID; -    matchlen++; -    // set name to the ent's current name or last recorded name -    if( ptr->ent ) -    { -      if( ptr->ent->client ) -        name = ptr->ent->client->pers.netname; -      else -        name = "<world>"; // any non-client action -    } -    else -      name = ptr->name; -    switch( ptr->fate ) -    { -      case BF_BUILT: -        action = "^2built^7 a"; -        break; -      case BF_DECONNED: -        action = "^3DECONSTRUCTED^7 a"; -        break; -      case BF_DESTROYED: -        action = "destroyed a"; -        break; -      case BF_TEAMKILLED: -        action = "^1TEAMKILLED^7 a"; -        break; -      default: -        action = "\0"; // erm -        break; -    } -    // handle buildables removed by markdecon -    if( ptr->marked ) -    { -      buildHistory_t *mark; -      int j, markdecon[ BA_NUM_BUILDABLES ], and = 2; -      char bnames[ 32 ], *article; -      mark = ptr; -      // count the number of buildables -      memset( markdecon, 0, sizeof( markdecon ) ); -      while( ( mark = mark->marked ) ) -        markdecon[ mark->buildable ]++; -      // reverse order makes grammar easier -      for( j = BA_NUM_BUILDABLES; j >= 0; j-- ) -      { -        buildablename = BG_FindHumanNameForBuildable( j ); -        // plural is easy -        if( markdecon[ j ] > 1 ) -          Com_sprintf( bnames, 32, "%d %ss", markdecon[ j ], buildablename ); -        // use an appropriate article -        else if( markdecon[ j ] == 1 ) -        { -          if( BG_FindUniqueTestForBuildable( j ) ) -            article = "the"; // if only one -          else if( strchr( "aeiouAEIOU", *buildablename ) ) -            article = "an"; // if first char is vowel -          else -            article = "a"; -          Com_sprintf( bnames, 32, "%s %s", article, buildablename ); -        } -        else -          continue; // none of this buildable -        // C grammar: x, y, and z -        // the integer and is 2 initially, the test means it is used on the -        // second sprintf only, the reverse order makes this second to last -        // the comma is only printed if there is already some markstring i.e. -        // not the first time ( which would put it on the end of the string ) -        Com_sprintf( markstring, sizeof( markstring ), "%s%s %s%s", bnames,  -            ( *markstring ) ? "," : "", ( and-- == 1 ) ? "and " : "", markstring ); -      } -    } -    buildablename = BG_FindHumanNameForBuildable( ptr->buildable ); -    switch( BG_FindTeamForBuildable( ptr->buildable ) ) -    { -      case PTE_ALIENS:  -        teamchar = "^1A";  -        break; -      case PTE_HUMANS:  -        teamchar = "^4H";  -        break; -      default:  -        teamchar = " "; // space so it lines up neatly -        break; -    } -    // prepend the information to the string as we go back in buildhistory -    // so the earliest events are at the top -    Com_sprintf( message, MAX_STRING_CHARS, "%3d %s^7 %s^7 %s%s %s%s%s\n%s",  -        ptr->ID, teamchar, name, action,  -        ( strchr( "aeiouAEIOU", buildablename[ 0 ] ) ) ? "n" : "",  -        buildablename, ( markstring[ 0 ] ) ? ", removing " : "",  -        markstring, message ); -  } -  for( ; ptr; ptr = ptr->next ) -  { -    if( builder && builder != ptr->ent ) -      continue; -    if( team != PTE_NONE && team != BG_FindTeamForBuildable( ptr->buildable ) ) -      continue; -    matchlen++; -  } -  if( matchlen ) -    ADMP( va( "%s^3!buildlog: showing log entries %d - %d of %d\n", message, -        firstID, lastID, matchlen ) ); -  else -    ADMP( "^3!buildlog: ^7no log entries match those criteria\n" ); -  return qtrue; -} +  char command[ MAX_ADMIN_CMD_LEN ]; +  char teamName[ sizeof( "aliens" ) ]; +  team_t team; +  qboolean lock, fail = qfalse; -qboolean G_admin_revert( gentity_t *ent, int skiparg ) -{ -  int i = 0, j = 0, repeat = 1, ID = 0, len, matchlen=0; -  pTeam_t team = PTE_NONE; -  qboolean force = qfalse, reached = qfalse; -  gentity_t *builder = NULL, *targ; -  buildHistory_t *ptr, *tmp, *mark, *prev; -  vec3_t dist; -  char argbuf[ 64 ], *name, *bname, *action, *article; -  len = G_CountBuildLog( ); -  if( !len ) -  { -    ADMP( "^3!revert: ^7no build log found\n" ); -    return qfalse; -  } -  if( G_SayArgc( ) < 2 + skiparg ) +  trap_Argv( 0, command, sizeof( command ) ); +  if( trap_Argc() < 2 )    { -    ADMP( "^3!revert: ^7usage: !revert (^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)\n" ); +    ADMP( va( "^3%s: ^7usage: %s [a|h]\n", command, command ) );      return qfalse;    } -  for( i = 1; i + skiparg < G_SayArgc( ); i++ ) -  { -    char arg[ 64 ], err[ MAX_STRING_CHARS ]; -    int pids[ MAX_CLIENTS ]; -    G_SayArgv( i + skiparg, arg, sizeof arg ); -    switch( arg[ 0 ]) -    { -      case 'x': -        repeat = atoi( arg + 1 ); -        break; -      case '#': -        ID = atoi( arg + 1 ); -        break; -      case '-': -        if(G_ClientNumbersFromString(arg + 1, pids) != 1) -        { -          G_MatchOnePlayer(pids, err, sizeof err); -          ADMP(va("^3!revert: ^7%s\n", err)); -          return qfalse; -        } -        builder = g_entities + *pids; -        break; -      case 'A': -      case 'a': -        team = PTE_ALIENS; -        break; -      case 'H': -      case 'h': -        team = PTE_HUMANS; -        break; -      case '!': -        force = qtrue; -        break; -      default: -        ADMP( "^3!revert: ^7usage: !revert (^5xnum^7) (^5#ID^7) (^5-name|num^7) (^5a|h^7)\n" ); -        return qfalse; -    } -  } -  if( repeat > 25 ) -  { -    ADMP( "^3!revert: ^7to avoid flooding, can only revert 25 builds at a time\n" ); -    repeat = 25; -  } -  for( i = 0, ptr = prev = level.buildHistory; repeat > 0; repeat--, j = 0 ) +  lock = !Q_stricmp( command, "lock" ); +  trap_Argv( 1, teamName, sizeof( teamName ) ); +  team = G_TeamFromString( teamName ); + +  if( team == TEAM_ALIENS )    { -    if( !ptr )  -      break; // run out of bhist -    if( !reached && ID ) -    { -      if( ptr->ID == ID ) -        reached = qtrue; -      else -      { -        prev = ptr; -        ptr = ptr->next; -        repeat++; -        continue; -      } -    } -    if( ( team != PTE_NONE &&  -        team != BG_FindTeamForBuildable( ptr->buildable ) ) ||  -        ( builder && builder != ptr->ent )) -    { -      // team doesn't match, so skip this ptr and reset prev -      prev = ptr; -      ptr = ptr->next; -      // we don't want to count this one so counteract the decrement by the for -      repeat++; -      continue; -    } -    // get the ent's current or last recorded name -    if( ptr->ent ) -    { -      if( ptr->ent->client ) -        name = ptr->ent->client->pers.netname; -      else -        name = "<world>"; // non-client actions -    } +    if( level.alienTeamLocked == lock ) +      fail = qtrue;      else -      name = ptr->name; -    bname = BG_FindHumanNameForBuildable( ptr->buildable );  -    action = ""; -    switch( ptr->fate ) -    { -      case BF_BUILT: -        action = "^2build^7"; -        for( j = MAX_CLIENTS, targ = g_entities + j; -            j < level.num_entities; j++, targ++ ) -        { -          // easy checks first to save time -          if( targ->s.eType != ET_BUILDABLE ) -            continue; -          if( targ->s.modelindex != ptr->buildable ) -            continue;  -          VectorSubtract( targ->s.pos.trBase, ptr->origin, dist ); -#define FIND_BUILDABLE_TOLERANCE 5 -          if( VectorLength( dist ) > FIND_BUILDABLE_TOLERANCE ) -            continue; // number is somewhat arbitrary, watch for false pos/neg -          // if we didn't continue then it's this one, unlink it but we can't -          // free it yet, because the markdecon buildables might not place -          trap_UnlinkEntity( targ ); -          break; -        } -        // if there are marked buildables to replace, and we aren't overriding  -        // space check, check they can fit before acting -        if( ptr->marked && !force ) -        { -          for( mark = ptr->marked; mark; mark = mark->marked ) -            if( !G_RevertCanFit( mark ) ) -            { -              trap_LinkEntity( targ ); // put it back, we failed -              // scariest sprintf ever: -              Com_sprintf( argbuf, sizeof argbuf, "%s%s%s%s%s%s%s!", -                  ( repeat > 1 ) ? "x" : "", ( repeat > 1 ) ? va( "%d ", repeat ) : "", -                  ( ID ) ? "#" : "", ( ID ) ? va( "%d ", ptr->ID ) : "", -                  ( builder ) ? "-" : "", ( builder ) ? va( "%d ", builder - g_entities ) : "",  -                  ( team == PTE_ALIENS ) ? "a " : ( team == PTE_HUMANS ) ? "h " : "" ); -              ADMP( va( "^3!revert: ^7revert aborted: reverting this %s would conflict with " -                  "another buildable, use ^3!revert %s ^7to override\n", action, argbuf ) ); -              return qfalse; -            } -         } -         // Prevent teleport glitch when reverting an occupied hovel -         if( targ->s.modelindex == BA_A_HOVEL && -             targ->active ) -         { -           gentity_t *builder = targ->builder; -           vec3_t    newOrigin; -           vec3_t    newAngles; - -           VectorCopy( targ->s.angles, newAngles ); -           newAngles[ ROLL ] = 0; - -           VectorCopy( targ->s.origin, newOrigin ); -           VectorMA( newOrigin, 1.0f, targ->s.origin2, newOrigin ); - -           //prevent lerping -           builder->client->ps.eFlags ^= EF_TELEPORT_BIT; -           builder->client->ps.eFlags &= ~EF_NODRAW; -           G_UnlaggedClear( builder ); - -           G_SetOrigin( builder, newOrigin ); -           VectorCopy( newOrigin, builder->client->ps.origin ); -           G_SetClientViewAngle( builder, newAngles ); - -           //client leaves hovel -           builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; -         } - -          // if we haven't returned yet then we're good to go, free it -          G_FreeEntity( targ ); -          // put the marked buildables back and mark them again -          if( ptr->marked ) // there may be a more efficient way of doing this -          { -            for( mark = ptr->marked; mark; mark = mark->marked ) -              G_SpawnRevertedBuildable( mark, qtrue );  -          } -        break; -      case BF_DECONNED: -        if( !action[ 0 ] ) action = "^3deconstruction^7"; -      case BF_TEAMKILLED: -        if( !action[ 0 ] ) action ="^1TEAMKILL^7"; -      case BF_DESTROYED: -        if( !action[ 0 ] ) action = "destruction"; -        // if we're not overriding and the replacement can't fit, as before -        if( !force && !G_RevertCanFit( ptr ) ) -        { -          Com_sprintf( argbuf, sizeof argbuf, "%s%s%s%s%s%s%s!", -              ( repeat > 1 ) ? "x" : "", ( repeat > 1 ) ? va( "%d ", repeat ) : "", -              ( ID ) ? "#" : "", ( ID ) ? va( "%d ", ptr->ID ) : "", -              ( builder ) ? "-" : "", ( builder ) ? va( "%d ", builder - g_entities ) : "",  -              ( team == PTE_ALIENS ) ? "a " : ( team == PTE_HUMANS ) ? "h " : "" ); -          ADMP( va( "^3!revert: ^7revert aborted: reverting this %s would " -              "conflict with another buildable, use ^3!revert %s ^7to override\n", -              action, argbuf ) ); -          return qfalse; -        } -        // else replace it but don't mark it ( it might have been marked before -        // but it isn't that important ) -        G_SpawnRevertedBuildable( ptr, qfalse ); -        break; -      default: -        // if this happens something has gone wrong -        ADMP( "^3!revert: ^7incomplete or corrupted build log entry\n" ); -        /* quarantine and dispose of the log, it's dangerous -        trap_Cvar_Set( "g_buildLogMaxLength", "0" ); -        G_CountBuildLog( ); -        */ -        return qfalse; -      } -      if( j == level.num_entities ) -      { -        ADMP( va( "^3!revert: ^7could not find logged buildable #%d\n", ptr->ID )); -        prev = ptr; -        ptr = ptr->next; -        continue; -      } -      // this is similar to the buildlog stuff -      if( BG_FindUniqueTestForBuildable( ptr->buildable ) ) -        article = "the"; -      else if( strchr( "aeiouAEIOU", *bname ) ) -        article = "an"; -      else -        article = "a"; -      AP( va( "print \"%s^7 reverted %s^7'%s %s of %s %s\n\"",  -          ( ent ) ? G_admin_adminPrintName( ent ) : "console", -          name, strchr( "Ss", name[ strlen( name ) - 1 ] ) ? "" : "s", -          action, article, bname ) );  -      matchlen++; -      // remove the reverted entry -      // ptr moves on, prev just readjusts ->next unless it is about to be  -      // freed, in which case it is forced to move on too -      tmp = ptr; -      if( ptr == level.buildHistory ) -        prev = level.buildHistory = ptr = ptr->next; -      else -        prev->next = ptr = ptr->next; -      G_Free( tmp ); -  } -  if( ID && !reached ) -  { -    ADMP( "^3!revert: ^7no buildlog entry with that ID\n" ); -    return qfalse; +      level.alienTeamLocked = lock;    } -   -  if( !matchlen ) +  else if( team == TEAM_HUMANS )    { -     ADMP( "^3!revert: ^7no log entries match those criteria\n" ); -     return qfalse; +    if( level.humanTeamLocked == lock ) +      fail = qtrue; +    else +      level.humanTeamLocked = lock;    }    else    { -    ADMP( va( "^3!revert: ^7reverted %d buildlog events\n", matchlen ) );   -  } -   -  return qtrue; -} - -void G_Unescape( char *input, char *output, int len ); -qboolean G_StringReplaceCvars( char *input, char *output, int len ); - -qboolean G_admin_info( gentity_t *ent, int skiparg ) -{ -  fileHandle_t infoFile; -  int length; -  char filename[ MAX_OSPATH ], message[ MAX_STRING_CHARS ];  -  if( G_SayArgc() == 2 + skiparg ) -    G_SayArgv( 1 + skiparg, filename, sizeof( filename ) ); -  else if( G_SayArgc() == 1 + skiparg ) -    Q_strncpyz( filename, "default", sizeof( filename ) ); -  else -  { -    ADMP( "^3!info: ^7usage: ^3!info ^7(^5subject^7)\n" ); +    ADMP( va( "^3%s: ^7invalid team: '%s'\n", command, teamName ) );      return qfalse;    } -  Com_sprintf( filename, sizeof( filename ), "info/info-%s.txt", filename ); -  length = trap_FS_FOpenFile( filename, &infoFile, FS_READ ); -  if( length <= 0 || !infoFile ) -  { -    trap_FS_FCloseFile( infoFile ); -    ADMP( "^3!info: ^7no relevant information is available\n" ); -    return qfalse; -  } -  else -  { -    int i; -    char *cr; -    trap_FS_Read( message, sizeof( message ), infoFile ); -    if( length < sizeof( message ) ) -      message[ length ] = '\0'; -    else -      message[ sizeof( message ) - 1 ] = '\0'; -    trap_FS_FCloseFile( infoFile ); -    // strip carriage returns for windows platforms -    while( ( cr = strchr( message, '\r' ) ) ) -      memmove( cr, cr + 1, strlen( cr + 1 ) + 1 ); -#define MAX_INFO_PARSE_LOOPS 100 -    for( i = 0; i < MAX_INFO_PARSE_LOOPS && -        G_StringReplaceCvars( message, message, sizeof( message ) ); i++ ); -    G_Unescape( message, message, sizeof( message ) ); -    if( i == MAX_INFO_PARSE_LOOPS ) -      G_Printf( S_COLOR_YELLOW "WARNING: %s exceeds MAX_INFO_PARSE_LOOPS\n", filename ); -    ADMP( va( "%s\n", message ) ); -    return qtrue; -  } -} - -void G_Unescape( char *input, char *output, int len ) -{ -  // \n -> newline, \%c -> %c -  // output is terminated at output[len - 1] -  // it's OK for input to equal output, because our position in input is always -  // equal or greater than our position in output -  // however, if output is later in the same string as input, a crash is pretty -  // much inevitable -  int i, j; -  for( i = j = 0; input[i] && j + 1 < len; i++, j++ ) -  { -    if( input[i] == '\\' ) -    { -      if( !input[++i] ) -      { -        output[j] = '\0'; -        return; -      } -      else if( input[i] == 'n' ) -        output[j] = '\n'; -      else -        output[j] = input[i]; -    } -    else -      output[j] = input[i]; -  } -  output[j] = '\0'; -} -qboolean G_StringReplaceCvars( char *input, char *output, int len ) -{ -  int i, outNum = 0; -  char cvarName[ 64 ], cvarValue[ MAX_CVAR_VALUE_STRING ]; -  char *outputBuffer; -  qboolean doneAnything = qfalse; -  if( len <= 0 ) -    return qfalse; -  // use our own internal buffer in case output == input -  outputBuffer = G_Alloc( len ); -  len -= 1; // fit in a terminator -  while( *input && outNum < len ) +  if( fail )    { -    if( *input == '\\' && input[1] && outNum < len - 1 ) -    { -      outputBuffer[ outNum++ ] = *input++; -      outputBuffer[ outNum++ ] = *input++; -    } -    else if( *input == '$' ) -    { -      doneAnything = qtrue; -      input++; -      if( *input == '{' )  -        input++; -      for( i = 0; *input && ( isalnum( *input ) || *input == '_' ) &&  -          i < 63; i++ ) -        cvarName[ i ] = *input++; -      cvarName[ i ] = '\0'; -      if( *input == '}' )  -        input++; -      trap_Cvar_VariableStringBuffer( cvarName, cvarValue, sizeof( cvarValue ) ); -      if( cvarValue[ 0 ] ) -      { -        for( i = 0; cvarValue[ i ] && outNum < len; i++ ) -          outputBuffer[ outNum++ ] = cvarValue[ i ]; -      } -    } -    else -      outputBuffer[ outNum++ ] = *input++; -  } -  outputBuffer[ outNum ] = '\0'; -  Q_strncpyz( output, outputBuffer, len ); -  G_Free( outputBuffer ); -  return doneAnything; -} - -/* -================ - G_admin_print +    ADMP( va( "^3%s: ^7the %s team is %s locked\n", +      command, BG_TeamName( team ), lock ? "already" : "not currently" ) ); - This function facilitates the ADMP define.  ADMP() is similar to CP except - that it prints the message to the server console if ent is not defined. -================ -*/ -void G_admin_print( gentity_t *ent, char *m ) -{ -  if( ent ) -    trap_SendServerCommand( ent - level.gentities, va( "print \"%s\"", m ) ); -  else -  { -    char m2[ MAX_STRING_CHARS ]; -    if( !trap_Cvar_VariableIntegerValue( "com_ansiColor" ) ) -    { -      G_DecolorString( m, m2 ); -      G_Printf( m2 ); -    } -    else -      G_Printf( m ); +    return qfalse;    } -} -void G_admin_buffer_begin() -{ -  g_bfb[ 0 ] = '\0'; -} +  admin_log( BG_TeamName( team ) ); +  AP( va( "print \"^3%s: ^7the %s team has been %slocked by %s\n\"", +    command, BG_TeamName( team ), lock ? "" : "un", +    ent ? ent->client->pers.netname : "console" ) ); -void G_admin_buffer_end( gentity_t *ent ) -{ -  ADMP( g_bfb ); +  return qtrue;  } -void G_admin_buffer_print( gentity_t *ent, char *m ) +qboolean G_admin_builder( gentity_t *ent )  { -  // 1022 - strlen("print 64 \"\"") - 1 -  if( strlen( m ) + strlen( g_bfb ) >= 1009 ) -  { -    ADMP( g_bfb ); -    g_bfb[ 0 ] = '\0'; -  } -  Q_strcat( g_bfb, sizeof( g_bfb ), m ); -} +  vec3_t     forward, right, up; +  vec3_t     start, end, dist; +  trace_t    tr; +  gentity_t  *traceEnt; +  buildLog_t *log; +  int        i; +  qboolean   buildlog; +  char       logid[ 20 ] = {""}; - -void G_admin_cleanup() -{ -  int i = 0; - -  for( i = 0; i < MAX_ADMIN_LEVELS && g_admin_levels[ i ]; i++ ) -  { -    G_Free( g_admin_levels[ i ] ); -    g_admin_levels[ i ] = NULL; -  } -  for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) -  { -    G_Free( g_admin_admins[ i ] ); -    g_admin_admins[ i ] = NULL; -  } -  for( i = 0; i < MAX_ADMIN_BANS && g_admin_bans[ i ]; i++ ) -  { -    G_Free( g_admin_bans[ i ] ); -    g_admin_bans[ i ] = NULL; -  } -  for( i = 0; i < MAX_ADMIN_COMMANDS && g_admin_commands[ i ]; i++ ) -  { -    G_Free( g_admin_commands[ i ] ); -    g_admin_commands[ i ] = NULL; -  } -} - -qboolean G_admin_L0(gentity_t *ent, int skiparg ){ -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ] = {""}; -  char testname[ MAX_NAME_LENGTH ] = {""}; -  char err[ MAX_STRING_CHARS ]; -  qboolean numeric = qtrue; -  int i; -  int id = -1; -  gentity_t *vic; - -  if( G_SayArgc() < 2 + skiparg ) +  if( !ent )    { -    ADMP( "^3!L0: ^7usage: !L0 [name|slot#|admin#]\n" ); +    ADMP( "^3builder: ^7console can't aim.\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, testname, sizeof( testname ) ); -  G_SanitiseString( testname, name, sizeof( name ) ); -  for( i = 0; i < sizeof( name ) && name[ i ] ; i++ ) -  { -    if( name[ i ] < '0' || name[ i ] > '9' ) -    { -      numeric = qfalse; -      break; -    } -  } -  if( numeric ) -  { -    id = atoi( name ); -  } +  buildlog = G_admin_permission( ent, "buildlog" ); + +  AngleVectors( ent->client->ps.viewangles, forward, right, up ); +  if( ent->client->pers.teamSelection != TEAM_NONE && +      ent->client->sess.spectatorState == SPECTATOR_NOT ) +    CalcMuzzlePoint( ent, forward, right, up, start );    else -  { -    if( G_ClientNumbersFromString( name, pids ) != 1 ) -    { -      G_MatchOnePlayer( pids, err, sizeof( err ) ); -      ADMP( va( "^3!L0: ^7%s\n", err ) ); -      return qfalse; -    } -    id = pids[ 0 ]; -  } +    VectorCopy( ent->client->ps.origin, start ); +  VectorMA( start, 1000, forward, end ); -  if (id >= 0 && id < level.maxclients) +  trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); +  traceEnt = &g_entities[ tr.entityNum ]; +  if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) )    { -    vic = &g_entities[ id ]; -    if( !vic || !(vic->client) || vic->client->pers.connected != CON_CONNECTED ) +    if( !buildlog && +        ent->client->pers.teamSelection != TEAM_NONE && +        ent->client->pers.teamSelection != traceEnt->buildableTeam )      { -      ADMP( "^3!L0:^7 no one connected by that slot number\n" ); +      ADMP( "^3builder: ^7structure not owned by your team\n" );        return qfalse;      } -    if( G_admin_level( vic ) != 1 ) +    if( buildlog )      { -      ADMP( "^3!L0:^7 intended victim is not level 1\n" ); -      return qfalse; -    } -  } -  else if (id >= MAX_CLIENTS && id < MAX_CLIENTS + MAX_ADMIN_ADMINS -    && g_admin_admins[ id - MAX_CLIENTS ] ) -  { -    if( g_admin_admins[ id - MAX_CLIENTS ]->level != 1 ) -    { -      ADMP( "^3!L0:^7 intended victim is not level 1\n" ); -      return qfalse; +      for( i = 0 ; buildlog && i < level.numBuildLogs; i++ ) +      { +        log = &level.buildLog[ ( level.buildId - i - 1 ) % MAX_BUILDLOG ]; +        if( log->fate != BF_CONSTRUCT || traceEnt->s.modelindex != log->modelindex ) +          continue; + +        VectorSubtract( traceEnt->s.pos.trBase, log->origin, dist ); +        if( VectorLengthSquared( dist ) < 2.0f ) +          Com_sprintf( logid, sizeof( logid ), ", buildlog #%d", +                       MAX_CLIENTS + level.buildId - i - 1 ); +      }      } + +    ADMP( va( "^3builder: ^7%s%s%s^7%s\n", +      BG_Buildable( traceEnt->s.modelindex )->humanName, +      traceEnt->builtBy ? " built by " : "", +      traceEnt->builtBy ? +        traceEnt->builtBy->name[ traceEnt->builtBy->nameOffset ] : +        "", +      buildlog ? ( logid[ 0 ] ? logid : ", not in buildlog" ) : "" ) );    }    else -  { -    ADMP( "^3!L0:^7 no match.  use !listplayers or !listadmins to " -      "find an appropriate number to use instead of name.\n" ); -    return qfalse; -  } - -  trap_SendConsoleCommand( EXEC_APPEND, va( "!setlevel %d 0;", id ) ); +    ADMP( "^3builder: ^7no structure found under crosshair\n" );    return qtrue;  } -qboolean G_admin_L1(gentity_t *ent, int skiparg ){ -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], *reason, err[ MAX_STRING_CHARS ]; -  int minargc; - -  minargc = 2 + skiparg; - -  if( G_SayArgc() < minargc ) -  { -    ADMP( "^3!L1: ^7usage: !L1 [name]\n" ); -    return qfalse; -  } -  G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -  reason = G_SayConcatArgs( 2 + skiparg ); -  if( G_ClientNumbersFromString( name, pids ) != 1 ) -  { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!L1: ^7%s\n", err ) ); -    return qfalse; -  } -  if( G_admin_level(&g_entities[ pids[ 0 ] ] )>0 ) -  { -    ADMP( "^3!L1: ^7Sorry, but that person is already higher than level 0.\n" ); -    return qfalse; -  } -  -  trap_SendConsoleCommand( EXEC_APPEND,va( "!setlevel %d 1;", pids[ 0 ] ) ); -  return qtrue; -} - -qboolean G_admin_invisible( gentity_t *ent, int skiparg ) +qboolean G_admin_pause( gentity_t *ent )  { -  if( !ent ) -  { -    ADMP( "!invisible: console can not become invisible.\n" ); -    return qfalse; -  } -   -  if ( ent->client->sess.invisible != qtrue ) +  if( !level.pausedTime )    { -    // Make the player invisible -    G_ChangeTeam( ent, PTE_NONE ); -    ent->client->sess.invisible = qtrue; -    ClientUserinfoChanged( ent->client->pers.connection->clientNum, qfalse ); -    G_admin_namelog_update( ent->client, qtrue ); -    trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " disconnected\n\"", ent->client->pers.netname ) ); +    AP( va( "print \"^3pause: ^7%s^7 paused the game.\n\"", +          ( ent ) ? ent->client->pers.netname : "console" ) ); +    level.pausedTime = 1; +    trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" );    }    else    { -    // Make the player visible -    ent->client->sess.invisible = qfalse; -    ClientUserinfoChanged( ent->client->pers.connection->clientNum, qfalse ); -    G_admin_namelog_update( ent->client, qfalse ); -    trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", ent->client->pers.netname ) ); -    trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", ent->client->pers.netname ) ); -  } -  return qtrue; -} +    // Prevent accidental pause->unpause race conditions by two admins +    if( level.pausedTime < 1000 ) +    { +      ADMP( "^3pause: ^7Unpausing so soon assumed accidental and ignored.\n" ); +      return qfalse; +    } -qboolean G_admin_decon( gentity_t *ent, int skiparg ) -{ -  int i = 0, j = 0, repeat = 24, pids[ MAX_CLIENTS ], len, matchlen = 0; -  pTeam_t team = PTE_NONE; -  qboolean force = qfalse, reached = qfalse; -  gentity_t *builder = NULL, *targ; -  buildHistory_t *ptr, *tmp, *mark, *prev; -  vec3_t dist; -  char arg[ 64 ], err[ MAX_STRING_CHARS ], *name, *bname, *action, *article, *reason; -  len = G_CountBuildLog( ); - -  if( !len ) -  { -    ADMP( "^3!decon: ^7no build log found, aborting...\n" ); -    return qfalse; -  } +    AP( va( "print \"^3pause: ^7%s^7 unpaused the game (Paused for %d sec) \n\"", +          ( ent ) ? ent->client->pers.netname : "console",  +          (int) ( (float) level.pausedTime / 1000.0f ) ) ); +    trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" ); -  if( G_SayArgc( ) < 2 + skiparg ) -  { -    ADMP( "^3!decon: ^7usage: !decon (^5name|num^7)\n" ); -    return qfalse; +    level.pausedTime = 0;    } -  G_SayArgv( 1 + skiparg, arg, sizeof( arg ) ); -  if( G_ClientNumbersFromString( arg, pids ) != 1 ) -  { -    G_MatchOnePlayer( pids, err, sizeof( err ) ); -    ADMP( va( "^3!decon: ^7%s\n", err ) ); -    return qfalse; -  } +  return qtrue; +} -  builder = g_entities + *pids; -  if( builder->client->sess.invisible == qtrue ) -  { -    ADMP( va( "^3!decon: ^7no connected player by the name or slot #\n" ) ); -    return qfalse; +static char *fates[] = +{ +  "^2built^7", +  "^3deconstructed^7", +  "^7replaced^7", +  "^3destroyed^7", +  "^1TEAMKILLED^7", +  "^7unpowered^7", +  "removed" +}; +qboolean G_admin_buildlog( gentity_t *ent ) +{ +  char       search[ MAX_NAME_LENGTH ] = {""}; +  char       s[ MAX_NAME_LENGTH ] = {""}; +  char       n[ MAX_NAME_LENGTH ]; +  char       stamp[ 8 ]; +  int        id = -1; +  int        printed = 0; +  int        time; +  int        start = MAX_CLIENTS + level.buildId - level.numBuildLogs; +  int        i = 0, j; +  buildLog_t *log; + +  if( !level.buildId ) +  { +    ADMP( "^3buildlog: ^7log is empty\n" ); +    return qtrue;    } -  if( !admin_higher( ent, builder ) ) +  if( trap_Argc() == 3 )    { -    ADMP( "^3!decon: ^7sorry, but your intended victim has a higher admin" -        "level than you\n"); -    return qfalse; +    trap_Argv( 2, search, sizeof( search ) ); +    start = atoi( search );    } - -  for( i = 0, ptr = prev = level.buildHistory; repeat > 0; repeat--, j = 0 ) +  if( trap_Argc() > 1 )    { -    if( !ptr ) -        break; -    if( builder && builder != ptr->ent ) -    { -      // team doesn't match, so skip this ptr and reset prev -      prev = ptr; -      ptr = ptr->next; -      // we don't want to count this one so counteract the decrement by the for -      repeat++; -      continue; -    } -    // get the ent's current or last recorded name -    if( ptr->ent ) -    { -      if( ptr->ent->client ) -        name = ptr->ent->client->pers.netname; -      else -        name = "<world>"; // non-client actions -    } -    else -      name = ptr->name; -    bname = BG_FindHumanNameForBuildable( ptr->buildable ); -    action = ""; -    switch( ptr->fate ) +    trap_Argv( 1, search, sizeof( search ) ); +    for( i = search[ 0 ] == '-'; isdigit( search[ i ] ); i++ ); +    if( i && !search[ i ] )      { -      case BF_BUILT: -        prev = ptr; -        ptr = ptr->next; -        repeat++; -        continue; -      case BF_DESTROYED: -        prev = ptr; -        ptr = ptr->next; -        repeat++; -      case BF_DECONNED: -        if( !action[0] ) action = "^3deconstruction^7"; -      case BF_TEAMKILLED: -        if( !action[0] ) action = "^1TEAMKILL^7"; -        // if we're not overriding and the replacement can't fit, as before -        if( !G_RevertCanFit( ptr ) ) -        { -          prev = ptr; -          ptr = ptr->next; -          repeat++; -          continue; -        } -        // else replace it but don't mark it ( it might have been marked before -        // but it isn't that important ) -        G_SpawnRevertedBuildable( ptr, qfalse ); -        break; -      default: -        // if this happens something has gone wrong -        ADMP( "^3!decon: ^7incomplete or corrupted build log entry\n" ); -        /* quarantine and dispose of the log, it's dangerous -        trap_Cvar_Set( "g_buildLogMaxLength", "0" ); -        G_CountBuildLog( ); -        */ +      id = atoi( search ); +      if( trap_Argc() == 2 && ( id < 0 || id >= MAX_CLIENTS ) ) +      { +        start = id; +        id = -1; +      } +      else if( id < 0 || id >= MAX_CLIENTS || +        level.clients[ id ].pers.connected != CON_CONNECTED ) +      { +        ADMP( "^3buildlog: ^7invalid client id\n" );          return qfalse; +      }      } -      // this is similar to the buildlog stuff -    if( BG_FindUniqueTestForBuildable( ptr->buildable ) ) -      article = "the"; -    else if( strchr( "aeiouAEIOU", *bname ) ) -      article = "an";      else -      article = "a"; -    AP( va( "print \"%s^7 reverted %s^7'%s %s of %s %s\n\"", -        ( ent ) ? G_admin_adminPrintName( ent ) : "console", -        name, strchr( "Ss", name[ strlen( name ) - 1 ] ) ? "" : "s", -        action, article, bname ) ); -    matchlen++; -    // remove the reverted entry -    // ptr moves on, prev just readjusts ->next unles it is about to be -    // freed, in which case it is forced to move on too -    tmp = ptr; -    if( ptr == level.buildHistory ) -      prev = level.buildHistory = ptr = ptr->next; -    else -      prev->next = ptr = ptr->next; -    G_Free( tmp ); +      G_SanitiseString( search, s, sizeof( s ) );    } +  else +    start = MAX( -MAX_ADMIN_LISTITEMS, -level.buildId ); -  if( !matchlen ) +  if( start < 0 ) +    start = MAX( level.buildId - level.numBuildLogs, start + level.buildId ); +  else +    start -= MAX_CLIENTS; +  if( start < level.buildId - level.numBuildLogs || start >= level.buildId )    { -    ADMP( "^3!decopn: ^7This user doesn't seem to have deconned anything...\n" ); +    ADMP( "^3buildlog: ^7invalid build ID\n" );      return qfalse;    } -  ADMP( va( "^3!decon: ^7reverted %d buildlog events\n", matchlen ) ); -  admin_create_ban( ent, -      builder->client->pers.netname, -      builder->client->pers.guid, -      builder->client->pers.ip, G_admin_parse_time( g_deconBanTime.string ), -      ( *reason ) ? reason : "^1Decon" ); -  if( g_admin.string[ 0 ] ) -    admin_writeconfig(); - -  trap_SendServerCommand( pids[ 0 ], -      va( "disconnect \"You have been kicked.\n%s^7\nreason:\n%s\n%s\"", -      ( ent ) ? va( "admin:\n%s", G_admin_adminPrintName( ent ) ) : "admin\nconsole", -      ( *reason ) ? reason : "^1Decon" ) ); - -  trap_DropClient( pids[ 0 ], va( "kicked%s^7, reason: %s", -      ( ent ) ? va( " by %s", G_admin_adminPrintName( ent ) ) : " by console",  -      ( *reason ) ? reason : "^1Decon" ) ); - -  return qtrue; -} +  if( ent && ent->client->pers.teamSelection != TEAM_NONE ) +    trap_SendServerCommand( -1, +      va( "print \"^3buildlog: ^7%s^7 requested a log of recent building activity\n\"", +           ent->client->pers.netname  ) ); -qboolean G_admin_setdevmode( gentity_t *ent, int skiparg ) -{ -  char str[ 5 ]; - -  if( G_SayArgc() != 2 + skiparg ) -  { -    ADMP( "^3!setdevmode: ^7usage: !setdevmode [on|off]\n" ); -    return qfalse; -  } -  G_SayArgv( 1 + skiparg, str, sizeof( str ) ); - -  if( !Q_stricmp( str, "on" ) ) +  ADMBP_begin(); +  for( i = start; i < level.buildId && printed < MAX_ADMIN_LISTITEMS; i++ )    { -    if( g_cheats.integer ) +    log = &level.buildLog[ i % MAX_BUILDLOG ]; +    if( id >= 0 && id < MAX_CLIENTS )      { -      ADMP( "^3!setdevmode: ^7developer mode is already on\n" ); -      return qfalse; +      if( log->actor != level.clients[ id ].pers.namelog ) +        continue;      } -    trap_Cvar_Set( "sv_cheats", "1" ); -    trap_Cvar_Update( &g_cheats ); -    AP( va( "print \"^3!setdevmode: ^7%s ^7has switched developer mode on\n\"", -            ent ? G_admin_adminPrintName( ent ) : "console" ) ); -  } -  else if( !Q_stricmp( str, "off" ) ) -  { -    if( !g_cheats.integer ) +    else if( s[ 0 ] )      { -      ADMP( "^3!setdevmode: ^7developer mode is already off\n" ); -      return qfalse; +      if( !log->actor ) +        continue; +      for( j = 0; j < MAX_NAMELOG_NAMES && log->actor->name[ j ][ 0 ]; j++ ) +      { +        G_SanitiseString( log->actor->name[ j ], n, sizeof( n ) ); +        if( strstr( n, s ) ) +          break; +      } +      if( j >= MAX_NAMELOG_NAMES || !log->actor->name[ j ][ 0 ] ) +        continue;      } -    trap_Cvar_Set( "sv_cheats", "0" ); -    trap_Cvar_Update( &g_cheats ); -    AP( va( "print \"^3!setdevmode: ^7%s ^7has switched developer mode off\n\"", -            ent ? G_admin_adminPrintName( ent ) : "console" ) ); -  } -  else -  { -    ADMP( "^3!setdevmode: ^7usage: !setdevmode [on|off]\n" ); -    return qfalse; -  } - +    printed++; +    time = ( log->time - level.startTime ) / 1000; +    Com_sprintf( stamp, sizeof( stamp ), "%3d:%02d", time / 60, time % 60 ); +    ADMBP( va( "^2%c^7%-3d %s %s^7%s%s%s %s%s%s\n", +      log->actor && log->fate != BF_REPLACE && log->fate != BF_UNPOWER ? +        '*' : ' ', +      i + MAX_CLIENTS, +      log->actor && ( log->fate == BF_REPLACE || log->fate == BF_UNPOWER ) ? +        "    \\_" : stamp, +      BG_Buildable( log->modelindex )->humanName, +      log->builtBy && log->fate != BF_CONSTRUCT ? +        " (built by " : +        "", +      log->builtBy && log->fate != BF_CONSTRUCT ? +        log->builtBy->name[ log->builtBy->nameOffset ] : +        "", +      log->builtBy && log->fate != BF_CONSTRUCT ? +        "^7)" : +        "", +      fates[ log->fate ], +      log->actor ? " by " : "", +      log->actor ? +        log->actor->name[ log->actor->nameOffset ] : +        "" ) ); +  } +  ADMBP( va( "^3buildlog: ^7showing %d build logs %d - %d of %d - %d.  %s\n", +    printed, start + MAX_CLIENTS, i + MAX_CLIENTS - 1, +    level.buildId + MAX_CLIENTS - level.numBuildLogs, +    level.buildId + MAX_CLIENTS - 1, +    i < level.buildId ? va( "run 'buildlog %s%s%d' to see more", +      search, search[ 0 ] ? " " : "", i + MAX_CLIENTS ) : "" ) ); +  ADMBP_end();    return qtrue;  } -qboolean G_admin_hstage( gentity_t *ent, int skiparg ) +qboolean G_admin_revert( gentity_t *ent )  { +  char       arg[ MAX_TOKEN_CHARS ]; +  char       time[ MAX_DURATION_LENGTH ]; +  int        id; +  buildLog_t *log; -  char lvl_chr[ MAX_STRING_CHARS ]; -  int minargc; -  int lvl; - - -  minargc = 2 + skiparg; - -  if( G_SayArgc() < minargc ) +  if( trap_Argc() != 2 )    { -    ADMP( "^3!hstage: ^7hstage: !hstage [#]\n" ); +    ADMP( "^3revert: ^7usage: revert [id]\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, lvl_chr, sizeof( lvl_chr ) ); - -  lvl = atoi(lvl_chr); - -  lvl -= 1; -  trap_SendConsoleCommand( EXEC_APPEND, va( "g_humanStage %i", lvl ) ); - -  return qtrue; - -} - -qboolean G_admin_astage( gentity_t *ent, int skiparg ) -{ - -  char lvl_chr[ MAX_STRING_CHARS ]; -  int minargc; -  int lvl; - - -  minargc = 2 + skiparg; - -  if( G_SayArgc() < minargc ) +  trap_Argv( 1, arg, sizeof( arg ) ); +  id = atoi( arg ) - MAX_CLIENTS; +  if( id < level.buildId - level.numBuildLogs || id >= level.buildId )    { -    ADMP( "^3!astage: ^7astage: !astage [#]\n" ); +    ADMP( "^3revert: ^7invalid id\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, lvl_chr, sizeof( lvl_chr ) ); - -  lvl = atoi(lvl_chr); - -  lvl -= 1; -  trap_SendConsoleCommand( EXEC_APPEND, va( "g_alienStage %i", lvl ) ); - -  return qtrue; - -} - -qboolean G_admin_bubble( gentity_t *ent, int skiparg ) -{ -  int pids[ MAX_CLIENTS ]; -  char name[ MAX_NAME_LENGTH ], err[ MAX_STRING_CHARS ]; -  gentity_t *vic; - -  if(g_Bubbles.integer) -  { -   if( G_SayArgc() < 2 + skiparg ) -   { -     ADMP( "^3!bubble: ^7usage: !bubble [name|slot#]\n" ); -     return qfalse; -   } -   G_SayArgv( 1 + skiparg, name, sizeof( name ) ); -   if( G_ClientNumbersFromString( name, pids ) != 1 ) -   { -     G_MatchOnePlayer( pids, err, sizeof( err ) ); -     ADMP( va( "^3!bubble: ^7%s\n", err ) ); -     return qfalse; -   } -  vic = &g_entities[ pids[ 0 ] ]; -  if(vic->client->sess.invisible == qtrue) - { -    ADMP( va( "^3!bubble: ^7no connected player by that name or slot #\n" ) ); -    return qfalse; - } -   if( !admin_higher( ent, &g_entities[ pids[ 0 ] ] ) ) -   { -     ADMP( "^3!bubble: ^7sorry, but your intended victim has a higher admin" -         " level than you\n" ); -     return qfalse; -  } - - -  if( vic->client->pers.bubbleTime ) -    vic->client->pers.bubbleTime = 0; -  else -    vic->client->pers.bubbleTime = level.time + 500; - -  AP( va( "print \"^3!bubble: ^7bubbles %s for %s^7 by %s\n\"", -    ( vic->client->pers.bubbleTime ) ? "enabled" : "disabled", -    vic->client->pers.netname, -    ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -  } -  else +  log = &level.buildLog[ id % MAX_BUILDLOG ]; +  if( !log->actor || log->fate == BF_REPLACE || log->fate == BF_UNPOWER )    { -     ADMP( "^3!bubble: ^7sorry, but bubbles have been disabled on this server.\n" ); -     return qfalse; +    // fixme: then why list them with an id # in build log ? - rez +    ADMP( "^3revert: ^7you can only revert direct player actions, " +      "indicated by ^2* ^7in buildlog\n" ); +    return qfalse;    } -  return qtrue; -} - -qboolean G_admin_scrim(gentity_t *ent, int skiparg ) -{ -  char state[5]; -   -	if( G_SayArgc() < 2 + skiparg ) -	{ -	  ADMP( "^3!scrim: ^7usage: !scrim [on|off]\n" ); -	  return qfalse; -	} -	 -	G_SayArgv( 1 + skiparg, state, sizeof( state ) ); -	   -	if( !Q_stricmp(state, "on") ) -	{ -		if( g_scrimMode.integer != 0 ) -		{ -			ADMP( "^3!scrim: ^7scrim mode is already enabled.\n" ); -			return qfalse; -		} -		AP( va( "print \"^3!scrim: ^7%s ^7turned scrim mode ^2on^7\n\"", ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -		trap_Cvar_Set( "g_scrimMode", "1" ); -	}  -	else if( !Q_stricmp(state, "off") ) -	{ -		if( g_scrimMode.integer == 0 ) -		{ -			ADMP( "^3!scrim: ^7scrim mode is already disabled.\n" ); -			return qfalse; -		} -		AP( va( "print \"^3!scrim: ^7%s ^7turned scrim mode ^1off^7\n\"", ( ent ) ? G_admin_adminPrintName( ent ) : "console" ) ); -		trap_Cvar_Set( "g_scrimMode", "0" ); -		 -	} else { -		ADMP( "^3!scrim: ^7usage: !scrim [on|off]\n" ); -		return qfalse; -	} -	 +  G_admin_duration( ( level.time - log->time ) / 1000, time, +    sizeof( time ) ); +  admin_log( arg ); +  AP( va( "print \"^3revert: ^7%s^7 reverted %d %s over the past %s\n\"", +    ent ? ent->client->pers.netname : "console", +    level.buildId - id, +    ( level.buildId - id ) > 1 ? "changes" : "change", +    time ) ); +  G_BuildLogRevert( id );    return qtrue;  } -qboolean G_admin_give(gentity_t *ent, int skiparg) -{ -	char arg_name_raw[MAX_NAME_LENGTH]; -	char arg_name[MAX_NAME_LENGTH]; -	char arg_amount[30]; -	int target_id, amount; -	gentity_t *target; -	const char *currency; - -	if (G_SayArgc() < 3 + skiparg) { -		ADMP("^3!give: ^7usage: !give [player] [amount]\n"); -		return qfalse; -	} - -	G_SayArgv(1 + skiparg, arg_name_raw, sizeof(arg_name_raw)); -	G_SanitiseString(arg_name_raw, arg_name, sizeof(arg_name)); - -	if (is_numeric(arg_name)) { -		target_id = atoi(arg_name); - -		if (target_id < 0 || target_id >= MAX_CLIENTS) { -			ADMP(va("^3!give: ^7invalid client number\n")); -			return qfalse; -		} -	} else { -		int pids[ MAX_CLIENTS ]; - -		if (G_ClientNumbersFromString(arg_name, pids) != 1) { -			char error[MAX_STRING_CHARS]; - -			G_MatchOnePlayer(pids, error, sizeof(error)); -			ADMP(va("^3!give: ^7%s\n", error)); -			return qfalse; -		} - -		target_id = pids[0]; -	} - - -	target = g_entities + target_id; - -	if (!target->client || -	    target->client->pers.connected != CON_CONNECTED) { -	invalid_target: -		ADMP("^3!give: ^7invalid target\n"); -		return qfalse; -	} - -	G_SayArgv(2 + skiparg, arg_amount, sizeof(arg_amount)); -	amount = atoi(arg_amount); - -	switch (target->client->pers.teamSelection) { -	case PTE_ALIENS: -		if (amount < -9 || amount > 9) { -		too_big: -			ADMP("^3!give: ^7amount is too big\n"); -			return qfalse; -		} - -		currency = "evo"; -		break; - -	case PTE_HUMANS: -		if (amount < -2000 || amount > 2000) -			goto too_big; - -		currency = "credit"; -		break; - -	default: -		goto invalid_target; -	} - -	G_AddCreditToClient(target->client, amount, qtrue); -	AP(va("print \"^3!give: ^7%s^7 was given %i %s%s by ^7%s^7\n\"", -	   target->client->pers.netname, amount, currency, -	   (abs(amount) != 1 ? "s" : ""), -	   ent ? G_admin_adminPrintName(ent) : "console")); - -	return qtrue; -} - -extern mapRotations_t mapRotations; +/* +================ + G_admin_print -qboolean G_admin_setrotation(gentity_t *ent, int skiparg) + This function facilitates the ADMP define.  ADMP() is similar to CP except + that it prints the message to the server console if ent is not defined. +================ +*/ +void G_admin_print( gentity_t *ent, const char *m )  { -  char new_rotation[MAX_NAME_LENGTH]; -  int i; - -  if (G_SayArgc() < 2 + skiparg) -  { -    ADMP("^3!setrotation: ^7usage: !setrotation [rotation]\n"); -    ADMP("Available rotations:\n"); -    goto rotationlist; -  } - -  G_SayArgv(1 + skiparg, new_rotation, sizeof(new_rotation)); - -  for( i = 0; i < mapRotations.numRotations; i++ ) -  { -    if( Q_stricmp( mapRotations.rotations[ i ].name, new_rotation ) == 0 ) -    { -      G_StartMapRotation(new_rotation, qfalse); -      trap_SendServerCommand( -1, va("print \"^3!setrotation: ^7rotation ^3%s ^7was started by %s", -        new_rotation, ent ? G_admin_adminPrintName(ent) : "console")); -      return qtrue; -    } -  } -  ADMP("^3!setrotation: ^7rotation not found. Available rotations:\n"); -  goto rotationlist; -  rotationlist: +  if( ent ) +    trap_SendServerCommand( ent - level.gentities, va( "print \"%s\"", m ) ); +  else    { -    for( i = 0; i < mapRotations.numRotations; i++ ) +    char m2[ MAX_STRING_CHARS ]; +    if( !trap_Cvar_VariableIntegerValue( "com_ansiColor" ) )      { -      ADMP(va("    %s\n", mapRotations.rotations[ i ].name)); +      G_DecolorString( m, m2, sizeof( m2 ) ); +      trap_Print( m2 );      } -    ADMP(va("Number of available rotations: ^3%d\n", mapRotations.numRotations)); +    else +      trap_Print( m );    } -  return qfalse;  } -qboolean G_admin_versions(gentity_t *ent, int skiparg) +void G_admin_buffer_begin( void )  { -	int i; - -	ADMBP_begin(); - -	for (i = 0; i < level.maxclients; i++) { -		gclient_t *client = level.clients + i; -		char userinfo[ MAX_INFO_STRING ], *p; - -		if (client->pers.connected == CON_DISCONNECTED) -			continue; - -		ADMBP(va("%02i ", i)); - -		trap_GetUserinfo(i, userinfo, sizeof(userinfo)); -		p = Info_ValueForKey(userinfo, "version"); - -		if (p[0]) -			ADMBP(va("'%s'\n", p)); -		else { -			p = Info_ValueForKey(userinfo, "cl_voip"); - -			if (p[0]) -				ADMBP("probably GPP or newer\n"); -			else -				ADMBP("probably stock 1.1\n"); -		} -	} - -	ADMBP_end(); -	return qtrue; -} - -static int calc_ff_pct(statsCounters_t *sc) { -	if (sc->dmgdone + sc->structdmgdone <= 0) { -		if (sc->ffdmgdone <= 0) -			return 0; -		else -			return 100; -	} // else { -		// return round((float)sc->ffdmgdone / (sc->ffdmgdone + sc->dmgdone + sc->structdmgdone) * 100); -	// } +  g_bfb[ 0 ] = '\0';  } -qboolean G_admin_showff(gentity_t *ent, int skiparg) +void G_admin_buffer_end( gentity_t *ent )  { -	char arg_name_raw[MAX_NAME_LENGTH]; -	char arg_name[MAX_NAME_LENGTH]; -	int target_id, ffpct; -	gentity_t *target; -	statsCounters_t *sc; - -	if (G_SayArgc() == 1 + skiparg) { -		int i; -		char team[4]; -		gclient_t *client; - -		ADMBP_begin(); -		ADMBP("^3!showff:^7 friendly fire damage percentage for all connected players\n"); - -		for (i = 0; i < level.maxclients; i++) {  -			client = &level.clients[i]; - -			if (client->pers.connected != CON_CONNECTED) -				continue; - -			if (client->pers.teamSelection == PTE_HUMANS) -				Com_sprintf( team, sizeof( team ), "^4H", team); -			else if (client->pers.teamSelection == PTE_ALIENS) -				Com_sprintf( team, sizeof( team ), "^1A", team); -			else -				Com_sprintf( team, sizeof( team ), "^3S", team); - -			ffpct = calc_ff_pct(&client->pers.statscounters); -			ADMBP(va("%2d %s ^1%3d%% ^7%s^7\n", i, team, ffpct, client->pers.netname)); -		} - -		ADMBP("^7for detailed information, use ^3!showff player|slot^7\n"); -		ADMBP_end(); -		return qtrue; -	} - -	G_SayArgv(1 + skiparg, arg_name_raw, sizeof(arg_name_raw)); -	G_SanitiseString(arg_name_raw, arg_name, sizeof(arg_name)); - -	if (is_numeric(arg_name)) { -		target_id = atoi(arg_name); - -		if (target_id < 0 || target_id >= MAX_CLIENTS) { -			ADMP(va("^3!showff: ^7invalid client number\n")); -			return qfalse; -		} -	} else { -		int pids[MAX_CLIENTS]; - -		if (G_ClientNumbersFromString(arg_name, pids) != 1) { -			char error[MAX_STRING_CHARS]; - -			G_MatchOnePlayer(pids, error, sizeof(error)); -			ADMP(va("^3!showff: ^7%s\n", error)); -			return qfalse; -		} - -		target_id = pids[0]; -	} - -	target = g_entities + target_id; -	sc     = &target->client->pers.statscounters; -	ffpct  = calc_ff_pct(sc); - -	ADMP(va("^3!showff: ^7detailed FF information for %s^7:\n", -	        target->client->pers.netname)); -	ADMP(va("^7damage to: Enemies: ^1%d^7, structures: ^1%d^7, friendlies: ^1%d\n", -	        sc->dmgdone, sc->structdmgdone, sc->ffdmgdone)); -	ADMP(va("dealt ^1%d%%^7 of their total damage to the team\n", ffpct)); - -	return qtrue; +  ADMP( g_bfb );  } -void G_admin_tklog_cleanup( void ) +void G_admin_buffer_print( gentity_t *ent, const char *m )  { -  int i; - -  for( i = 0; i < MAX_ADMIN_TKLOGS && g_admin_tklog[ i ]; i++ ) +  // 1022 - strlen("print 64 \"\"") - 1 +  if( strlen( m ) + strlen( g_bfb ) >= 1009 )    { -    G_Free( g_admin_tklog[ i ] ); -    g_admin_tklog[ i ] = NULL; +    ADMP( g_bfb ); +    g_bfb[ 0 ] = '\0';    } - -  admin_tklog_index = 0; +  Q_strcat( g_bfb, sizeof( g_bfb ), m );  } -void G_admin_tklog_log( gentity_t *attacker, gentity_t *victim, int meansOfDeath ) -{ -  g_admin_tklog_t *tklog; -  int previous; -  int count = 1; - -  if( !attacker ) -    return; - -  previous = admin_tklog_index - 1; -  if( previous < 0 ) -    previous = MAX_ADMIN_TKLOGS - 1; - -  if( g_admin_tklog[ previous ] ) -    count = g_admin_tklog[ previous ]->id + 1; -  if( g_admin_tklog[ admin_tklog_index ] ) -    tklog = g_admin_tklog[ admin_tklog_index ]; -  else -    tklog = G_Alloc( sizeof( g_admin_tklog_t ) ); - -  memset( tklog, 0, sizeof( g_admin_tklog_t ) ); -  tklog->id = count; -  tklog->time = level.time - level.startTime; -  Q_strncpyz( tklog->name, attacker->client->pers.netname, sizeof( tklog->name ) ); - -  if( victim ) -  { -    Q_strncpyz( tklog->victim, victim->client->pers.netname, sizeof( tklog->victim ) ); -    tklog->damage = victim->client->tkcredits[ attacker->s.number ]; -    tklog->value = victim->client->ps.stats[ STAT_MAX_HEALTH ]; -  } -  else -  { -    Q_strncpyz( tklog->victim, "^3BLEEDING", sizeof( tklog->victim ) ); -    tklog->damage = attacker->client->pers.statscounters.spreebleeds; -    tklog->value = g_bleedingSpree.integer * 100; -  } - -  tklog->team = attacker->client->ps.stats[ STAT_PTEAM ]; -  if( meansOfDeath == MOD_GRENADE ) -    tklog->weapon = WP_GRENADE; -  else if( tklog->team == PTE_HUMANS ) -    tklog->weapon = attacker->s.weapon; -  else -    tklog->weapon = attacker->client->ps.stats[ STAT_PCLASS ]; - -  g_admin_tklog[ admin_tklog_index ] = tklog; -  admin_tklog_index++; -  if( admin_tklog_index >= MAX_ADMIN_TKLOGS ) -    admin_tklog_index = 0; -} - -qboolean G_admin_tklog( gentity_t *ent, int skiparg ) +void G_admin_cleanup( void )  { -  g_admin_tklog_t *results[ 10 ]; -  int result_index = 0; -  char *search_name = NULL; -  int index; -  int skip = 0; -  int skipped = 0; -  int checked = 0; -  char n1[ MAX_NAME_LENGTH ]; -  char fmt_name[ 16 ]; -  char argbuf[ 32 ]; -  char *weaponName; -  int name_length = 10; -  int max_id = 0; -  int i; -  qboolean match; - -  memset( results, 0, sizeof( results ) ); - -  index = admin_tklog_index; -  for( i = 0; i < 10; i++ ) -  { -    int prev; - -    prev = index - 1; -    if( prev < 0 ) -      prev = MAX_ADMIN_TKLOGS - 1; -    if( !g_admin_tklog[ prev ] ) -      break; -    if( g_admin_tklog[ prev ]->id > max_id ) -      max_id = g_admin_tklog[ prev ]->id; -    index = prev; -  } - -  if( G_SayArgc() > 1 + skiparg ) -  { -    G_SayArgv( 1 + skiparg, argbuf, sizeof( argbuf ) ); -    if( ( *argbuf >= '0' && *argbuf <= '9' ) || *argbuf == '-' ) -    { -      int id; - -      id = atoi( argbuf ); -      if( id < 0 ) -        id += ( max_id - 9 ); -      else if( id <= max_id - MAX_ADMIN_TKLOGS ) -        id = max_id - MAX_ADMIN_TKLOGS + 1; - -      if( id + 9 >= max_id ) -        id = max_id - 9; -      if( id < 1 ) -        id = 1; -      for( i = 0; i < MAX_ADMIN_TKLOGS; i++ ) -      { -        if( g_admin_tklog[ i ]->id == id ) -        { -          index = i; -          break; -        } -      } -    } -    else -    { -      search_name = argbuf; -    } - -    if( G_SayArgc() > 2 + skiparg && ( search_name ) ) -    { -      char skipbuf[ 4 ]; -      G_SayArgv( 2 + skiparg, skipbuf, sizeof( skipbuf ) ); -      skip = atoi( skipbuf ); -    } -  } +  g_admin_level_t *l; +  g_admin_admin_t *a; +  g_admin_ban_t *b; +  g_admin_command_t *c; +  void *n; -  if( search_name ) +  for( l = g_admin_levels; l; l = n )    { -    g_admin_tklog_t *result_swap[ 10 ]; - -    memset( &result_swap, 0, sizeof( result_swap ) ); - -    index = admin_tklog_index - 1; -    if( index < 0 ) -      index = MAX_ADMIN_TKLOGS - 1; - -    while( g_admin_tklog[ index ] && -      checked < MAX_ADMIN_TKLOGS && -      result_index < 10 ) -    { -      match = qfalse; - -      G_SanitiseString( g_admin_tklog[ index ]->name, n1, sizeof( n1 ) ); -      if( strstr( n1, search_name ) ) -        match = qtrue; - -      if( match && skip > 0 ) -      { -        match = qfalse; -        skip--; -        skipped++; -      } -      if( match ) -      { -        result_swap[ result_index ] = g_admin_tklog[ index ]; -        result_index++; -      } - -      checked++; -      index--; -      if( index < 0 ) -        index = MAX_ADMIN_TKLOGS - 1; -    } -    // search runs backwards, turn it around -    for( i = 0; i < result_index; i++ ) -      results[ i ] = result_swap[ result_index - i - 1 ]; +    n = l->next; +    BG_Free( l );    } -  else +  g_admin_levels = NULL; +  for( a = g_admin_admins; a; a = n )    { -    while( g_admin_tklog[ index ] && result_index < 10 ) -    { -      results[ result_index ] = g_admin_tklog[ index ]; -      result_index++; -      index++; -      if( index >= MAX_ADMIN_TKLOGS ) -        index = 0; -    } +    n = a->next; +    BG_Free( a );    } - -  for( i = 0; results[ i ] && i < 10; i++ ) -  { -    int l; - -    G_DecolorString( results[ i ]->name, n1 ); -    l = strlen( n1 ); -    if( l > name_length ) -      name_length = l; -  } -  ADMBP_begin( ); -  for( i = 0; results[ i ] && i < 10; i++ ) +  g_admin_admins = NULL; +  for( b = g_admin_bans; b; b = n )    { -    int t; - -    t = results[ i ]->time / 1000; - -    G_DecolorString( results[ i ]->name, n1 ); -    Com_sprintf( fmt_name, sizeof( fmt_name ), "%%%ds",  -      ( name_length + (int)( strlen( results[ i ]->name ) - strlen( n1 ) ) ) ); -    Com_sprintf( n1, sizeof( n1 ), fmt_name, results[ i ]->name ); - -    if( results[ i ]->team == PTE_HUMANS ) -      weaponName = BG_FindNameForWeapon( results[ i ]->weapon ); -    else -      weaponName = BG_FindNameForClassNum( results[ i ]->weapon ); - -    ADMBP( va( "^7%3d %3d:%02d %s^7 %3d / %3d %10s %s^7\n", -      results[ i ]->id, -      t / 60, t % 60, -      n1, -      results[ i ]->damage, -      results[ i ]->value, -      weaponName, -      results[ i ]->victim ) ); -  } -  if( search_name ) -  { -    ADMBP( va( "^3!tklog:^7 Showing %d matches for '%s^7'.", -      result_index, -      argbuf ) ); -    if( checked < MAX_ADMIN_TKLOGS && g_admin_tklog[ checked ] ) -      ADMBP( va( " run '!tklog %s^7 %d' to see more", -       argbuf, -       skipped + result_index ) ); -    ADMBP( "\n" ); -  } -  else if ( results[ 0 ] ) -  { -    ADMBP( va( "^3!tklog:^7 Showing %d - %d of %d.\n", -      results[ 0 ]->id, -      results[ result_index - 1 ]->id, -      max_id ) ); +    n = b->next; +    BG_Free( b );    } -  else +  g_admin_bans = NULL; +  for( c = g_admin_commands; c; c = n )    { -    ADMBP( "^3!tklog:^7 log is empty.\n" ); +    n = c->next; +    BG_Free( c );    } -  ADMBP_end( ); - -  return qtrue; +  g_admin_commands = NULL; +  BG_DefragmentMemory( );  } -qboolean G_admin_sm( gentity_t *ent, int skiparg ) +qboolean G_admin_sm( gentity_t *ent )  {    const char *s;    char feature[ 16 ]; -  if( G_SayArgc() < 2 + skiparg ) +  if( trap_Argc() < 2 )    {      usage: -    ADMP( "^3!sm: ^7usage: !sm ipa <IPA>\n" ); +    ADMP( "^3sm: ^7usage: sm ipa <IPA>\n" );      return qfalse;    } -  s = G_SayConcatArgs( 1 + skiparg ); +  s = ConcatArgs( 1 );    if( strchr( s, '\n' ) || strchr( s, '\r' ) )    { -    ADMP( "^3!sm: ^7invalid character\n" ); +    ADMP( "^3sm: ^7invalid character\n" );      return qfalse;    } -  G_SayArgv( 1 + skiparg, feature, sizeof( feature ) ); +  trap_Argv( 1, feature, sizeof( feature ) );    if( !Q_stricmp( feature, "ipa" ) )    {      char ipa[ 32 ]; -    int parts[ 4 ]; -    if( G_SayArgc() > 3 + skiparg ) +    if( trap_Argc() > 3 )      { -      ADMP( "^3!sm: ^7excessive arguments\n" ); +      ADMP( "^3sm: ^7excessive arguments\n" );        goto usage;      } -    G_SayArgv( 2 + skiparg, ipa, sizeof( ipa ) ); - -    // have a well-formed IPv4 address, because Schachtmeister silently drops all invalid requests - -    if( sscanf( ipa, "%i.%i.%i.%i", &parts[0], &parts[1], &parts[2], &parts[3] ) != 4 -        || parts[0] < 0 || parts[0] > 255 -        || parts[1] < 0 || parts[1] > 255 -        || parts[2] < 0 || parts[2] > 255 -        || parts[3] < 0 || parts[3] > 255 ) -    { -      ADMP( "^3!sm: ^7invalid IP address\n" ); -      return qfalse; -    } - -    Com_sprintf( ipa, sizeof( ipa ), "%i.%i.%i.%i", parts[0], parts[1], parts[2], parts[3] ); +    trap_Argv( 2, ipa, sizeof( ipa ) );      if( rand() % 2 /* FIXME cache hit */ )      {        const char *answer = "interesting"; -      ADMP( va( "^3!sm: ^7IP address %s is: %s\n", ipa, answer ) ); +      ADMP( va( "^3sm: ^7IP address '%s^7' is: %s\n", ipa, answer ) );        return qtrue;      } -    ADMP( "^3!sm: ^7hmm...\n" ); +    ADMP( "^3sm: ^7hmm...\n" );      trap_SendConsoleCommand( EXEC_APPEND, va( "smq ipa \"%s\"\n", ipa ) );    }    else      goto usage;    return qtrue; -} +}
\ No newline at end of file diff --git a/src/game/g_admin.h b/src/game/g_admin.h index 25cf2a7..3ab445e 100644 --- a/src/game/g_admin.h +++ b/src/game/g_admin.h @@ -1,12 +1,13 @@  /*  =========================================================================== -Copyright (C) 2004-2006 Tony J. White +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -15,8 +16,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -31,100 +32,59 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define ADMBP_begin() G_admin_buffer_begin()  #define ADMBP_end() G_admin_buffer_end(ent) -#define MAX_ADMIN_LEVELS 32  -#define MAX_ADMIN_ADMINS 1024 -#define MAX_ADMIN_BANS 1024 -#define MAX_ADMIN_NAMELOGS 128 -#define MAX_ADMIN_NAMELOG_NAMES 5 -#define MAX_ADMIN_ADMINLOGS 128 -#define MAX_ADMIN_ADMINLOG_ARGS 50  #define MAX_ADMIN_FLAG_LEN 20  #define MAX_ADMIN_FLAGS 1024 -#define MAX_ADMIN_COMMANDS 64  #define MAX_ADMIN_CMD_LEN 20  #define MAX_ADMIN_BAN_REASON 50 -#define MAX_ADMIN_BANSUSPEND_DAYS 14 -#define MAX_ADMIN_TKLOGS 64  /*   * IMMUNITY - cannot be vote kicked, vote muted   * NOCENSORFLOOD - cannot be censored or flood protected - * TEAMCHANGEFREE - never loses credits for changing teams   * SPECALLCHAT - can see team chat as a spectator   * FORCETEAMCHANGE - can switch teams any time, regardless of balance   * UNACCOUNTABLE - does not need to specify a reason for a kick/ban   * NOVOTELIMIT - can call a vote at any time (regardless of a vote being   * disabled or voting limitations)   * CANPERMBAN - does not need to specify a duration for a ban - * TEAMCHATCMD - can run commands from team chat + * CANIPBAN - allows banning not-necessarily-connected players with CIDR notation   * ACTIVITY - inactivity rules do not apply to them   * IMMUTABLE - admin commands cannot be used on them - * INCOGNITO - does not show up as an admin in !listplayers - * SEESINCOGNITO - sees registered name of players flagged with INCOGNITO - * ADMINCHAT - receives and can send /a admin messages - * SEESFULLLISTPLAYERS - sees all information in !listplayers  - * DBUILDER - permanent designated builder - * STEALTH - uses admin stealth - * SPECIAL - allows some special permissions (unlimited votes etc) - * SPECIALNAME - allows black text in name - * .NOCHAT - mutes a player on connect - * .NOVOTE - disallows voting by a player + * INCOGNITO - does not show up as an admin in /listplayers   * ALLFLAGS - all flags (including command flags) apply to this player + * ADMINCHAT - receives and can send /a admin messages   */ - - -#define ADMF_IMMUNITY            "IMMUNITY" -#define ADMF_NOCENSORFLOOD       "NOCENSORFLOOD" -#define ADMF_TEAMCHANGEFREE      "TEAMCHANGEFREE" -#define ADMF_SPEC_ALLCHAT        "SPECALLCHAT" -#define ADMF_FORCETEAMCHANGE     "FORCETEAMCHANGE" -#define ADMF_UNACCOUNTABLE       "UNACCOUNTABLE" -#define ADMF_NO_VOTE_LIMIT       "NOVOTELIMIT" -#define ADMF_CAN_PERM_BAN        "CANPERMBAN" -#define ADMF_TEAMCHAT_CMD        "TEAMCHATCMD" -#define ADMF_ACTIVITY            "ACTIVITY" - -#define ADMF_IMMUTABLE           "IMMUTABLE" -#define ADMF_INCOGNITO           "INCOGNITO" -#define ADMF_SEESINCOGNITO       "SEESINCOGNITO" -#define ADMF_ADMINCHAT           "ADMINCHAT" -#define ADMF_HIGHADMINCHAT       "HIGHADMINCHAT" -#define ADMF_SEESFULLLISTPLAYERS "SEESFULLLISTPLAYERS" -#define ADMF_DBUILDER            "DBUILDER" -#define ADMF_ADMINSTEALTH        "STEALTH" -#define ADMF_ALLFLAGS            "ALLFLAGS" - -#define ADMF_BAN_IMMUNITY        "BANIMMUNITY" - -#define ADMF_SPECIAL             "SPECIAL" -#define ADMF_SPECIALNAME         "SPECIALNAME" - -#define ADMF_NOSCRIMRESTRICTION  "NOSCRIMRESTRICTION" -#define ADMF_NOAUTOBAHN          "NOAUTOBAHN" - -#define ADMF_NO_BUILD            ".NOBUILD" -#define ADMF_NO_CHAT             ".NOCHAT" -#define ADMF_NO_VOTE             ".NOVOTE" -#define ADMF_FAKE_NO_VOTE        ".FAKENOVOTE" +#define ADMF_IMMUNITY        "IMMUNITY" +#define ADMF_NOCENSORFLOOD   "NOCENSORFLOOD" +#define ADMF_SPEC_ALLCHAT    "SPECALLCHAT" +#define ADMF_FORCETEAMCHANGE "FORCETEAMCHANGE" +#define ADMF_UNACCOUNTABLE   "UNACCOUNTABLE" +#define ADMF_NO_VOTE_LIMIT   "NOVOTELIMIT" +#define ADMF_CAN_PERM_BAN    "CANPERMBAN" +#define ADMF_CAN_IP_BAN      "CANIPBAN" +#define ADMF_ACTIVITY        "ACTIVITY" + +#define ADMF_IMMUTABLE       "IMMUTABLE" +#define ADMF_INCOGNITO       "INCOGNITO" +#define ADMF_ALLFLAGS        "ALLFLAGS" +#define ADMF_ADMINCHAT       "ADMINCHAT"  #define MAX_ADMIN_LISTITEMS 20  #define MAX_ADMIN_SHOWBANS 10 -#define MAX_ADMIN_MAPLOG_LENGTH 5 -// important note: QVM does not seem to allow a single char to be a -// member of a struct at init time.  flag has been converted to char*  typedef struct  {    char *keyword; -  qboolean ( * handler ) ( gentity_t *ent, int skiparg ); +  qboolean ( * handler ) ( gentity_t *ent ); +  qboolean silent;    char *flag; -  char *function;  // used for !help -  char *syntax;  // used for !help +  char *function;  // used in /adminhelp +  char *syntax;  // used in /adminhelp  }  g_admin_cmd_t;  typedef struct g_admin_level  { +  struct g_admin_level *next;    int level;    char name[ MAX_NAME_LENGTH ];    char flags[ MAX_ADMIN_FLAGS ]; @@ -133,30 +93,48 @@ g_admin_level_t;  typedef struct g_admin_admin  { +  struct g_admin_admin *next; +  int level;    char guid[ 33 ];    char name[ MAX_NAME_LENGTH ]; -  int level;    char flags[ MAX_ADMIN_FLAGS ]; -  int seen;  }  g_admin_admin_t; +#define ADDRLEN 16 +/* +addr_ts are passed as "arg" to admin_search for IP address matching +admin_search prints (char *)arg, so the stringified address needs to be first +*/ +typedef struct +{ +  char str[ 44 ]; +  enum +  { +    IPv4, +    IPv6 +  } type; +  byte addr[ ADDRLEN ]; +  int mask; +} addr_t; +  typedef struct g_admin_ban  { +  struct g_admin_ban *next;    char name[ MAX_NAME_LENGTH ];    char guid[ 33 ]; -  char ip[ 20 ]; +  addr_t ip;    char reason[ MAX_ADMIN_BAN_REASON ]; -  char made[ 18 ]; // big enough for strftime() %c +  char made[ 20 ]; // "YYYY-MM-DD hh:mm:ss"    int expires; -  int suspend;    char banner[ MAX_NAME_LENGTH ]; -  int  bannerlevel; +  int warnCount;  }  g_admin_ban_t;  typedef struct g_admin_command  { +  struct g_admin_command *next;    char command[ MAX_ADMIN_CMD_LEN ];    char exec[ MAX_QPATH ];    char desc[ 50 ]; @@ -164,156 +142,60 @@ typedef struct g_admin_command  }  g_admin_command_t; -typedef struct -{ -  int ratingTime; -  int queryTime; -  int dispatchTime; -  int rating; -  char *comment; -} schachtmeisterJudgement_t; - -typedef struct g_admin_namelog -{ -  char      name[ MAX_ADMIN_NAMELOG_NAMES ][MAX_NAME_LENGTH ]; -  char      ip[ 16 ]; -  char      guid[ 33 ]; -  int       slot; -  qboolean  banned; -  qboolean  muted; -  int       muteExpires; -  qboolean  denyBuild; -  int       denyHumanWeapons; -  int       denyAlienClasses; -  int       specExpires; -  int       voteCount; -  schachtmeisterJudgement_t smj; -} -g_admin_namelog_t; - -typedef struct g_admin_adminlog -{ -  char      name[ MAX_NAME_LENGTH ]; -  char      command[ MAX_ADMIN_CMD_LEN ]; -  char      args[ MAX_ADMIN_ADMINLOG_ARGS ]; -  int       id; -  int       time; -  int       level; -  qboolean  success; -} -g_admin_adminlog_t; - -typedef struct g_admin_tklog -{ -  char      name[ MAX_NAME_LENGTH ]; -  char      victim[ MAX_NAME_LENGTH ]; -  int       id; -  int       time; -  int       damage; -  int       value; -  int       team; -  int       weapon; -} -g_admin_tklog_t; +void G_admin_register_cmds( void ); +void G_admin_unregister_cmds( void ); +void G_admin_cmdlist( gentity_t *ent ); -qboolean G_admin_ban_check( char *userinfo, char *reason, int rlen ); -qboolean G_admin_cmd_check( gentity_t *ent, qboolean say ); -qboolean G_admin_readconfig( gentity_t *ent, int skiparg ); +qboolean G_admin_ban_check( gentity_t *ent, char *reason, int rlen ); +qboolean G_admin_cmd_check( gentity_t *ent ); +qboolean G_admin_readconfig( gentity_t *ent );  qboolean G_admin_permission( gentity_t *ent, const char *flag );  qboolean G_admin_name_check( gentity_t *ent, char *name, char *err, int len ); -void G_admin_namelog_update( gclient_t *ent, qboolean disconnect ); -void G_admin_maplog_result( char *flag ); -int G_admin_level( gentity_t *ent ); -void G_admin_set_adminname( gentity_t *ent ); -char* G_admin_adminPrintName( gentity_t *ent ); - -qboolean G_admin_seen(gentity_t *ent, int skiparg ); -void G_admin_seen_update( char *guid ); - -// ! command functions -qboolean G_admin_time( gentity_t *ent, int skiparg ); -qboolean G_admin_setlevel( gentity_t *ent, int skiparg ); -qboolean G_admin_flaglist( gentity_t *ent, int skiparg ); -qboolean G_admin_flag( gentity_t *ent, int skiparg ); -qboolean G_admin_kick( gentity_t *ent, int skiparg ); -qboolean G_admin_adjustban( gentity_t *ent, int skiparg ); -qboolean G_admin_subnetban( gentity_t *ent, int skiparg ); -qboolean G_admin_suspendban( gentity_t *ent, int skiparg ); -qboolean G_admin_ban( gentity_t *ent, int skiparg ); -qboolean G_admin_unban( gentity_t *ent, int skiparg ); -qboolean G_admin_putteam( gentity_t *ent, int skiparg ); -qboolean G_admin_adminlog( gentity_t *ent, int skiparg ); -void G_admin_adminlog_cleanup( void ); -void G_admin_adminlog_log( gentity_t *ent, char *command, char *args, int skiparg, qboolean success ); -qboolean G_admin_listadmins( gentity_t *ent, int skiparg ); -qboolean G_admin_listlayouts( gentity_t *ent, int skiparg ); -qboolean G_admin_listplayers( gentity_t *ent, int skiparg ); -qboolean G_admin_listmaps( gentity_t *ent, int skiparg ); -qboolean G_admin_listrotation( gentity_t *ent, int skiparg ); -qboolean G_admin_map( gentity_t *ent, int skiparg ); -qboolean G_admin_devmap( gentity_t *ent, int skiparg ); -void G_admin_maplog_update( void ); -qboolean G_admin_maplog( gentity_t *ent, int skiparg ); -qboolean G_admin_layoutsave( gentity_t *ent, int skiparg ); -qboolean G_admin_demo( gentity_t *ent, int skiparg ); -qboolean G_admin_mute( gentity_t *ent, int skiparg ); -qboolean G_admin_denybuild( gentity_t *ent, int skiparg ); -qboolean G_admin_denyweapon( gentity_t *ent, int skiparg ); -qboolean G_admin_showbans( gentity_t *ent, int skiparg ); -qboolean G_admin_help( gentity_t *ent, int skiparg ); -qboolean G_admin_admintest( gentity_t *ent, int skiparg ); -qboolean G_admin_allready( gentity_t *ent, int skiparg ); -qboolean G_admin_cancelvote( gentity_t *ent, int skiparg ); -qboolean G_admin_passvote( gentity_t *ent, int skiparg ); -qboolean G_admin_spec999( gentity_t *ent, int skiparg ); -qboolean G_admin_register( gentity_t *ent, int skiparg ); -qboolean G_admin_rename( gentity_t *ent, int skiparg ); -qboolean G_admin_restart( gentity_t *ent, int skiparg ); -qboolean G_admin_nobuild( gentity_t *ent, int skiparg ); -qboolean G_admin_nextmap( gentity_t *ent, int skiparg ); -qboolean G_admin_namelog( gentity_t *ent, int skiparg ); -qboolean G_admin_lock( gentity_t *ent, int skiparg ); -qboolean G_admin_unlock( gentity_t *ent, int skiparg ); -qboolean G_admin_info( gentity_t *ent, int skiparg ); -qboolean G_admin_buildlog( gentity_t *ent, int skiparg ); -qboolean G_admin_revert( gentity_t *ent, int skiparg ); -qboolean G_admin_decon( gentity_t *ent, int skiparg ); -qboolean G_admin_pause( gentity_t *ent, int skiparg ); -qboolean G_admin_L0( gentity_t *ent, int skiparg ); -qboolean G_admin_L1( gentity_t *ent, int skiparg ); -qboolean G_admin_putmespec( gentity_t *ent, int skiparg ); -qboolean G_admin_warn( gentity_t *ent, int skiparg ); -qboolean G_admin_designate( gentity_t *ent, int skiparg ); -qboolean G_admin_cp( gentity_t *ent, int skiparg ); - -qboolean G_admin_slap( gentity_t *ent, int skiparg ); -qboolean G_admin_drop( gentity_t *ent, int skiparg ); -qboolean G_admin_invisible( gentity_t *ent, int skiparg ); -qboolean G_admin_setdevmode( gentity_t *ent, int skiparg ); -qboolean G_admin_hstage( gentity_t *ent, int skiparg ); -qboolean G_admin_astage( gentity_t *ent, int skiparg ); -qboolean G_admin_bubble( gentity_t *ent, int skiparg ); -qboolean G_admin_scrim( gentity_t *ent, int skiparg ); -qboolean G_admin_give( gentity_t *ent, int skiparg ); -qboolean G_admin_setrotation( gentity_t *ent, int skiparg ); -qboolean G_admin_versions( gentity_t *ent, int skiparg ); -qboolean G_admin_showff(gentity_t *ent, int skiparg); -qboolean G_admin_tklog( gentity_t *ent, int skiparg ); -void G_admin_tklog_cleanup( void ); -void G_admin_tklog_log( gentity_t *attacker, gentity_t *victim, int meansOfDeath ); -void G_admin_IPA_judgement( const char *ipa, int rating, const char *comment ); -qboolean G_admin_sm( gentity_t *ent, int skiparg ); -void G_admin_schachtmeisterFrame( void ); -qboolean G_admin_is_restricted(gentity_t *ent, qboolean sendMessage); - -void G_admin_print( gentity_t *ent, char *m ); -void G_admin_buffer_print( gentity_t *ent, char *m ); +g_admin_admin_t *G_admin_admin( const char *guid ); +void G_admin_authlog( gentity_t *ent ); + +// admin command functions +qboolean G_admin_time( gentity_t *ent ); +qboolean G_admin_setlevel( gentity_t *ent ); +qboolean G_admin_kick( gentity_t *ent ); +qboolean G_admin_addlayout( gentity_t *ent ); +qboolean G_admin_setivo( gentity_t *ent ); +qboolean G_admin_adjustban( gentity_t *ent ); +qboolean G_admin_ban( gentity_t *ent ); +qboolean G_admin_unban( gentity_t *ent ); +qboolean G_admin_putteam( gentity_t *ent ); +qboolean G_admin_listadmins( gentity_t *ent ); +qboolean G_admin_listlayouts( gentity_t *ent ); +qboolean G_admin_listplayers( gentity_t *ent ); +qboolean G_admin_changemap( gentity_t *ent ); +qboolean G_admin_mute( gentity_t *ent ); +qboolean G_admin_denybuild( gentity_t *ent ); +qboolean G_admin_showbans( gentity_t *ent ); +qboolean G_admin_adminhelp( gentity_t *ent ); +qboolean G_admin_admintest( gentity_t *ent ); +qboolean G_admin_allready( gentity_t *ent ); +qboolean G_admin_endvote( gentity_t *ent ); +qboolean G_admin_spec999( gentity_t *ent ); +qboolean G_admin_transform( gentity_t *ent ); +qboolean G_admin_rename( gentity_t *ent ); +qboolean G_admin_restart( gentity_t *ent ); +qboolean G_admin_nextmap( gentity_t *ent ); +qboolean G_admin_setnextmap( gentity_t *ent ); +qboolean G_admin_namelog( gentity_t *ent ); +qboolean G_admin_lock( gentity_t *ent ); +qboolean G_admin_pause( gentity_t *ent ); +qboolean G_admin_builder( gentity_t *ent ); +qboolean G_admin_buildlog( gentity_t *ent ); +qboolean G_admin_revert( gentity_t *ent ); +qboolean G_admin_setdevmode( gentity_t *ent ); +qboolean G_admin_sm( gentity_t *ent ); + +void G_admin_print( gentity_t *ent, const char *m ); +void G_admin_buffer_print( gentity_t *ent, const char *m );  void G_admin_buffer_begin( void );  void G_admin_buffer_end( gentity_t *ent );  void G_admin_duration( int secs, char *duration, int dursize );  void G_admin_cleanup( void ); -void G_admin_namelog_cleanup( void ); -void admin_writeconfig( void );  #endif /* ifndef _G_ADMIN_H */ diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c index 461c2d0..05135c4 100644 --- a/src/game/g_buildable.c +++ b/src/game/g_buildable.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,16 +17,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */  #include "g_local.h" -// from g_combat.c -extern char *modNames[ ]; -  /*  ================  G_SetBuildableAnim @@ -35,19 +33,17 @@ Triggers an animation client side  */  void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force )  { -  int localAnim = anim; +  int localAnim = anim | ( ent->s.legsAnim & ANIM_TOGGLEBIT );    if( force )      localAnim |= ANIM_FORCEBIT; -  // don't toggle the togglebit more than once per frame +  // don't flip the togglebit more than once per frame    if( ent->animTime != level.time )    { -    localAnim |= ( ( ent->s.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT );      ent->animTime = level.time; +    localAnim ^= ANIM_TOGGLEBIT;    } -  else -    localAnim |= ent->s.legsAnim & ANIM_TOGGLEBIT;    ent->s.legsAnim = localAnim;  } @@ -71,8 +67,8 @@ G_CheckSpawnPoint  Check if a spawn at a specified point is valid  ===============  */ -gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal, -    buildable_t spawn, vec3_t spawnOrigin ) +gentity_t *G_CheckSpawnPoint( int spawnNum, const vec3_t origin, +    const vec3_t normal, buildable_t spawn, vec3_t spawnOrigin )  {    float   displacement;    vec3_t  mins, maxs; @@ -80,83 +76,40 @@ gentity_t *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal,    vec3_t  localOrigin;    trace_t tr; -  BG_FindBBoxForBuildable( spawn, mins, maxs ); +  BG_BuildableBoundingBox( spawn, mins, maxs );    if( spawn == BA_A_SPAWN )    {      VectorSet( cmins, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX, -MAX_ALIEN_BBOX );      VectorSet( cmaxs,  MAX_ALIEN_BBOX,  MAX_ALIEN_BBOX,  MAX_ALIEN_BBOX ); -    displacement = ( maxs[ 2 ] + MAX_ALIEN_BBOX ) * M_ROOT3; +    displacement = ( maxs[ 2 ] + MAX_ALIEN_BBOX ) * M_ROOT3 + 1.0f;      VectorMA( origin, displacement, normal, localOrigin ); - -    trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT ); - -    if( tr.entityNum != ENTITYNUM_NONE ) -      return &g_entities[ tr.entityNum ]; - -    trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_PLAYERSOLID ); - -    if( tr.entityNum == ENTITYNUM_NONE ) -    { -      if( spawnOrigin != NULL ) -        VectorCopy( localOrigin, spawnOrigin ); - -      return NULL; -    } -    else -      return &g_entities[ tr.entityNum ];    }    else if( spawn == BA_H_SPAWN )    { -    BG_FindBBoxForClass( PCL_HUMAN, cmins, cmaxs, NULL, NULL, NULL ); +    BG_ClassBoundingBox( PCL_HUMAN, cmins, cmaxs, NULL, NULL, NULL );      VectorCopy( origin, localOrigin );      localOrigin[ 2 ] += maxs[ 2 ] + fabs( cmins[ 2 ] ) + 1.0f; - -    trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT ); - -    if( tr.entityNum != ENTITYNUM_NONE ) -      return &g_entities[ tr.entityNum ]; - -    trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, -1, MASK_PLAYERSOLID ); - -    if( tr.entityNum == ENTITYNUM_NONE ) -    { -      if( spawnOrigin != NULL ) -        VectorCopy( localOrigin, spawnOrigin ); - -      return NULL; -    } -    else -      return &g_entities[ tr.entityNum ];    } +  else +    return NULL; -  return NULL; -} +  trap_Trace( &tr, origin, NULL, NULL, localOrigin, spawnNum, MASK_SHOT ); -/* -================ -G_NumberOfDependants +  if( tr.entityNum != ENTITYNUM_NONE ) +    return &g_entities[ tr.entityNum ]; -Return number of entities that depend on this one -================ -*/ -static int G_NumberOfDependants( gentity_t *self ) -{ -  int       i, n = 0; -  gentity_t *ent; +  trap_Trace( &tr, localOrigin, cmins, cmaxs, localOrigin, ENTITYNUM_NONE, MASK_PLAYERSOLID ); -  for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) -  { -    if( ent->s.eType != ET_BUILDABLE ) -      continue; +  if( tr.entityNum != ENTITYNUM_NONE ) +    return &g_entities[ tr.entityNum ]; -    if( ent->parentNode == self ) -      n++; -  } +  if( spawnOrigin != NULL ) +    VectorCopy( localOrigin, spawnOrigin ); -  return n; +  return NULL;  }  #define POWER_REFRESH_TIME  2000 @@ -168,191 +121,403 @@ G_FindPower  attempt to find power for self, return qtrue if successful  ================  */ -static qboolean G_FindPower( gentity_t *self ) +qboolean G_FindPower( gentity_t *self, qboolean searchUnspawned )  { -  int       i; -  gentity_t *ent; +  int       i, j; +  gentity_t *ent, *ent2;    gentity_t *closestPower = NULL;    int       distance = 0; -  int       minDistance = 10000; +  int       minDistance = REPEATER_BASESIZE + 1;    vec3_t    temp_v; -  if( self->biteam != BIT_HUMANS ) +  if( self->buildableTeam != TEAM_HUMANS )      return qfalse; -  //reactor is always powered +  // Reactor is always powered    if( self->s.modelindex == BA_H_REACTOR ) -    return qtrue; +  { +    self->parentNode = self; -  //if this already has power then stop now -  if( self->parentNode && self->parentNode->powered )      return qtrue; +  } -  //reset parent -  self->parentNode = NULL; +  // Handle repeaters +  if( self->s.modelindex == BA_H_REPEATER ) +  { +    self->parentNode = G_Reactor( ); -  //iterate through entities -  for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) +    return self->parentNode != NULL; +  } + +  // Iterate through entities +  for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ )    {      if( ent->s.eType != ET_BUILDABLE )        continue; -    //if entity is a power item calculate the distance to it +    // If entity is a power item calculate the distance to it      if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) && -        ent->spawned ) +        ( searchUnspawned || ent->spawned ) && ent->powered && ent->health > 0 )      { -      VectorSubtract( self->s.origin, ent->s.origin, temp_v ); +      VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v );        distance = VectorLength( temp_v ); -      if( distance < minDistance && ent->powered && -          ( ( ent->s.modelindex == BA_H_REACTOR && -            distance <= REACTOR_BASESIZE ) || -          ( ent->s.modelindex == BA_H_REPEATER && -            distance <= REPEATER_BASESIZE ) ) ) { +      // Always prefer a reactor if there is one in range +      if( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE ) +      { +        // Only power as much BP as the reactor can hold +        if( self->s.modelindex != BA_NONE ) +        { +          int buildPoints = g_humanBuildPoints.integer; + +          // Scan the buildables in the reactor zone +          for( j = MAX_CLIENTS, ent2 = g_entities + j; j < level.num_entities; j++, ent2++ ) +          { +            if( ent2->s.eType != ET_BUILDABLE ) +              continue; + +            if( ent2 == self ) +              continue; + +            if( ent2->parentNode == ent ) +            { +              buildPoints -= BG_Buildable( ent2->s.modelindex )->buildPoints; +            } +          } + +          buildPoints -= level.humanBuildPointQueue; + +          buildPoints -= BG_Buildable( self->s.modelindex )->buildPoints; +          if( buildPoints >= 0 ) +          { +            self->parentNode = ent; +            return qtrue; +          } +          else +          { +            // a buildable can still be built if it shares BP from two zones + +            // TODO: handle combined power zones here +          } +        } + +        // Dummy buildables don't need to look for zones +        else +        { +          self->parentNode = ent; +          return qtrue; +        } +      } +      else if( distance < minDistance ) +      { +        // It's a repeater, so check that enough BP will be available to power +        // the buildable but only if self is a real buildable + +        if( self->s.modelindex != BA_NONE ) +        { +          int buildPoints = g_humanRepeaterBuildPoints.integer; + +          // Scan the buildables in the repeater zone +          for( j = MAX_CLIENTS, ent2 = g_entities + j; j < level.num_entities; j++, ent2++ ) +          { +            if( ent2->s.eType != ET_BUILDABLE ) +              continue; + +            if( ent2 == self ) +              continue; + +            if( ent2->parentNode == ent ) +              buildPoints -= BG_Buildable( ent2->s.modelindex )->buildPoints; +          } + +          if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active ) +            buildPoints -= level.buildPointZones[ ent->buildPointZone ].queuedBuildPoints; + +          buildPoints -= BG_Buildable( self->s.modelindex )->buildPoints; + +          if( buildPoints >= 0 ) +          { +            closestPower = ent; +            minDistance = distance; +          } +          else +          { +            // a buildable can still be built if it shares BP from two zones + +            // TODO: handle combined power zones here +          } +        }       +        else +        { +          // Dummy buildables don't need to look for zones            closestPower = ent;            minDistance = distance; +        }        }      }    } -  //if there were no power items nearby give up -  if( closestPower ) { -    self->parentNode = closestPower; -    return qtrue; -  } -  else -    return qfalse; +  self->parentNode = closestPower; +  return self->parentNode != NULL;  }  /*  ================ -G_IsPowered +G_PowerEntityForPoint -Simple wrapper to G_FindPower to check if a location has power +Simple wrapper to G_FindPower to find the entity providing +power for the specified point  ================  */ -qboolean G_IsPowered( vec3_t origin ) +gentity_t *G_PowerEntityForPoint( const vec3_t origin )  {    gentity_t dummy;    dummy.parentNode = NULL; -  dummy.biteam = BIT_HUMANS; +  dummy.buildableTeam = TEAM_HUMANS;    dummy.s.modelindex = BA_NONE; -  VectorCopy( origin, dummy.s.origin ); +  VectorCopy( origin, dummy.r.currentOrigin ); -  return G_FindPower( &dummy ); +  if( G_FindPower( &dummy, qfalse ) ) +    return dummy.parentNode; +  else +    return NULL;  }  /*  ================ -G_FindDCC +G_PowerEntityForEntity -attempt to find a controlling DCC for self, return qtrue if successful +Simple wrapper to G_FindPower to find the entity providing +power for the specified entity  ================  */ -static qboolean G_FindDCC( gentity_t *self ) +gentity_t *G_PowerEntityForEntity( gentity_t *ent ) +{ +  if( G_FindPower( ent, qfalse ) ) +    return ent->parentNode; +  return NULL; +} + +/* +================ +G_IsPowered + +Check if a location has power, returning the entity type +that is providing it +================ +*/ +buildable_t G_IsPowered( vec3_t origin ) +{ +  gentity_t *ent = G_PowerEntityForPoint( origin ); + +  if( ent ) +    return ent->s.modelindex; +  else +    return BA_NONE; +} + + +/* +================== +G_GetBuildPoints + +Get the number of build points from a position +================== +*/ +int G_GetBuildPoints( const vec3_t pos, team_t team ) +{ +  if( G_TimeTilSuddenDeath( ) <= 0 ) +  { +    return 0; +  } +  else if( team == TEAM_ALIENS ) +  { +    return level.alienBuildPoints; +  } +  else if( team == TEAM_HUMANS ) +  { +    gentity_t *powerPoint = G_PowerEntityForPoint( pos ); + +    if( powerPoint && powerPoint->s.modelindex == BA_H_REACTOR ) +      return level.humanBuildPoints; + +    if( powerPoint && powerPoint->s.modelindex == BA_H_REPEATER && +        powerPoint->usesBuildPointZone && level.buildPointZones[ powerPoint->buildPointZone ].active ) +    { +      return level.buildPointZones[ powerPoint->buildPointZone ].totalBuildPoints - +             level.buildPointZones[ powerPoint->buildPointZone ].queuedBuildPoints; +    } + +    // Return the BP of the main zone by default +    return level.humanBuildPoints; +  } + +  return 0; +} + +/* +================== +G_GetMarkedBuildPoints + +Get the number of marked build points from a position +================== +*/ +int G_GetMarkedBuildPoints( const vec3_t pos, team_t team )  { -  int       i;    gentity_t *ent; -  gentity_t *closestDCC = NULL; -  int       distance = 0; -  int       minDistance = 10000; -  vec3_t    temp_v; -  qboolean  foundDCC = qfalse; +  int       i; +  int sum = 0; -  if( self->biteam != BIT_HUMANS ) -    return qfalse; +  if( G_TimeTilSuddenDeath( ) <= 0 ) +    return 0; -  //if this already has dcc then stop now -  if( self->dccNode && self->dccNode->powered ) -    return qtrue; +  if( !g_markDeconstruct.integer ) +    return 0; -  //reset parent -  self->dccNode = NULL; +  for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) +  { +    if( ent->s.eType != ET_BUILDABLE ) +      continue; -  //iterate through entities -  for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) +    if( team == TEAM_HUMANS && +        ent->s.modelindex != BA_H_REACTOR && +        ent->s.modelindex != BA_H_REPEATER && +        ent->parentNode != G_PowerEntityForPoint( pos ) ) +      continue; + +    if( !ent->inuse ) +      continue; + +    if( ent->health <= 0 ) +      continue; + +    if( ent->buildableTeam != team ) +      continue; + +    if( ent->deconstruct ) +      sum += BG_Buildable( ent->s.modelindex )->buildPoints; +  } + +  return sum; +} + +/* +================== +G_InPowerZone + +See if a buildable is inside of another power zone. +Return pointer to provider if so. +It's different from G_FindPower because FindPower for +providers will find themselves. +(This doesn't check if power zones overlap) +================== +*/ +gentity_t *G_InPowerZone( gentity_t *self ) +{ +  int         i; +  gentity_t   *ent; +  int         distance; +  vec3_t      temp_v; + +  for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ )    {      if( ent->s.eType != ET_BUILDABLE )        continue; -    //if entity is a dcc calculate the distance to it -    if( ent->s.modelindex == BA_H_DCC && ent->spawned ) +    if( ent == self ) +      continue; + +    if( !ent->spawned ) +      continue; + +    if( ent->health <= 0 ) +      continue; + +    // if entity is a power item calculate the distance to it +    if( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) && +        ent->spawned && ent->powered )      { -      VectorSubtract( self->s.origin, ent->s.origin, temp_v ); +      VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v );        distance = VectorLength( temp_v ); -      if( ( !foundDCC || distance < minDistance ) && ent->powered ) -      { -        closestDCC = ent; -        minDistance = distance; -        foundDCC = qtrue; -      } + +      if( ent->s.modelindex == BA_H_REACTOR && distance <= REACTOR_BASESIZE ) +        return ent; +      else if( ent->s.modelindex == BA_H_REPEATER && distance <= REPEATER_BASESIZE ) +        return ent;      }    } -  //if there was no nearby DCC give up -  if( !foundDCC ) -    return qfalse; - -  self->dccNode = closestDCC; - -  return qtrue; +  return NULL;  }  /*  ================ -G_IsDCCBuilt +G_FindDCC -simple wrapper to G_FindDCC to check for a dcc +attempt to find a controlling DCC for self, return number found  ================  */ -qboolean G_IsDCCBuilt( void ) +int G_FindDCC( gentity_t *self )  { -  gentity_t dummy; +  int       i; +  gentity_t *ent; +  int       distance = 0; +  vec3_t    temp_v; +  int       foundDCC = 0; -  memset( &dummy, 0, sizeof( gentity_t ) ); +  if( self->buildableTeam != TEAM_HUMANS ) +    return 0; -  dummy.dccNode = NULL; -  dummy.biteam = BIT_HUMANS; +  //iterate through entities +  for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) +  { +    if( ent->s.eType != ET_BUILDABLE ) +      continue; -  return G_FindDCC( &dummy ); +    //if entity is a dcc calculate the distance to it +    if( ent->s.modelindex == BA_H_DCC && ent->spawned ) +    { +      VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v ); +      distance = VectorLength( temp_v ); +      if( distance < DC_RANGE && ent->powered ) +      { +        foundDCC++;  +      } +    } +  } + +  return foundDCC;  }  /*  ================ -G_FindOvermind +G_IsDCCBuilt -Attempt to find an overmind for self +See if any powered DCC exists  ================  */ -static qboolean G_FindOvermind( gentity_t *self ) +qboolean G_IsDCCBuilt( void )  {    int       i;    gentity_t *ent; -  if( self->biteam != BIT_ALIENS ) -    return qfalse; +  for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) +  { +    if( ent->s.eType != ET_BUILDABLE ) +      continue; -  //if this already has overmind then stop now -  if( self->overmindNode && self->overmindNode->health > 0 ) -    return qtrue; +    if( ent->s.modelindex != BA_H_DCC ) +      continue; -  //reset parent -  self->overmindNode = NULL; +    if( !ent->spawned ) +      continue; -  //iterate through entities -  for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) -  { -    if( ent->s.eType != ET_BUILDABLE ) +    if( ent->health <= 0 )        continue; -    //if entity is an overmind calculate the distance to it -    if( ent->s.modelindex == BA_A_OVERMIND && ent->spawned && ent->health > 0 ) -    { -      self->overmindNode = ent; -      return qtrue; -    } +    return qtrue;    }    return qfalse; @@ -360,21 +525,46 @@ static qboolean G_FindOvermind( gentity_t *self )  /*  ================ -G_IsOvermindBuilt +G_Reactor +G_Overmind + +Since there's only one of these and we quite often want to find them, cache the +results, but check them for validity each time -Simple wrapper to G_FindOvermind to check if a location has an overmind +The code here will break if more than one reactor or overmind is allowed, even +if one of them is dead/unspawned  ================  */ -qboolean G_IsOvermindBuilt( void ) +static gentity_t *G_FindBuildable( buildable_t buildable );  + +gentity_t *G_Reactor( void )  { -  gentity_t dummy; +  static gentity_t *rc; -  memset( &dummy, 0, sizeof( gentity_t ) ); +  // If cache becomes invalid renew it +  if( !rc || rc->s.eType != ET_BUILDABLE || rc->s.modelindex != BA_H_REACTOR ) +    rc = G_FindBuildable( BA_H_REACTOR ); -  dummy.overmindNode = NULL; -  dummy.biteam = BIT_ALIENS; +  // If we found it and it's alive, return it +  if( rc && rc->spawned && rc->health > 0 ) +    return rc; -  return G_FindOvermind( &dummy ); +  return NULL; +} + +gentity_t *G_Overmind( void ) +{ +  static gentity_t *om; + +  // If cache becomes invalid renew it +  if( !om || om->s.eType != ET_BUILDABLE || om->s.modelindex != BA_A_OVERMIND ) +    om = G_FindBuildable( BA_A_OVERMIND ); + +  // If we found it and it's alive, return it +  if( om && om->spawned && om->health > 0 ) +    return om; + +  return NULL;  }  /* @@ -384,7 +574,7 @@ G_FindCreep  attempt to find creep for self, return qtrue if successful  ================  */ -static qboolean G_FindCreep( gentity_t *self ) +qboolean G_FindCreep( gentity_t *self )  {    int       i;    gentity_t *ent; @@ -394,21 +584,23 @@ static qboolean G_FindCreep( gentity_t *self )    vec3_t    temp_v;    //don't check for creep if flying through the air -  if( self->s.groundEntityNum == -1 ) +  if( !self->client && self->s.groundEntityNum == ENTITYNUM_NONE )      return qtrue; -  //if self does not have a parentNode or it's parentNode is invalid find a new one -  if( ( self->parentNode == NULL ) || !self->parentNode->inuse ) +  //if self does not have a parentNode or its parentNode is invalid, then find a new one +  if( self->client || self->parentNode == NULL || !self->parentNode->inuse || +      self->parentNode->health <= 0 )    { -    for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) +    for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ )      {        if( ent->s.eType != ET_BUILDABLE )          continue; -      if( ( ent->s.modelindex == BA_A_SPAWN || ent->s.modelindex == BA_A_OVERMIND ) && -          ent->spawned ) +      if( ( ent->s.modelindex == BA_A_SPAWN ||  +            ent->s.modelindex == BA_A_OVERMIND ) && +          ent->spawned && ent->health > 0 )        { -        VectorSubtract( self->s.origin, ent->s.origin, temp_v ); +        VectorSubtract( self->r.currentOrigin, ent->r.currentOrigin, temp_v );          distance = VectorLength( temp_v );          if( distance < minDistance )          { @@ -420,13 +612,17 @@ static qboolean G_FindCreep( gentity_t *self )      if( minDistance <= CREEP_BASESIZE )      { -      self->parentNode = closestSpawn; +      if( !self->client ) +        self->parentNode = closestSpawn;        return qtrue;      }      else        return qfalse;    } +  if( self->client ) +    return qfalse; +    //if we haven't returned by now then we must already have a valid parent    return qtrue;  } @@ -446,7 +642,7 @@ static qboolean G_IsCreepHere( vec3_t origin )    dummy.parentNode = NULL;    dummy.s.modelindex = BA_NONE; -  VectorCopy( origin, dummy.s.origin ); +  VectorCopy( origin, dummy.r.currentOrigin );    return G_FindCreep( &dummy );  } @@ -466,12 +662,12 @@ static void G_CreepSlow( gentity_t *self )    int         i, num;    gentity_t   *enemy;    buildable_t buildable = self->s.modelindex; -  float       creepSize = (float)BG_FindCreepSizeForBuildable( buildable ); +  float       creepSize = (float)BG_Buildable( buildable )->creepSize;    VectorSet( range, creepSize, creepSize, creepSize ); -  VectorAdd( self->s.origin, range, maxs ); -  VectorSubtract( self->s.origin, range, mins ); +  VectorAdd( self->r.currentOrigin, range, maxs ); +  VectorSubtract( self->r.currentOrigin, range, mins );    //find humans    num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); @@ -479,12 +675,11 @@ static void G_CreepSlow( gentity_t *self )    {      enemy = &g_entities[ entityList[ i ] ]; -    if( enemy->flags & FL_NOTARGET ) -      continue; +   if( enemy->flags & FL_NOTARGET ) +     continue; -    if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && -        enemy->client->ps.groundEntityNum != ENTITYNUM_NONE && -        G_Visible( self, enemy ) ) +    if( enemy->client && enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && +        enemy->client->ps.groundEntityNum != ENTITYNUM_NONE )      {        enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED;        enemy->client->lastCreepSlowTime = level.time; @@ -503,41 +698,27 @@ static void nullDieFunction( gentity_t *self, gentity_t *inflictor, gentity_t *a  {  } -static void G_BuildableDeathSound( gentity_t *self ) -{ -  gentity_t *snd = G_TempEntity( self->r.currentOrigin, EV_GENERAL_SOUND ); -  snd->s.eventParm = G_SoundIndex( BG_FindTeamForBuildable( self->s.modelindex ) == PTE_HUMANS ? -                                   "sound/buildables/human/destroyed" : "sound/buildables/alien/construct2" ); -} - -/* -================ -freeBuildable -================ -*/ -static void freeBuildable( gentity_t *self ) -{ -  G_FreeEntity( self ); -} - -  //==================================================================================  /*  ================ -A_CreepRecede +AGeneric_CreepRecede -Called when an alien spawn dies +Called when an alien buildable dies  ================  */ -void A_CreepRecede( gentity_t *self ) +void AGeneric_CreepRecede( gentity_t *self )  {    //if the creep just died begin the recession    if( !( self->s.eFlags & EF_DEAD ) )    {      self->s.eFlags |= EF_DEAD; +    G_QueueBuildPoints( self ); + +    G_RewardAttackers( self ); +      G_AddEvent( self, EV_BUILD_DESTROY, 0 );      if( self->spawned ) @@ -546,7 +727,7 @@ void A_CreepRecede( gentity_t *self )        self->s.time = -( level.time -            (int)( (float)CREEP_SCALEDOWN_TIME *                   ( 1.0f - ( (float)( level.time - self->buildTime ) / -                            (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) ); +                            (float)BG_Buildable( self->s.modelindex )->buildTime ) ) ) );    }    //creep is still receeding @@ -556,71 +737,30 @@ void A_CreepRecede( gentity_t *self )      G_FreeEntity( self );  } - - - -//================================================================================== - - - -  /*  ================ -ASpawn_Melt +AGeneric_Blast -Called when an alien spawn dies +Called when an Alien buildable explodes after dead state  ================  */ -void ASpawn_Melt( gentity_t *self ) +void AGeneric_Blast( gentity_t *self )  { -  G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, -    self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); - -  //start creep recession -  if( !( self->s.eFlags & EF_DEAD ) ) -  { -    self->s.eFlags |= EF_DEAD; -    G_AddEvent( self, EV_BUILD_DESTROY, 0 ); - -    if( self->spawned ) -      self->s.time = -level.time; -    else -      self->s.time = -( level.time - -          (int)( (float)CREEP_SCALEDOWN_TIME * -                 ( 1.0f - ( (float)( level.time - self->buildTime ) / -                            (float)BG_FindBuildTimeForBuildable( self->s.modelindex ) ) ) ) ); -  } - -  //not dead yet -  if( ( self->timestamp + 10000 ) > level.time ) -    self->nextthink = level.time + 500; -  else //dead now -    G_FreeEntity( self ); -} - -/* -================ -ASpawn_Blast - -Called when an alien spawn dies -================ -*/ -void ASpawn_Blast( gentity_t *self ) -{ -  vec3_t  dir; +  vec3_t dir;    VectorCopy( self->s.origin2, dir );    //do a bit of radius damage -  G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, -    self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); +  G_SelectiveRadiusDamage( self->s.pos.trBase, g_entities + self->killedBy, self->splashDamage, +                           self->splashRadius, self, self->splashMethodOfDeath, +                           TEAM_ALIENS );    //pretty events and item cleanup    self->s.eFlags |= EF_NODRAW; //don't draw the model once it's destroyed    G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) );    self->timestamp = level.time; -  self->think = ASpawn_Melt; -  self->nextthink = level.time + 500; //wait .5 seconds before damaging others +  self->think = AGeneric_CreepRecede; +  self->nextthink = level.time + 500;    self->r.contents = 0;    //stop collisions...    trap_LinkEntity( self ); //...requires a relink @@ -628,74 +768,93 @@ void ASpawn_Blast( gentity_t *self )  /*  ================ -ASpawn_Die +AGeneric_Die -Called when an alien spawn dies +Called when an Alien buildable is killed and enters a brief dead state prior to +exploding.  ================  */ -void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +void AGeneric_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )  { -  buildHistory_t *new; -  new = G_Alloc( sizeof( buildHistory_t ) ); -  new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; -  new->ent = ( attacker && attacker->client ) ? attacker : NULL; -  if( new->ent ) -    new->name[ 0 ] = 0; -  else -    Q_strncpyz( new->name, "<world>", 8 ); -  new->buildable = self->s.modelindex; -  VectorCopy( self->s.pos.trBase, new->origin ); -  VectorCopy( self->s.angles, new->angles ); -  VectorCopy( self->s.origin2, new->origin2 ); -  VectorCopy( self->s.angles2, new->angles2 ); -  new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; -  new->next = NULL; -  G_LogBuild( new ); - -  G_BuildableDeathSound( self ); -    G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue );    G_SetIdleBuildableAnim( self, BANIM_DESTROYED );    self->die = nullDieFunction; -  self->think = ASpawn_Blast; +  self->killedBy = attacker - g_entities; +  self->think = AGeneric_Blast; +  self->s.eFlags &= ~EF_FIRING; //prevent any firing effects +  self->powered = qfalse;    if( self->spawned )      self->nextthink = level.time + 5000;    else      self->nextthink = level.time; //blast immediately -  self->s.eFlags &= ~EF_FIRING; //prevent any firing effects +  G_RemoveRangeMarkerFrom( self ); +  G_LogDestruction( self, attacker, mod ); +} -  if( attacker && attacker->client ) +/* +================ +AGeneric_CreepCheck + +Tests for creep and kills the buildable if there is none +================ +*/ +void AGeneric_CreepCheck( gentity_t *self ) +{ +  gentity_t *spawn; + +  spawn = self->parentNode; +  if( !G_FindCreep( self ) )    { -    if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -    { -      if( self->s.modelindex == BA_A_OVERMIND ) -        G_AddCreditToClient( attacker->client, OVERMIND_VALUE, qtrue ); -      else if( self->s.modelindex == BA_A_SPAWN ) -        G_AddCreditToClient( attacker->client, ASPAWN_VALUE, qtrue ); -    } +    if( spawn ) +      G_Damage( self, NULL, g_entities + spawn->killedBy, NULL, NULL, +                self->health, 0, MOD_NOCREEP );      else -    { -      G_TeamCommand( PTE_ALIENS, -        va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", -          BG_FindHumanNameForBuildable( self->s.modelindex ),  -          attacker->client->pers.netname ) ); -      G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", -          BG_FindHumanNameForBuildable( self->s.modelindex ),  -          attacker->client->pers.netname );  -    } -    G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", -      attacker->client->ps.clientNum, self->s.modelindex, mod, -      attacker->client->pers.netname,  -      BG_FindNameForBuildable( self->s.modelindex ), -      modNames[ mod ] ); +      G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_NOCREEP ); +    return;    } +  G_CreepSlow( self ); +} + +/* +================ +AGeneric_Think + +A generic think function for Alien buildables +================ +*/ +void AGeneric_Think( gentity_t *self ) +{ +  self->powered = G_Overmind( ) != NULL; +  self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink; +  AGeneric_CreepCheck( self );  }  /*  ================ +AGeneric_Pain + +A generic pain function for Alien buildables +================ +*/ +void AGeneric_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ +  if( self->health <= 0 ) +    return; +     +  // Alien buildables only have the first pain animation defined +  G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); +} + + + + +//================================================================================== + +/* +================  ASpawn_Think  think function for Alien Spawn @@ -708,16 +867,21 @@ void ASpawn_Think( gentity_t *self )    if( self->spawned )    {      //only suicide if at rest -    if( self->s.groundEntityNum ) +    if( self->s.groundEntityNum != ENTITYNUM_NONE )      { -      if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin, +      if( ( ent = G_CheckSpawnPoint( self->s.number, self->r.currentOrigin,                self->s.origin2, BA_A_SPAWN, NULL ) ) != NULL )        {          // If the thing blocking the spawn is a buildable, kill it.           // If it's part of the map, kill self.           if( ent->s.eType == ET_BUILDABLE )          { -          G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); +          // don't queue the bp from this +          if( ent->builtBy && ent->builtBy->slot >= 0 ) +            G_Damage( ent, NULL, g_entities + ent->builtBy->slot, NULL, NULL, 10000, 0, MOD_SUICIDE ); +          else +            G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); +            G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue );          }          else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) @@ -725,61 +889,16 @@ void ASpawn_Think( gentity_t *self )            G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );            return;          } -        else if( g_antiSpawnBlock.integer && ent->client &&  -                 ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -        { -          //spawnblock protection -          if( self->spawnBlockTime && level.time - self->spawnBlockTime > 10000 ) -          { -            //five seconds of countermeasures and we're still blocked -            //time for something more drastic -            G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_TRIGGER_HURT ); -            self->spawnBlockTime += 2000; -            //inappropriate MOD but prints an apt obituary -          } -          else if( self->spawnBlockTime && level.time - self->spawnBlockTime > 5000 ) -          //five seconds of blocked by client and... -          { -            //random direction -            vec3_t velocity; -            velocity[0] = crandom() * g_antiSpawnBlock.integer; -            velocity[1] = crandom() * g_antiSpawnBlock.integer; -            velocity[2] = g_antiSpawnBlock.integer; -                 -            VectorAdd( ent->client->ps.velocity, velocity, ent->client->ps.velocity ); -            trap_SendServerCommand( ent-g_entities, "cp \"Don't spawn block!\"" ); -          } -          else if( !self->spawnBlockTime ) -            self->spawnBlockTime = level.time; -       } -       if( ent->s.eType == ET_CORPSE ) -         G_FreeEntity( ent ); //quietly remove + +        if( ent->s.eType == ET_CORPSE ) +          G_FreeEntity( ent ); //quietly remove        } -      else -       self->spawnBlockTime = 0;      }    }    G_CreepSlow( self ); -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); -} - -/* -================ -ASpawn_Pain - -pain function for Alien Spawn -================ -*/ -void ASpawn_Pain( gentity_t *self, gentity_t *attacker, int damage ) -{ -  G_SetBuildableAnim( self, BANIM_PAIN1, qfalse ); - -  if ( self->s.modelindex == BA_A_OVERMIND && self->health > 0 && -       attacker && attacker->client && attacker->client->pers.teamSelection == PTE_ALIENS ) -    G_TeamCommand( PTE_ALIENS, va( "print \"Overmind ^3DAMAGED^7 by ^1TEAMMATE^7 %s^7\n\"", -                   attacker->client->pers.netname )); +  self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink;  } @@ -805,39 +924,26 @@ Think function for Alien Overmind  */  void AOvermind_Think( gentity_t *self )  { -  int       entityList[ MAX_GENTITIES ]; -  vec3_t    range = { OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE, OVERMIND_ATTACK_RANGE }; -  vec3_t    mins, maxs; -  int       i, num; -  gentity_t *enemy; - -  VectorAdd( self->s.origin, range, maxs ); -  VectorSubtract( self->s.origin, range, mins ); +  int    i;    if( self->spawned && ( self->health > 0 ) )    {      //do some damage -    num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); -    for( i = 0; i < num; i++ ) +    if( G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, +          self->splashRadius, self, MOD_OVERMIND, TEAM_ALIENS ) )      { -      enemy = &g_entities[ entityList[ i ] ]; - -      if( enemy->flags & FL_NOTARGET )  -        continue; - -      if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -      { -        self->timestamp = level.time; -        G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, -          self->splashRadius, self, MOD_OVERMIND, PTE_ALIENS ); -        G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); -      } +      self->timestamp = level.time; +      G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );      }      // just in case an egg finishes building after we tell overmind to stfu      if( level.numAlienSpawns > 0 )        level.overmindMuted = qfalse; +    // shut up during intermission +    if( level.intermissiontime ) +      level.overmindMuted = qtrue; +      //low on spawns      if( !level.overmindMuted && level.numAlienSpawns <= 0 &&          level.time > self->overmindSpawnsTimer ) @@ -885,14 +991,13 @@ void AOvermind_Think( gentity_t *self )    G_CreepSlow( self ); -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +  self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink;  } -  //================================================================================== @@ -903,12 +1008,15 @@ void AOvermind_Think( gentity_t *self )  ================  ABarricade_Pain -pain function for Alien Spawn +Barricade pain animation depends on shrunk state  ================  */  void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage )  { -  if( rand( ) % 2 ) +  if( self->health <= 0 ) +    return; + +  if( !self->shrunkTime )      G_SetBuildableAnim( self, BANIM_PAIN1, qfalse );    else      G_SetBuildableAnim( self, BANIM_PAIN2, qfalse ); @@ -916,223 +1024,134 @@ void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage )  /*  ================ -ABarricade_Blast +ABarricade_Shrink -Called when an alien spawn dies +Set shrink state for a barricade. When unshrinking, checks to make sure there +is enough room.  ================  */ -void ABarricade_Blast( gentity_t *self ) +void ABarricade_Shrink( gentity_t *self, qboolean shrink )  { -  vec3_t  dir; - -  VectorCopy( self->s.origin2, dir ); +  if ( !self->spawned || self->health <= 0 ) +    shrink = qtrue; +  if ( shrink && self->shrunkTime ) +  { +    int anim; -  //do a bit of radius damage -  G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, -    self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); +    // We need to make sure that the animation has been set to shrunk mode +    // because we start out shrunk but with the construct animation when built +    self->shrunkTime = level.time; +    anim = self->s.torsoAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT ); +    if ( self->spawned && self->health > 0 && anim != BANIM_DESTROYED ) +    { +      G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); +      G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue ); +    } +    return; +  } -  //pretty events and item cleanup -  self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed -  G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); -  self->timestamp = level.time; -  self->think = A_CreepRecede; -  self->nextthink = level.time + 500; //wait .5 seconds before damaging others +  if ( !shrink && ( !self->shrunkTime || +       level.time < self->shrunkTime + BARRICADE_SHRINKTIMEOUT ) ) +    return; -  self->r.contents = 0;    //stop collisions... -  trap_LinkEntity( self ); //...requires a relink -} +  BG_BuildableBoundingBox( BA_A_BARRICADE, self->r.mins, self->r.maxs ); -/* -================ -ABarricade_Die +  if ( shrink ) +  { +    self->r.maxs[ 2 ] = (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP ); +    self->shrunkTime = level.time; -Called when an alien spawn dies -================ -*/ -void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) -{ -  buildHistory_t *new; -  new = G_Alloc( sizeof( buildHistory_t ) ); -  new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; -  new->ent = ( attacker && attacker->client ) ? attacker : NULL; -  if( new->ent ) -    new->name[ 0 ] = 0; +    // shrink animation, the destroy animation is used +    if ( self->spawned && self->health > 0 ) +    { +      G_SetBuildableAnim( self, BANIM_ATTACK1, qtrue ); +      G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); +    } +  }    else -    Q_strncpyz( new->name, "<world>", 8 ); -  new->buildable = self->s.modelindex; -  VectorCopy( self->s.pos.trBase, new->origin ); -  VectorCopy( self->s.angles, new->angles ); -  VectorCopy( self->s.origin2, new->origin2 ); -  VectorCopy( self->s.angles2, new->angles2 ); -  new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; -  new->next = NULL; -  G_LogBuild( new ); - -  G_BuildableDeathSound( self ); - -  G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); -  G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); - -  self->die = nullDieFunction; -  self->think = ABarricade_Blast; -  self->s.eFlags &= ~EF_FIRING; //prevent any firing effects +  { +    trace_t tr; +    int anim; -  if( self->spawned ) -    self->nextthink = level.time + 5000; -  else -    self->nextthink = level.time; //blast immediately +    trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, +                self->r.currentOrigin, self->s.number, MASK_PLAYERSOLID ); +    if ( tr.startsolid || tr.fraction < 1.0f ) +    { +      self->r.maxs[ 2 ] = (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP ); +      return; +    } +    self->shrunkTime = 0; -  if( attacker && attacker->client ) -  { -    if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) +    // unshrink animation, IDLE2 has been hijacked for this +    anim = self->s.legsAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT ); +    if ( self->spawned && self->health > 0 && +         anim != BANIM_CONSTRUCT1 && anim != BANIM_CONSTRUCT2 )      { -      G_TeamCommand( PTE_ALIENS, -        va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", -          BG_FindHumanNameForBuildable( self->s.modelindex ),  -          attacker->client->pers.netname ) ); -      G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", -          BG_FindHumanNameForBuildable( self->s.modelindex ),  -          attacker->client->pers.netname );  +      G_SetIdleBuildableAnim( self, BG_Buildable( BA_A_BARRICADE )->idleAnim ); +      G_SetBuildableAnim( self, BANIM_ATTACK2, qtrue );      } -    G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", -      attacker->client->ps.clientNum, self->s.modelindex, mod, -      attacker->client->pers.netname,  -      BG_FindNameForBuildable( self->s.modelindex ), -      modNames[ mod ] );    } + +  // a change in size requires a relink +  if ( self->spawned ) +    trap_LinkEntity( self );  }  /*  ================ -ABarricade_Think +ABarricade_Die -Think function for Alien Barricade +Called when an alien barricade dies  ================  */ -void ABarricade_Think( gentity_t *self ) +void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod )  { - -  self->powered = G_IsOvermindBuilt( ); - -  //if there is no creep nearby die -  if( !G_FindCreep( self ) ) -  { -    G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); -    return; -  } - -  G_CreepSlow( self ); - -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +  AGeneric_Die( self, inflictor, attacker, damage, mod ); +  ABarricade_Shrink( self, qtrue );  } - - - -//================================================================================== - - - - -void AAcidTube_Think( gentity_t *self ); -  /*  ================ -AAcidTube_Damage +ABarricade_Think -Damage function for Alien Acid Tube +Think function for Alien Barricade  ================  */ -void AAcidTube_Damage( gentity_t *self ) +void ABarricade_Think( gentity_t *self )  { -  if( self->spawned ) -  { -    if( !( self->s.eFlags & EF_FIRING ) ) -    { -      self->s.eFlags |= EF_FIRING; -      G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) ); -    } - -    if( ( self->timestamp + ACIDTUBE_REPEAT ) > level.time ) -      self->think = AAcidTube_Damage; -    else -    { -      self->think = AAcidTube_Think; -      self->s.eFlags &= ~EF_FIRING; -    } - -    //do some damage -    G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, -      self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); -  } - -  G_CreepSlow( self ); +  AGeneric_Think( self ); -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +  // Shrink if unpowered +  ABarricade_Shrink( self, !self->powered );  }  /*  ================ -AAcidTube_Think +ABarricade_Touch -Think function for Alien Acid Tube +Barricades shrink when they are come into contact with an Alien that can +pass through  ================  */ -void AAcidTube_Think( gentity_t *self ) -{ -  int       entityList[ MAX_GENTITIES ]; -  vec3_t    range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE }; -  vec3_t    mins, maxs; -  int       i, num; -  gentity_t *enemy; - -  self->powered = G_IsOvermindBuilt( ); -  VectorAdd( self->s.origin, range, maxs ); -  VectorSubtract( self->s.origin, range, mins ); +void ABarricade_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ +  gclient_t *client = other->client; +  int client_z, min_z; -  //if there is no creep nearby die -  if( !G_FindCreep( self ) ) -  { -    G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); +  if( !client || client->pers.teamSelection != TEAM_ALIENS )      return; -  } - -  if( self->spawned && G_FindOvermind( self ) ) -  { -    //do some damage -    num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); -    for( i = 0; i < num; i++ ) -    { -      enemy = &g_entities[ entityList[ i ] ]; - -      if( enemy->flags & FL_NOTARGET ) -        continue; - -      if( !G_Visible( self, enemy ) ) -        continue; - -      if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -      { -        if( level.paused || enemy->client->pers.paused ) -          continue; -        self->timestamp = level.time; -        self->think = AAcidTube_Damage; -        self->nextthink = level.time + 100; -        G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); -        return; -      } -    } -  } - -  G_CreepSlow( self ); -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +  // Client must be high enough to pass over. Note that STEPSIZE (18) is +  // hardcoded here because we don't include bg_local.h! +  client_z = other->r.currentOrigin[ 2 ] + other->r.mins[ 2 ]; +  min_z = self->r.currentOrigin[ 2 ] - 18 + +          (int)( self->r.maxs[ 2 ] * BARRICADE_SHRINKPROP ); +  if( client_z < min_z ) +    return; +  ABarricade_Shrink( self, qtrue );  } - - -  //================================================================================== @@ -1140,40 +1159,27 @@ void AAcidTube_Think( gentity_t *self )  /*  ================ -AHive_Think +AAcidTube_Think -Think function for Alien Hive +Think function for Alien Acid Tube  ================  */ -void AHive_Think( gentity_t *self ) +void AAcidTube_Think( gentity_t *self )  {    int       entityList[ MAX_GENTITIES ];    vec3_t    range = { ACIDTUBE_RANGE, ACIDTUBE_RANGE, ACIDTUBE_RANGE };    vec3_t    mins, maxs;    int       i, num;    gentity_t *enemy; -  vec3_t    dirToTarget; - -  self->powered = G_IsOvermindBuilt( ); -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +  AGeneric_Think( self ); -  VectorAdd( self->s.origin, range, maxs ); -  VectorSubtract( self->s.origin, range, mins ); +  VectorAdd( self->r.currentOrigin, range, maxs ); +  VectorSubtract( self->r.currentOrigin, range, mins ); -  //if there is no creep nearby die -  if( !G_FindCreep( self ) ) +  // attack nearby humans +  if( self->spawned && self->health > 0 && self->powered )    { -    G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); -    return; -  } - -  if( self->timestamp < level.time ) -    self->active = qfalse; //nothing has returned in HIVE_REPEAT seconds, forget about it - -  if( self->spawned && !self->active && G_FindOvermind( self ) ) -  { -    //do some damage      num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );      for( i = 0; i < num; i++ )      { @@ -1182,33 +1188,26 @@ void AHive_Think( gentity_t *self )        if( enemy->flags & FL_NOTARGET )          continue; -      if( enemy->health <= 0 ) -        continue; - -      if( !G_Visible( self, enemy ) ) +      if( !G_Visible( self, enemy, CONTENTS_SOLID ) )          continue; -      if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +      if( enemy->client && enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )        { -        if( level.paused || enemy->client->pers.paused ) -          continue; -        self->active = qtrue; -        self->target_ent = enemy; -        self->timestamp = level.time + HIVE_REPEAT; - -        VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); -        VectorNormalize( dirToTarget ); -        vectoangles( dirToTarget, self->turretAim ); - -        //fire at target -        FireWeapon( self ); -        G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); +        // start the attack animation +        if( level.time >= self->timestamp + ACIDTUBE_REPEAT_ANIM ) +        { +          self->timestamp = level.time; +          G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); +          G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) ); +        } +         +        G_SelectiveRadiusDamage( self->s.pos.trBase, self, ACIDTUBE_DAMAGE, +                                 ACIDTUBE_RANGE, self, MOD_ATUBE, TEAM_ALIENS );                            +        self->nextthink = level.time + ACIDTUBE_REPEAT;          return;        }      }    } - -  G_CreepSlow( self );  } @@ -1216,284 +1215,111 @@ void AHive_Think( gentity_t *self )  //================================================================================== - - - -#define HOVEL_TRACE_DEPTH 128.0f -  /*  ================ -AHovel_Blocked +AHive_CheckTarget -Is this hovel entrance blocked? +Returns true and fires the hive missile if the target is valid  ================  */ -qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit ) +static qboolean AHive_CheckTarget( gentity_t *self, gentity_t *enemy )  { -  vec3_t    forward, normal, origin, start, end, angles, hovelMaxs; -  vec3_t    mins, maxs; -  float     displacement; -  trace_t   tr; - -  BG_FindBBoxForBuildable( BA_A_HOVEL, NULL, hovelMaxs ); -  BG_FindBBoxForClass( player->client->ps.stats[ STAT_PCLASS ], -                       mins, maxs, NULL, NULL, NULL ); - -  VectorCopy( hovel->s.origin2, normal ); -  AngleVectors( hovel->s.angles, forward, NULL, NULL ); -  VectorInverse( forward ); - -  displacement = VectorMaxComponent( maxs ) + -                 VectorMaxComponent( hovelMaxs ) + 1.0f; - -  VectorMA( hovel->s.origin, displacement, forward, origin ); - -  VectorCopy( hovel->s.origin, start ); -  VectorCopy( origin, end ); - -  // see if there's something between the hovel and its exit  -  // (eg built right up against a wall) -  trap_Trace( &tr, start, NULL, NULL, end, player->s.number, MASK_PLAYERSOLID ); -  if( tr.fraction < 1.0f ) -    return qtrue; - -  vectoangles( forward, angles ); - -  VectorMA( origin, HOVEL_TRACE_DEPTH, normal, start ); +  trace_t trace; +  vec3_t tip_origin, dirToTarget; -  //compute a place up in the air to start the real trace -  trap_Trace( &tr, origin, mins, maxs, start, player->s.number, MASK_PLAYERSOLID ); - -  VectorMA( origin, ( HOVEL_TRACE_DEPTH * tr.fraction ) - 1.0f, normal, start ); -  VectorMA( origin, -HOVEL_TRACE_DEPTH, normal, end ); - -  trap_Trace( &tr, start, mins, maxs, end, player->s.number, MASK_PLAYERSOLID ); - -  VectorCopy( tr.endpos, origin ); - -  trap_Trace( &tr, origin, mins, maxs, origin, player->s.number, MASK_PLAYERSOLID ); +  // Check if this is a valid target +  if( enemy->health <= 0 || !enemy->client || +      enemy->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS ) +    return qfalse; -  if( provideExit ) -  { -    G_SetOrigin( player, origin ); -    VectorCopy( origin, player->client->ps.origin ); -    // nudge -    VectorMA( normal, 200.0f, forward, player->client->ps.velocity ); -    G_SetClientViewAngle( player, angles ); -  } +  if( enemy->flags & FL_NOTARGET ) +    return qfalse; -  if( tr.fraction < 1.0f ) -    return qtrue; -  else +  // Check if the tip of the hive can see the target +  VectorMA( self->s.pos.trBase, self->r.maxs[ 2 ], self->s.origin2, +            tip_origin ); +  if( Distance( tip_origin, enemy->r.currentOrigin ) > HIVE_SENSE_RANGE )      return qfalse; -} -/* -================ -APropHovel_Blocked +  trap_Trace( &trace, tip_origin, NULL, NULL, enemy->s.pos.trBase, +              self->s.number, MASK_SHOT ); +  if( trace.fraction < 1.0f && trace.entityNum != enemy->s.number ) +    return qfalse; -Wrapper to test a hovel placement for validity -================ -*/ -static qboolean APropHovel_Blocked( vec3_t origin, vec3_t angles, vec3_t normal, -                                    gentity_t *player ) -{ -  gentity_t hovel; +  self->active = qtrue; +  self->target_ent = enemy; +  self->timestamp = level.time + HIVE_REPEAT; -  VectorCopy( origin, hovel.s.origin ); -  VectorCopy( angles, hovel.s.angles ); -  VectorCopy( normal, hovel.s.origin2 ); +  VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); +  VectorNormalize( dirToTarget ); +  vectoangles( dirToTarget, self->turretAim ); -  return AHovel_Blocked( &hovel, player, qfalse ); +  // Fire at target +  FireWeapon( self ); +  G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); +  return qtrue;  }  /*  ================ -AHovel_Use +AHive_Think -Called when an alien uses a hovel +Think function for Alien Hive  ================  */ -void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) +void AHive_Think( gentity_t *self )  { -  vec3_t  hovelOrigin, hovelAngles, inverseNormal; +  int       start; -  if( self->spawned && G_FindOvermind( self ) ) -  { -    if( self->active ) -    { -      //this hovel is in use -      G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_OCCUPIED ); -    } -    else if( ( ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ) || -               ( activator->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) ) && -             activator->health > 0 && self->health > 0 ) -    { -      if( AHovel_Blocked( self, activator, qfalse ) ) -      { -        //you can get in, but you can't get out -        G_TriggerMenu( activator->client->ps.clientNum, MN_A_HOVEL_BLOCKED ); -        return; -      } +  AGeneric_Think( self ); -      self->active = qtrue; -      G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); - -      //prevent lerping -      activator->client->ps.eFlags ^= EF_TELEPORT_BIT; -      activator->client->ps.eFlags |= EF_NODRAW; -      G_UnlaggedClear( activator ); +  // Hive missile hasn't returned in HIVE_REPEAT seconds, forget about it +  if( self->timestamp < level.time ) +    self->active = qfalse; -      activator->client->ps.stats[ STAT_STATE ] |= SS_HOVELING; -      activator->client->hovel = self; -      self->builder = activator; -       -      // Cancel pending suicides -      activator->suicideTime = 0; +  // Find a target to attack +  if( self->spawned && !self->active && self->powered ) +  { +    int i, num, entityList[ MAX_GENTITIES ]; +    vec3_t mins, maxs, +           range = { HIVE_SENSE_RANGE, HIVE_SENSE_RANGE, HIVE_SENSE_RANGE }; -      VectorCopy( self->s.pos.trBase, hovelOrigin ); -      VectorMA( hovelOrigin, 128.0f, self->s.origin2, hovelOrigin ); +    VectorAdd( self->r.currentOrigin, range, maxs ); +    VectorSubtract( self->r.currentOrigin, range, mins ); -      VectorCopy( self->s.origin2, inverseNormal ); -      VectorInverse( inverseNormal ); -      vectoangles( inverseNormal, hovelAngles ); +    num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); -      VectorCopy( activator->s.pos.trBase, activator->client->hovelOrigin ); +    if( num == 0 ) +      return; -      G_SetOrigin( activator, hovelOrigin ); -      VectorCopy( hovelOrigin, activator->client->ps.origin ); -      G_SetClientViewAngle( activator, hovelAngles ); +    start = rand( ) / ( RAND_MAX / num + 1 ); +    for( i = start; i < num + start; i++ ) +    { +      if( AHive_CheckTarget( self, g_entities + entityList[ i % num ] ) ) +        return;      }    }  } -  /*  ================ -AHovel_Think +AHive_Pain -Think for alien hovel +pain function for Alien Hive  ================  */ -void AHovel_Think( gentity_t *self ) +void AHive_Pain( gentity_t *self, gentity_t *attacker, int damage )  { -  self->powered = G_IsOvermindBuilt( ); -  if( self->spawned ) -  { -    if( self->active ) -      G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); -    else -      G_SetIdleBuildableAnim( self, BANIM_IDLE1 ); -  } - -  G_CreepSlow( self ); - -  self->nextthink = level.time + 200; -} - -/* -================ -AHovel_Die - -Die for alien hovel -================ -*/ -void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) -{ -  vec3_t  dir; - -  buildHistory_t *new; -  new = G_Alloc( sizeof( buildHistory_t ) ); -  new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; -  new->ent = ( attacker && attacker->client ) ? attacker : NULL; -  if( new->ent ) -    new->name[ 0 ] = 0; -  else -    Q_strncpyz( new->name, "<world>", 8 ); -  new->buildable = self->s.modelindex; -  VectorCopy( self->s.pos.trBase, new->origin ); -  VectorCopy( self->s.angles, new->angles ); -  VectorCopy( self->s.origin2, new->origin2 ); -  VectorCopy( self->s.angles2, new->angles2 ); -  new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ? BF_TEAMKILLED : BF_DESTROYED; -  new->next = NULL; -  G_LogBuild( new ); - -  G_BuildableDeathSound( self ); - -  VectorCopy( self->s.origin2, dir ); - -  //do a bit of radius damage -  G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, -    self->splashRadius, self, self->splashMethodOfDeath, PTE_ALIENS ); - -  //pretty events and item cleanup -  self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed -  G_AddEvent( self, EV_ALIEN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); -  self->s.eFlags &= ~EF_FIRING; //prevent any firing effects -  self->timestamp = level.time; -  self->think = ASpawn_Melt; -  self->nextthink = level.time + 500; //wait .5 seconds before damaging others -  self->die = nullDieFunction; - -  //if the hovel is occupied free the occupant -  if( self->active ) -  { -    gentity_t *builder = self->builder; -    vec3_t    newOrigin; -    vec3_t    newAngles; - -    VectorCopy( self->s.angles, newAngles ); -    newAngles[ ROLL ] = 0; - -    VectorCopy( self->s.origin, newOrigin ); -    VectorMA( newOrigin, 1.0f, self->s.origin2, newOrigin ); - -    //prevent lerping -    builder->client->ps.eFlags ^= EF_TELEPORT_BIT; -    builder->client->ps.eFlags &= ~EF_NODRAW; -    G_UnlaggedClear( builder ); - -    G_SetOrigin( builder, newOrigin ); -    VectorCopy( newOrigin, builder->client->ps.origin ); -    G_SetClientViewAngle( builder, newAngles ); +  if( self->spawned && self->powered && !self->active ) +    AHive_CheckTarget( self, attacker ); -    //client leaves hovel -    builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; -  } - -  self->r.contents = 0;    //stop collisions... -  trap_LinkEntity( self ); //...requires a relink - -  if( attacker && attacker->client ) -  { -    if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -    { -      G_TeamCommand( PTE_ALIENS, -        va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", -          BG_FindHumanNameForBuildable( self->s.modelindex ),  -          attacker->client->pers.netname ) ); -      G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", -          BG_FindHumanNameForBuildable( self->s.modelindex ),  -          attacker->client->pers.netname );  -    } -    G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", -      attacker->client->ps.clientNum, self->s.modelindex, mod, -      attacker->client->pers.netname,  -      BG_FindNameForBuildable( self->s.modelindex ), -      modNames[ mod ] ); -  } +  G_SetBuildableAnim( self, BANIM_PAIN1, qfalse );  } - - -  //================================================================================== - -  /*  ================  ABooster_Touch @@ -1505,20 +1331,20 @@ void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace )  {    gclient_t *client = other->client; -  if( !self->spawned || self->health <= 0 ) -    return; - -  if( !G_FindOvermind( self ) ) +  if( !self->spawned || !self->powered || self->health <= 0 )      return;    if( !client )      return; -  if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +  if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )      return; +  if( other->flags & FL_NOTARGET ) +    return; // notarget cancels even beneficial effects? +    client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; -  client->lastBoostedTime = level.time; +  client->boostedTime = level.time;  } @@ -1540,11 +1366,11 @@ void ATrapper_FireOnEnemy( gentity_t *self, int firespeed, float range )    gentity_t *enemy = self->enemy;    vec3_t    dirToTarget;    vec3_t    halfAcceleration, thirdJerk; -  float     distanceToTarget = BG_FindRangeForBuildable( self->s.modelindex ); +  float     distanceToTarget = BG_Buildable( self->s.modelindex )->turretRange;    int       lowMsec = 0;    int       highMsec = (int)( (      ( ( distanceToTarget * LOCKBLOB_SPEED ) + -      ( distanceToTarget * BG_FindSpeedForClass( enemy->client->ps.stats[ STAT_PCLASS ] ) ) ) / +      ( distanceToTarget * BG_Class( enemy->client->ps.stats[ STAT_CLASS ] )->speed ) ) /      ( LOCKBLOB_SPEED * LOCKBLOB_SPEED ) ) * 1000.0f );    VectorScale( enemy->acceleration, 1.0f / 2.0f, halfAcceleration ); @@ -1603,9 +1429,9 @@ qboolean ATrapper_CheckTarget( gentity_t *self, gentity_t *target, int range )      return qfalse;    if( target->flags & FL_NOTARGET ) // is the target cheating?      return qfalse; -  if( target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) // one of us? +  if( target->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) // one of us?      return qfalse; -  if( target->client->sess.sessionTeam == TEAM_SPECTATOR ) // is the target alive? +  if( target->client->sess.spectatorState != SPECTATOR_NOT ) // is the target alive?      return qfalse;    if( target->health <= 0 ) // is the target still alive?      return qfalse; @@ -1638,10 +1464,14 @@ Used by ATrapper_Think to locate enemy gentities  void ATrapper_FindEnemy( gentity_t *ent, int range )  {    gentity_t *target; +  int       i; +  int       start; -  //iterate through entities -  for( target = g_entities; target < &g_entities[ level.num_entities ]; target++ ) +  // iterate through entities +  start = rand( ) / ( RAND_MAX / level.num_entities + 1 ); +  for( i = start; i < level.num_entities + start; i++ )    { +    target = g_entities + ( i % level.num_entities );      //if target is not valid keep searching      if( !ATrapper_CheckTarget( ent, target, range ) )        continue; @@ -1664,23 +1494,12 @@ think function for Alien Defense  */  void ATrapper_Think( gentity_t *self )  { -  int range =     BG_FindRangeForBuildable( self->s.modelindex ); -  int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex ); +  int range =     BG_Buildable( self->s.modelindex )->turretRange; +  int firespeed = BG_Buildable( self->s.modelindex )->turretFireSpeed; -  self->powered = G_IsOvermindBuilt( ); +  AGeneric_Think( self ); -  G_CreepSlow( self ); - -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); - -  //if there is no creep nearby die -  if( !G_FindCreep( self ) ) -  { -    G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); -    return; -  } - -  if( self->spawned && G_FindOvermind( self ) ) +  if( self->spawned && self->powered )    {      //if the current target is not valid find a new one      if( !ATrapper_CheckTarget( self, self->enemy, range ) ) @@ -1698,48 +1517,304 @@ void ATrapper_Think( gentity_t *self ) +  //================================================================================== +  /*  ================ -HRepeater_Think +G_SuicideIfNoPower -Think for human power repeater +Destroy human structures that have been unpowered too long  ================  */ -void HRepeater_Think( gentity_t *self ) +static qboolean G_SuicideIfNoPower( gentity_t *self ) +{ +  if( self->buildableTeam != TEAM_HUMANS ) +    return qfalse; + +  if( !self->powered ) +  { +    // if the power hasn't reached this buildable for some time, then destroy the buildable +    if( self->count == 0 ) +      self->count = level.time; +    else if( ( level.time - self->count ) >= HUMAN_BUILDABLE_INACTIVE_TIME ) +    { +      if( self->parentNode ) +        G_Damage( self, NULL, g_entities + self->parentNode->killedBy, +                  NULL, NULL, self->health, 0, MOD_NOCREEP ); +      else +        G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_NOCREEP ); +      return qtrue; +    } +  } +  else +    self->count = 0; + +  return qfalse; +} + +/* +================ +G_IdlePowerState + +Set buildable idle animation to match power state +================ +*/ +static void G_IdlePowerState( gentity_t *self ) +{ +  if( self->powered ) +  { +    if( self->s.torsoAnim == BANIM_IDLE3 ) +      G_SetIdleBuildableAnim( self, BG_Buildable( self->s.modelindex )->idleAnim ); +  } +  else +  { +    if( self->s.torsoAnim != BANIM_IDLE3 ) +      G_SetIdleBuildableAnim( self, BANIM_IDLE3 ); +  } +} + + + + +//================================================================================== + + + + +/* +================ +HSpawn_Disappear + +Called when a human spawn is destroyed before it is spawned +think function +================ +*/ +void HSpawn_Disappear( gentity_t *self ) +{ +  self->timestamp = level.time; +  G_QueueBuildPoints( self ); +  G_RewardAttackers( self ); + +  G_FreeEntity( self ); +} + + +/* +================ +HSpawn_blast + +Called when a human spawn explodes +think function +================ +*/ +void HSpawn_Blast( gentity_t *self ) +{ +  vec3_t  dir; + +  // we don't have a valid direction, so just point straight up +  dir[ 0 ] = dir[ 1 ] = 0; +  dir[ 2 ] = 1; + +  self->timestamp = level.time; + +  //do some radius damage +  G_RadiusDamage( self->s.pos.trBase, g_entities + self->killedBy, self->splashDamage, +    self->splashRadius, self, self->splashMethodOfDeath ); + +  // begin freeing build points +  G_QueueBuildPoints( self ); +  G_RewardAttackers( self ); +  // turn into an explosion +  self->s.eType = ET_EVENTS + EV_HUMAN_BUILDABLE_EXPLOSION; +  self->freeAfterEvent = qtrue; +  G_AddEvent( self, EV_HUMAN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); +} + + +/* +================ +HSpawn_die + +Called when a human spawn dies +================ +*/ +void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +{ +  G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); +  G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + +  self->die = nullDieFunction; +  self->killedBy = attacker - g_entities; +  self->powered = qfalse; //free up power +  self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + +  if( self->spawned ) +  { +    self->think = HSpawn_Blast; +    self->nextthink = level.time + HUMAN_DETONATION_DELAY; +  } +  else +  { +    self->think = HSpawn_Disappear; +    self->nextthink = level.time; //blast immediately +  } + +  G_RemoveRangeMarkerFrom( self ); +  G_LogDestruction( self, attacker, mod ); +} + +/* +================ +HSpawn_Think + +Think for human spawn +================ +*/ +void HSpawn_Think( gentity_t *self )  { -  int       i; -  qboolean  reactor = qfalse;    gentity_t *ent; +  // set parentNode +  self->powered = G_FindPower( self, qfalse ); + +  if( G_SuicideIfNoPower( self ) ) +    return; +    if( self->spawned )    { -    //iterate through entities -    for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) +    //only suicide if at rest +    if( self->s.groundEntityNum != ENTITYNUM_NONE )      { -      if( ent->s.eType != ET_BUILDABLE ) -        continue; +      if( ( ent = G_CheckSpawnPoint( self->s.number, self->r.currentOrigin, +              self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL ) +      { +        // If the thing blocking the spawn is a buildable, kill it.  +        // If it's part of the map, kill self.  +        if( ent->s.eType == ET_BUILDABLE ) +        { +          G_Damage( ent, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); +          G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); +        } +        else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) +        { +          G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); +          return; +        } -      if( ent->s.modelindex == BA_H_REACTOR && ent->spawned ) -        reactor = qtrue; +        if( ent->s.eType == ET_CORPSE ) +          G_FreeEntity( ent ); //quietly remove +      }      }    } -  if( G_NumberOfDependants( self ) == 0 ) +  self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink; +} + + + + +//================================================================================== + + + + +/* +================ +HRepeater_Die + +Called when a repeater dies +================ +*/ +static void HRepeater_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +{ +  G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); +  G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); + +  self->die = nullDieFunction; +  self->killedBy = attacker - g_entities; +  self->powered = qfalse; //free up power +  self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + +  if( self->spawned )    { -    //if no dependants for x seconds then disappear -    if( self->count < 0 ) -      self->count = level.time; -    else if( self->count > 0 && ( ( level.time - self->count ) > REPEATER_INACTIVE_TIME ) ) -      G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); +    self->think = HSpawn_Blast; +    self->nextthink = level.time + HUMAN_DETONATION_DELAY;    }    else -    self->count = -1; +  { +    self->think = HSpawn_Disappear; +    self->nextthink = level.time; //blast immediately +  } + +  G_RemoveRangeMarkerFrom( self ); +  G_LogDestruction( self, attacker, mod ); + +  if( self->usesBuildPointZone ) +  { +    buildPointZone_t *zone = &level.buildPointZones[self->buildPointZone]; + +    zone->active = qfalse; +    self->usesBuildPointZone = qfalse; +  } +} + +/* +================ +HRepeater_Think + +Think for human power repeater +================ +*/ +void HRepeater_Think( gentity_t *self ) +{ +  int               i; +  gentity_t         *powerEnt; +  buildPointZone_t  *zone; + +  self->powered = G_FindPower( self, qfalse ); + +  powerEnt = G_InPowerZone( self ); +  if( powerEnt != NULL ) +  { +    // If the repeater is inside of another power zone then suicide +    // Attribute death to whoever built the reactor if that's a human, +    // which will ensure that it does not queue the BP +    if( powerEnt->builtBy && powerEnt->builtBy->slot >= 0 ) +      G_Damage( self, NULL, g_entities + powerEnt->builtBy->slot, NULL, NULL, self->health, 0, MOD_SUICIDE ); +    else +      G_Damage( self, NULL, NULL, NULL, NULL, self->health, 0, MOD_SUICIDE ); +    return; +  } + +  G_IdlePowerState( self ); -  self->powered = reactor; +  // Initialise the zone once the repeater has spawned +  if( self->spawned && ( !self->usesBuildPointZone || !level.buildPointZones[ self->buildPointZone ].active ) ) +  { +    // See if a free zone exists +    for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ ) +    { +      zone = &level.buildPointZones[ i ]; + +      if( !zone->active ) +      { +        // Initialise the BP queue with no BP queued +        zone->queuedBuildPoints = 0; +        zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer; +        zone->nextQueueTime = level.time; +        zone->active = qtrue; + +        self->buildPointZone = zone - level.buildPointZones; +        self->usesBuildPointZone = qtrue; + +        break; +      } +    } +  }    self->nextthink = level.time + POWER_REFRESH_TIME;  } @@ -1753,19 +1828,13 @@ Use for human power repeater  */  void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator )  { -  if( self->health <= 0 ) +  if( self->health <= 0 || !self->spawned )      return; -  if( !self->spawned ) -    return; - -  if( other ) +  if( other && other->client )      G_GiveClientMaxAmmo( other, qtrue );  } - -#define DCC_ATTACK_PERIOD 10000 -  /*  ================  HReactor_Think @@ -1776,75 +1845,70 @@ Think function for Human Reactor  void HReactor_Think( gentity_t *self )  {    int       entityList[ MAX_GENTITIES ]; -  vec3_t    range = { REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE, REACTOR_ATTACK_RANGE }; +  vec3_t    range = { REACTOR_ATTACK_RANGE, +                      REACTOR_ATTACK_RANGE, +                      REACTOR_ATTACK_RANGE }; +  vec3_t    dccrange = { REACTOR_ATTACK_DCC_RANGE, +                         REACTOR_ATTACK_DCC_RANGE, +                         REACTOR_ATTACK_DCC_RANGE };    vec3_t    mins, maxs;    int       i, num;    gentity_t *enemy, *tent; -  VectorAdd( self->s.origin, range, maxs ); -  VectorSubtract( self->s.origin, range, mins ); +  if( self->dcc ) +  { +    VectorAdd( self->r.currentOrigin, dccrange, maxs ); +    VectorSubtract( self->r.currentOrigin, dccrange, mins ); +  } +  else +  { +    VectorAdd( self->r.currentOrigin, range, maxs ); +    VectorSubtract( self->r.currentOrigin, range, mins ); +  }    if( self->spawned && ( self->health > 0 ) )    { -    //do some damage +    qboolean fired = qfalse; + +    // Creates a tesla trail for every target      num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );      for( i = 0; i < num; i++ )      {        enemy = &g_entities[ entityList[ i ] ]; - +      if( !enemy->client || +          enemy->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS ) +        continue;        if( enemy->flags & FL_NOTARGET )          continue; -      if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -      { -        if( level.paused || enemy->client->pers.paused ) -          continue; -        self->timestamp = level.time; -        G_SelectiveRadiusDamage( self->s.pos.trBase, self, REACTOR_ATTACK_DAMAGE, -          REACTOR_ATTACK_RANGE, self, MOD_REACTOR, PTE_HUMANS ); - -        tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL ); - -        VectorCopy( self->s.pos.trBase, tent->s.origin2 ); - -        tent->s.generic1 = self->s.number; //src -        tent->s.clientNum = enemy->s.number; //dest -      } +      tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL ); +      tent->s.misc = self->s.number; //src +      tent->s.clientNum = enemy->s.number; //dest +      VectorCopy( self->s.pos.trBase, tent->s.origin2 ); +      fired = qtrue;      } -    //reactor under attack -    if( self->health < self->lastHealth && -        level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) ) +    // Actual damage is done by radius +    if( fired )      { -      level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD; -      G_BroadcastEvent( EV_DCC_ATTACK, 0 ); +      self->timestamp = level.time; +      if( self->dcc ) +        G_SelectiveRadiusDamage( self->s.pos.trBase, self, +                                 REACTOR_ATTACK_DCC_DAMAGE, +                                 REACTOR_ATTACK_DCC_RANGE, self, +                                 MOD_REACTOR, TEAM_HUMANS ); +      else +        G_SelectiveRadiusDamage( self->s.pos.trBase, self, +                                 REACTOR_ATTACK_DAMAGE, +                                 REACTOR_ATTACK_RANGE, self, +                                 MOD_REACTOR, TEAM_HUMANS );      } - -    self->lastHealth = self->health;    } -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); -} - -/* -================ -HReactor_Pain -================ -*/ - -void HReactor_Pain( gentity_t *self, gentity_t *attacker, int damage) -{ -	if (self->health <= 0) -		return; - -	if (!attacker || !attacker->client) -		return; - -	if (attacker->client->pers.teamSelection != PTE_HUMANS) -		return; - -	G_TeamCommand(PTE_HUMANS, va( "print \"Reactor ^3DAMAGED^7 by ^1TEAMMATE^7 %s^7\n\"", -	              attacker->client->pers.netname)); +  if( self->dcc ) +    self->nextthink = level.time + REACTOR_ATTACK_DCC_REPEAT; +  else +    self->nextthink = level.time + REACTOR_ATTACK_REPEAT;  }  //================================================================================== @@ -1863,7 +1927,7 @@ void HArmoury_Activate( gentity_t *self, gentity_t *other, gentity_t *activator    if( self->spawned )    {      //only humans can activate this -    if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) +    if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS )        return;      //if this is powered then call the armoury menu @@ -1886,7 +1950,9 @@ void HArmoury_Think( gentity_t *self )    //make sure we have power    self->nextthink = level.time + POWER_REFRESH_TIME; -  self->powered = G_FindPower( self ); +  self->powered = G_FindPower( self, qfalse ); + +  G_SuicideIfNoPower( self );  } @@ -1910,7 +1976,9 @@ void HDCC_Think( gentity_t *self )    //make sure we have power    self->nextthink = level.time + POWER_REFRESH_TIME; -  self->powered = G_FindPower( self ); +  self->powered = G_FindPower( self, qfalse ); + +  G_SuicideIfNoPower( self );  } @@ -1918,6 +1986,26 @@ void HDCC_Think( gentity_t *self )  //================================================================================== + + + +/* +================ +HMedistat_Die + +Die function for Human Medistation +================ +*/ +void HMedistat_Die( gentity_t *self, gentity_t *inflictor, +                    gentity_t *attacker, int damage, int mod ) +{ +  //clear target's healing flag +  if( self->enemy && self->enemy->client ) +    self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE; + +  HSpawn_Die( self, inflictor, attacker, damage, mod ); +} +  /*  ================  HMedistat_Think @@ -1933,15 +2021,22 @@ void HMedistat_Think( gentity_t *self )    gentity_t *player;    qboolean  occupied = qfalse; -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +  self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink; + +  self->powered = G_FindPower( self, qfalse ); +  if( G_SuicideIfNoPower( self ) ) +    return; +  G_IdlePowerState( self ); + +  //clear target's healing flag +  if( self->enemy && self->enemy->client ) +    self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE;    //make sure we have power -  if( !( self->powered = G_FindPower( self ) ) ) +  if( !self->powered )    {      if( self->active )      { -      G_SetBuildableAnim( self, BANIM_CONSTRUCT2, qtrue ); -      G_SetIdleBuildableAnim( self, BANIM_IDLE1 );        self->active = qfalse;        self->enemy = NULL;      } @@ -1952,8 +2047,8 @@ void HMedistat_Think( gentity_t *self )    if( self->spawned )    { -    VectorAdd( self->s.origin, self->r.maxs, maxs ); -    VectorAdd( self->s.origin, self->r.mins, mins ); +    VectorAdd( self->r.currentOrigin, self->r.maxs, maxs ); +    VectorAdd( self->r.currentOrigin, self->r.mins, mins );      mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ];      maxs[ 2 ] += 60; //player height @@ -1961,19 +2056,27 @@ void HMedistat_Think( gentity_t *self )      //if active use the healing idle      if( self->active )        G_SetIdleBuildableAnim( self, BANIM_IDLE2 ); - +            //check if a previous occupier is still here      num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );      for( i = 0; i < num; i++ )      {        player = &g_entities[ entityList[ i ] ]; -      if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +      if( player->flags & FL_NOTARGET ) +        continue; // notarget cancels even beneficial effects? +       +      //remove poison from everyone, not just the healed player +      if( player->client && player->client->ps.stats[ STAT_STATE ] & SS_POISONED ) +        player->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + +      if( self->enemy == player && player->client && +          player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && +          player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && +          PM_Alive( player->client->ps.pm_type ) )        { -        if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && -            player->client->ps.pm_type != PM_DEAD && -            self->enemy == player ) -          occupied = qtrue; +        occupied = qtrue; +        player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE;        }      } @@ -1986,13 +2089,14 @@ void HMedistat_Think( gentity_t *self )        {          player = &g_entities[ entityList[ i ] ]; -    if( player->flags & FL_NOTARGET ) -      continue; // notarget cancels even beneficial effects? +        if( player->flags & FL_NOTARGET ) +          continue; // notarget cancels even beneficial effects? -        if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +        if( player->client && player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )          { -          if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && -              player->client->ps.pm_type != PM_DEAD ) +          if( ( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] || +                player->client->ps.stats[ STAT_STAMINA ] < STAMINA_MAX ) && +              PM_Alive( player->client->ps.pm_type ) )            {              self->enemy = player; @@ -2001,6 +2105,7 @@ void HMedistat_Think( gentity_t *self )              {                G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );                self->active = qtrue; +              player->client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE;              }            }            else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) ) @@ -2017,26 +2122,25 @@ void HMedistat_Think( gentity_t *self )        self->active = qfalse;      } -    else if( self->enemy ) //heal! +    else if( self->enemy && self->enemy->client ) //heal!      { -      if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_POISONED ) -        self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; +      if( self->enemy->client->ps.stats[ STAT_STAMINA ] <  STAMINA_MAX ) +        self->enemy->client->ps.stats[ STAT_STAMINA ] += STAMINA_MEDISTAT_RESTORE; -      if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ) -        self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; +      if( self->enemy->client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX ) +        self->enemy->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; -      self->enemy->health++; +      if( self->enemy->health < self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] ) +      { +        self->enemy->health++; +        self->enemy->client->ps.stats[ STAT_HEALTH ] = self->enemy->health; +      }        //if they're completely healed, give them a medkit -      if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] && -          !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) ) -        BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats ); - -      // if completely healed, cancel retribution        if( self->enemy->health >= self->enemy->client->ps.stats[ STAT_MAX_HEALTH ] )        { -        for( i = 0; i < MAX_CLIENTS; i++ ) -          self->enemy->client->tkcredits[ i ] = 0; +        if( !BG_InventoryContainsUpgrade( UP_MEDKIT, self->enemy->client->ps.stats ) ) +          BG_AddUpgradeToInventory( UP_MEDKIT, self->enemy->client->ps.stats );        }      }    } @@ -2052,6 +2156,39 @@ void HMedistat_Think( gentity_t *self )  /*  ================ +HMGTurret_CheckTarget + +Used by HMGTurret_Think to check enemies for validity +================ +*/ +qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, +                                qboolean los_check ) +{ +  trace_t   tr; +  vec3_t    dir, end; + +  if( !target || target->health <= 0 || !target->client || +      target->client->pers.teamSelection != TEAM_ALIENS ) +    return qfalse; + +  if( target->flags & FL_NOTARGET ) +    return qfalse; +     +  if( !los_check ) +    return qtrue; + +  // Accept target if we can line-trace to it +  VectorSubtract( target->s.pos.trBase, self->s.pos.trBase, dir ); +  VectorNormalize( dir ); +  VectorMA( self->s.pos.trBase, MGTURRET_RANGE, dir, end ); +  trap_Trace( &tr, self->s.pos.trBase, NULL, NULL, end, +              self->s.number, MASK_SHOT ); +  return tr.entityNum == target - g_entities; +} + + +/* +================  HMGTurret_TrackEnemy  Used by HMGTurret_Think to track enemy location @@ -2062,27 +2199,8 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self )    vec3_t  dirToTarget, dttAdjusted, angleToTarget, angularDiff, xNormal;    vec3_t  refNormal = { 0.0f, 0.0f, 1.0f };    float   temp, rotAngle; -  float   accuracyTolerance, angularSpeed; - -  if( self->lev1Grabbed ) -  { -    //can't turn fast if grabbed -    accuracyTolerance = MGTURRET_GRAB_ACCURACYTOLERANCE; -    angularSpeed = MGTURRET_GRAB_ANGULARSPEED; -  } -  else if( self->dcced ) -  { -    accuracyTolerance = MGTURRET_DCC_ACCURACYTOLERANCE; -    angularSpeed = MGTURRET_DCC_ANGULARSPEED; -  } -  else -  { -    accuracyTolerance = MGTURRET_ACCURACYTOLERANCE; -    angularSpeed = MGTURRET_ANGULARSPEED; -  }    VectorSubtract( self->enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); -    VectorNormalize( dirToTarget );    CrossProduct( self->s.origin2, refNormal, xNormal ); @@ -2096,10 +2214,10 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self )    angularDiff[ YAW ] = AngleSubtract( self->s.angles2[ YAW ], angleToTarget[ YAW ] );    //if not pointing at our target then move accordingly -  if( angularDiff[ PITCH ] < (-accuracyTolerance) ) -    self->s.angles2[ PITCH ] += angularSpeed; -  else if( angularDiff[ PITCH ] > accuracyTolerance ) -    self->s.angles2[ PITCH ] -= angularSpeed; +  if( angularDiff[ PITCH ] < 0 && angularDiff[ PITCH ] < (-MGTURRET_ANGULARSPEED) ) +    self->s.angles2[ PITCH ] += MGTURRET_ANGULARSPEED; +  else if( angularDiff[ PITCH ] > 0 && angularDiff[ PITCH ] > MGTURRET_ANGULARSPEED ) +    self->s.angles2[ PITCH ] -= MGTURRET_ANGULARSPEED;    else      self->s.angles2[ PITCH ] = angleToTarget[ PITCH ]; @@ -2112,10 +2230,10 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self )      self->s.angles2[ PITCH ] = (-360) + MGTURRET_VERTICALCAP;    //if not pointing at our target then move accordingly -  if( angularDiff[ YAW ] < (-accuracyTolerance) ) -    self->s.angles2[ YAW ] += angularSpeed; -  else if( angularDiff[ YAW ] > accuracyTolerance ) -    self->s.angles2[ YAW ] -= angularSpeed; +  if( angularDiff[ YAW ] < 0 && angularDiff[ YAW ] < ( -MGTURRET_ANGULARSPEED ) ) +    self->s.angles2[ YAW ] += MGTURRET_ANGULARSPEED; +  else if( angularDiff[ YAW ] > 0 && angularDiff[ YAW ] > MGTURRET_ANGULARSPEED ) +    self->s.angles2[ YAW ] -= MGTURRET_ANGULARSPEED;    else      self->s.angles2[ YAW ] = angleToTarget[ YAW ]; @@ -2123,132 +2241,110 @@ qboolean HMGTurret_TrackEnemy( gentity_t *self )    RotatePointAroundVector( dirToTarget, xNormal, dttAdjusted, -rotAngle );    vectoangles( dirToTarget, self->turretAim ); -  //if pointing at our target return true -  if( fabs( angleToTarget[ YAW ] - self->s.angles2[ YAW ] ) <= accuracyTolerance && -      fabs( angleToTarget[ PITCH ] - self->s.angles2[ PITCH ] ) <= accuracyTolerance ) -    return qtrue; - -  return qfalse; +  //fire if target is within accuracy +  return ( fabs( angularDiff[ YAW ] ) - MGTURRET_ANGULARSPEED <= +           MGTURRET_ACCURACY_TO_FIRE ) && +         ( fabs( angularDiff[ PITCH ] ) - MGTURRET_ANGULARSPEED <= +           MGTURRET_ACCURACY_TO_FIRE );  }  /*  ================ -HMGTurret_CheckTarget +HMGTurret_FindEnemy -Used by HMGTurret_Think to check enemies for validity +Used by HMGTurret_Think to locate enemy gentities  ================  */ -qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ignorePainted ) +void HMGTurret_FindEnemy( gentity_t *self )  { -  trace_t   trace; -  gentity_t *traceEnt; - -  if( !target ) -    return qfalse; - -  if( target->flags & FL_NOTARGET ) -    return qfalse; - -  if( !target->client ) -    return qfalse; - -  if( level.paused || target->client->pers.paused ) -    return qfalse; - -  if( target->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) -    return qfalse; - -  if( target->health <= 0 ) -    return qfalse; - -  if( Distance( self->s.origin, target->s.pos.trBase ) > MGTURRET_RANGE ) -    return qfalse; - -  //some turret has already selected this target -  if( self->dcced && target->targeted && target->targeted->powered && !ignorePainted ) -    return qfalse; - -  trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT ); +  int       entityList[ MAX_GENTITIES ]; +  vec3_t    range; +  vec3_t    mins, maxs; +  int       i, num; +  gentity_t *target; +  int       start; -  traceEnt = &g_entities[ trace.entityNum ]; +  self->enemy = NULL; +     +  // Look for targets in a box around the turret +  VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE ); +  VectorAdd( self->r.currentOrigin, range, maxs ); +  VectorSubtract( self->r.currentOrigin, range, mins ); +  num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); -  if( !traceEnt->client ) -    return qfalse; +  if( num == 0 ) +    return; -  if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) -    return qfalse; +  start = rand( ) / ( RAND_MAX / num + 1 ); +  for( i = start; i < num + start ; i++ ) +  { +    target = &g_entities[ entityList[ i % num ] ]; +    if( !HMGTurret_CheckTarget( self, target, qtrue ) ) +      continue; -  return qtrue; +    self->enemy = target; +    return; +  }  } -  /*  ================ -HMGTurret_FindEnemy +HMGTurret_State -Used by HMGTurret_Think to locate enemy gentities +Raise or lower MG turret towards desired state  ================  */ -void HMGTurret_FindEnemy( gentity_t *self ) +enum { +  MGT_STATE_INACTIVE, +  MGT_STATE_DROP, +  MGT_STATE_RISE, +  MGT_STATE_ACTIVE +}; + +static qboolean HMGTurret_State( gentity_t *self, int state )  { -  int       entityList[ MAX_GENTITIES ]; -  vec3_t    range; -  vec3_t    mins, maxs; -  int       i, num; -  gentity_t *target; +  float angle; -  VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE ); -  VectorAdd( self->s.origin, range, maxs ); -  VectorSubtract( self->s.origin, range, mins ); +  if( self->waterlevel == state ) +    return qfalse; -  //find aliens -  num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); -  for( i = 0; i < num; i++ ) -  { -    target = &g_entities[ entityList[ i ] ]; +  angle = AngleNormalize180( self->s.angles2[ PITCH ] ); -    if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) +  if( state == MGT_STATE_INACTIVE ) +  { +    if( angle < MGTURRET_VERTICALCAP )      { -      //if target is not valid keep searching -      if( !HMGTurret_CheckTarget( self, target, qfalse ) ) -        continue; +      if( self->waterlevel != MGT_STATE_DROP ) +      { +        self->speed = 0.25f; +        self->waterlevel = MGT_STATE_DROP; +      } +      else +        self->speed *= 1.25f; -      //we found a target -      self->enemy = target; -      return; +      self->s.angles2[ PITCH ] =  +        MIN( MGTURRET_VERTICALCAP, angle + self->speed ); +      return qtrue;      } +    else +      self->waterlevel = MGT_STATE_INACTIVE;    } - -  if( self->dcced ) +  else if( state == MGT_STATE_ACTIVE )    { -    //check again, this time ignoring painted targets -    for( i = 0; i < num; i++ ) +    if( !self->enemy && angle > 0.0f )      { -      target = &g_entities[ entityList[ i ] ]; - -      if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -      { -        //if target is not valid keep searching -        if( !HMGTurret_CheckTarget( self, target, qtrue ) ) -          continue; - -        //we found a target -        self->enemy = target; -        return; -      } +      self->waterlevel = MGT_STATE_RISE; +      self->s.angles2[ PITCH ] = +        MAX( 0.0f, angle - MGTURRET_ANGULARSPEED * 0.5f );      } +    else +      self->waterlevel = MGT_STATE_ACTIVE;    } -  //couldn't find a target -  self->enemy = NULL; +  return qfalse;  } -#define MGTURRET_DROOPSCALE 0.5f -#define TURRET_REST_TIME  5000 -#define TURRET_REST_SPEED 3.0f -#define TURRET_REST_TOLERANCE 4.0f -  /*  ================  HMGTurret_Think @@ -2258,69 +2354,73 @@ Think function for MG turret  */  void HMGTurret_Think( gentity_t *self )  { -  int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex ); +  self->nextthink = level.time +  +                    BG_Buildable( self->s.modelindex )->nextthink; -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); - -  //used for client side muzzle flashes +  // Turn off client side muzzle flashes    self->s.eFlags &= ~EF_FIRING; -  //if not powered don't do anything and check again for power next think -  if( !( self->powered = G_FindPower( self ) ) ) -  { -    if( self->spawned ) -    { -      // unpowered turret barrel falls to bottom of range -      float droop; +  self->powered = G_FindPower( self, qfalse ); +  if( G_SuicideIfNoPower( self ) ) +    return; +  G_IdlePowerState( self ); -      droop = AngleNormalize180( self->s.angles2[ PITCH ] ); -      if( droop < MGTURRET_VERTICALCAP ) -      { -        droop +=  MGTURRET_DROOPSCALE; -        if( droop > MGTURRET_VERTICALCAP ) -          droop = MGTURRET_VERTICALCAP; -        self->s.angles2[ PITCH ] = droop; -        return; -      } -    } +  // If not powered or spawned don't do anything +  if( !self->powered ) +  { +    // if power loss drop turret +    if( self->spawned && +        HMGTurret_State( self, MGT_STATE_INACTIVE ) ) +      return;      self->nextthink = level.time + POWER_REFRESH_TIME;      return;    } - -  if( self->spawned ) +  if( !self->spawned ) +    return; +     +  // If the current target is not valid find a new enemy +  if( !HMGTurret_CheckTarget( self, self->enemy, qtrue ) )    { -    //find a dcc for self -    self->dcced = G_FindDCC( self ); - -    //if the current target is not valid find a new one -    if( !HMGTurret_CheckTarget( self, self->enemy, qfalse ) ) -    { -      if( self->enemy ) -        self->enemy->targeted = NULL; - -      HMGTurret_FindEnemy( self ); -    } +    self->active = qfalse; +    self->turretSpinupTime = -1; +    HMGTurret_FindEnemy( self ); +  } +  // if newly powered raise turret +  HMGTurret_State( self, MGT_STATE_ACTIVE ); +  if( !self->enemy ) +    return; -    //if a new target cannot be found don't do anything -    if( !self->enemy ) -      return; +  // Track until we can hit the target +  if( !HMGTurret_TrackEnemy( self ) ) +  { +    self->active = qfalse; +    self->turretSpinupTime = -1; +    return; +  } -    self->enemy->targeted = self; +  // Update spin state +  if( !self->active && self->timestamp < level.time ) +  { +    self->active = qtrue; -    //if we are pointing at our target and we can fire shoot it -    if( HMGTurret_TrackEnemy( self ) && ( self->count < level.time ) ) -    { -      //fire at target -      FireWeapon( self ); +    self->turretSpinupTime = level.time + MGTURRET_SPINUP_TIME; +    G_AddEvent( self, EV_MGTURRET_SPINUP, 0 ); +  } -      self->s.eFlags |= EF_FIRING; -      G_AddEvent( self, EV_FIRE_WEAPON, 0 ); -      G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); +  // Not firing or haven't spun up yet +  if( !self->active || self->turretSpinupTime > level.time ) +    return; +     +  // Fire repeat delay +  if( self->timestamp > level.time ) +    return; -      self->count = level.time + firespeed; -    } -  } +  FireWeapon( self ); +  self->s.eFlags |= EF_FIRING; +  self->timestamp = level.time + BG_Buildable( self->s.modelindex )->turretFireSpeed; +  G_AddEvent( self, EV_FIRE_WEAPON, 0 ); +  G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse );  } @@ -2340,54 +2440,51 @@ Think function for Tesla Generator  */  void HTeslaGen_Think( gentity_t *self )  { -  int       entityList[ MAX_GENTITIES ]; -  vec3_t    range; -  vec3_t    mins, maxs; -  vec3_t    dir; -  int       i, num; -  gentity_t *enemy; +  self->nextthink = level.time + BG_Buildable( self->s.modelindex )->nextthink; -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +  self->powered = G_FindPower( self, qfalse ); +  if( G_SuicideIfNoPower( self ) ) +    return; +  G_IdlePowerState( self );    //if not powered don't do anything and check again for power next think -  if( !( self->powered = G_FindPower( self ) ) || !( self->dcced = G_FindDCC( self ) ) ) +  if( !self->powered )    {      self->s.eFlags &= ~EF_FIRING;      self->nextthink = level.time + POWER_REFRESH_TIME;      return;    } -  if( self->spawned && self->count < level.time ) +  if( self->spawned && self->timestamp < level.time )    { -    //used to mark client side effects +    vec3_t origin, range, mins, maxs; +    int entityList[ MAX_GENTITIES ], i, num; + +    // Communicates firing state to client      self->s.eFlags &= ~EF_FIRING; +    // Move the muzzle from the entity origin up a bit to fire over turrets +    VectorMA( self->r.currentOrigin, self->r.maxs[ 2 ], self->s.origin2, origin ); +      VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE ); -    VectorAdd( self->s.origin, range, maxs ); -    VectorSubtract( self->s.origin, range, mins ); +    VectorAdd( origin, range, maxs ); +    VectorSubtract( origin, range, mins ); -    //find aliens +    // Attack nearby Aliens      num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );      for( i = 0; i < num; i++ )      { -      enemy = &g_entities[ entityList[ i ] ]; +      self->enemy = &g_entities[ entityList[ i ] ]; -      if( enemy->flags & FL_NOTARGET ) +      if( self->enemy->flags & FL_NOTARGET )          continue; -      if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && -          enemy->health > 0 && -          !level.paused && !enemy->client->pers.paused && -          Distance( enemy->s.pos.trBase, self->s.pos.trBase ) <= TESLAGEN_RANGE ) -      { -        VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dir ); -        VectorNormalize( dir ); -        vectoangles( dir, self->turretAim ); - -        //fire at target +      if( self->enemy->client && self->enemy->health > 0 && +          self->enemy->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS && +          Distance( origin, self->enemy->s.pos.trBase ) <= TESLAGEN_RANGE )          FireWeapon( self ); -      }      } +    self->enemy = NULL;      if( self->s.eFlags & EF_FIRING )      { @@ -2396,7 +2493,7 @@ void HTeslaGen_Think( gentity_t *self )        //doesn't really need an anim        //G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); -      self->count = level.time + TESLAGEN_REPEAT; +      self->timestamp = level.time + TESLAGEN_REPEAT;      }    }  } @@ -2410,228 +2507,125 @@ void HTeslaGen_Think( gentity_t *self )  /* -================ -HSpawn_Disappear - -Called when a human spawn is destroyed before it is spawned -think function -================ +============ +G_QueueValue +============  */ -void HSpawn_Disappear( gentity_t *self ) + +static int G_QueueValue( gentity_t *self )  { -  vec3_t  dir; +  int       i; +  int       damageTotal = 0; +  int       queuePoints; +  double    queueFraction = 0; -  // we don't have a valid direction, so just point straight up -  dir[ 0 ] = dir[ 1 ] = 0; -  dir[ 2 ] = 1; +  for( i = 0; i < level.maxclients; i++ ) +  { +    gentity_t *player = g_entities + i; -  self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed -  self->timestamp = level.time; +    damageTotal += self->credits[ i ]; + +    if( self->buildableTeam != player->client->pers.teamSelection ) +      queueFraction += (double) self->credits[ i ]; +  } -  self->think = freeBuildable; -  self->nextthink = level.time + 100; +  if( damageTotal > 0 ) +    queueFraction = queueFraction / (double) damageTotal; +  else // all damage was done by nonclients, so queue everything +    queueFraction = 1.0; -  self->r.contents = 0;    //stop collisions... -  trap_LinkEntity( self ); //...requires a relink +  queuePoints = (int) ( queueFraction * (double) BG_Buildable( self->s.modelindex )->buildPoints ); +  return queuePoints;  } -  /* -================ -HSpawn_blast - -Called when a human spawn explodes -think function -================ +============ +G_QueueBuildPoints +============  */ -void HSpawn_Blast( gentity_t *self ) +void G_QueueBuildPoints( gentity_t *self )  { -  vec3_t  dir; +  gentity_t *powerEntity; +  int       queuePoints; -  // we don't have a valid direction, so just point straight up -  dir[ 0 ] = dir[ 1 ] = 0; -  dir[ 2 ] = 1; +  queuePoints = G_QueueValue( self ); -  self->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed -  G_AddEvent( self, EV_HUMAN_BUILDABLE_EXPLOSION, DirToByte( dir ) ); -  self->timestamp = level.time; +  if( !queuePoints ) +    return; +       +  switch( self->buildableTeam ) +  { +    default: +    case TEAM_NONE: +      return; -  //do some radius damage -  G_RadiusDamage( self->s.pos.trBase, self, self->splashDamage, -    self->splashRadius, self, 0, self->splashMethodOfDeath ); +    case TEAM_ALIENS: +      if( !level.alienBuildPointQueue ) +        level.alienNextQueueTime = level.time + g_alienBuildQueueTime.integer; -  self->think = freeBuildable; -  self->nextthink = level.time + 100; +      level.alienBuildPointQueue += queuePoints; +      break; -  self->r.contents = 0;    //stop collisions... -  trap_LinkEntity( self ); //...requires a relink -} +    case TEAM_HUMANS: +      powerEntity = G_PowerEntityForEntity( self ); +      if( powerEntity ) +      { +        int nqt; +        switch( powerEntity->s.modelindex ) +        { +          case BA_H_REACTOR: +            nqt = G_NextQueueTime( level.humanBuildPointQueue, +                                   g_humanBuildPoints.integer, +                                   g_humanBuildQueueTime.integer ); +            if( !level.humanBuildPointQueue || +                level.time + nqt < level.humanNextQueueTime ) +              level.humanNextQueueTime = level.time + nqt; + +            level.humanBuildPointQueue += queuePoints; +            break; -/* -================ -HSpawn_die +          case BA_H_REPEATER: +            if( powerEntity->usesBuildPointZone && +                level.buildPointZones[ powerEntity->buildPointZone ].active ) +            { +              buildPointZone_t *zone = &level.buildPointZones[ powerEntity->buildPointZone ]; -Called when a human spawn dies -================ -*/ -void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) -{ -  buildHistory_t *new; -  new = G_Alloc( sizeof( buildHistory_t ) ); -  new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; -  new->ent = ( attacker && attacker->client ) ? attacker : NULL; -  if( new->ent ) -    new->name[ 0 ] = 0; -  else -    Q_strncpyz( new->name, "<world>", 8 ); -  new->buildable = self->s.modelindex; -  VectorCopy( self->s.pos.trBase, new->origin ); -  VectorCopy( self->s.angles, new->angles ); -  VectorCopy( self->s.origin2, new->origin2 ); -  VectorCopy( self->s.angles2, new->angles2 ); -  new->fate = ( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) ? BF_TEAMKILLED : BF_DESTROYED; -  new->next = NULL; -  G_LogBuild( new ); - -  G_BuildableDeathSound( self ); - -  //pretty events and cleanup -  G_SetBuildableAnim( self, BANIM_DESTROY1, qtrue ); -  G_SetIdleBuildableAnim( self, BANIM_DESTROYED ); +              nqt = G_NextQueueTime( zone->queuedBuildPoints, +                                     zone->totalBuildPoints, +                                     g_humanRepeaterBuildQueueTime.integer ); -  self->die = nullDieFunction; -  self->powered = qfalse; //free up power -  //prevent any firing effects and cancel structure protection -  self->s.eFlags &= ~( EF_FIRING | EF_DBUILDER ); +              if( !zone->queuedBuildPoints || +                  level.time + nqt < zone->nextQueueTime ) +                zone->nextQueueTime = level.time + nqt; -  if( self->spawned ) -  { -    self->think = HSpawn_Blast; -    self->nextthink = level.time + HUMAN_DETONATION_DELAY; -  } -  else -  { -    self->think = HSpawn_Disappear; -    self->nextthink = level.time; //blast immediately -  } +              zone->queuedBuildPoints += queuePoints; +            } +            break; -  if( attacker && attacker->client ) -  { -    if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -    { -      if( self->s.modelindex == BA_H_REACTOR ) -        G_AddCreditToClient( attacker->client, REACTOR_VALUE, qtrue ); -      else if( self->s.modelindex == BA_H_SPAWN ) -        G_AddCreditToClient( attacker->client, HSPAWN_VALUE, qtrue ); -    } -    else -    { -      G_TeamCommand( PTE_HUMANS, -        va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", -          BG_FindHumanNameForBuildable( self->s.modelindex ),  -          attacker->client->pers.netname ) ); -      G_LogOnlyPrintf("%s ^3DESTROYED^7 by teammate %s^7\n", -          BG_FindHumanNameForBuildable( self->s.modelindex ),  -          attacker->client->pers.netname );  -    } -    G_LogPrintf( "Decon: %i %i %i: %s^7 destroyed %s by %s\n", -      attacker->client->ps.clientNum, self->s.modelindex, mod, -      attacker->client->pers.netname,  -      BG_FindNameForBuildable( self->s.modelindex ), -      modNames[ mod ] ); +          default: +            break; +        } +      }    }  }  /* -================ -HSpawn_Think - -Think for human spawn -================ +============ +G_NextQueueTime +============  */ -void HSpawn_Think( gentity_t *self ) +int G_NextQueueTime( int queuedBP, int totalBP, int queueBaseRate )  { -  gentity_t *ent; - -  // spawns work without power -  self->powered = qtrue; - -  if( self->spawned ) -  { -    //only suicide if at rest -    if( self->s.groundEntityNum ) -    { -      if( ( ent = G_CheckSpawnPoint( self->s.number, self->s.origin, -              self->s.origin2, BA_H_SPAWN, NULL ) ) != NULL ) -      { -        // If the thing blocking the spawn is a buildable, kill it.  -        // If it's part of the map, kill self.  -        if( ent->s.eType == ET_BUILDABLE ) -        { -          G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); -          G_SetBuildableAnim( self, BANIM_SPAWN1, qtrue ); -        } -        else if( ent->s.number == ENTITYNUM_WORLD || ent->s.eType == ET_MOVER ) -        { -          G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); -          return; -        } -        else if( g_antiSpawnBlock.integer && ent->client &&  -                 ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -        { -          //spawnblock protection -          if( self->spawnBlockTime && level.time - self->spawnBlockTime > 10000 ) -          { -            //five seconds of countermeasures and we're still blocked -            //time for something more drastic -            G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_TRIGGER_HURT ); -            self->spawnBlockTime += 2000; -            //inappropriate MOD but prints an apt obituary -          } -          else if( self->spawnBlockTime && level.time - self->spawnBlockTime > 5000 ) -            //five seconds of blocked by client and... -          { -            //random direction -            vec3_t velocity; -            velocity[0] = crandom() * g_antiSpawnBlock.integer; -            velocity[1] = crandom() * g_antiSpawnBlock.integer; -            velocity[2] = g_antiSpawnBlock.integer; -                 -            VectorAdd( ent->client->ps.velocity, velocity, ent->client->ps.velocity ); -            trap_SendServerCommand( ent-g_entities, "cp \"Don't spawn block!\"" ); -          } -          else if( !self->spawnBlockTime ) -            self->spawnBlockTime = level.time; -        } +  float fractionQueued; -        if( ent->s.eType == ET_CORPSE ) -          G_FreeEntity( ent ); //quietly remove -      } -      else -        self->spawnBlockTime = 0; -    } +  if( totalBP == 0 ) +    return 0; -    //spawn under attack -    if( self->health < self->lastHealth && -        level.time > level.humanBaseAttackTimer && G_IsDCCBuilt( ) ) -    { -      level.humanBaseAttackTimer = level.time + DCC_ATTACK_PERIOD; -      G_BroadcastEvent( EV_DCC_ATTACK, 0 ); -    } - -    self->lastHealth = self->health; -  } - -  self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +  fractionQueued = queuedBP / (float)totalBP; +  return ( 1.0f - fractionQueued ) * queueBaseRate;  } - - - -//================================================================================== - -  /*  ============  G_BuildableTouchTriggers @@ -2653,18 +2647,18 @@ void G_BuildableTouchTriggers( gentity_t *ent )    if( ent->health <= 0 )      return; -  BG_FindBBoxForBuildable( ent->s.modelindex, bmins, bmaxs ); +  BG_BuildableBoundingBox( ent->s.modelindex, bmins, bmaxs ); -  VectorAdd( ent->s.origin, bmins, mins ); -  VectorAdd( ent->s.origin, bmaxs, maxs ); +  VectorAdd( ent->r.currentOrigin, bmins, mins ); +  VectorAdd( ent->r.currentOrigin, bmaxs, maxs );    VectorSubtract( mins, range, mins );    VectorAdd( maxs, range, maxs );    num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); -  VectorAdd( ent->s.origin, bmins, mins ); -  VectorAdd( ent->s.origin, bmaxs, maxs ); +  VectorAdd( ent->r.currentOrigin, bmins, mins ); +  VectorAdd( ent->r.currentOrigin, bmaxs, maxs );    for( i = 0; i < num; i++ )    { @@ -2700,66 +2694,85 @@ General think function for buildables  */  void G_BuildableThink( gentity_t *ent, int msec )  { -  int bHealth = BG_FindHealthForBuildable( ent->s.modelindex ); -  int bRegen = BG_FindRegenRateForBuildable( ent->s.modelindex ); -  int bTime = BG_FindBuildTimeForBuildable( ent->s.modelindex ); - -  //pack health, power and dcc +  int maxHealth = BG_Buildable( ent->s.modelindex )->health; +  int regenRate = BG_Buildable( ent->s.modelindex )->regenRate; +  int buildTime = BG_Buildable( ent->s.modelindex )->buildTime;    //toggle spawned flag for buildables -  if( !ent->spawned && ent->health > 0 ) +  if( !ent->spawned && ent->health > 0 && !level.pausedTime )    { -    if( ent->buildTime + bTime < level.time ) +    if( ent->buildTime + buildTime < level.time ) +    {        ent->spawned = qtrue; +      if( ent->s.modelindex == BA_A_OVERMIND ) +      { +        G_TeamCommand( TEAM_ALIENS, "cp \"The Overmind has awakened!\"" ); +      } +    }    } -  ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_MASK ); - -  if( ent->s.generic1 < 0 ) -    ent->s.generic1 = 0; - -  if( ent->powered ) -    ent->s.generic1 |= B_POWERED_TOGGLEBIT; - -  if( ent->dcced ) -    ent->s.generic1 |= B_DCCED_TOGGLEBIT; - -  if( ent->spawned ) -    ent->s.generic1 |= B_SPAWNED_TOGGLEBIT; - -  if( ent->deconstruct ) -    ent->s.generic1 |= B_MARKED_TOGGLEBIT; - +  // Timer actions    ent->time1000 += msec; -    if( ent->time1000 >= 1000 )    {      ent->time1000 -= 1000; -    if( !ent->spawned && ent->health > 0 ) -      ent->health += (int)( ceil( (float)bHealth / (float)( bTime * 0.001 ) ) ); -    else if( ent->biteam == BIT_ALIENS && ent->health > 0 && ent->health < bHealth && -        bRegen && ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) -      ent->health += bRegen; +    if( ent->health > 0 && ent->health < maxHealth ) +    { +      if( !ent->spawned ) +        ent->health += (int)( ceil( (float)maxHealth / (float)( buildTime * 0.001f ) ) ); +      else +      { +        if( ent->buildableTeam == TEAM_ALIENS && regenRate && +          ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) +        { +          ent->health += regenRate; +        } +        else if( ent->buildableTeam == TEAM_HUMANS && ent->dcc && +          ( ent->lastDamageTime + HUMAN_REGEN_DAMAGE_TIME ) < level.time ) +        { +          ent->health += DC_HEALRATE * ent->dcc; +        } +      } -    if( ent->health > bHealth ) -      ent->health = bHealth; +      if( ent->health >= maxHealth ) +      { +        int i; +        ent->health = maxHealth; +        for( i = 0; i < MAX_CLIENTS; i++ ) +          ent->credits[ i ] = 0; +      } +    }    } -  if( ent->lev1Grabbed && ent->lev1GrabTime + LEVEL1_GRAB_TIME < level.time ) -    ent->lev1Grabbed = qfalse; -    if( ent->clientSpawnTime > 0 )      ent->clientSpawnTime -= msec;    if( ent->clientSpawnTime < 0 )      ent->clientSpawnTime = 0; -  //check if this buildable is touching any triggers +  ent->dcc = ( ent->buildableTeam != TEAM_HUMANS ) ? 0 : G_FindDCC( ent ); + +  // Set health +  ent->s.misc = MAX( ent->health, 0 ); + +  // Set flags +  ent->s.eFlags &= ~( EF_B_POWERED | EF_B_SPAWNED | EF_B_MARKED ); +  if( ent->powered ) +    ent->s.eFlags |= EF_B_POWERED; + +  if( ent->spawned ) +    ent->s.eFlags |= EF_B_SPAWNED; + +  if( ent->deconstruct ) +    ent->s.eFlags |= EF_B_MARKED; + +  // Check if this buildable is touching any triggers    G_BuildableTouchTriggers( ent ); -  //fall back on normal physics routines -  G_Physics( ent, msec ); +  // Fall back on normal physics routines +  if( msec != 0 ) +    G_Physics( ent, msec );  } @@ -2790,7 +2803,7 @@ qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable )      if( ent->s.eType != ET_BUILDABLE )        continue; -    if( ent->biteam == BIT_HUMANS && !ent->powered ) +    if( ent->buildableTeam == TEAM_HUMANS && !ent->powered )        continue;      if( ent->s.modelindex == buildable && ent->spawned ) @@ -2800,20 +2813,29 @@ qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable )    return qfalse;  } -static qboolean G_BoundsIntersect(const vec3_t mins, const vec3_t maxs, -                                  const vec3_t mins2, const vec3_t maxs2) +/* +================ +G_FindBuildable + +Finds a buildable of the specified type +================ +*/ +static gentity_t *G_FindBuildable( buildable_t buildable )  { -  if ( maxs[0] < mins2[0] || -       maxs[1] < mins2[1] || -       maxs[2] < mins2[2] || -       mins[0] > maxs2[0] || -       mins[1] > maxs2[1] || -       mins[2] > maxs2[2]) +  int       i; +  gentity_t *ent; + +  for( i = MAX_CLIENTS, ent = g_entities + i; +       i < level.num_entities; i++, ent++ )    { -    return qfalse; +    if( ent->s.eType != ET_BUILDABLE ) +      continue; + +    if( ent->s.modelindex == buildable && !( ent->s.eFlags & EF_DEAD ) ) +      return ent;    } -  return qtrue; +  return NULL;  }  /* @@ -2829,15 +2851,15 @@ static qboolean G_BuildablesIntersect( buildable_t a, vec3_t originA,    vec3_t minsA, maxsA;    vec3_t minsB, maxsB; -  BG_FindBBoxForBuildable( a, minsA, maxsA ); +  BG_BuildableBoundingBox( a, minsA, maxsA );    VectorAdd( minsA, originA, minsA );    VectorAdd( maxsA, originA, maxsA ); -  BG_FindBBoxForBuildable( b, minsB, maxsB ); +  BG_BuildableBoundingBox( b, minsB, maxsB );    VectorAdd( minsB, originB, minsB );    VectorAdd( maxsB, originB, maxsB ); -  return G_BoundsIntersect( minsA, maxsA, minsB, maxsB ); +  return BoundsIntersect( minsA, maxsA, minsB, maxsB );  }  /* @@ -2860,7 +2882,6 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b )      BA_A_TRAPPER,      BA_A_HIVE,      BA_A_BOOSTER, -    BA_A_HOVEL,      BA_A_SPAWN,      BA_A_OVERMIND, @@ -2882,30 +2903,55 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b )    buildableA = *(gentity_t **)a;    buildableB = *(gentity_t **)b; -    // Prefer the one that collides with the thing we're building    aMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin, -      buildableA->s.modelindex, buildableA->s.origin ); +      buildableA->s.modelindex, buildableA->r.currentOrigin );    bMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin, -      buildableB->s.modelindex, buildableB->s.origin ); +      buildableB->s.modelindex, buildableB->r.currentOrigin );    if( aMatches && !bMatches )      return -1; -  if( !aMatches && bMatches ) +  else if( !aMatches && bMatches )      return 1; +  // If the only spawn is marked, prefer it last +  if( cmpBuildable == BA_A_SPAWN || cmpBuildable == BA_H_SPAWN ) +  { +    if( ( buildableA->s.modelindex == BA_A_SPAWN && level.numAlienSpawns == 1 ) || +        ( buildableA->s.modelindex == BA_H_SPAWN && level.numHumanSpawns == 1 ) ) +      return 1; + +    if( ( buildableB->s.modelindex == BA_A_SPAWN && level.numAlienSpawns == 1 ) || +        ( buildableB->s.modelindex == BA_H_SPAWN && level.numHumanSpawns == 1 ) ) +      return -1; +  } +    // If one matches the thing we're building, prefer it    aMatches = ( buildableA->s.modelindex == cmpBuildable );    bMatches = ( buildableB->s.modelindex == cmpBuildable );    if( aMatches && !bMatches )      return -1; -  if( !aMatches && bMatches ) +  else if( !aMatches && bMatches )      return 1; -  // If they're the same type then pick the one marked earliest +  // They're the same type    if( buildableA->s.modelindex == buildableB->s.modelindex ) +  { +    gentity_t *powerEntity = G_PowerEntityForPoint( cmpOrigin ); + +    // Prefer the entity that is providing power for this point +    aMatches = ( powerEntity == buildableA ); +    bMatches = ( powerEntity == buildableB ); +    if( aMatches && !bMatches ) +      return -1; +    else if( !aMatches && bMatches ) +      return 1; + +    // Pick the one marked earliest      return buildableA->deconstructTime - buildableB->deconstructTime; +  } -  for( i = 0; i < sizeof( precedence ) / sizeof( precedence[ 0 ] ); i++ ) +  // Resort to preference list +  for( i = 0; i < ARRAY_LEN( precedence ); i++ )    {      if( buildableA->s.modelindex == precedence[ i ] )        aPrecedence = i; @@ -2919,17 +2965,49 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b )  /*  =============== +G_ClearDeconMarks + +Remove decon mark from all buildables +=============== +*/ +void G_ClearDeconMarks( void ) +{ +  int       i; +  gentity_t *ent; + +  for( i = MAX_CLIENTS, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) +  { +    if( !ent->inuse ) +      continue; + +    if( ent->s.eType != ET_BUILDABLE ) +      continue; + +    ent->deconstruct = qfalse; +  } +} + +/* +===============  G_FreeMarkedBuildables  Free up build points for a team by deconstructing marked buildables  ===============  */ -void G_FreeMarkedBuildables( void ) +void G_FreeMarkedBuildables( gentity_t *deconner, char *readable, int rsize, +  char *nums, int nsize )  {    int       i; +  int       bNum; +  int       listItems = 0; +  int       totalListItems = 0;    gentity_t *ent; -  buildHistory_t *new, *last; -  last = level.buildHistory; +  int       removalCounts[ BA_NUM_BUILDABLES ] = {0}; + +  if( readable && rsize ) +    readable[ 0 ] = '\0'; +  if( nums && nsize ) +    nums[ 0 ] = '\0';    if( !g_markDeconstruct.integer )      return; // Not enabled, can't deconstruct anything @@ -2937,24 +3015,43 @@ void G_FreeMarkedBuildables( void )    for( i = 0; i < level.numBuildablesForRemoval; i++ )    {      ent = level.markedBuildables[ i ]; +    bNum = BG_Buildable( ent->s.modelindex )->number; + +    if( removalCounts[ bNum ] == 0 ) +      totalListItems++; -    new = G_Alloc( sizeof( buildHistory_t ) ); -    new->ID = -1; -    new->ent = NULL; -    Q_strncpyz( new->name, "<markdecon>", 12 ); -    new->buildable = ent->s.modelindex; -    VectorCopy( ent->s.pos.trBase, new->origin ); -    VectorCopy( ent->s.angles, new->angles ); -    VectorCopy( ent->s.origin2, new->origin2 ); -    VectorCopy( ent->s.angles2, new->angles2 ); -    new->fate = BF_DECONNED; -    new->next = NULL; -    new->marked = NULL; - -    last = last->marked = new; +    G_Damage( ent, NULL, deconner, NULL, NULL, ent->health, 0, MOD_REPLACE ); +    removalCounts[ bNum ]++; + +    if( nums ) +      Q_strcat( nums, nsize, va( " %d", (int)( ent - g_entities ) ) ); + +    G_RemoveRangeMarkerFrom( ent );      G_FreeEntity( ent );    } + +  if( !readable ) +    return; + +  for( i = 0; i < BA_NUM_BUILDABLES; i++ ) +  { +    if( removalCounts[ i ] ) +    { +      if( listItems ) +      { +        if( listItems == ( totalListItems - 1 ) ) +          Q_strcat( readable, rsize,  va( "%s and ", +            ( totalListItems > 2 ) ? "," : "" ) ); +        else +          Q_strcat( readable, rsize, ", " ); +      } +      Q_strcat( readable, rsize, va( "%s", BG_Buildable( i )->humanName ) ); +      if( removalCounts[ i ] > 1 ) +        Q_strcat( readable, rsize, va( " (%dx)", removalCounts[ i ] ) ); +      listItems++; +    } +  }  }  /* @@ -2962,20 +3059,20 @@ void G_FreeMarkedBuildables( void )  G_SufficientBPAvailable  Determine if enough build points can be released for the buildable -and list the buildables that much be destroyed if this is the case +and list the buildables that must be destroyed if this is the case  ===============  */  static itemBuildError_t G_SufficientBPAvailable( buildable_t     buildable,                                                   vec3_t          origin )  { -  int       i; -  int       numBuildables = 0; -  int       pointsYielded = 0; -  gentity_t *ent; -  qboolean  unique = BG_FindUniqueTestForBuildable( buildable ); -  int       remainingBP, remainingSpawns; -  int               team; -  int               buildPoints, buildpointsneeded; +  int               i; +  int               numBuildables = 0; +  int               numRequired = 0; +  int               pointsYielded = 0; +  gentity_t         *ent; +  team_t            team = BG_Buildable( buildable )->team; +  int               buildPoints = BG_Buildable( buildable )->buildPoints; +  int               remainingBP, remainingSpawns;    qboolean          collision = qfalse;    int               collisionCount = 0;    qboolean          repeaterInRange = qfalse; @@ -2984,29 +3081,35 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t     buildable,    buildable_t       spawn;    buildable_t       core;    int               spawnCount = 0; +  qboolean          changed = qtrue;    level.numBuildablesForRemoval = 0; -  buildPoints = buildpointsneeded = BG_FindBuildPointsForBuildable( buildable ); -  team = BG_FindTeamForBuildable( buildable ); -  if( team == BIT_ALIENS ) +  if( team == TEAM_ALIENS )    { -    remainingBP     = level.alienBuildPoints; +    remainingBP     = G_GetBuildPoints( origin, team );      remainingSpawns = level.numAlienSpawns; -    bpError         = IBE_NOASSERT; +    bpError         = IBE_NOALIENBP;      spawn           = BA_A_SPAWN;      core            = BA_A_OVERMIND;    } -  else if( team == BIT_HUMANS ) +  else if( team == TEAM_HUMANS )    { -    remainingBP     = level.humanBuildPoints; +    if( buildable == BA_H_REACTOR || buildable == BA_H_REPEATER ) +      remainingBP   = level.humanBuildPoints; +    else +      remainingBP   = G_GetBuildPoints( origin, team ); +      remainingSpawns = level.numHumanSpawns; -    bpError         = IBE_NOPOWER; +    bpError         = IBE_NOHUMANBP;      spawn           = BA_H_SPAWN;      core            = BA_H_REACTOR;    }    else +  { +    Com_Error( ERR_FATAL, "team is %d", team );      return IBE_NONE; +  }    // Simple non-marking case    if( !g_markDeconstruct.integer ) @@ -3020,18 +3123,15 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t     buildable,        if( ent->s.eType != ET_BUILDABLE )          continue; -      if( G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->s.origin ) ) +      if( G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->r.currentOrigin ) )          return IBE_NOROOM;      }      return IBE_NONE;    } -  buildpointsneeded -= remainingBP; -    // Set buildPoints to the number extra that are required -  if( !g_markDeconstructMode.integer ) -    buildPoints -= remainingBP; +  buildPoints -= remainingBP;    // Build a list of buildable entities    for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) @@ -3039,15 +3139,27 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t     buildable,      if( ent->s.eType != ET_BUILDABLE )        continue; -    collision = G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->s.origin ); +    collision = G_BuildablesIntersect( buildable, origin, ent->s.modelindex, ent->r.currentOrigin );      if( collision ) +    { +      // Don't allow replacements at all +      if( g_markDeconstruct.integer == 1 ) +        return IBE_NOROOM; + +      // Only allow replacements of the same type +      if( g_markDeconstruct.integer == 2 && ent->s.modelindex != buildable ) +        return IBE_NOROOM; + +      // Any other setting means anything goes +        collisionCount++; +    }      // Check if this is a repeater and it's in range      if( buildable == BA_H_REPEATER &&          buildable == ent->s.modelindex && -        Distance( ent->s.origin, origin ) < REPEATER_BASESIZE ) +        Distance( ent->r.currentOrigin, origin ) < REPEATER_BASESIZE )      {        repeaterInRange = qtrue;        repeaterInRangeCount++; @@ -3055,55 +3167,76 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t     buildable,      else        repeaterInRange = qfalse; +    // Don't allow marked buildables to be replaced in another zone, +    // unless the marked buildable isn't in a zone (and thus unpowered) +    if( team == TEAM_HUMANS && +        buildable != BA_H_REACTOR && +        buildable != BA_H_REPEATER && +        ent->parentNode != G_PowerEntityForPoint( origin ) ) +      continue; +      if( !ent->inuse )        continue;      if( ent->health <= 0 )        continue; -    if( ent->biteam != team ) +    if( ent->buildableTeam != team )        continue; -    // Don't allow destruction of hovel with granger inside -    if( ent->s.modelindex == BA_A_HOVEL && ent->active ) -    { -      if( buildable == BA_A_HOVEL ) -        return IBE_HOVEL; -      else -        continue; -    } -      // Explicitly disallow replacement of the core buildable with anything      // other than the core buildable      if( ent->s.modelindex == core && buildable != core )        continue; +    // Don't allow a power source to be replaced by a dependant +    if( team == TEAM_HUMANS && +        G_PowerEntityForPoint( origin ) == ent && +        buildable != BA_H_REPEATER && +        buildable != core ) +      continue; + +    // Don't include unpowered buildables +    if( !collision && !ent->powered ) +      continue; +      if( ent->deconstruct )      {        level.markedBuildables[ numBuildables++ ] = ent; +      // Buildables that are marked here will always end up at the front of the +      // removal list, so just incrementing numBuildablesForRemoval is sufficient        if( collision || repeaterInRange )        { +        // Collided with something, so we definitely have to remove it or +        // it's a repeater that intersects the new repeater's power area, +        // so it must be removed +          if( collision )            collisionCount--;          if( repeaterInRange )            repeaterInRangeCount--; -        pointsYielded += BG_FindBuildPointsForBuildable(  ent->s.modelindex ); +        if( ent->powered ) +          pointsYielded += BG_Buildable( ent->s.modelindex )->buildPoints;          level.numBuildablesForRemoval++;        } -      else if( unique && ent->s.modelindex == buildable ) +      else if( BG_Buildable( ent->s.modelindex )->uniqueTest && +               ent->s.modelindex == buildable )        {          // If it's a unique buildable, it must be replaced by the same type -        pointsYielded += BG_FindBuildPointsForBuildable(  ent->s.modelindex ); +        if( ent->powered ) +          pointsYielded += BG_Buildable( ent->s.modelindex )->buildPoints;          level.numBuildablesForRemoval++;        }      }    } +  numRequired = level.numBuildablesForRemoval; +    // We still need build points, but have no candidates for removal -  if( buildpointsneeded > 0 && numBuildables == 0 ) +  if( buildPoints > 0 && numBuildables == 0 )      return bpError;    // Collided with something we can't remove @@ -3112,7 +3245,7 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t     buildable,    // There are one or more repeaters we can't remove    if( repeaterInRangeCount > 0 ) -    return IBE_RPTWARN2; +    return IBE_RPTPOWERHERE;    // Sort the list    cmpBuildable = buildable; @@ -3125,23 +3258,50 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t     buildable,         level.numBuildablesForRemoval++ )    {      ent = level.markedBuildables[ level.numBuildablesForRemoval ]; -    pointsYielded += BG_FindBuildPointsForBuildable( ent->s.modelindex ); +    if( ent->powered ) +      pointsYielded += BG_Buildable( ent->s.modelindex )->buildPoints; +  } + +  // Do another pass to see if we can meet quota with fewer buildables +  //  than we have now due to mismatches between priority and BP amounts +  //  by repeatedly testing if we can chop off the first thing that isn't +  //  required by rules of collision/uniqueness, which are always at the head +  while( changed && level.numBuildablesForRemoval > 1 &&  +         level.numBuildablesForRemoval > numRequired ) +  { +    int pointsUnYielded = 0; +    changed = qfalse; +    ent = level.markedBuildables[ numRequired ]; +    if( ent->powered ) +      pointsUnYielded = BG_Buildable( ent->s.modelindex )->buildPoints; + +    if( pointsYielded - pointsUnYielded >= buildPoints ) +    { +      pointsYielded -= pointsUnYielded; +      memmove( &level.markedBuildables[ numRequired ], +               &level.markedBuildables[ numRequired + 1 ], +               ( level.numBuildablesForRemoval - numRequired )  +                 * sizeof( gentity_t * ) ); +      level.numBuildablesForRemoval--; +      changed = qtrue; +    }    } -  // Make sure we're not removing the last spawn    for( i = 0; i < level.numBuildablesForRemoval; i++ )    {      if( level.markedBuildables[ i ]->s.modelindex == spawn )        spawnCount++;    } + +  // Make sure we're not removing the last spawn    if( !g_cheats.integer && remainingSpawns > 0 && ( remainingSpawns - spawnCount ) < 1 ) -    return IBE_NORMAL; +    return IBE_LASTSPAWN;    // Not enough points yielded -  if( pointsYielded < buildpointsneeded ) +  if( pointsYielded < buildPoints )      return bpError; - -  return IBE_NONE; +  else +    return IBE_NONE;  }  /* @@ -3190,39 +3350,34 @@ G_CanBuild  Checks to see if a buildable can be built  ================  */ -itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ) +itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, +                             vec3_t origin, vec3_t normal, int *groundEntNum )  {    vec3_t            angles; -  vec3_t            entity_origin, normal; -  vec3_t            mins, maxs, nbmins, nbmaxs; -  vec3_t	    nbVect; +  vec3_t            entity_origin; +  vec3_t            mins, maxs;    trace_t           tr1, tr2, tr3; -  int               i; -  itemBuildError_t  reason = IBE_NONE; +  itemBuildError_t  reason = IBE_NONE, tempReason;    gentity_t         *tempent;    float             minNormal;    qboolean          invert;    int               contents;    playerState_t     *ps = &ent->client->ps; -  int               buildPoints; -  gentity_t	    *tmp; -  itemBuildError_t  tempReason;    // Stop all buildables from interacting with traces    G_SetBuildableLinkState( qfalse ); -  BG_FindBBoxForBuildable( buildable, mins, maxs ); +  BG_BuildableBoundingBox( buildable, mins, maxs );    BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, trap_Trace, entity_origin, angles, &tr1 ); -    trap_Trace( &tr2, entity_origin, mins, maxs, entity_origin, ent->s.number, MASK_PLAYERSOLID );    trap_Trace( &tr3, ps->origin, NULL, NULL, entity_origin, ent->s.number, MASK_PLAYERSOLID );    VectorCopy( entity_origin, origin ); - +  *groundEntNum = tr1.entityNum;    VectorCopy( tr1.plane.normal, normal ); -  minNormal = BG_FindMinNormalForBuildable( buildable ); -  invert = BG_FindInvertNormalForBuildable( buildable ); +  minNormal = BG_Buildable( buildable )->minNormal; +  invert = BG_Buildable( buildable )->invertNormal;    //can we build at this angle?    if( !( normal[ 2 ] >= minNormal || ( invert && normal[ 2 ] <= -minNormal ) ) ) @@ -3232,174 +3387,93 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance      reason = IBE_NORMAL;    contents = trap_PointContents( entity_origin, -1 ); -  buildPoints = BG_FindBuildPointsForBuildable( buildable ); -   -  //check if we are near a nobuild marker, if so, can't build here... -  for( i = 0; i < MAX_GENTITIES; i++ ) -  { -	tmp = &g_entities[ i ]; -	 -	if( !tmp->noBuild.isNB ) -	continue; -	 -	nbVect[0] = tmp->noBuild.Area; -	nbVect[1] = tmp->noBuild.Area; -	nbVect[2] = tmp->noBuild.Height; -	   -    	VectorSubtract( origin, nbVect, nbmins ); -  	VectorAdd( origin, nbVect, nbmaxs ); -	 -	if( trap_EntityContact( nbmins, nbmaxs, tmp ) ) -	reason = IBE_PERMISSION; -	 -  } -  if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + +  if( ( tempReason = G_SufficientBPAvailable( buildable, origin ) ) != IBE_NONE ) +    reason = tempReason; + +  if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )    {      //alien criteria -    if( buildable == BA_A_HOVEL ) +    // Check there is an Overmind +    if( buildable != BA_A_OVERMIND )      { -      vec3_t    builderMins, builderMaxs; - -      //this assumes the adv builder is the biggest thing that'll use the hovel -      BG_FindBBoxForClass( PCL_ALIEN_BUILDER0_UPG, builderMins, builderMaxs, NULL, NULL, NULL ); - -      if( APropHovel_Blocked( origin, angles, normal, ent ) ) -        reason = IBE_HOVELEXIT; +      if( !G_Overmind( ) ) +        reason = IBE_NOOVERMIND;      }      //check there is creep near by for building on -    if( BG_FindCreepTestForBuildable( buildable ) ) +    if( BG_Buildable( buildable )->creepTest )      {        if( !G_IsCreepHere( entity_origin ) )          reason = IBE_NOCREEP;      } -    //check permission to build here -    if( tr1.surfaceFlags & SURF_NOALIENBUILD || tr1.surfaceFlags & SURF_NOBUILD || -        contents & CONTENTS_NOALIENBUILD || contents & CONTENTS_NOBUILD ) +    // Check permission to build here +    if( tr1.surfaceFlags & SURF_NOALIENBUILD || contents & CONTENTS_NOALIENBUILD )        reason = IBE_PERMISSION; - -    //look for an Overmind -    for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) -    { -      if( tempent->s.eType != ET_BUILDABLE ) -        continue; -      if( tempent->s.modelindex == BA_A_OVERMIND && tempent->spawned && -        tempent->health > 0 ) -        break; -    } - -    //if none found... -    if( i >= level.num_entities && buildable != BA_A_OVERMIND ) -      reason = IBE_NOOVERMIND; - -    //can we only have one of these? -    if( BG_FindUniqueTestForBuildable( buildable ) ) -    { -      for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) -      { -        if( tempent->s.eType != ET_BUILDABLE ) -          continue; - -        if( tempent->s.modelindex == buildable && !tempent->deconstruct ) -        { -          switch( buildable ) -          { -            case BA_A_OVERMIND: -              reason = IBE_OVERMIND; -              break; - -            case BA_A_HOVEL: -              reason = IBE_HOVEL; -              break; - -            default: -              Com_Error( ERR_FATAL, "No reason for denying build of %d\n", buildable ); -              break; -          } - -          break; -        } -      } -    }    } -  else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +  else if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )    {      //human criteria -    if( !G_IsPowered( entity_origin ) ) + +    // Check for power +    if( G_IsPowered( entity_origin ) == BA_NONE )      {        //tell player to build a repeater to provide power        if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER ) -        reason = IBE_REPEATER; +        reason = IBE_NOPOWERHERE;      }      //this buildable requires a DCC -    if( BG_FindDCCTestForBuildable( buildable ) && !G_IsDCCBuilt( ) ) +    if( BG_Buildable( buildable )->dccTest && !G_IsDCCBuilt( ) )        reason = IBE_NODCC;      //check that there is a parent reactor when building a repeater      if( buildable == BA_H_REPEATER )      { -      for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) -      { -        if( tempent->s.eType != ET_BUILDABLE ) -          continue; - -        if( tempent->s.modelindex == BA_H_REACTOR ) -          break; -      } - -      if( i >= level.num_entities ) -      { -        //no reactor present - -        //check for other nearby repeaters -        for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) -        { -          if( tempent->s.eType != ET_BUILDABLE ) -            continue; - -          if( tempent->s.modelindex == BA_H_REPEATER && -              Distance( tempent->s.origin, entity_origin ) < REPEATER_BASESIZE ) -          { -            reason = IBE_RPTWARN2; -            break; -          } -        } - -        if( reason == IBE_NONE ) -          reason = IBE_RPTWARN; -      } -      else if( G_IsPowered( entity_origin ) ) -        reason = IBE_RPTWARN2; +      tempent = G_Reactor( ); + +      if( tempent == NULL ) // No reactor +        reason = IBE_RPTNOREAC;    +      else if( g_markDeconstruct.integer && G_IsPowered( entity_origin ) == BA_H_REACTOR ) +        reason = IBE_RPTPOWERHERE; +      else if( !g_markDeconstruct.integer && G_IsPowered( entity_origin ) ) +        reason = IBE_RPTPOWERHERE;      } -    //check permission to build here -    if( tr1.surfaceFlags & SURF_NOHUMANBUILD || tr1.surfaceFlags & SURF_NOBUILD || -        contents & CONTENTS_NOHUMANBUILD || contents & CONTENTS_NOBUILD ) +    // Check permission to build here +    if( tr1.surfaceFlags & SURF_NOHUMANBUILD || contents & CONTENTS_NOHUMANBUILD )        reason = IBE_PERMISSION; +  } + +  // Check permission to build here +  if( tr1.surfaceFlags & SURF_NOBUILD || contents & CONTENTS_NOBUILD ) +    reason = IBE_PERMISSION; -    //can we only build one of these? -    if( BG_FindUniqueTestForBuildable( buildable ) ) +  // Can we only have one of these? +  if( BG_Buildable( buildable )->uniqueTest ) +  { +    tempent = G_FindBuildable( buildable ); +    if( tempent && !tempent->deconstruct )      { -      for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) +      switch( buildable )        { -        if( tempent->s.eType != ET_BUILDABLE ) -          continue; +        case BA_A_OVERMIND: +          reason = IBE_ONEOVERMIND; +          break; -        if( tempent->s.modelindex == BA_H_REACTOR && !tempent->deconstruct ) -        { -          reason = IBE_REACTOR; +        case BA_H_REACTOR: +          reason = IBE_ONEREACTOR; +          break; + +        default: +          Com_Error( ERR_FATAL, "No reason for denying build of %d", buildable );            break; -        }        }      }    } -  if( ( tempReason = G_SufficientBPAvailable( buildable, origin ) ) != IBE_NONE ) -    reason = tempReason; -    // Relink buildables    G_SetBuildableLinkState( qtrue ); @@ -3413,8 +3487,8 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance    }    //this item does not fit here -  if( reason == IBE_NONE && ( tr2.fraction < 1.0 || tr3.fraction < 1.0 ) ) -    return IBE_NOROOM; +  if( reason == IBE_NONE && ( tr2.fraction < 1.0f || tr3.fraction < 1.0f ) ) +    reason = IBE_NOROOM;    if( reason != IBE_NONE )      level.numBuildablesForRemoval = 0; @@ -3422,28 +3496,55 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance    return reason;  } +  /* -============== -G_BuildingExists -============== +================ +G_AddRangeMarkerForBuildable +================  */ -qboolean G_BuildingExists( int bclass )  +static void G_AddRangeMarkerForBuildable( gentity_t *self )  { -  int               i; -  gentity_t         *tempent; -  //look for an Armoury -  for (i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) +  gentity_t *rm; + +  switch( self->s.modelindex )    { -    if( tempent->s.eType != ET_BUILDABLE ) -     continue; -    if( tempent->s.modelindex == bclass && tempent->health > 0 ) -    { -      return qtrue; -    } +    case BA_A_SPAWN: +    case BA_A_OVERMIND: +    case BA_A_ACIDTUBE: +    case BA_A_TRAPPER: +    case BA_A_HIVE: +    case BA_H_MGTURRET: +    case BA_H_TESLAGEN: +    case BA_H_DCC: +    case BA_H_REACTOR: +    case BA_H_REPEATER: +      break; +    default: +      return;    } -  return qfalse; + +  rm = G_Spawn(); +  rm->classname = "buildablerangemarker"; +  rm->r.svFlags = SVF_BROADCAST | SVF_CLIENTMASK; +  rm->s.eType = ET_RANGE_MARKER; +  rm->s.modelindex = self->s.modelindex; + +  self->rangeMarker = rm;  } +/* +================ +G_RemoveRangeMarkerFrom +================ +*/ +void G_RemoveRangeMarkerFrom( gentity_t *self ) +{ +  if( self->rangeMarker ) +  { +    G_FreeEntity( self->rangeMarker ); +    self->rangeMarker = NULL; +  } +}  /*  ================ @@ -3452,142 +3553,105 @@ G_Build  Spawns a buildable  ================  */ -static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles ) +static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, +  const vec3_t origin, const vec3_t normal, const vec3_t angles, int groundEntNum )  {    gentity_t *built; -  buildHistory_t *new; -  vec3_t    normal; -     -  // initialise the buildhistory so other functions can use it -  if( builder && builder->client ) -  { -    new = G_Alloc( sizeof( buildHistory_t ) ); -    G_LogBuild( new ); -  } - -  // Free existing buildables -  G_FreeMarkedBuildables( ); - -  //spawn the buildable -  built = G_Spawn(); - -  built->s.eType = ET_BUILDABLE; - -  built->classname = BG_FindEntityNameForBuildable( buildable ); +  char      readable[ MAX_STRING_CHARS ]; +  char      buildnums[ MAX_STRING_CHARS ]; +  buildLog_t *log; -  built->s.modelindex = buildable; //so we can tell what this is on the client side -  built->biteam = built->s.modelindex2 = BG_FindTeamForBuildable( buildable ); - -  BG_FindBBoxForBuildable( buildable, built->r.mins, built->r.maxs ); +  if( builder->client ) +    log = G_BuildLogNew( builder, BF_CONSTRUCT ); +  else +    log = NULL; -  // detect the buildable's normal vector -  if( !builder->client ) -  { -    // initial base layout created by server +  // Free existing buildables +  G_FreeMarkedBuildables( builder, readable, sizeof( readable ), +    buildnums, sizeof( buildnums ) ); -    if( builder->s.origin2[ 0 ] -      || builder->s.origin2[ 1 ] -      || builder->s.origin2[ 2 ] ) -    { -      VectorCopy( builder->s.origin2, normal ); -    } -    else if( BG_FindTrajectoryForBuildable( buildable ) == TR_BUOYANCY ) -      VectorSet( normal, 0.0f, 0.0f, -1.0f ); -    else  -      VectorSet( normal, 0.0f, 0.0f, 1.0f ); -  }  -  else +  if( builder->client )    { -    // in-game building by a player - -    if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) -    { -      if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) -        VectorSet( normal, 0.0f, 0.0f, -1.0f ); -      else -        VectorCopy( builder->client->ps.grapplePoint, normal ); -    } -    else -      VectorSet( normal, 0.0f, 0.0f, 1.0f ); +    // Spawn the buildable +    built = G_Spawn();    } +  else +    built = builder; -  // when building the initial layout, spawn the entity slightly off its -  // target surface so that it can be "dropped" onto it  -  if( !builder->client ) -    VectorMA( origin, 1.0f, normal, origin ); +  built->s.eType = ET_BUILDABLE; +  built->killedBy = ENTITYNUM_NONE; +  built->classname = BG_Buildable( buildable )->entityName; +  built->s.modelindex = buildable; +  built->buildableTeam = built->s.modelindex2 = BG_Buildable( buildable )->team; +  BG_BuildableBoundingBox( buildable, built->r.mins, built->r.maxs );    built->health = 1; -  built->splashDamage = BG_FindSplashDamageForBuildable( buildable ); -  built->splashRadius = BG_FindSplashRadiusForBuildable( buildable ); -  built->splashMethodOfDeath = BG_FindMODForBuildable( buildable ); +  built->splashDamage = BG_Buildable( buildable )->splashDamage; +  built->splashRadius = BG_Buildable( buildable )->splashRadius; +  built->splashMethodOfDeath = BG_Buildable( buildable )->meansOfDeath; -  built->nextthink = BG_FindNextThinkForBuildable( buildable ); +  built->nextthink = BG_Buildable( buildable )->nextthink;    built->takedamage = qtrue;    built->spawned = qfalse;    built->buildTime = built->s.time = level.time; -  built->spawnBlockTime = 0;    // build instantly in cheat mode    if( builder->client && g_cheats.integer )    { -    built->health = BG_FindHealthForBuildable( buildable ); +    built->health = BG_Buildable( buildable )->health;      built->buildTime = built->s.time = -      level.time - BG_FindBuildTimeForBuildable( buildable ); +      level.time - BG_Buildable( buildable )->buildTime;    }    //things that vary for each buildable that aren't in the dbase    switch( buildable )    {      case BA_A_SPAWN: -      built->die = ASpawn_Die; +      built->die = AGeneric_Die;        built->think = ASpawn_Think; -      built->pain = ASpawn_Pain; +      built->pain = AGeneric_Pain;        break;      case BA_A_BARRICADE:        built->die = ABarricade_Die;        built->think = ABarricade_Think;        built->pain = ABarricade_Pain; +      built->touch = ABarricade_Touch; +      built->shrunkTime = 0; +      ABarricade_Shrink( built, qtrue );        break;      case BA_A_BOOSTER: -      built->die = ABarricade_Die; -      built->think = ABarricade_Think; -      built->pain = ABarricade_Pain; +      built->die = AGeneric_Die; +      built->think = AGeneric_Think; +      built->pain = AGeneric_Pain;        built->touch = ABooster_Touch;        break;      case BA_A_ACIDTUBE: -      built->die = ABarricade_Die; +      built->die = AGeneric_Die;        built->think = AAcidTube_Think; -      built->pain = ASpawn_Pain; +      built->pain = AGeneric_Pain;        break;      case BA_A_HIVE: -      built->die = ABarricade_Die; +      built->die = AGeneric_Die;        built->think = AHive_Think; -      built->pain = ASpawn_Pain; +      built->pain = AHive_Pain;        break;      case BA_A_TRAPPER: -      built->die = ABarricade_Die; +      built->die = AGeneric_Die;        built->think = ATrapper_Think; -      built->pain = ASpawn_Pain; +      built->pain = AGeneric_Pain;        break;      case BA_A_OVERMIND: -      built->die = ASpawn_Die; +      built->die = AGeneric_Die;        built->think = AOvermind_Think; -      built->pain = ASpawn_Pain; -      break; - -    case BA_A_HOVEL: -      built->die = AHovel_Die; -      built->use = AHovel_Use; -      built->think = AHovel_Think; -      built->pain = ASpawn_Pain; +      built->pain = AGeneric_Pain;        break;      case BA_H_SPAWN: @@ -3618,7 +3682,7 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori      case BA_H_MEDISTAT:        built->think = HMedistat_Think; -      built->die = HSpawn_Die; +      built->die = HMedistat_Die;        break;      case BA_H_REACTOR: @@ -3626,12 +3690,11 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori        built->die = HSpawn_Die;        built->use = HRepeater_Use;        built->powered = built->active = qtrue; -      built->pain = HReactor_Pain;        break;      case BA_H_REPEATER:        built->think = HRepeater_Think; -      built->die = HSpawn_Die; +      built->die = HRepeater_Die;        built->use = HRepeater_Use;        built->count = -1;        break; @@ -3641,152 +3704,91 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori        break;    } -  built->s.number = built - g_entities;    built->r.contents = CONTENTS_BODY;    built->clipmask = MASK_PLAYERSOLID;    built->enemy = NULL; -  built->s.weapon = BG_FindProjTypeForBuildable( buildable ); +  built->s.weapon = BG_Buildable( buildable )->turretProjType;    if( builder->client ) -  { -    built->builtBy = builder->client->ps.clientNum; - -    if( builder->client->pers.designatedBuilder ) -    { -      built->s.eFlags |= EF_DBUILDER; // designated builder protection -    } -  } +    built->builtBy = builder->client->pers.namelog; +  else if( builder->builtBy ) +    built->builtBy = builder->builtBy;    else -    built->builtBy = -1; +    built->builtBy = NULL;    G_SetOrigin( built, origin ); -   -  // gently nudge the buildable onto the surface :) -  VectorScale( normal, -50.0f, built->s.pos.trDelta );    // set turret angles    VectorCopy( builder->s.angles2, built->s.angles2 ); -  VectorCopy( angles, built->s.angles ); -  built->s.angles[ PITCH ] = 0.0f; +  VectorCopy( angles, built->s.apos.trBase ); +  VectorCopy( angles, built->r.currentAngles ); +  built->s.apos.trBase[ PITCH ] = 0.0f; +  built->r.currentAngles[ PITCH ] = 0.0f;    built->s.angles2[ YAW ] = angles[ YAW ]; -  built->s.pos.trType = BG_FindTrajectoryForBuildable( buildable ); -  built->s.pos.trTime = level.time; -  built->physicsBounce = BG_FindBounceForBuildable( buildable ); -  built->s.groundEntityNum = -1; +  built->s.angles2[ PITCH ] = MGTURRET_VERTICALCAP; +  built->physicsBounce = BG_Buildable( buildable )->bounce; -  built->s.generic1 = (int)( ( (float)built->health / -        (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_MASK ); +  built->s.groundEntityNum = groundEntNum; +  if( groundEntNum == ENTITYNUM_NONE ) +  { +    built->s.pos.trType = BG_Buildable( buildable )->traj; +    built->s.pos.trTime = level.time; +    // gently nudge the buildable onto the surface :) +    VectorScale( normal, -50.0f, built->s.pos.trDelta ); +  } -  if( built->s.generic1 < 0 ) -    built->s.generic1 = 0; +  built->s.misc = MAX( built->health, 0 ); -  if( BG_FindTeamForBuildable( built->s.modelindex ) == PTE_ALIENS ) +  if( BG_Buildable( buildable )->team == TEAM_ALIENS )    {      built->powered = qtrue; -    built->s.generic1 |= B_POWERED_TOGGLEBIT; +    built->s.eFlags |= EF_B_POWERED;    } -  else if( ( built->powered = G_FindPower( built ) ) ) -    built->s.generic1 |= B_POWERED_TOGGLEBIT; +  else if( ( built->powered = G_FindPower( built, qfalse ) ) ) +    built->s.eFlags |= EF_B_POWERED; -  if( ( built->dcced = G_FindDCC( built ) ) ) -    built->s.generic1 |= B_DCCED_TOGGLEBIT; - -  built->s.generic1 &= ~B_SPAWNED_TOGGLEBIT; +  built->s.eFlags &= ~EF_B_SPAWNED;    VectorCopy( normal, built->s.origin2 );    G_AddEvent( built, EV_BUILD_CONSTRUCT, 0 ); -  G_SetIdleBuildableAnim( built, BG_FindAnimForBuildable( buildable ) ); +  G_SetIdleBuildableAnim( built, BG_Buildable( buildable )->idleAnim ); -  if( built->builtBy >= 0 ) +  if( built->builtBy )      G_SetBuildableAnim( built, BANIM_CONSTRUCT1, qtrue );    trap_LinkEntity( built ); -   -   -  if( builder->client )  + +  if( builder && builder->client )    { -     builder->client->pers.statscounters.structsbuilt++; -     if( builder->client->pers.teamSelection == PTE_ALIENS )  -     { -       level.alienStatsCounters.structsbuilt++; -     } -     else if( builder->client->pers.teamSelection == PTE_HUMANS ) -     { -       level.humanStatsCounters.structsbuilt++; -     } +    G_TeamCommand( builder->client->ps.stats[ STAT_TEAM ], +      va( "print \"%s ^2built^7 by %s%s%s\n\"", +        BG_Buildable( built->s.modelindex )->humanName, +        builder->client->pers.netname, +        ( readable[ 0 ] ) ? "^7, ^3replacing^7 " : "", +        readable ) ); +    G_LogPrintf( "Construct: %d %d %s%s: %s" S_COLOR_WHITE " is building " +      "%s%s%s\n", +      (int)( builder - g_entities ), +      (int)( built - g_entities ), +      BG_Buildable( built->s.modelindex )->name, +      buildnums, +      builder->client->pers.netname, +      BG_Buildable( built->s.modelindex )->humanName, +      readable[ 0 ] ? ", replacing " : "", +      readable );    } -  if( builder->client ) { -    G_TeamCommand( builder->client->pers.teamSelection, -      va( "print \"%s is ^2being built^7 by %s^7\n\"", -        BG_FindHumanNameForBuildable( built->s.modelindex ),  -        builder->client->pers.netname ) ); -    G_LogPrintf("Build: %i %i 0: %s^7 is ^2building^7 %s\n", -      builder->client->ps.clientNum, -      built->s.modelindex, -      builder->client->pers.netname,  -      BG_FindNameForBuildable( built->s.modelindex ) ); -  } +  if( log ) +    G_BuildLogSet( log, built ); -  // ok we're all done building, so what we log here should be the final values -  if( builder && builder->client ) // log ingame building only -  { -    new = level.buildHistory; -    new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; -    new->ent = builder; -    new->name[ 0 ] = 0; -    new->buildable = buildable; -    VectorCopy( built->s.pos.trBase, new->origin ); -    VectorCopy( built->s.angles, new->angles ); -    VectorCopy( built->s.origin2, new->origin2 ); -    VectorCopy( built->s.angles2, new->angles2 ); -    new->fate = BF_BUILT; -  } -   -   if( builder && builder->client ) -     built->bdnumb = new->ID; -   else -     built->bdnumb = -1; +  G_AddRangeMarkerForBuildable( built );    return built;  } -static void G_SpawnMarker( vec3_t origin ) -{ -	gentity_t *nb; -	int i; -   -  	// Make the marker... -	nb = G_Spawn( ); -	nb->s.modelindex = 0; //Coder humor is win -	VectorCopy( origin, nb->s.pos.trBase ); -	VectorCopy( origin, nb->r.currentOrigin ); -	nb->noBuild.isNB = qtrue; -	nb->noBuild.Area = level.nbArea; -	nb->noBuild.Height = level.nbHeight; -	trap_LinkEntity( nb ); -	 -	// Log markers made... -	for( i = 0; i < MAX_GENTITIES; i++ ) -	{ -		if( level.nbMarkers[ i ].Marker != NULL ) -		continue; -		 -		level.nbMarkers[ i ].Marker = nb; -		VectorCopy( origin, level.nbMarkers[ i ].Origin ); -		SnapVector( level.nbMarkers[ i ].Origin ); -		break; -	} -	 -  	// End nobuild mode... -	level.noBuilding = qfalse; -	level.nbArea = 0.0f; -	level.nbHeight = 0.0f; -} -  /*  =================  G_BuildIfValid @@ -3795,27 +3797,19 @@ G_BuildIfValid  qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable )  {    float         dist; -  vec3_t        origin; +  vec3_t        origin, normal; +  int           groundEntNum; -  dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); +  dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; -  switch( G_CanBuild( ent, buildable, dist, origin ) ) +  switch( G_CanBuild( ent, buildable, dist, origin, normal, &groundEntNum ) )    {      case IBE_NONE: -      if( level.noBuilding ) -      { -      	vec3_t    mins; -  	BG_FindBBoxForBuildable( buildable, mins, NULL ); -  	origin[2] += mins[2]; -	 -      	G_SpawnMarker( origin ); -	return qtrue; -      } -      G_Build( ent, buildable, origin, ent->s.apos.trBase ); +      G_Build( ent, buildable, origin, normal, ent->s.apos.trBase, groundEntNum );        return qtrue; -    case IBE_NOASSERT: -      G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT ); +    case IBE_NOALIENBP: +      G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOBP );        return qfalse;      case IBE_NOOVERMIND: @@ -3826,96 +3820,44 @@ qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable )        G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP );        return qfalse; -    case IBE_OVERMIND: -      G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND ); -      return qfalse; - -    case IBE_HOVEL: -      G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL ); -      return qfalse; - -    case IBE_HOVELEXIT: -      G_TriggerMenu( ent->client->ps.clientNum, MN_A_HOVEL_EXIT ); +    case IBE_ONEOVERMIND: +      G_TriggerMenu( ent->client->ps.clientNum, MN_A_ONEOVERMIND );        return qfalse;      case IBE_NORMAL: -      if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -        G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL ); -      else -        G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL ); +      G_TriggerMenu( ent->client->ps.clientNum, MN_B_NORMAL );        return qfalse;      case IBE_PERMISSION: -      if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -        G_TriggerMenu( ent->client->ps.clientNum, MN_H_NORMAL ); -      else -        G_TriggerMenu( ent->client->ps.clientNum, MN_A_NORMAL ); +      G_TriggerMenu( ent->client->ps.clientNum, MN_B_NORMAL );        return qfalse; -    case IBE_REACTOR: -      G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR ); +    case IBE_ONEREACTOR: +      G_TriggerMenu( ent->client->ps.clientNum, MN_H_ONEREACTOR );        return qfalse; -    case IBE_REPEATER: -      G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER ); +    case IBE_NOPOWERHERE: +      G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWERHERE );        return qfalse;      case IBE_NOROOM: -      if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -        G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOM ); -      else -        G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOROOM ); +      G_TriggerMenu( ent->client->ps.clientNum, MN_B_NOROOM );        return qfalse; -    case IBE_NOPOWER: -      G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER ); +    case IBE_NOHUMANBP: +      G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOBP);        return qfalse;      case IBE_NODCC:        G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC );        return qfalse; -    case IBE_SPWNWARN: -      if( level.noBuilding ) -      { -      	vec3_t    mins; -  	BG_FindBBoxForBuildable( buildable, mins, NULL ); -  	origin[2] += mins[2]; -      	G_SpawnMarker( origin ); -	return qtrue; -      } -      G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN ); -      G_Build( ent, buildable, origin, ent->s.apos.trBase ); -      return qtrue; - -    case IBE_TNODEWARN: -      if( level.noBuilding ) -      { -      	vec3_t    mins; -  	BG_FindBBoxForBuildable( buildable, mins, NULL ); -  	origin[2] += mins[2]; -      	G_SpawnMarker( origin ); -	return qtrue; -      } -      G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN ); -      G_Build( ent, buildable, origin, ent->s.apos.trBase ); -      return qtrue; - -    case IBE_RPTWARN: -      if( level.noBuilding ) -      { -      	vec3_t    mins; -  	BG_FindBBoxForBuildable( buildable, mins, NULL ); -  	origin[2] += mins[2]; -      	G_SpawnMarker( origin ); -	return qtrue; -      } -      G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN ); -      G_Build( ent, buildable, origin, ent->s.apos.trBase ); -      return qtrue; +    case IBE_RPTPOWERHERE: +      G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTPOWERHERE ); +      return qfalse; -    case IBE_RPTWARN2: -      G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN2 ); +    case IBE_LASTSPAWN: +      G_TriggerMenu( ent->client->ps.clientNum, MN_B_LASTSPAWN );        return qfalse;      default: @@ -3933,33 +3875,41 @@ Traces down to find where an item should rest, instead of letting them  free fall from their spawn points  ================  */ -static void G_FinishSpawningBuildable( gentity_t *ent ) +static gentity_t *G_FinishSpawningBuildable( gentity_t *ent, qboolean force )  {    trace_t     tr; -  vec3_t      dest; +  vec3_t      normal, dest;    gentity_t   *built;    buildable_t buildable = ent->s.modelindex; -  built = G_Build( ent, buildable, ent->s.pos.trBase, ent->s.angles ); -  G_FreeEntity( ent ); +  if( ent->s.origin2[ 0 ] || ent->s.origin2[ 1 ] || ent->s.origin2[ 2 ] ) +    VectorCopy( ent->s.origin2, normal ); +  else if( BG_Buildable( buildable )->traj == TR_BUOYANCY ) +    VectorSet( normal, 0.0f, 0.0f, -1.0f ); +  else +    VectorSet( normal, 0.0f, 0.0f, 1.0f ); + +  built = G_Build( ent, buildable, ent->r.currentOrigin, +                   normal, ent->r.currentAngles, ENTITYNUM_NONE );    built->takedamage = qtrue;    built->spawned = qtrue; //map entities are already spawned -  built->health = BG_FindHealthForBuildable( buildable ); -  built->s.generic1 |= B_SPAWNED_TOGGLEBIT; +  built->health = BG_Buildable( buildable )->health; +  built->s.eFlags |= EF_B_SPAWNED;    // drop towards normal surface    VectorScale( built->s.origin2, -4096.0f, dest ); -  VectorAdd( dest, built->s.origin, dest ); +  VectorAdd( dest, built->r.currentOrigin, dest ); -  trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask ); +  trap_Trace( &tr, built->r.currentOrigin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask ); -  if( tr.startsolid ) +  if( tr.startsolid && !force )    {      G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n", -              built->classname, vtos( built->s.origin ) ); +              built->classname, vtos( built->r.currentOrigin ) ); +    G_RemoveRangeMarkerFrom( built );      G_FreeEntity( built ); -    return; +    return NULL;    }    //point items in the correct direction @@ -3971,6 +3921,20 @@ static void G_FinishSpawningBuildable( gentity_t *ent )    G_SetOrigin( built, tr.endpos );    trap_LinkEntity( built ); +  return built; +} + +/* +============ +G_SpawnBuildableThink + +Complete spawning a buildable using its placeholder +============ +*/ +static void G_SpawnBuildableThink( gentity_t *ent ) +{ +  G_FinishSpawningBuildable( ent, qfalse ); +  G_BuildableThink( ent, 0 );  }  /* @@ -3990,76 +3954,96 @@ void G_SpawnBuildable( gentity_t *ent, buildable_t buildable )    // some movers spawn on the second frame, so delay item    // spawns until the third frame so they can ride trains    ent->nextthink = level.time + FRAMETIME * 2; -  ent->think = G_FinishSpawningBuildable; +  ent->think = G_SpawnBuildableThink; +} + +void G_ParseCSVBuildablePlusList( const char *string, int *buildables, int buildablesSize ) +{ +  char      buffer[ MAX_STRING_CHARS ]; +  int       i = 0; +  char      *p, *q; +  qboolean  EOS = qfalse; + +  Q_strncpyz( buffer, string, MAX_STRING_CHARS ); + +  p = q = buffer; + +  while( *p != '\0' && i < buildablesSize - 1 ) +  { +    //skip to first , or EOS +    while( *p != ',' && *p != '\0' ) +      p++; + +    if( *p == '\0' ) +      EOS = qtrue; + +    *p = '\0'; + +    //strip leading whitespace +    while( *q == ' ' ) +      q++; + +    if( !Q_stricmp( q, "alien" ) ) +    { +      buildable_t b; +      for( b = BA_A_SPAWN; b <= BA_A_HIVE && i < buildablesSize - 1; ++b ) +        buildables[ i++ ] = b; + +      if( i < buildablesSize - 1 ) +        buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_ALIENS; +    } +    else if( !Q_stricmp( q, "human" ) ) +    { +      buildable_t b; +      for( b = BA_H_SPAWN; b <= BA_H_REPEATER && i < buildablesSize - 1; ++b ) +        buildables[ i++ ] = b; + +      if( i < buildablesSize - 1 ) +        buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_HUMANS; +    } +    else if( !Q_stricmp( q, "ivo_spectator" ) ) +      buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_NONE; +    else if( !Q_stricmp( q, "ivo_alien" ) ) +      buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_ALIENS; +    else if( !Q_stricmp( q, "ivo_human" ) ) +      buildables[ i++ ] = BA_NUM_BUILDABLES + TEAM_HUMANS; +    else +    { +      buildables[ i ] = BG_BuildableByName( q )->number; +      if( buildables[ i ] == BA_NONE ) +        Com_Printf( S_COLOR_YELLOW "WARNING: unknown buildable or special identifier %s\n", q ); +      else +        i++; +    } + +    if( !EOS ) +    { +      p++; +      q = p; +    } +    else +      break; +  } + +  buildables[ i ] = BA_NONE;  } - /* - ============ - G_CheckDBProtection -  - Count how many designated builders are in both teams and - if none found in some team, cancel protection for all - structures of that team - ============ - */ -  - void G_CheckDBProtection( void ) - { -   int alienDBs = 0, humanDBs = 0, i; -   gentity_t *ent; -  -   // count designated builders -   for( i = 0, ent = g_entities + i; i < level.maxclients; i++, ent++) -   { -     if( !ent->client || ( ent->client->pers.connected != CON_CONNECTED ) ) -       continue; -  -     if( ent->client->pers.designatedBuilder) -     { -       if( ent->client->pers.teamSelection == PTE_HUMANS ) -       { -         humanDBs++; -       } -       else if( ent->client->pers.teamSelection == PTE_ALIENS ) -       { -         alienDBs++; -       } -     } -   } -  -   // both teams have designate builders, we're done -   if( alienDBs > 0 && humanDBs > 0 ) -     return; -  -   // cancel protection if needed -   for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++) -   { -     if( ent->s.eType != ET_BUILDABLE) -       continue; -  -     if( ( !alienDBs && ent->biteam == BIT_ALIENS ) || -       ( !humanDBs && ent->biteam == BIT_HUMANS ) ) -     { -       ent->s.eFlags &= ~EF_DBUILDER; -     } -   } - } -   /*  ============  G_LayoutSave -  ============  */ -void G_LayoutSave( char *name ) +void G_LayoutSave( char *lstr )  { +  char *lstrPipePtr; +  qboolean bAllowed[ BA_NUM_BUILDABLES + NUM_TEAMS ];    char map[ MAX_QPATH ];    char fileName[ MAX_OSPATH ];    fileHandle_t f;    int len;    int i;    gentity_t *ent; -  char *s; +  const char *s;    trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );    if( !map[ 0 ] ) @@ -4067,7 +4051,22 @@ void G_LayoutSave( char *name )      G_Printf( "LayoutSave( ): no map is loaded\n" );      return;    } -  Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, name ); + +  if( ( lstrPipePtr = strchr( lstr, '|' ) ) ) +  { +    int bList[ BA_NUM_BUILDABLES + NUM_TEAMS ]; +    *lstrPipePtr = '\0'; +    G_ParseCSVBuildablePlusList( lstr, &bList[ 0 ], sizeof( bList ) / sizeof( bList[ 0 ] ) ); +    memset( bAllowed, 0, sizeof( bAllowed ) ); +    for( i = 0; bList[ i ] != BA_NONE; i++ ) +      bAllowed[ bList[ i ] ] = qtrue; +    *lstrPipePtr = '|'; +    lstr = lstrPipePtr + 1; +  } +  else +    bAllowed[ BA_NONE ] = qtrue; // allow all + +  Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, lstr );    len = trap_FS_FOpenFile( fileName, &f, FS_WRITE );    if( len < 0 ) @@ -4076,22 +4075,48 @@ void G_LayoutSave( char *name )      return;    } -  G_Printf("layoutsave: saving layout to %s\n", fileName ); +  G_Printf( "layoutsave: saving layout to %s\n", fileName );    for( i = MAX_CLIENTS; i < level.num_entities; i++ )    { +    const char *name; +      ent = &level.gentities[ i ]; -    if( ent->s.eType != ET_BUILDABLE ) +    if( ent->s.eType == ET_BUILDABLE ) +    { +      if( !bAllowed[ BA_NONE ] && !bAllowed[ ent->s.modelindex ] ) +        continue; +      name = BG_Buildable( ent->s.modelindex )->name; +    } +    else if( ent->count == 1 && !strcmp( ent->classname, "info_player_intermission" ) ) +    { +      if( !bAllowed[ BA_NONE ] && !bAllowed[ BA_NUM_BUILDABLES + TEAM_NONE ] ) +        continue; +      name = "ivo_spectator"; +    } +    else if( ent->count == 1 && !strcmp( ent->classname, "info_alien_intermission" ) ) +    { +      if( !bAllowed[ BA_NONE ] && !bAllowed[ BA_NUM_BUILDABLES + TEAM_ALIENS ] ) +        continue; +      name = "ivo_alien"; +    } +    else if( ent->count == 1 && !strcmp( ent->classname, "info_human_intermission" ) ) +    { +      if( !bAllowed[ BA_NONE ] && !bAllowed[ BA_NUM_BUILDABLES + TEAM_HUMANS ] ) +        continue; +      name = "ivo_human"; +    } +    else        continue; -    s = va( "%i %f %f %f %f %f %f %f %f %f %f %f %f\n", -      ent->s.modelindex, -      ent->s.pos.trBase[ 0 ], -      ent->s.pos.trBase[ 1 ], -      ent->s.pos.trBase[ 2 ], -      ent->s.angles[ 0 ], -      ent->s.angles[ 1 ], -      ent->s.angles[ 2 ], +    s = va( "%s %f %f %f %f %f %f %f %f %f %f %f %f\n", +      name, +      ent->r.currentOrigin[ 0 ], +      ent->r.currentOrigin[ 1 ], +      ent->r.currentOrigin[ 2 ], +      ent->r.currentAngles[ 0 ], +      ent->r.currentAngles[ 1 ], +      ent->r.currentAngles[ 2 ],        ent->s.origin2[ 0 ],        ent->s.origin2[ 1 ],        ent->s.origin2[ 2 ], @@ -4103,6 +4128,11 @@ void G_LayoutSave( char *name )    trap_FS_FCloseFile( f );  } +/* +============ +G_LayoutList +============ +*/  int G_LayoutList( const char *map, char *list, int len )  {    // up to 128 single character layout names could fit in layouts @@ -4112,7 +4142,7 @@ int G_LayoutList( const char *map, char *list, int len )    int  count = 0;    char *filePtr; -  Q_strcat( layouts, sizeof( layouts ), "*BUILTIN* " );   +  Q_strcat( layouts, sizeof( layouts ), "*BUILTIN* " );    numFiles = trap_FS_GetFileList( va( "layouts/%s", map ), ".dat",      fileList, sizeof( fileList ) );    filePtr = fileList; @@ -4121,7 +4151,7 @@ int G_LayoutList( const char *map, char *list, int len )      fileLen = strlen( filePtr );      listLen = strlen( layouts );      if( fileLen < 5 ) -      continue;  +      continue;      // list is full, stop trying to add to it      if( ( listLen + fileLen ) >= sizeof( layouts ) ) @@ -4149,27 +4179,31 @@ int G_LayoutList( const char *map, char *list, int len )  ============  G_LayoutSelect -set level.layout based on g_layouts or g_layoutAuto +set level.layout based on g_nextLayout, g_layouts or g_layoutAuto  ============  */  void G_LayoutSelect( void )  { -  char fileName[ MAX_OSPATH ]; -  char layouts[ MAX_CVAR_VALUE_STRING ]; -  char layouts2[ MAX_CVAR_VALUE_STRING ]; -  char *l;  +  char layouts[ ( MAX_CVAR_VALUE_STRING - 1 ) * 9 + 1 ]; +  char layouts2[ ( MAX_CVAR_VALUE_STRING - 1 ) * 9 + 1 ]; +  char *l;    char map[ MAX_QPATH ];    char *s;    int cnt = 0;    int layoutNum; -  Q_strncpyz( layouts, g_layouts.string, sizeof( layouts ) ); +  Q_strncpyz( layouts, g_nextLayout.string, sizeof( layouts ) ); +  if( !layouts[ 0 ] ) +  { +    for( layoutNum = 0; layoutNum < 9; ++layoutNum ) +      Q_strcat( layouts, sizeof( layouts ), g_layouts[ layoutNum ].string ); +  }    trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); -  // one time use cvar  -  trap_Cvar_Set( "g_layouts", "" ); -  -  // pick an included layout at random if no list has been provided  +  // one time use cvar +  trap_Cvar_Set( "g_nextLayout", "" ); + +  // pick an included layout at random if no list has been provided    if( !layouts[ 0 ] && g_layoutAuto.integer )    {      G_LayoutList( map, layouts, sizeof( layouts ) ); @@ -4181,20 +4215,13 @@ void G_LayoutSelect( void )    Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) );    l = &layouts2[ 0 ];    layouts[ 0 ] = '\0'; -  s = COM_ParseExt( &l, qfalse ); -  while( *s ) +  while( 1 )    { -    if( !Q_stricmp( s, "*BUILTIN*" ) ) -    { -      Q_strcat( layouts, sizeof( layouts ), s ); -      Q_strcat( layouts, sizeof( layouts ), " " ); -      cnt++; -      s = COM_ParseExt( &l, qfalse ); -      continue; -    } -     -    Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, s ); -    if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) > 0 ) +    s = COM_ParseExt( &l, qfalse ); +    if( !*s ) +      break; + +    if( strchr( s, '+' ) || strchr( s, '|' ) || G_LayoutExists( map, s ) )      {        Q_strcat( layouts, sizeof( layouts ), s );        Q_strcat( layouts, sizeof( layouts ), " " ); @@ -4202,7 +4229,6 @@ void G_LayoutSelect( void )      }      else        G_Printf( S_COLOR_YELLOW "WARNING: layout \"%s\" does not exist\n", s ); -    s = COM_ParseExt( &l, qfalse );    }    if( !cnt )    { @@ -4210,335 +4236,178 @@ void G_LayoutSelect( void )          "found, using map default\n" );        return;    } -  layoutNum = ( rand( ) % cnt ) + 1; +  layoutNum = rand( ) / ( RAND_MAX / cnt + 1 ) + 1;    cnt = 0;    Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) );    l = &layouts2[ 0 ]; -  s = COM_ParseExt( &l, qfalse ); -  while( *s ) +  while( 1 )    { +    s = COM_ParseExt( &l, qfalse ); +    if( !*s ) +      break; +      Q_strncpyz( level.layout, s, sizeof( level.layout ) );      cnt++;      if( cnt >= layoutNum )        break; -    s = COM_ParseExt( &l, qfalse );    } -  G_Printf("using layout \"%s\" from list ( %s)\n", level.layout, layouts );  -} - -static void G_LayoutBuildItem( buildable_t buildable, vec3_t origin, -  vec3_t angles, vec3_t origin2, vec3_t angles2 ) -{ -  gentity_t *builder; -   -  builder = G_Spawn( ); -  builder->client = 0; -  VectorCopy( origin, builder->s.pos.trBase ); -  VectorCopy( angles, builder->s.angles ); -  VectorCopy( origin2, builder->s.origin2 ); -  VectorCopy( angles2, builder->s.angles2 ); -  G_SpawnBuildable( builder, buildable ); +  G_Printf( "using layout \"%s\" from list (%s)\n", level.layout, layouts );  }  /*  ============ -G_InstantBuild - -This function is extremely similar to the few functions that place a  -buildable on map load. It exists because G_LayoutBuildItem takes a couple -of frames to finish spawning it, so it's not truly instant -Do not call this function immediately after the map loads - that's what -G_LayoutBuildItem is for. +G_LayoutBuildItem  ============  */ -gentity_t *G_InstantBuild( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 ) +static void G_LayoutBuildItem( buildable_t buildable, vec3_t origin, +  vec3_t angles, vec3_t origin2, vec3_t angles2 )  { -  gentity_t *builder, *built; -  trace_t   tr; -  vec3_t    dest; -   +  gentity_t *builder; +    builder = G_Spawn( ); -  builder->client = 0; -  VectorCopy( origin, builder->s.pos.trBase ); -  VectorCopy( angles, builder->s.angles ); +  builder->classname = "builder"; +  VectorCopy( origin, builder->r.currentOrigin ); +  VectorCopy( angles, builder->r.currentAngles );    VectorCopy( origin2, builder->s.origin2 );    VectorCopy( angles2, builder->s.angles2 ); -//old method didn't quite work out -//builder->s.modelindex = buildable; -//G_FinishSpawningBuildable( builder ); - -  built = G_Build( builder, buildable, builder->s.pos.trBase, builder->s.angles ); -  G_FreeEntity( builder ); - -  built->takedamage = qtrue; -  built->spawned = qtrue; //map entities are already spawned -  built->health = BG_FindHealthForBuildable( buildable ); -  built->s.generic1 |= B_SPAWNED_TOGGLEBIT; - -  // drop towards normal surface -  VectorScale( built->s.origin2, -4096.0f, dest ); -  VectorAdd( dest, built->s.origin, dest ); - -  trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask ); -  if( tr.startsolid ) -  { -    G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n", -         built->classname, vtos( built->s.origin ) ); -    G_FreeEntity( built ); -    return NULL; -  } - -  //point items in the correct direction -  VectorCopy( tr.plane.normal, built->s.origin2 ); - -  // allow to ride movers -  built->s.groundEntityNum = tr.entityNum; - -  G_SetOrigin( built, tr.endpos ); - -  trap_LinkEntity( built ); -  return built; +  G_SpawnBuildable( builder, buildable );  } -/* -============ -G_SpawnRevertedBuildable - -Given a buildhistory, try to replace the lost buildable -============ -*/ -void G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark ) +static void G_SpawnIntermissionViewOverride( char *cn, vec3_t origin, vec3_t angles )  { -  vec3_t mins, maxs; -  int i, j, blockCount, blockers[ MAX_GENTITIES ]; -  gentity_t *targ, *built, *toRecontent[ MAX_GENTITIES ]; - -  BG_FindBBoxForBuildable( bh->buildable, mins, maxs ); -  VectorAdd( bh->origin, mins, mins ); -  VectorAdd( bh->origin, maxs, maxs ); -  blockCount = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); -  for( i = j = 0; i < blockCount; i++ ) -  { -    targ = g_entities + blockers[ i ]; -    if( targ->s.eType == ET_BUILDABLE ) -      G_FreeEntity( targ ); -    else if( targ->s.eType == ET_PLAYER ) -    { -      targ->r.contents = 0; // make it intangible -      toRecontent[ j++ ] = targ; // and remember it -    } -  } -  level.numBuildablesForRemoval = 0; -  built = G_InstantBuild( bh->buildable, bh->origin, bh->angles, bh->origin2, bh->angles2 ); -  if( built ) +  gentity_t *spot = G_Find( NULL, FOFS( classname ), cn ); +  if( !spot )    { -    built->r.contents = 0; -    built->think = G_CommitRevertedBuildable; -    built->nextthink = level.time; -    built->deconstruct = mark; +    spot = G_Spawn(); +    spot->classname = cn;    } -  for( i = 0; i < j; i++ ) -    toRecontent[ i ]->r.contents = CONTENTS_BODY; -} - -/* -============ -G_CommitRevertedBuildable +  spot->count = 1; -Check if there's anyone occupying me, and if not, become solid and operate as  -normal. Else, try to get rid of them. -============ -*/ -void G_CommitRevertedBuildable( gentity_t *ent ) -{ -  gentity_t *targ; -  int i, n, occupants[ MAX_GENTITIES ]; -  vec3_t mins, maxs; -  int victims = 0; - -  VectorAdd( ent->s.origin, ent->r.mins, mins ); -  VectorAdd( ent->s.origin, ent->r.maxs, maxs ); -  trap_UnlinkEntity( ent ); -  n = trap_EntitiesInBox( mins, maxs, occupants, MAX_GENTITIES ); -  trap_LinkEntity( ent ); - -  for( i = 0; i < n; i++ ) -  { -    vec3_t gtfo; -    targ = g_entities + occupants[ i ]; -    if( targ->client ) -    { -      VectorSet( gtfo, crandom() * 150, crandom() * 150, random() * 150 ); -      VectorAdd( targ->client->ps.velocity, gtfo, targ->client->ps.velocity ); -      victims++; -    } -  } -  if( !victims ) -  { // we're in the clear! -    ent->r.contents = MASK_PLAYERSOLID; -    trap_LinkEntity( ent ); // relink -    // oh dear, manual think set -    switch( ent->s.modelindex ) -    { -      case BA_A_SPAWN: -         ent->think = ASpawn_Think; -         break; -      case BA_A_BARRICADE:  -      case BA_A_BOOSTER: -        ent->think = ABarricade_Think; -        break; -      case BA_A_ACIDTUBE: -        ent->think = AAcidTube_Think; -        break; -      case BA_A_HIVE: -        ent->think = AHive_Think; -        break; -      case BA_A_TRAPPER: -        ent->think = ATrapper_Think; -        break; -      case BA_A_OVERMIND: -        ent->think = AOvermind_Think; -        break; -      case BA_A_HOVEL: -        ent->think = AHovel_Think; -        break; -      case BA_H_SPAWN: -        ent->think = HSpawn_Think; -        break; -      case BA_H_MGTURRET: -        ent->think = HMGTurret_Think; -        break; -      case BA_H_TESLAGEN: -        ent->think = HTeslaGen_Think; -        break; -      case BA_H_ARMOURY: -        ent->think = HArmoury_Think; -        break; -      case BA_H_DCC: -        ent->think = HDCC_Think; -        break; -      case BA_H_MEDISTAT: -        ent->think = HMedistat_Think; -        break; -      case BA_H_REACTOR: -        ent->think = HReactor_Think; -        break; -      case BA_H_REPEATER: -        ent->think = HRepeater_Think; -        break; -    } -    ent->nextthink = level.time + BG_FindNextThinkForBuildable( ent->s.modelindex ); -    // oh if only everything was that simple -    return; -  } -#define REVERT_THINK_INTERVAL 50 -  ent->nextthink = level.time + REVERT_THINK_INTERVAL; -} - -/* -============ -G_RevertCanFit - -take a bhist and make sure you're not overwriting anything by placing it -============ -*/ -qboolean G_RevertCanFit( buildHistory_t *bh ) -{ -  int i, num, blockers[ MAX_GENTITIES ]; -  vec3_t mins, maxs; -  gentity_t *targ; -  vec3_t dist; - -  BG_FindBBoxForBuildable( bh->buildable, mins, maxs ); -  VectorAdd( bh->origin, mins, mins ); -  VectorAdd( bh->origin, maxs, maxs ); -  num = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); -  for( i = 0; i < num; i++ ) -  { -    targ = g_entities + blockers[ i ]; -    if( targ->s.eType == ET_BUILDABLE ) -    { -      VectorSubtract( bh->origin, targ->s.pos.trBase, dist ); -      if( targ->s.modelindex == bh->buildable && VectorLength( dist ) < 10 && targ->health <= 0 ) -        continue; // it's the same buildable, hasn't blown up yet -      else -        return qfalse; // can't get rid of this one -    } -    else -      continue; -  } -  return qtrue; +  VectorCopy( origin, spot->r.currentOrigin ); +  VectorCopy( angles, spot->r.currentAngles );  }  /*  ============  G_LayoutLoad - -load the layout .dat file indicated by level.layout and spawn buildables -as if a builder was creating them  ============  */ -void G_LayoutLoad( void ) +void G_LayoutLoad( char *lstr )  { +  char *lstrPlusPtr, *lstrPipePtr; +  qboolean bAllowed[ BA_NUM_BUILDABLES + NUM_TEAMS ];    fileHandle_t f;    int len; -  char *layout; +  char *layout, *layoutHead;    char map[ MAX_QPATH ]; -  int buildable = BA_NONE; +  char buildName[ MAX_TOKEN_CHARS ]; +  int buildable;    vec3_t origin = { 0.0f, 0.0f, 0.0f };    vec3_t angles = { 0.0f, 0.0f, 0.0f };    vec3_t origin2 = { 0.0f, 0.0f, 0.0f };    vec3_t angles2 = { 0.0f, 0.0f, 0.0f };    char line[ MAX_STRING_CHARS ]; -  int i = 0; +  int i; -  if( !level.layout[ 0 ] || !Q_stricmp( level.layout, "*BUILTIN*" ) ) +  if( !lstr[ 0 ] || !Q_stricmp( lstr, "*BUILTIN*" ) )      return; -  + +  loadAnotherLayout: +  lstrPlusPtr = strchr( lstr, '+' ); +  if( lstrPlusPtr ) +    *lstrPlusPtr = '\0'; + +  if( ( lstrPipePtr = strchr( lstr, '|' ) ) ) +  { +    int bList[ BA_NUM_BUILDABLES + NUM_TEAMS ]; +    *lstrPipePtr = '\0'; +    G_ParseCSVBuildablePlusList( lstr, &bList[ 0 ], sizeof( bList ) / sizeof( bList[ 0 ] ) ); +    memset( bAllowed, 0, sizeof( bAllowed ) ); +    for( i = 0; bList[ i ] != BA_NONE; i++ ) +      bAllowed[ bList[ i ] ] = qtrue; +    *lstrPipePtr = '|'; +    lstr = lstrPipePtr + 1; +  } +  else +    bAllowed[ BA_NONE ] = qtrue; // allow all +    trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); -  len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, level.layout ), +  len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, lstr ),      &f, FS_READ );    if( len < 0 )    { -    G_Printf( "ERROR: layout %s could not be opened\n", level.layout ); +    G_Printf( "ERROR: layout %s could not be opened\n", lstr );      return;    } -  layout = G_Alloc( len + 1 ); +  layoutHead = layout = BG_Alloc( len + 1 );    trap_FS_Read( layout, len, f ); -  *( layout + len ) = '\0'; +  layout[ len ] = '\0';    trap_FS_FCloseFile( f ); +  i = 0;    while( *layout )    {      if( i >= sizeof( line ) - 1 )      {        G_Printf( S_COLOR_RED "ERROR: line overflow in %s before \"%s\"\n", -       va( "layouts/%s/%s.dat", map, level.layout ), line ); -      return;  +       va( "layouts/%s/%s.dat", map, lstr ), line ); +      break;      }      line[ i++ ] = *layout;      line[ i ] = '\0';      if( *layout == '\n' )      { -      i = 0;  -      sscanf( line, "%d %f %f %f %f %f %f %f %f %f %f %f %f\n", -        &buildable, +      i = 0; +      sscanf( line, "%s %f %f %f %f %f %f %f %f %f %f %f %f\n", +        buildName,          &origin[ 0 ], &origin[ 1 ], &origin[ 2 ],          &angles[ 0 ], &angles[ 1 ], &angles[ 2 ],          &origin2[ 0 ], &origin2[ 1 ], &origin2[ 2 ],          &angles2[ 0 ], &angles2[ 1 ], &angles2[ 2 ] ); -      if( buildable > BA_NONE && buildable < BA_NUM_BUILDABLES ) -        G_LayoutBuildItem( buildable, origin, angles, origin2, angles2 ); +      buildable = BG_BuildableByName( buildName )->number; +      if( !Q_stricmp( buildName, "ivo_spectator" ) ) +      { +        if( bAllowed[ BA_NONE ] || bAllowed[ BA_NUM_BUILDABLES + TEAM_NONE ] ) +          G_SpawnIntermissionViewOverride( "info_player_intermission", origin, angles ); +      } +      else if( !Q_stricmp( buildName, "ivo_alien" ) ) +      { +        if( bAllowed[ BA_NONE ] || bAllowed[ BA_NUM_BUILDABLES + TEAM_ALIENS ] ) +          G_SpawnIntermissionViewOverride( "info_alien_intermission", origin, angles ); +      } +      else if( !Q_stricmp( buildName, "ivo_human" ) ) +      { +        if( bAllowed[ BA_NONE ] || bAllowed[ BA_NUM_BUILDABLES + TEAM_HUMANS ] ) +          G_SpawnIntermissionViewOverride( "info_human_intermission", origin, angles ); +      } +      else if( buildable <= BA_NONE || buildable >= BA_NUM_BUILDABLES ) +        G_Printf( S_COLOR_YELLOW "WARNING: bad buildable name (%s) in layout." +          " skipping\n", buildName );        else -        G_Printf( S_COLOR_YELLOW "WARNING: bad buildable number (%d) in " -          " layout.  skipping\n", buildable ); +      { +        if( bAllowed[ BA_NONE ] || bAllowed[ buildable ] ) +          G_LayoutBuildItem( buildable, origin, angles, origin2, angles2 ); +      }      }      layout++;    } +  BG_Free( layoutHead ); + +  if( lstrPlusPtr ) +  { +    *lstrPlusPtr = '+'; +    lstr = lstrPlusPtr + 1; +    goto loadAnotherLayout; +  }  } -void G_BaseSelfDestruct( pTeam_t team ) +/* +============ +G_BaseSelfDestruct +============ +*/ +void G_BaseSelfDestruct( team_t team )  {    int       i;    gentity_t *ent; @@ -4550,192 +4419,274 @@ void G_BaseSelfDestruct( pTeam_t team )        continue;      if( ent->s.eType != ET_BUILDABLE )        continue; -    if( team == PTE_HUMANS && ent->biteam != BIT_HUMANS ) -      continue; -    if( team == PTE_ALIENS && ent->biteam != BIT_ALIENS ) +    if( ent->buildableTeam != team )        continue;      G_Damage( ent, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE );    }  } - int G_LogBuild( buildHistory_t *new ) - {  -   new->next = level.buildHistory; -   level.buildHistory = new; -   return G_CountBuildLog(); - } -  - int G_CountBuildLog( void ) - { -   buildHistory_t *ptr, *mark; -   int i = 0, overflow; -   for( ptr = level.buildHistory; ptr; ptr = ptr->next, i++ ); -   if( i > g_buildLogMaxLength.integer ) -   { -     for( overflow = i - g_buildLogMaxLength.integer; overflow > 0; overflow-- ) -     { -       ptr = level.buildHistory; -       while( ptr->next ) -       { -     if( ptr->next->next ) -       ptr = ptr->next; -     else -     { -       while( ( mark = ptr->next ) ) -       { -         ptr->next = ptr->next->marked; -             G_Free( mark ); -       } -     } -       } -     } -     return g_buildLogMaxLength.integer; -   } -   return i; - } -  - char *G_FindBuildLogName( int id ) - { -   buildHistory_t *ptr; -  -   for( ptr = level.buildHistory; ptr && ptr->ID != id; ptr = ptr->next ); -   if( ptr ) -   { -     if( ptr->ent ) -     { -       if( ptr->ent->client ) -         return ptr->ent->client->pers.netname; -     } -     else if( ptr->name[ 0 ] ) -     { -       return ptr->name; -     } -   } -  -   return "<buildlog entry expired>"; - } -   /*  ============ -G_NobuildLoad - -load the nobuild markers that were previously saved (if there are any). +build log  ============  */ -void G_NobuildLoad( void ) +buildLog_t *G_BuildLogNew( gentity_t *actor, buildFate_t fate )  { -  fileHandle_t f; -  int len; -  char *nobuild; -  char map[ MAX_QPATH ]; -  vec3_t origin = { 0.0f, 0.0f, 0.0f }; -  char line[ MAX_STRING_CHARS ]; -  int i = 0; -  gentity_t *nb; -  float area; -  float height; +  buildLog_t *log = &level.buildLog[ level.buildId++ % MAX_BUILDLOG ]; + +  if( level.numBuildLogs < MAX_BUILDLOG ) +    level.numBuildLogs++; +  log->time = level.time; +  log->fate = fate; +  log->actor = actor && actor->client ? actor->client->pers.namelog : NULL; +  return log; +} -  trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); -  len = trap_FS_FOpenFile( va( "nobuild/%s.dat", map ), -    &f, FS_READ ); -  if( len < 0 ) +void G_BuildLogSet( buildLog_t *log, gentity_t *ent ) +{ +  log->modelindex = ent->s.modelindex; +  log->deconstruct = ent->deconstruct; +  log->deconstructTime = ent->deconstructTime; +  log->builtBy = ent->builtBy; +  VectorCopy( ent->r.currentOrigin, log->origin ); +  VectorCopy( ent->r.currentAngles, log->angles ); +  VectorCopy( ent->s.origin2, log->origin2 ); +  VectorCopy( ent->s.angles2, log->angles2 ); +  log->powerSource = ent->parentNode ? ent->parentNode->s.modelindex : BA_NONE; +  log->powerValue = G_QueueValue( ent ); +} + +void G_BuildLogAuto( gentity_t *actor, gentity_t *buildable, buildFate_t fate ) +{ +  G_BuildLogSet( G_BuildLogNew( actor, fate ), buildable ); +} + +void G_BuildLogRevertThink( gentity_t *ent ) +{ +  gentity_t *built; +  vec3_t    mins, maxs; +  int       blockers[ MAX_GENTITIES ]; +  int       num; +  int       victims = 0; +  int       i; + +  if( ent->suicideTime > 0 )    { -    // This isn't needed since nobuild is pretty much optional... -    //G_Printf( "ERROR: nobuild for %s could not be opened\n", map ); -    return; +    BG_BuildableBoundingBox( ent->s.modelindex, mins, maxs ); +    VectorAdd( ent->s.pos.trBase, mins, mins ); +    VectorAdd( ent->s.pos.trBase, maxs, maxs ); +    num = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); +    for( i = 0; i < num; i++ ) +    { +      gentity_t *targ; +      vec3_t    push; + +      targ = g_entities + blockers[ i ]; +      if( targ->client ) +      { +        float val = ( targ->client->ps.eFlags & EF_WALLCLIMB) ? 300.0 : 150.0; + +        VectorSet( push, crandom() * val, crandom() * val, random() * val ); +        VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity ); +        victims++; +      } +    } + +    ent->suicideTime--; + +    if( victims ) +    { +      // still a blocker +      ent->nextthink = level.time + FRAMETIME; +      return; +    }    } -  nobuild = G_Alloc( len + 1 ); -  trap_FS_Read( nobuild, len, f ); -  *( nobuild + len ) = '\0'; -  trap_FS_FCloseFile( f ); -  while( *nobuild ) + +  built = G_FinishSpawningBuildable( ent, qtrue ); +  built->buildTime = built->s.time = 0; +  G_KillBox( built ); + +  G_LogPrintf( "revert: restore %d %s\n", +    (int)( built - g_entities ), BG_Buildable( built->s.modelindex )->name ); + +  G_BuildableThink( built, 0 ); +} + +void G_BuildLogRevert( int id ) +{ +  buildLog_t *log; +  gentity_t  *ent; +  int        i; +  vec3_t     dist; + +  level.numBuildablesForRemoval = 0; + +  level.numBuildLogs -= level.buildId - id; +  while( level.buildId > id )    { -    if( i >= sizeof( line ) - 1 ) +    log = &level.buildLog[ --level.buildId % MAX_BUILDLOG ]; +    if( log->fate == BF_CONSTRUCT )      { -      return;  +      for( i = MAX_CLIENTS; i < level.num_entities; i++ ) +      { +        ent = &g_entities[ i ]; +        if( ( ( ent->s.eType == ET_BUILDABLE && +                ent->health > 0 ) || +              ( ent->s.eType == ET_GENERAL && +                ent->think == G_BuildLogRevertThink ) ) && +            ent->s.modelindex == log->modelindex ) +        { +          VectorSubtract( ent->s.pos.trBase, log->origin, dist ); +          if( VectorLengthSquared( dist ) <= 2.0f ) +          { +            if( ent->s.eType == ET_BUILDABLE ) +              G_LogPrintf( "revert: remove %d %s\n", +                (int)( ent - g_entities ), BG_Buildable( ent->s.modelindex )->name ); +            G_RemoveRangeMarkerFrom( ent ); +            G_FreeEntity( ent ); +            break; +          } +        } +      }      } -     -    line[ i++ ] = *nobuild; -    line[ i ] = '\0'; -    if( *nobuild == '\n' ) +    else      { -      i = 0;  -      sscanf( line, "%f %f %f %f %f\n", -        &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], &area, &height  ); - -	// Make the marker... -	nb = G_Spawn( ); -	nb->s.modelindex = 0; -	VectorCopy( origin, nb->s.pos.trBase ); -	VectorCopy( origin, nb->r.currentOrigin ); -	nb->noBuild.isNB = qtrue; -	nb->noBuild.Area = area; -	nb->noBuild.Height = height; -	trap_LinkEntity( nb ); -	 -	// Log markers made... -	for( i = 0; i < MAX_GENTITIES; i++ ) -	{ -		if( level.nbMarkers[ i ].Marker != NULL ) -		continue; -		 -		level.nbMarkers[ i ].Marker = nb; -		VectorCopy( origin, level.nbMarkers[ i ].Origin ); -		SnapVector( level.nbMarkers[ i ].Origin ); -		break; -	} -       +      gentity_t  *builder = G_Spawn(); +      builder->classname = "builder"; + +      VectorCopy( log->origin, builder->r.currentOrigin ); +      VectorCopy( log->angles, builder->r.currentAngles ); +      VectorCopy( log->origin2, builder->s.origin2 ); +      VectorCopy( log->angles2, builder->s.angles2 ); +      builder->s.modelindex = log->modelindex; +      builder->deconstruct = log->deconstruct; +      builder->deconstructTime = log->deconstructTime; +      builder->builtBy = log->builtBy; + +      builder->think = G_BuildLogRevertThink; +      builder->nextthink = level.time + FRAMETIME; + +      // Number of thinks before giving up and killing players in the way +      builder->suicideTime = 30;  + +      if( log->fate == BF_DESTROY || log->fate == BF_TEAMKILL ) +      { +        int value = log->powerValue; + +        if( BG_Buildable( log->modelindex )->team == TEAM_ALIENS ) +        { +          level.alienBuildPointQueue = +            MAX( 0, level.alienBuildPointQueue - value ); +        } +        else +        { +          if( log->powerSource == BA_H_REACTOR ) +          { +            level.humanBuildPointQueue = +              MAX( 0, level.humanBuildPointQueue - value ); +          } +          else if( log->powerSource == BA_H_REPEATER ) +          { +            gentity_t        *source; +            buildPointZone_t *zone; + +            source = G_PowerEntityForPoint( log->origin ); +            if( source && source->usesBuildPointZone ) +            { +              zone = &level.buildPointZones[ source->buildPointZone ]; +              zone->queuedBuildPoints = +                MAX( 0, zone->queuedBuildPoints - value ); +            } +          } +        } +      }      } -    nobuild++;    }  }  /* -============ -G_NobuildSave -Save all currently placed nobuild markers into the "nobuild" folder -============ +================ +G_UpdateBuildableRangeMarkers +================  */ -void G_NobuildSave( void ) +void G_UpdateBuildableRangeMarkers( void )  { -  char map[ MAX_QPATH ]; -  char fileName[ MAX_OSPATH ]; -  fileHandle_t f; -  int len; -  int i; -  gentity_t *ent; -  char *s; +  // is the entity 64-bit client-masking extension available? +  qboolean maskingExtension = ( trap_Cvar_VariableIntegerValue( "sv_gppExtension" ) >= 1 ); -  trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); -  if( !map[ 0 ] ) +  gentity_t *e; +  for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e )    { -    G_Printf( "NobuildSave( ): no map is loaded\n" ); -    return; -  } -  Com_sprintf( fileName, sizeof( fileName ), "nobuild/%s.dat", map ); +    buildable_t bType; +    team_t bTeam; +    int i; -  len = trap_FS_FOpenFile( fileName, &f, FS_WRITE ); -  if( len < 0 ) -  { -    G_Printf( "nobuildsave: could not open %s\n", fileName ); -    return; -  } +    if( e->s.eType != ET_BUILDABLE || !e->rangeMarker ) +      continue; -  G_Printf("nobuildsave: saving nobuild to %s\n", fileName ); +    bType = e->s.modelindex; +    bTeam = BG_Buildable( bType )->team; -  for( i = 0; i < MAX_GENTITIES; i++ ) -  { -    ent = &level.gentities[ i ]; -    if( ent->noBuild.isNB != qtrue ) -      continue; +    e->rangeMarker->s.pos = e->s.pos; +    if( bType == BA_A_HIVE || bType == BA_H_TESLAGEN ) +      VectorMA( e->s.pos.trBase, e->r.maxs[ 2 ], e->s.origin2, e->rangeMarker->s.pos.trBase ); +    else if( bType == BA_A_TRAPPER || bType == BA_H_MGTURRET ) +      vectoangles( e->s.origin2, e->rangeMarker->s.apos.trBase ); -    s = va( "%f %f %f %f %f\n", -      ent->r.currentOrigin[ 0 ], -      ent->r.currentOrigin[ 1 ], -      ent->r.currentOrigin[ 2 ], -      ent->noBuild.Area, -      ent->noBuild.Height ); -    trap_FS_Write( s, strlen( s ), f ); +    e->rangeMarker->r.singleClient = 0; +    e->rangeMarker->r.hack.generic1 = 0; + +    // remove any previously added NOCLIENT flags from the hack below +    e->rangeMarker->r.svFlags &= ~SVF_NOCLIENT; + +    for( i = 0; i < level.maxclients; ++i ) +    { +      gclient_t *client; +      team_t team; +      qboolean weaponDisplays, wantsToSee; + +      client = &level.clients[ i ]; +      if( client->pers.connected != CON_CONNECTED ) +        continue; + +      if( i >= 32 && !maskingExtension ) +      { +        // resort to not sending range markers at all +        if( !trap_Cvar_VariableIntegerValue( "g_rangeMarkerWarningGiven" ) ) +        { +          trap_SendServerCommand( -1, "print \"" S_COLOR_YELLOW "WARNING: There is no " +            "support for entity 64-bit client-masking on this server. Please update " +            "your server executable. Until then, range markers will not be displayed " +            "while there are clients with client numbers above 31 in the game.\n\"" ); +          trap_Cvar_Set( "g_rangeMarkerWarningGiven", "1" ); +        } + +        for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) +        { +          if( e->s.eType == ET_BUILDABLE && e->rangeMarker ) +            e->rangeMarker->r.svFlags |= SVF_NOCLIENT; +        } + +        return; +      } + +      team = client->pers.teamSelection; +      if( team != TEAM_NONE ) +      { +        weaponDisplays = ( BG_InventoryContainsWeapon( WP_HBUILD, client->ps.stats ) || +          client->ps.weapon == WP_ABUILD || client->ps.weapon == WP_ABUILD2 ); +      } +      wantsToSee = !!( client->pers.buildableRangeMarkerMask & ( 1 << bType ) ); + +      if( ( team == TEAM_NONE || ( team == bTeam && weaponDisplays ) ) && wantsToSee ) +      { +        if( i >= 32 ) +          e->rangeMarker->r.hack.generic1 |= 1 << ( i - 32 ); +        else +          e->rangeMarker->r.singleClient |= 1 << i; +      } +    } + +    trap_LinkEntity( e->rangeMarker );    } -  trap_FS_FCloseFile( f );  } diff --git a/src/game/g_client.c b/src/game/g_client.c index c77d4f7..3481af9 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -81,156 +82,35 @@ void SP_info_human_intermission( gentity_t *ent )  /*  =============== -G_OverflowCredits -=============== -*/ -void G_OverflowCredits( gclient_t *doner, int credits ) -{ -  int i; -  int maxCredits; -  int clientNum; - -  if( !g_creditOverflow.integer ) -    return; - -  if( doner->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -  { -    maxCredits = ALIEN_MAX_KILLS; -    clientNum = level.lastCreditedAlien; -  } -  else if( doner->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -  { -    maxCredits = HUMAN_MAX_CREDITS; -    clientNum = level.lastCreditedHuman; -  } -  else -  { -    return; -  } - -  if( g_creditOverflow.integer == 1 ) -  { -    // distribute to everyone on team -    gentity_t *vic; - -    i = 0; -    while( credits > 0 && i < level.maxclients ) -    { -      i++; -      clientNum++; -      if( clientNum >= level.maxclients ) -        clientNum = 0; - -      vic = &g_entities[ clientNum ]; -      if( vic->client->ps.stats[ STAT_PTEAM ] != doner->ps.stats[ STAT_PTEAM ] || -          vic->client->ps.persistant[ PERS_CREDIT ] >= maxCredits ) -        continue; - -      if( vic->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -        level.lastCreditedAlien = clientNum; -      else -        level.lastCreditedHuman = clientNum; - -      if( vic->client->ps.persistant[ PERS_CREDIT ] + credits > maxCredits ) -      { -        credits -= maxCredits - vic->client->ps.persistant[ PERS_CREDIT ]; -        vic->client->ps.persistant[ PERS_CREDIT ] = maxCredits; -      } -      else -      { -        vic->client->ps.persistant[ PERS_CREDIT ] += credits; -        return; -      } -    } -  } -  else if( g_creditOverflow.integer == 2 ) -  { -    // distribute by team rank -    gclient_t *cl; - -    for( i = 0; i < level.numPlayingClients && credits > 0; i++ ) -    { -      // get the client list sorted by rank -      cl = &level.clients[ level.sortedClients[ i ] ]; -      if( cl->ps.stats[ STAT_PTEAM ] != doner->ps.stats[ STAT_PTEAM ] || -          cl->ps.persistant[ PERS_CREDIT ] >= maxCredits ) -        continue; - -      if( cl->ps.persistant[ PERS_CREDIT ] + credits > maxCredits ) -      { -        credits -= maxCredits - cl->ps.persistant[ PERS_CREDIT ]; -        cl->ps.persistant[ PERS_CREDIT ] = maxCredits; -      } -      else -      { -        cl->ps.persistant[ PERS_CREDIT ] += credits; -        return; -      } -    } -  } -} - -/* -===============  G_AddCreditToClient  ===============  */  void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap )  { +  int capAmount; +    if( !client )      return; -  //if we're already at the max and trying to add credit then stop -  if( cap ) -  { -    if( client->pers.teamSelection == PTE_ALIENS ) -    { -      if( client->pers.credit >= ALIEN_MAX_KILLS && -          credit > 0 ) -      { -        G_OverflowCredits( client, credit ); -        return; -      } -    } -    else if( client->pers.teamSelection == PTE_HUMANS ) -    { -      if( client->pers.credit >= HUMAN_MAX_CREDITS && -          credit > 0 ) -      { -        G_OverflowCredits( client, credit ); -        return; -      } -    } -  } - -  client->pers.credit += credit; - -  if( cap ) +  if( cap && credit > 0 )    { -    if( client->pers.teamSelection == PTE_ALIENS ) +    capAmount = client->pers.teamSelection == TEAM_ALIENS ? +                ALIEN_MAX_CREDITS : HUMAN_MAX_CREDITS; +    if( client->pers.credit < capAmount )      { -      if( client->pers.credit > ALIEN_MAX_KILLS ) -      { -        G_OverflowCredits( client, client->ps.persistant[ PERS_CREDIT ] - ALIEN_MAX_KILLS ); -        client->pers.credit = ALIEN_MAX_KILLS; -      } -    } -    else if( client->pers.teamSelection == PTE_HUMANS ) -    { -      if( client->pers.credit > HUMAN_MAX_CREDITS ) -      { -        G_OverflowCredits( client, client->ps.persistant[ PERS_CREDIT ] - HUMAN_MAX_CREDITS ); -        client->pers.credit = HUMAN_MAX_CREDITS; -      } +      client->pers.credit += credit; +      if( client->pers.credit > capAmount ) +        client->pers.credit = capAmount;      }    } +  else +    client->pers.credit += credit;    if( client->pers.credit < 0 )      client->pers.credit = 0; -  // keep PERS_CREDIT in sync if not following  -  if( client->sess.spectatorState != SPECTATOR_FOLLOW ) -    client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; +  // Copy to ps so the client can access it +  client->ps.persistant[ PERS_CREDIT ] = client->pers.credit;  } @@ -255,8 +135,8 @@ qboolean SpotWouldTelefrag( gentity_t *spot )    gentity_t *hit;    vec3_t    mins, maxs; -  VectorAdd( spot->s.origin, playerMins, mins ); -  VectorAdd( spot->s.origin, playerMaxs, maxs ); +  VectorAdd( spot->r.currentOrigin, playerMins, mins ); +  VectorAdd( spot->r.currentOrigin, playerMaxs, maxs );    num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );    for( i = 0; i < num; i++ ) @@ -270,75 +150,6 @@ qboolean SpotWouldTelefrag( gentity_t *spot )    return qfalse;  } -/* -================ -G_SelectNearestDeathmatchSpawnPoint - -Find the spot that we DON'T want to use -================ -*/ -#define MAX_SPAWN_POINTS  128 -gentity_t *G_SelectNearestDeathmatchSpawnPoint( vec3_t from ) -{ -  gentity_t *spot; -  vec3_t    delta; -  float     dist, nearestDist; -  gentity_t *nearestSpot; - -  nearestDist = 999999; -  nearestSpot = NULL; -  spot = NULL; - -  while( (spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) -  { -    VectorSubtract( spot->s.origin, from, delta ); -    dist = VectorLength( delta ); - -    if( dist < nearestDist ) -    { -      nearestDist = dist; -      nearestSpot = spot; -    } -  } - -  return nearestSpot; -} - - -/* -================ -G_SelectRandomDeathmatchSpawnPoint - -go to a random point that doesn't telefrag -================ -*/ -#define MAX_SPAWN_POINTS  128 -gentity_t *G_SelectRandomDeathmatchSpawnPoint( void ) -{ -  gentity_t *spot; -  int       count; -  int       selection; -  gentity_t *spots[ MAX_SPAWN_POINTS ]; - -  count = 0; -  spot = NULL; - -  while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) -  { -    if( SpotWouldTelefrag( spot ) ) -      continue; - -    spots[ count ] = spot; -    count++; -  } - -  if( !count ) // no spots that won't telefrag -    return G_Find( NULL, FOFS( classname ), "info_player_deathmatch" ); - -  selection = rand( ) % count; -  return spots[ selection ]; -} -  /*  =========== @@ -347,7 +158,7 @@ G_SelectRandomFurthestSpawnPoint  Chooses a player start, deathmatch start, etc  ============  */ -gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) +static gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles )  {    gentity_t *spot;    vec3_t    delta; @@ -364,7 +175,7 @@ gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin,      if( SpotWouldTelefrag( spot ) )        continue; -    VectorSubtract( spot->s.origin, avoidPoint, delta ); +    VectorSubtract( spot->r.currentOrigin, avoidPoint, delta );      dist = VectorLength( delta );      for( i = 0; i < numSpots; i++ ) @@ -406,18 +217,18 @@ gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin,      if( !spot )        G_Error( "Couldn't find a spawn point" ); -    VectorCopy( spot->s.origin, origin ); +    VectorCopy( spot->r.currentOrigin, origin );      origin[ 2 ] += 9; -    VectorCopy( spot->s.angles, angles ); +    VectorCopy( spot->r.currentAngles, angles );      return spot;    }    // select a random spot from the spawn points furthest away    rnd = random( ) * ( numSpots / 2 ); -  VectorCopy( list_spot[ rnd ]->s.origin, origin ); +  VectorCopy( list_spot[ rnd ]->r.currentOrigin, origin );    origin[ 2 ] += 9; -  VectorCopy( list_spot[ rnd ]->s.angles, angles ); +  VectorCopy( list_spot[ rnd ]->r.currentAngles, angles );    return list_spot[ rnd ];  } @@ -425,102 +236,45 @@ gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin,  /*  ================ -G_SelectAlienSpawnPoint - -go to a random point that doesn't telefrag -================ -*/ -gentity_t *G_SelectAlienSpawnPoint( vec3_t preference ) -{ -  gentity_t *spot; -  int       count; -  gentity_t *spots[ MAX_SPAWN_POINTS ]; - -  if( level.numAlienSpawns <= 0 ) -    return NULL; - -  count = 0; -  spot = NULL; - -  while( ( spot = G_Find( spot, FOFS( classname ), -    BG_FindEntityNameForBuildable( BA_A_SPAWN ) ) ) != NULL ) -  { -    if( !spot->spawned ) -      continue; - -    if( spot->health <= 0 ) -      continue; - -    if( !spot->s.groundEntityNum ) -      continue; - -    if( spot->clientSpawnTime > 0 ) -      continue; - -    if( G_CheckSpawnPoint( spot->s.number, spot->s.origin, -          spot->s.origin2, BA_A_SPAWN, NULL ) != NULL ) -      continue; - -    spots[ count ] = spot; -    count++; -  } - -  if( !count ) -    return NULL; - -  return G_ClosestEnt( preference, spots, count ); -} - - -/* -================ -G_SelectHumanSpawnPoint +G_SelectSpawnBuildable -go to a random point that doesn't telefrag +find the nearest buildable of the right type that is +spawned/healthy/unblocked etc.  ================  */ -gentity_t *G_SelectHumanSpawnPoint( vec3_t preference ) +static gentity_t *G_SelectSpawnBuildable( vec3_t preference, buildable_t buildable )  { -  gentity_t *spot; -  int       count; -  gentity_t *spots[ MAX_SPAWN_POINTS ]; - -  if( level.numHumanSpawns <= 0 ) -    return NULL; +  gentity_t *search, *spot; -  count = 0; -  spot = NULL; +  search = spot = NULL; -  while( ( spot = G_Find( spot, FOFS( classname ), -    BG_FindEntityNameForBuildable( BA_H_SPAWN ) ) ) != NULL ) +  while( ( search = G_Find( search, FOFS( classname ), +    BG_Buildable( buildable )->entityName ) ) != NULL )    { -    if( !spot->spawned ) +    if( !search->spawned )        continue; -    if( spot->health <= 0 ) +    if( search->health <= 0 )        continue; -    if( !spot->s.groundEntityNum ) +    if( search->s.groundEntityNum == ENTITYNUM_NONE )        continue; -    if( spot->clientSpawnTime > 0 ) +    if( search->clientSpawnTime > 0 )        continue; -    if( G_CheckSpawnPoint( spot->s.number, spot->s.origin, -          spot->s.origin2, BA_H_SPAWN, NULL ) != NULL ) +    if( G_CheckSpawnPoint( search->s.number, search->r.currentOrigin, +          search->s.origin2, buildable, NULL ) != NULL )        continue; -    spots[ count ] = spot; -    count++; +    if( !spot || DistanceSquared( preference, search->r.currentOrigin ) < +                 DistanceSquared( preference, spot->r.currentOrigin ) ) +      spot = search;    } -  if( !count ) -    return NULL; - -  return G_ClosestEnt( preference, spots, count ); +  return spot;  } -  /*  ===========  G_SelectSpawnPoint @@ -541,25 +295,35 @@ G_SelectTremulousSpawnPoint  Chooses a player start, deathmatch start, etc  ============  */ -gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles ) +gentity_t *G_SelectTremulousSpawnPoint( team_t team, vec3_t preference, vec3_t origin, vec3_t angles )  {    gentity_t *spot = NULL; -  if( team == PTE_ALIENS ) -    spot = G_SelectAlienSpawnPoint( preference ); -  else if( team == PTE_HUMANS ) -    spot = G_SelectHumanSpawnPoint( preference ); +  if( team == TEAM_ALIENS ) +  { +    if( level.numAlienSpawns <= 0 ) +      return NULL; + +    spot = G_SelectSpawnBuildable( preference, BA_A_SPAWN ); +  } +  else if( team == TEAM_HUMANS ) +  { +    if( level.numHumanSpawns <= 0 ) +      return NULL; + +    spot = G_SelectSpawnBuildable( preference, BA_H_SPAWN ); +  }    //no available spots    if( !spot )      return NULL; -  if( team == PTE_ALIENS ) -    G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_A_SPAWN, origin ); -  else if( team == PTE_HUMANS ) -    G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_H_SPAWN, origin ); +  if( team == TEAM_ALIENS ) +    G_CheckSpawnPoint( spot->s.number, spot->r.currentOrigin, spot->s.origin2, BA_A_SPAWN, origin ); +  else if( team == TEAM_HUMANS ) +    G_CheckSpawnPoint( spot->s.number, spot->r.currentOrigin, spot->s.origin2, BA_H_SPAWN, origin ); -  VectorCopy( spot->s.angles, angles ); +  VectorCopy( spot->r.currentAngles, angles );    angles[ ROLL ] = 0;    return spot; @@ -569,42 +333,11 @@ gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t  /*  =========== -G_SelectInitialSpawnPoint - -Try to find a spawn point marked 'initial', otherwise -use normal spawn selection. -============ -*/ -gentity_t *G_SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) -{ -  gentity_t *spot; - -  spot = NULL; -  while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) -  { -    if( spot->spawnflags & 1 ) -      break; -  } - -  if( !spot || SpotWouldTelefrag( spot ) ) -  { -    return G_SelectSpawnPoint( vec3_origin, origin, angles ); -  } - -  VectorCopy( spot->s.origin, origin ); -  origin[ 2 ] += 9; -  VectorCopy( spot->s.angles, angles ); - -  return spot; -} - -/* -===========  G_SelectSpectatorSpawnPoint  ============  */ -gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) +static gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles )  {    FindIntermissionPoint( ); @@ -633,8 +366,8 @@ gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles )    if( !spot )      return G_SelectSpectatorSpawnPoint( origin, angles ); -  VectorCopy( spot->s.origin, origin ); -  VectorCopy( spot->s.angles, angles ); +  VectorCopy( spot->r.currentOrigin, origin ); +  VectorCopy( spot->r.currentAngles, angles );    return spot;  } @@ -658,8 +391,8 @@ gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles )    if( !spot )      return G_SelectSpectatorSpawnPoint( origin, angles ); -  VectorCopy( spot->s.origin, origin ); -  VectorCopy( spot->s.angles, angles ); +  VectorCopy( spot->r.currentOrigin, origin ); +  VectorCopy( spot->r.currentAngles, angles );    return spot;  } @@ -681,7 +414,7 @@ BodySink  After sitting around for five seconds, fall into the ground and dissapear  =============  */ -void BodySink( gentity_t *ent ) +static void BodySink( gentity_t *ent )  {    //run on first BodySink call    if( !ent->active ) @@ -706,40 +439,17 @@ void BodySink( gentity_t *ent )  /*  ============= -BodyFree - -After sitting around for a while the body becomes a freebie -============= -*/ -void BodyFree( gentity_t *ent ) -{ -  ent->killedBy = -1; - -  //if not claimed in the next minute destroy -  ent->think = BodySink; -  ent->nextthink = level.time + 60000; -} - - -/* -=============  SpawnCorpse  A player is respawning, so make an entity that looks  just like the existing corpse to leave behind.  =============  */ -void SpawnCorpse( gentity_t *ent ) +static void SpawnCorpse( gentity_t *ent )  {    gentity_t   *body;    int         contents; -  vec3_t      origin, dest; -  trace_t     tr; -  float       vDiff; - -  // prevent crashing everyone with bad corpsenum bug -  if( ent->client->pers.connected != CON_CONNECTED ) -    return; +  vec3_t      origin, mins;    VectorCopy( ent->r.currentOrigin, origin ); @@ -752,17 +462,18 @@ void SpawnCorpse( gentity_t *ent )    body = G_Spawn( ); -  VectorCopy( ent->s.apos.trBase, body->s.angles ); +  VectorCopy( ent->s.apos.trBase, body->s.apos.trBase ); +  VectorCopy( ent->s.apos.trBase, body->r.currentAngles );    body->s.eFlags = EF_DEAD;    body->s.eType = ET_CORPSE; -  body->s.number = body - g_entities;    body->timestamp = level.time;    body->s.event = 0;    body->r.contents = CONTENTS_CORPSE; -  body->s.clientNum = ent->client->ps.stats[ STAT_PCLASS ]; +  body->clipmask = MASK_DEADSOLID; +  body->s.clientNum = ent->client->ps.stats[ STAT_CLASS ];    body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; -  if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +  if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )      body->classname = "humanCorpse";    else      body->classname = "alienCorpse"; @@ -815,25 +526,19 @@ void SpawnCorpse( gentity_t *ent )    body->takedamage = qfalse; -  body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; -  ent->health = 0; +  body->health = ent->health;    //change body dimensions -  BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs ); -  vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ]; +  BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], mins, NULL, NULL, body->r.mins, body->r.maxs );    //drop down to match the *model* origins of ent and body -  VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff ); -  trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask ); -  VectorCopy( tr.endpos, origin ); +  origin[2] += mins[ 2 ] - body->r.mins[ 2 ];    G_SetOrigin( body, origin ); -  VectorCopy( origin, body->s.origin );    body->s.pos.trType = TR_GRAVITY;    body->s.pos.trTime = level.time;    VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); -  VectorCopy ( body->s.pos.trBase, body->r.currentOrigin );    trap_LinkEntity( body );  } @@ -846,7 +551,7 @@ G_SetClientViewAngle  ==================  */ -void G_SetClientViewAngle( gentity_t *ent, vec3_t angle ) +void G_SetClientViewAngle( gentity_t *ent, const vec3_t angle )  {    int     i; @@ -859,8 +564,9 @@ void G_SetClientViewAngle( gentity_t *ent, vec3_t angle )      ent->client->ps.delta_angles[ i ] = cmdAngle - ent->client->pers.cmd.angles[ i ];    } -  VectorCopy( angle, ent->s.angles ); -  VectorCopy( ent->s.angles, ent->client->ps.viewangles ); +  VectorCopy( angle, ent->s.apos.trBase ); +  VectorCopy( angle, ent->r.currentAngles ); +  VectorCopy( angle, ent->client->ps.viewangles );  }  /* @@ -870,52 +576,79 @@ respawn  */  void respawn( gentity_t *ent )  { +  int i; +    SpawnCorpse( ent ); -  //TA: Clients can't respawn - they must go thru the class cmd +  // Clients can't respawn - they must go through the class cmd    ent->client->pers.classSelection = PCL_NONE;    ClientSpawn( ent, NULL, NULL, NULL ); -} -/* -================ -TeamCount +  // stop any following clients that don't have sticky spec on +  for( i = 0; i < level.maxclients; i++ ) +  { +    if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && +        level.clients[ i ].sess.spectatorClient == ent - g_entities ) +    { +      if( !( level.clients[ i ].pers.stickySpec ) ) +      { +        if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) +          G_StopFollowing( &g_entities[ i ] ); +      } +      else +        G_FollowLockView( &g_entities[ i ] ); +    } +  } +} -Returns number of players on a team -================ -*/ -team_t TeamCount( int ignoreClientNum, int team ) +static qboolean G_IsEmoticon( const char *s, qboolean *escaped )  { -  int   i; -  int   count = 0; +  int i, j; +  const char *p = s; +  char emoticon[ MAX_EMOTICON_NAME_LEN ] = {""}; +  qboolean escape = qfalse; -  for( i = 0 ; i < level.maxclients ; i++ ) +  if( *p != '[' ) +    return qfalse; +  p++; +  if( *p == '[' )    { -    if( i == ignoreClientNum ) -      continue; - -    if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) -      continue; - -    if( level.clients[ i ].sess.sessionTeam == team ) -      count++; +    escape = qtrue; +    p++;    } - -  return count; +  i = 0; +  while( *p && i < ( MAX_EMOTICON_NAME_LEN - 1 ) ) +  { +    if( *p == ']' ) +    { +      for( j = 0; j < level.emoticonCount; j++ ) +      { +        if( !Q_stricmp( emoticon, level.emoticons[ j ].name ) ) +        { +          *escaped = escape; +          return qtrue; +        } +      } +      return qfalse; +    } +    emoticon[ i++ ] = *p; +    emoticon[ i ] = '\0'; +    p++; +  } +  return qfalse;  } -  /*  =========== -ClientCleanName +G_ClientCleanName  ============  */ -static void ClientCleanName( const char *in, char *out, int outSize, qboolean special ) +static void G_ClientCleanName( const char *in, char *out, int outSize )  {    int   len, colorlessLen; -  char  ch;    char  *p;    int   spaces; +  qboolean escaped;    qboolean invalid = qfalse;    //save room for trailing null byte @@ -927,44 +660,48 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp    *p = 0;    spaces = 0; -  while( 1 ) +  for( ; *in; in++ )    { -    ch = *in++; -    if( !ch ) -      break; -      // don't allow leading spaces -    if( !*p && ch == ' ' ) +    if( colorlessLen == 0 && *in == ' ' )        continue;      // don't allow nonprinting characters or (dead) console keys -    if( ch < ' ' || ch > '}' || ch == '`' || ch == '%' ) +    if( *in < ' ' || *in > '}' || *in == '`' )        continue;      // check colors -    if( Q_IsColorString( in - 1 ) ) +    if( Q_IsColorString( in ) )      { +      in++; +        // make sure room in dest for both chars        if( len > outSize - 2 )          break; -      *out++ = ch; -      len += 2; +      *out++ = Q_COLOR_ESCAPE; -      // solo trailing carat is not a color prefix -      if( !*in ) { -        *out++ = COLOR_WHITE; -        break; -      } +      *out++ = *in; -	  *out++ = *in; +      len += 2; +      continue; +    } +    else if( !g_emoticonsAllowedInNames.integer && G_IsEmoticon( in, &escaped ) ) +    { +      // make sure room in dest for both chars +      if( len > outSize - 2 ) +        break; -      in++; +      *out++ = '[';  +      *out++ = '[';  +      len += 2; +      if( escaped ) +        in++;        continue;      }      // don't allow too many consecutive spaces -    if( ch == ' ' ) +    if( *in == ' ' )      {        spaces++;        if( spaces > 3 ) @@ -976,7 +713,7 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp      if( len > outSize - 1 )        break; -    *out++ = ch; +    *out++ = *in;      colorlessLen++;      len++;    } @@ -984,7 +721,7 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp    *out = 0;    // don't allow names beginning with "[skipnotify]" because it messes up /ignore-related code -  if( !Q_strncmp( p, "[skipnotify]", 12 ) ) +  if( !Q_stricmpn( p, "[skipnotify]", 12 ) )      invalid = qtrue;    // don't allow comment-beginning strings because it messes up various parsers @@ -1002,37 +739,6 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp  /* -=================== -G_NextNewbieName - -Generate a unique, known-good name for an UnnamedPlayer -=================== -*/ -char *G_NextNewbieName( gentity_t *ent ) -{ -  char newname[ MAX_NAME_LENGTH ]; -  char namePrefix[ MAX_NAME_LENGTH - 4 ]; -  char err[ MAX_STRING_CHARS ]; - -  if( g_newbieNamePrefix.string[ 0 ] ) -    Q_strncpyz( namePrefix, g_newbieNamePrefix.string , sizeof( namePrefix ) ); -  else -    strcpy( namePrefix, "Newbie#" ); - -  while( level.numNewbies < 10000 ) -  { -    strcpy( newname, va( "%s%i", namePrefix, level.numNewbies ) ); -    if ( G_admin_name_check( ent, newname, err, sizeof( err ) ) ) -    { -      return va( "%s", newname ); -    } -    level.numNewbies++; // Only increments if the last requested name was used. -  } -  return "UnnamedPlayer"; -} - - -/*  ======================  G_NonSegModel @@ -1099,24 +805,19 @@ The game can override any of the settings and call trap_SetUserinfo  if desired.  ============  */ -void ClientUserinfoChanged( int clientNum, qboolean forceName ) +char *ClientUserinfoChanged( int clientNum, qboolean forceName )  {    gentity_t *ent; -  int       teamTask, teamLeader, health; -  char      *s; -  char      model[ MAX_QPATH ]; -  char      buffer[ MAX_QPATH ]; +  char *s, *s2; +  char      model[ MAX_QPATH] = { '\0' }; +  char      buffer[ MAX_QPATH ] = { '\0' };    char      filename[ MAX_QPATH ];    char      oldname[ MAX_NAME_LENGTH ];    char      newname[ MAX_NAME_LENGTH ];    char      err[ MAX_STRING_CHARS ];    qboolean  revertName = qfalse; -  qboolean  showRenameMsg = qtrue;    gclient_t *client; -  char      c1[ MAX_INFO_STRING ]; -  char      c2[ MAX_INFO_STRING ];    char      userinfo[ MAX_INFO_STRING ]; -  pTeam_t    team;    ent = g_entities + clientNum;    client = ent->client; @@ -1130,81 +831,49 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName )          "disconnect \"illegal or malformed userinfo\n\"" );      trap_DropClient( ent - g_entities,           "dropped: illegal or malformed userinfo"); +    return "Illegal or malformed userinfo";    } +  // If their userinfo overflowed, tremded is in the process of disconnecting them. +  // If we send our own disconnect, it won't work, so just return to prevent crashes later +  //  in this function. This check must come after the Info_Validate call. +  else if( !userinfo[ 0 ] ) +    return "Empty (overflowed) userinfo"; - -  // check for local client -  s = Info_ValueForKey( userinfo, "ip" ); - -  if( !strcmp( s, "localhost" ) ) -    client->pers.localClient = qtrue; - -  // check the item prediction -  s = Info_ValueForKey( userinfo, "cg_predictItems" ); - -  if( !atoi( s ) ) -    client->pers.predictItemPickup = qfalse; -  else -    client->pers.predictItemPickup = qtrue; +  // stickyspec toggle +  s = Info_ValueForKey( userinfo, "cg_stickySpec" );   +  client->pers.stickySpec = atoi( s ) != 0;    // set name    Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) );    s = Info_ValueForKey( userinfo, "name" ); - -  if ( !G_admin_permission( ent, ADMF_SPECIALNAME ) ) -    ClientCleanName( s, newname, sizeof( newname ), qfalse ); -  else -    ClientCleanName( s, newname, sizeof( newname ), qtrue ); +  G_ClientCleanName( s, newname, sizeof( newname ) );    if( strcmp( oldname, newname ) )    { -    if( !strlen( oldname ) && client->pers.connected != CON_CONNECTED ) -      showRenameMsg = qfalse; - -    // in case we need to revert and there's no oldname -    if ( !G_admin_permission( ent, ADMF_SPECIALNAME ) ) -      ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ), qfalse ); -    else -      ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ), qtrue ); -  -    if( g_newbieNumbering.integer ) +    if( !forceName && client->pers.namelog->nameChangeTime && +      level.time - client->pers.namelog->nameChangeTime <= +      g_minNameChangePeriod.value * 1000 )      { -      if( !strcmp( newname, "UnnamedPlayer" ) ) -        Q_strncpyz( newname, G_NextNewbieName( ent ), sizeof( newname ) ); -      if( !strcmp( oldname, "UnnamedPlayer" ) ) -        Q_strncpyz( oldname, G_NextNewbieName( ent ), sizeof( oldname ) ); +      trap_SendServerCommand( ent - g_entities, va( +        "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", +         g_minNameChangePeriod.integer ) ); +      revertName = qtrue;      } - - -    if( !forceName ) +    else if( !forceName && g_maxNameChanges.integer > 0 && +      client->pers.namelog->nameChanges >= g_maxNameChanges.integer  )      { -      if( G_IsMuted( client ) ) -      { -        trap_SendServerCommand( ent - g_entities, -            "print \"You cannot change your name while you are muted\n\"" ); -        revertName = qtrue; -      } -      else if( client->pers.nameChangeTime && -      ( level.time - client->pers.nameChangeTime ) -      <= ( g_minNameChangePeriod.value * 1000 ) ) -      { -        trap_SendServerCommand( ent - g_entities, va( -            "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", -            g_minNameChangePeriod.integer ) ); -        revertName = qtrue; -      } -      else if( g_maxNameChanges.integer > 0 -        && client->pers.nameChanges >= g_maxNameChanges.integer -        && !G_admin_permission( ent, ADMF_SPECIAL ) ) -      { -        trap_SendServerCommand( ent - g_entities, va( -            "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", -            g_maxNameChanges.integer ) ); -        revertName = qtrue; -      } +      trap_SendServerCommand( ent - g_entities, va( +        "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", +         g_maxNameChanges.integer ) ); +      revertName = qtrue;      } - -    if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) +    else if( !forceName && client->pers.namelog->muted ) +    { +      trap_SendServerCommand( ent - g_entities, +        "print \"You cannot change your name while you are muted\n\"" ); +      revertName = qtrue; +    } +    else if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) )      {        trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", err ) );        revertName = qtrue; @@ -1212,110 +881,96 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName )      if( revertName )      { -      Q_strncpyz( client->pers.netname, oldname, +      Q_strncpyz( client->pers.netname, *oldname ? oldname : "UnnamedPlayer",          sizeof( client->pers.netname ) );        Info_SetValueForKey( userinfo, "name", oldname );        trap_SetUserinfo( clientNum, userinfo );      }      else      { -      Q_strncpyz( client->pers.netname, newname, -        sizeof( client->pers.netname ) ); -      Info_SetValueForKey( userinfo, "name", newname ); -      trap_SetUserinfo( clientNum, userinfo ); -      if( client->pers.connected == CON_CONNECTED ) +      G_CensorString( client->pers.netname, newname, +        sizeof( client->pers.netname ), ent ); +      if( !forceName && client->pers.connected == CON_CONNECTED ) +      { +        client->pers.namelog->nameChangeTime = level.time; +        client->pers.namelog->nameChanges++; +      } +      if( *oldname )        { -        client->pers.nameChangeTime = level.time; -        client->pers.nameChanges++; +        G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\" \"%c%s%c^7\"\n", +                   clientNum, client->pers.ip.str, client->pers.guid, +                   oldname, client->pers.netname, +                   DECOLOR_OFF, client->pers.netname, DECOLOR_ON );        }      } +    G_namelog_update_name( client );    } -  if( client->sess.sessionTeam == TEAM_SPECTATOR ) +  if ( client->pers.teamSelection == TEAM_HUMANS )    { -    if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) -      Q_strncpyz( client->pers.netname, "scoreboard", sizeof( client->pers.netname ) ); -  } +    int i; +    qboolean found = qfalse; -  if( client->pers.connected >= CON_CONNECTING && showRenameMsg ) -  { -    if( strcmp( oldname, client->pers.netname ) ) -    { -      //dont show if players invisible -      if( client->sess.invisible != qtrue ) -        trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE -          " renamed to %s^7\n\"", oldname, client->pers.netname ) ); -      if( g_decolourLogfiles.integer) -      { -        char    decoloured[ MAX_STRING_CHARS ] = "";    -        if( g_decolourLogfiles.integer == 1 ) -    { -      Com_sprintf( decoloured, sizeof(decoloured), " (\"%s^7\" -> \"%s^7\")", oldname, client->pers.netname ); -      G_DecolorString( decoloured, decoloured ); -          G_LogPrintfColoured( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"%s\n", clientNum, -             client->pers.ip, client->pers.guid, oldname, client->pers.netname, decoloured ); -    } -    else -    { -          G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"%s\n", clientNum, -             client->pers.ip, client->pers.guid, oldname, client->pers.netname, decoloured ); -    } +    s = Info_ValueForKey(userinfo, "model"); -      } -      else +    for ( i = 0; i < level.playerModelCount; i++ ) +    { +      if ( !strcmp(s, level.playerModel[i]) )        { -      G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"\n", clientNum, -         client->pers.ip, client->pers.guid, oldname, client->pers.netname ); +        found = qtrue; +        break;        } -      G_admin_namelog_update( client, qfalse );      } -  } -  // set max health -  health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); -  client->pers.maxHealth = health; +    if ( !found ) +      s = NULL; +    else if ( !g_cheats.integer +           && !forceName +           && !G_admin_permission( ent, va("MODEL%s", s) ) ) +      s = NULL; -  if( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) -    client->pers.maxHealth = 100; - -  //hack to force a client update if the config string does not change between spawning -  if( client->pers.classSelection == PCL_NONE ) -    client->pers.maxHealth = 0; - -  // set model -  if( client->ps.stats[ STAT_PCLASS ] == PCL_HUMAN && BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) ) +    if (s) +    { +      s2 = Info_ValueForKey(userinfo, "skin"); +      s2 = GetSkin(s, s2); +    } +  } +  else    { -    Com_sprintf( buffer, MAX_QPATH, "%s/%s",  BG_FindModelNameForClass( PCL_HUMAN_BSUIT ), -                                              BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) ); +      s = NULL;    } -  else if( client->pers.classSelection == PCL_NONE ) + +  if( client->pers.classSelection == PCL_NONE )    {      //This looks hacky and frankly it is. The clientInfo string needs to hold different      //model details to that of the spawning class or the info change will not be      //registered and an axis appears instead of the player model. There is zero chance      //the player can spawn with the battlesuit, hence this choice. -    Com_sprintf( buffer, MAX_QPATH, "%s/%s",  BG_FindModelNameForClass( PCL_HUMAN_BSUIT ), -                                              BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) ); +    Com_sprintf( buffer, MAX_QPATH, "%s/%s",  BG_ClassConfig( PCL_HUMAN_BSUIT )->modelName, +                                              BG_ClassConfig( PCL_HUMAN_BSUIT )->skinName );    }    else    { -    Com_sprintf( buffer, MAX_QPATH, "%s/%s",  BG_FindModelNameForClass( client->pers.classSelection ), -                                              BG_FindSkinNameForClass( client->pers.classSelection ) ); -  } -  Q_strncpyz( model, buffer, sizeof( model ) ); +    if ( !(client->pers.classSelection == PCL_HUMAN_BSUIT) && s ) +    { +        Com_sprintf( buffer, MAX_QPATH, "%s/%s", s, s2 ); +    } +    else +    { +        Com_sprintf( buffer, MAX_QPATH, "%s/%s",  BG_ClassConfig( client->pers.classSelection )->modelName, +                                                  BG_ClassConfig( client->pers.classSelection )->skinName ); +    } -  //don't bother setting model type if spectating -  if( client->pers.classSelection != PCL_NONE ) -  {      //model segmentation      Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", -                 BG_FindModelNameForClass( client->pers.classSelection ) ); +                 BG_ClassConfig( client->pers.classSelection )->modelName );      if( G_NonSegModel( filename ) )        client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL;      else        client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL;    } +  Q_strncpyz( model, buffer, sizeof( model ) );    // wallwalk follow    s = Info_ValueForKey( userinfo, "cg_wwFollow" ); @@ -1333,13 +988,44 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName )    else      client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE; +  // always sprint +  s = Info_ValueForKey( userinfo, "cg_sprintToggle" ); + +  if( atoi( s ) ) +    client->ps.persistant[ PERS_STATE ] |= PS_SPRINTTOGGLE; +  else +    client->ps.persistant[ PERS_STATE ] &= ~PS_SPRINTTOGGLE; + +  // fly speed +  s = Info_ValueForKey( userinfo, "cg_flySpeed" ); + +  if( *s ) +    client->pers.flySpeed = atoi( s ); +  else +    client->pers.flySpeed = BG_Class( PCL_NONE )->speed; + +  // disable blueprint errors +  s = Info_ValueForKey( userinfo, "cg_disableBlueprintErrors" ); + +  if( atoi( s ) ) +    client->pers.disableBlueprintErrors = qtrue; +  else +    client->pers.disableBlueprintErrors = qfalse; + +  client->pers.buildableRangeMarkerMask = +    atoi( Info_ValueForKey( userinfo, "cg_buildableRangeMarkerMask" ) ); +    // teamInfo    s = Info_ValueForKey( userinfo, "teamoverlay" ); -  if( ! *s || atoi( s ) != 0 ) -    client->pers.teamInfo = qtrue; +  if( atoi( s ) != 0 ) +  { +    // teamoverlay was enabled so we need an update +    if( client->pers.teamInfo == 0 ) +      client->pers.teamInfo = 1; +  }    else -    client->pers.teamInfo = qfalse; +    client->pers.teamInfo = 0;    s = Info_ValueForKey( userinfo, "cg_unlagged" );    if( !s[0] || atoi( s ) != 0 ) @@ -1347,67 +1033,26 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName )    else      client->pers.useUnlagged = qfalse; -  // team task (0 = none, 1 = offence, 2 = defence) -  teamTask = atoi( Info_ValueForKey( userinfo, "teamtask" ) ); -  // team Leader (1 = leader, 0 is normal player) -  teamLeader = client->sess.teamLeader; - -  // colors -  strcpy( c1, Info_ValueForKey( userinfo, "color1" ) ); -  strcpy( c2, Info_ValueForKey( userinfo, "color2" ) ); - -  team = client->pers.teamSelection; +  Q_strncpyz( client->pers.voice, Info_ValueForKey( userinfo, "voice" ), +    sizeof( client->pers.voice ) );    // send over a subset of the userinfo keys so other clients can    // print scoreboards, display models, and play custom sounds -  if ( client->sess.invisible != qtrue ) -  { -    Com_sprintf( userinfo, sizeof( userinfo ), -      "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\" -      "hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\" -      "tl\\%d\\ig\\%16s", -      client->pers.netname, team, model, model, c1, c2, -      client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, -      teamLeader, BG_ClientListString( &client->sess.ignoreList ) ); - -    trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); -  } else { -    trap_SetConfigstring( CS_PLAYERS + clientNum, "" ); -  } + +  Com_sprintf( userinfo, sizeof( userinfo ), +    "n\\%s\\t\\%i\\model\\%s\\ig\\%16s\\v\\%s", +    client->pers.netname, client->pers.teamSelection, model, +    Com_ClientListString( &client->sess.ignoreList ), +    client->pers.voice ); + +  trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); +    /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/ -} -/* -=========== -LogAutobahn -=========== -*/ -void G_LogAutobahn(gentity_t *ent, const char *userinfo, int rating, -                   qboolean onConnect) -{ -	char ip_buffer[20]; -	const char *ip, *name, *verb; - -	verb = (onConnect ? "refused" : "dropped"); - -	if (userinfo) { -		Q_strncpyz(ip_buffer, Info_ValueForKey(userinfo, "ip"), -		           sizeof(ip_buffer)); -		ip = ip_buffer; -		name = Info_ValueForKey(userinfo, "name"); -	} else { -		ip = ent->client->pers.ip; -		name = ent->client->pers.netname; -	} - -	G_LogPrintf("Autobahn: %s %i %s %+i \"%s^7\"\n", verb, ent - g_entities, -	            ip, rating, name); - -	if (g_adminAutobahnNotify.integer) -		G_AdminsPrintf("Autobahn %s '%s^7' with rating %+i, connecting from %s.\n", -		               verb, name, rating, ip); +  return NULL;  } +  /*  ===========  ClientConnect @@ -1428,52 +1073,59 @@ to the server machine, but qfalse on map changes and tournement  restarts.  ============  */ -char *ClientConnect( int clientNum, qboolean firstTime ) +const char *ClientConnect( int clientNum, qboolean firstTime )  {    char      *value; +  char      *userInfoError;    gclient_t *client;    char      userinfo[ MAX_INFO_STRING ];    gentity_t *ent; -  char      guid[ 33 ]; -  char      ip[ 16 ] = {""};    char      reason[ MAX_STRING_CHARS ] = {""};    int       i;    ent = &g_entities[ clientNum ]; +  client = &level.clients[ clientNum ]; + +  // ignore if client already connected +  if( client->pers.connected != CON_DISCONNECTED ) +    return NULL; + +  ent->client = client; +  memset( client, 0, sizeof( *client ) );    trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );    value = Info_ValueForKey( userinfo, "cl_guid" ); -  Q_strncpyz( guid, value, sizeof( guid ) ); - -  // check for admin ban -  if( G_admin_ban_check( userinfo, reason, sizeof( reason ) ) ) -  { -    return va( "%s", reason ); -  } +  Q_strncpyz( client->pers.guid, value, sizeof( client->pers.guid ) ); -  // IP filtering -  // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 -  // recommanding PB based IP / GUID banning, the builtin system is pretty limited -  // check to see if they are on the banned IP list    value = Info_ValueForKey( userinfo, "ip" ); -  i = 0; -  while( *value && i < sizeof( ip ) - 2 ) +  // check for local client +  if( !strcmp( value, "localhost" ) ) +    client->pers.localClient = qtrue; +  G_AddressParse( value, &client->pers.ip ); + +  client->pers.admin = G_admin_admin( client->pers.guid ); + +  client->pers.alternateProtocol = trap_Cvar_VariableIntegerValue( va( "sv_clAltProto%i", clientNum ) ); + +  if( client->pers.alternateProtocol == 2 && client->pers.guid[ 0 ] == '\0' )    { -    if( *value != '.' && ( *value < '0' || *value > '9' ) ) -      break; -    ip[ i++ ] = *value; -    value++; +    size_t len = strlen( client->pers.ip.str ); +    if( len == 0 ) +      len = 1; +    for( i = 0; i < sizeof( client->pers.guid ) - 1; ++i ) +    { +      int j = client->pers.ip.str[ i % len ] + rand() / ( RAND_MAX / 16 + 1 ); +      client->pers.guid[ i ] = "0123456789ABCDEF"[ j % 16 ]; +    } +    client->pers.guid[ sizeof( client->pers.guid ) - 1 ] = '\0'; +    client->pers.guidless = qtrue;    } -  ip[ i ] = '\0'; -  if( G_FilterPacket( value ) ) -    return "You are banned from this server."; -  if( strlen( ip ) < 7 && strcmp( Info_ValueForKey( userinfo, "ip" ), "localhost" ) ) +  // check for admin ban +  if( G_admin_ban_check( ent, reason, sizeof( reason ) ) )    { -    G_AdminsPrintf( "Connect from client with invalid IP: '%s' NAME: '%s^7'\n", -                    ip, Info_ValueForKey( userinfo, "name" ) ); -    return "Invalid client data"; +    return va( "%s", reason );    }    // check for a password @@ -1483,66 +1135,12 @@ char *ClientConnect( int clientNum, qboolean firstTime )        strcmp( g_password.string, value ) != 0 )      return "Invalid password"; -  schachtmeisterJudgement_t *smj = NULL; - -  if (!(G_admin_permission_guid(guid, ADMF_NOAUTOBAHN) -     || G_admin_permission_guid(guid, ADMF_IMMUNITY))) -  { -    extern g_admin_namelog_t *g_admin_namelog[128]; -    for (i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[i]; ++i) -    { -      if (!Q_stricmp(g_admin_namelog[i]->ip, ip) -       || !Q_stricmp(g_admin_namelog[i]->guid, guid)) -      { -        schachtmeisterJudgement_t *j = &g_admin_namelog[i]->smj; -        if (j->ratingTime) -        { -          if (j->rating >= g_schachtmeisterClearThreshold.integer) -            break; -          else if (j->rating <= g_schachtmeisterAutobahnThreshold.integer) -          { -            G_LogAutobahn( ent, userinfo, j->rating, qtrue ); -            return g_schachtmeisterAutobahnMessage.string; -          } -          smj = j; -        } -        break; -      } -    } -  } - -  // they can connect -  ent->client = level.clients + clientNum; -  client = ent->client; - -  memset( client, 0, sizeof(*client) ); -    // add guid to session so we don't have to keep parsing userinfo everywhere -  if( !guid[ 0 ] ) -  { -    Q_strncpyz( client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", -      sizeof( client->pers.guid ) ); -  } -  else -  { -    Q_strncpyz( client->pers.guid, guid, sizeof( client->pers.guid ) ); -  } +  for( i = 0; i < sizeof( client->pers.guid ) - 1 && +              isxdigit( client->pers.guid[ i ] ); i++ ); -  Q_strncpyz( client->pers.ip, ip, sizeof( client->pers.ip ) ); -  client->pers.adminLevel = G_admin_level( ent ); - -  // do autoghost now so that there won't be any name conflicts later on -  if ( g_autoGhost.integer && client->pers.guid[ 0 ] != 'X' ) -  { -    for ( i = 0; i < MAX_CLIENTS; i++ ) -    { -      if ( i != ent - g_entities && g_entities[i].client && g_entities[i].client->pers.connected != CON_DISCONNECTED && !Q_stricmp( g_entities[i].client->pers.guid, client->pers.guid ) ) -      { -        trap_SendServerCommand( i, "disconnect \"You may not be connected to this server multiple times\"" ); -        trap_DropClient( i, "disconnected" ); -      } -    } -  } +  if( i < sizeof( client->pers.guid ) - 1 ) +    return "Invalid GUID";    client->pers.connected = CON_CONNECTING; @@ -1552,81 +1150,36 @@ char *ClientConnect( int clientNum, qboolean firstTime )    G_ReadSessionData( client ); -  if( firstTime ) -    client->pers.firstConnect = qtrue; -  else -    client->pers.firstConnect = qfalse; -    // get and distribute relevent paramters -  ClientUserinfoChanged( clientNum, qfalse ); -   -  G_admin_set_adminname( ent ); -   -  if( g_decolourLogfiles.integer ) -  { -   char    decoloured[ MAX_STRING_CHARS ] = "";    -   if( g_decolourLogfiles.integer == 1 ) -   { -     Com_sprintf( decoloured, sizeof(decoloured), " (\"%s^7\")", client->pers.netname ); -     G_DecolorString( decoloured, decoloured ); -     G_LogPrintfColoured( "ClientConnect: %i [%s] (%s) \"%s^7\"%s\n", clientNum, -        client->pers.ip, client->pers.guid, client->pers.netname, decoloured ); -   } -   else -   { -      G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\"%s\n", clientNum, -          client->pers.ip, client->pers.guid, client->pers.netname, decoloured ); -   } -  } -  else -  { -    G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\"\n", clientNum, -      client->pers.ip, client->pers.guid, client->pers.netname ); -  } -   -  if( client->pers.adminLevel ) -  {  -     G_LogPrintf( "ClientAuth: %i [%s] \"%s^7\" authenticated to admin level %i using GUID %s (^7%s)\n", clientNum, client->pers.ip, client->pers.netname, client->pers.adminLevel, client->pers.guid, client->pers.adminName ); -  } +  G_namelog_connect( client ); +  userInfoError = ClientUserinfoChanged( clientNum, qfalse ); +  if( userInfoError != NULL ) +    return userInfoError; + +  G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\" \"%c%s%c^7\"\n", +               clientNum, client->pers.ip.str, client->pers.guid, +               client->pers.netname, +               DECOLOR_OFF, client->pers.netname, DECOLOR_ON );    // don't do the "xxx connected" messages if they were caried over from previous level -  if( client->sess.invisible != qtrue ) -  { -    if( firstTime ) -      trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname ) ); +  if( firstTime ) +    trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"",  +                                    client->pers.netname ) ); -    // count current clients and rank for scoreboard -    CalculateRanks( ); -    G_admin_namelog_update( client, qfalse ); -  } +  if( client->pers.admin ) +    G_admin_authlog( ent ); + +  // count current clients and rank for scoreboard +  CalculateRanks( ); +      // if this is after !restart keepteams or !restart switchteams, apply said selection -  if ( client->sess.restartTeam != PTE_NONE ) { +  if ( client->sess.restartTeam != TEAM_NONE ) +  {      G_ChangeTeam( ent, client->sess.restartTeam ); -    client->sess.restartTeam = PTE_NONE; +    client->sess.restartTeam = TEAM_NONE;    } -	if( !( G_admin_permission( ent, ADMF_NOAUTOBAHN ) || -	      G_admin_permission( ent, ADMF_IMMUNITY ) ) ) -  { -    extern g_admin_namelog_t *g_admin_namelog[ 128 ]; -    for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) -    { -      if( !Q_stricmp( ip, g_admin_namelog[ i ]->ip ) || !Q_stricmp( guid, g_admin_namelog[ i ]->guid ) ) -      { -        schachtmeisterJudgement_t *j = &g_admin_namelog[i]->smj; -        if( j->ratingTime ) -        { -          if( j->rating >= g_schachtmeisterClearThreshold.integer ) -            break; -          else if( j->rating <= g_schachtmeisterAutobahnThreshold.integer ) -            return g_schachtmeisterAutobahnMessage.string; -          G_AdminsPrintf( "%s^7 (#%d) has rating %d\n", ent->client->pers.netname, ent - g_entities, j->rating ); -        } -        break; -      } -    } -  }    return NULL;  } @@ -1635,9 +1188,9 @@ char *ClientConnect( int clientNum, qboolean firstTime )  ===========  ClientBegin -called when a client has finished connecting, and is ready -to be placed into the level.  This will happen every level load, -and on transition between teams, but doesn't happen on respawns +Called when a client has finished connecting, and is ready +to be placed into the level. This will happen on every +level load and level restart, but doesn't happen on respawns.  ============  */  void ClientBegin( int clientNum ) @@ -1650,6 +1203,10 @@ void ClientBegin( int clientNum )    client = level.clients + clientNum; +  // ignore if client already entered the game +  if( client->pers.connected != CON_CONNECTING ) +    return; +    if( ent->r.linked )      trap_UnlinkEntity( ent ); @@ -1660,8 +1217,6 @@ void ClientBegin( int clientNum )    client->pers.connected = CON_CONNECTED;    client->pers.enterTime = level.time; -  client->pers.teamState.state = TEAM_BEGIN; -  client->pers.classSelection = PCL_NONE;    // save eflags around this, because changing teams will    // cause this to happen with a valid entity, and we @@ -1674,44 +1229,19 @@ void ClientBegin( int clientNum )    client->ps.eFlags = flags;    // locate ent at a spawn point -    ClientSpawn( ent, NULL, NULL, NULL ); -  // Ignore invisible players for this section: -  if ( client->sess.invisible != qtrue ) -  { -    trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); - -    // auto denybuild -    if( G_admin_permission( ent, ADMF_NO_BUILD ) ) -      client->pers.denyBuild = qtrue; - -    // auto mute flag -    if( G_admin_permission( ent, ADMF_NO_CHAT ) ) -      client->pers.muted = qtrue; +  trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); -    // name can change between ClientConnect() and ClientBegin() -    G_admin_namelog_update( client, qfalse ); +  G_namelog_restore( client ); -    if( g_scrimMode.integer == 1 ) -    { -    ADMP( "^5Scrim mode is enabled. Teams are locked and you can only use spectator chat.\n" );   -    } - -    // request the clients PTR code -    trap_SendServerCommand( ent - g_entities, "ptrcrequest" ); -  }    G_LogPrintf( "ClientBegin: %i\n", clientNum ); -  if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) && -      g_outdatedClientMessage.string[0] ) -  { -    trap_SendServerCommand( client->ps.clientNum, va( -      "print \"%s\n\"", g_outdatedClientMessage.string ) ); -  } -    // count current clients and rank for scoreboard    CalculateRanks( ); + +  // send the client a list of commands that can be used +  G_ListCommands( ent );  }  /* @@ -1723,7 +1253,7 @@ after the first ClientBegin, and after each respawn  Initializes all non-persistant parts of playerState  ============  */ -void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) +void ClientSpawn( gentity_t *ent, gentity_t *spawn, const vec3_t origin, const vec3_t angles )  {    int                 index;    vec3_t              spawn_origin, spawn_angles; @@ -1731,8 +1261,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    int                 i;    clientPersistant_t  saved;    clientSession_t     savedSess; +  qboolean            savedNoclip, savedCliprcontents;    int                 persistant[ MAX_PERSISTANT ]; -  gentity_t           *spawnPoint = NULL;    int                 flags;    int                 savedPing;    int                 teamLocal; @@ -1742,75 +1272,65 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    int                 maxAmmo, maxClips;    weapon_t            weapon; -    index = ent - g_entities;    client = ent->client;    teamLocal = client->pers.teamSelection; -  //TA: only start client if chosen a class and joined a team -  if( client->pers.classSelection == PCL_NONE && teamLocal == PTE_NONE ) +  //if client is dead and following teammate, stop following before spawning +  if( client->sess.spectatorClient != -1 )    { -    client->sess.sessionTeam = TEAM_SPECTATOR; +    client->sess.spectatorClient = -1;      client->sess.spectatorState = SPECTATOR_FREE;    } + +  // only start client if chosen a class and joined a team +  if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) +    client->sess.spectatorState = SPECTATOR_FREE;    else if( client->pers.classSelection == PCL_NONE ) -  { -    client->sess.sessionTeam = TEAM_SPECTATOR;      client->sess.spectatorState = SPECTATOR_LOCKED; -  } -   -  //if client is dead and following teammate, stop following before spawning -  if(ent->client->sess.spectatorClient!=-1) -  { -    ent->client->sess.spectatorClient = -1; -    ent->client->sess.spectatorState = SPECTATOR_FREE; -  } - -  if( origin != NULL ) -    VectorCopy( origin, spawn_origin ); -  if( angles != NULL ) -    VectorCopy( angles, spawn_angles ); +  // if client is dead and following teammate, stop following before spawning +  if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) +    G_StopFollowing( ent );    // find a spawn point    // do it before setting health back up, so farthest    // ranging doesn't count this client -  if( client->sess.sessionTeam == TEAM_SPECTATOR ) +  if( client->sess.spectatorState != SPECTATOR_NOT )    { -    if( teamLocal == PTE_NONE ) -      spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); -    else if( teamLocal == PTE_ALIENS ) -      spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); -    else if( teamLocal == PTE_HUMANS ) -      spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); +    if( teamLocal == TEAM_ALIENS ) +      spawn = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); +    else if( teamLocal == TEAM_HUMANS ) +      spawn = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); +    else +      spawn = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles );    }    else    { -    if( spawn == NULL ) +    if( origin == NULL || angles == NULL )      { -      G_Error( "ClientSpawn: spawn is NULL\n" ); +      G_Error( "ClientSpawn: origin or angles is NULL" );        return;      } -    spawnPoint = spawn; +    VectorCopy( origin, spawn_origin ); +    VectorCopy( angles, spawn_angles ); -    if( ent != spawn ) +    if( spawn != NULL && spawn != ent )      {        //start spawn animation on spawnPoint -      G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); +      G_SetBuildableAnim( spawn, BANIM_SPAWN1, qtrue ); -      if( spawnPoint->biteam == PTE_ALIENS ) -        spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; -      else if( spawnPoint->biteam == PTE_HUMANS ) -        spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; +      if( spawn->buildableTeam == TEAM_ALIENS ) +        spawn->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; +      else if( spawn->buildableTeam == TEAM_HUMANS ) +        spawn->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME;      }    } -  client->pers.teamState.state = TEAM_ACTIVE;    // toggle the teleport bit so the client knows to not lerp -  flags = ent->client->ps.eFlags & ( EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED ); -  flags ^= EF_TELEPORT_BIT; +  flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT;    G_UnlaggedClear( ent );    // clear everything but the persistant data @@ -1818,6 +1338,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    saved = client->pers;    savedSess = client->sess;    savedPing = client->ps.ping; +  savedNoclip = client->noclip; +  savedCliprcontents = client->cliprcontents;    for( i = 0; i < MAX_PERSISTANT; i++ )      persistant[ i ] = client->ps.persistant[ i ]; @@ -1828,6 +1350,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    client->pers = saved;    client->sess = savedSess;    client->ps.ping = savedPing; +  client->noclip = savedNoclip; +  client->cliprcontents = savedCliprcontents;    client->lastkilled_client = -1;    for( i = 0; i < MAX_PERSISTANT; i++ ) @@ -1837,11 +1361,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    // increment the spawncount so the client will detect the respawn    client->ps.persistant[ PERS_SPAWN_COUNT ]++; -  client->ps.persistant[ PERS_TEAM ] = client->sess.sessionTeam; - -  // restore really persistant things -  client->ps.persistant[ PERS_SCORE ] = client->pers.score; -  client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; +  client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState;    client->airOutTime = level.time + 12000; @@ -1853,52 +1373,58 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    ent->s.groundEntityNum = ENTITYNUM_NONE;    ent->client = &level.clients[ index ];    ent->takedamage = qtrue; -  ent->inuse = qtrue;    ent->classname = "player"; -  ent->r.contents = CONTENTS_BODY; -  ent->clipmask = MASK_PLAYERSOLID; +  if( client->noclip ) +    client->cliprcontents = CONTENTS_BODY; +  else +    ent->r.contents = CONTENTS_BODY; +  if( client->pers.teamSelection == TEAM_NONE ) +    ent->clipmask = MASK_DEADSOLID; +  else +    ent->clipmask = MASK_PLAYERSOLID;    ent->die = player_die;    ent->waterlevel = 0;    ent->watertype = 0; -  ent->flags = 0; +  ent->flags &= FL_GODMODE | FL_NOTARGET; -  //TA: calculate each client's acceleration +  // calculate each client's acceleration    ent->evaluateAcceleration = qtrue; -  client->ps.stats[ STAT_WEAPONS ] = 0; -  client->ps.stats[ STAT_WEAPONS2 ] = 0; -  client->ps.stats[ STAT_SLOTS ] = 0; +  client->ps.stats[ STAT_MISC ] = 0;    client->ps.eFlags = flags;    client->ps.clientNum = index; -  BG_FindBBoxForClass( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); +  BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); -  if( client->sess.sessionTeam != TEAM_SPECTATOR ) -    client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] = -      BG_FindHealthForClass( ent->client->pers.classSelection ); +  if( client->sess.spectatorState == SPECTATOR_NOT ) +    client->ps.stats[ STAT_MAX_HEALTH ] = +      BG_Class( ent->client->pers.classSelection )->health;    else -    client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] = 100; +    client->ps.stats[ STAT_MAX_HEALTH ] = 100;    // clear entity values    if( ent->client->pers.classSelection == PCL_HUMAN )    { -    BG_AddWeaponToInventory( WP_BLASTER, client->ps.stats );      BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats );      weapon = client->pers.humanItemSelection;    } -  else if( client->sess.sessionTeam != TEAM_SPECTATOR ) -    weapon = BG_FindStartWeaponForClass( ent->client->pers.classSelection ); +  else if( client->sess.spectatorState == SPECTATOR_NOT ) +    weapon = BG_Class( ent->client->pers.classSelection )->startWeapon;    else      weapon = WP_NONE; -  BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips ); -  BG_AddWeaponToInventory( weapon, client->ps.stats ); +  maxAmmo = BG_Weapon( weapon )->maxAmmo; +  maxClips = BG_Weapon( weapon )->maxClips; +  client->ps.stats[ STAT_WEAPON ] = weapon;    client->ps.ammo = maxAmmo;    client->ps.clips = maxClips; -  ent->client->ps.stats[ STAT_PCLASS ] = ent->client->pers.classSelection; -  ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection; +  // We just spawned, not changing weapons +  client->ps.persistant[ PERS_NEWWEAPON ] = 0; + +  ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; +  ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection;    ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;    ent->client->ps.stats[ STAT_STATE ] = 0; @@ -1911,18 +1437,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    if( ent == spawn )    {      ent->health *= ent->client->pers.evolveHealthFraction; -    client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; +    client->ps.stats[ STAT_HEALTH ] = ent->health;    }    //clear the credits array    for( i = 0; i < MAX_CLIENTS; i++ )      ent->credits[ i ] = 0; -  client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; - -  if( mod_jetpackFuel.value >= 10.0f ) { -    client->jetpackfuel = mod_jetpackFuel.value; -  } +  client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX;    G_SetOrigin( ent, spawn_origin );    VectorCopy( spawn_origin, client->ps.origin ); @@ -1931,10 +1453,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles  #define F_VEL   50.0f    //give aliens some spawn velocity -  if( client->sess.sessionTeam != TEAM_SPECTATOR && -      client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) +  if( client->sess.spectatorState == SPECTATOR_NOT && +      client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )    { -    if( ent == spawn ) +    if( spawn == NULL ) +    { +      G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); +    } +    else if( ent == spawn )      {        //evolution particle system        G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); @@ -1944,13 +1470,13 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles        spawn_angles[ YAW ] += 180.0f;        AngleNormalize360( spawn_angles[ YAW ] ); -      if( spawnPoint->s.origin2[ 2 ] > 0.0f ) +      if( spawn->s.origin2[ 2 ] > 0.0f )        {          vec3_t  forward, dir;          AngleVectors( spawn_angles, forward, NULL, NULL );          VectorScale( forward, F_VEL, forward ); -        VectorAdd( spawnPoint->s.origin2, forward, dir ); +        VectorAdd( spawn->s.origin2, forward, dir );          VectorNormalize( dir );          VectorScale( dir, UP_VEL, client->ps.velocity ); @@ -1959,11 +1485,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles        G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 );      }    } -  else if( client->sess.sessionTeam != TEAM_SPECTATOR && -           client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +  else if( client->sess.spectatorState == SPECTATOR_NOT && +           client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )    { -    spawn_angles[ YAW ] += 180.0f; -    AngleNormalize360( spawn_angles[ YAW ] ); +    if( spawn != NULL ) +    { +      spawn_angles[ YAW ] += 180.0f; +      AngleNormalize360( spawn_angles[ YAW ] ); +    }    }    // the respawned flag will be cleared after the attack and jump keys come up @@ -1972,13 +1501,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd );    G_SetClientViewAngle( ent, spawn_angles ); -  if( !( client->sess.sessionTeam == TEAM_SPECTATOR ) ) +  if( client->sess.spectatorState == SPECTATOR_NOT )    { -    /*G_KillBox( ent );*/ //blame this if a newly spawned client gets stuck in another      trap_LinkEntity( ent );      // force the base weapon up -    client->ps.weapon = WP_NONE; +    if( client->pers.teamSelection == TEAM_HUMANS ) +      G_ForceWeaponChange( ent, weapon ); +      client->ps.weaponstate = WEAPON_READY;    } @@ -1987,8 +1517,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    client->ps.pm_time = 100;    client->respawnTime = level.time; -  if( g_gradualFreeFunds.integer < 2 ) -    client->pers.lastFreekillTime = level.time; +  ent->nextRegenTime = level.time;    client->inactivityTime = level.time + g_inactivity.integer * 1000;    client->latched_buttons = 0; @@ -2002,21 +1531,10 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    else    {      // fire the targets of the spawn point -    if( !spawn ) -      G_UseTargets( spawnPoint, ent ); +    if( spawn != NULL && spawn != ent ) +      G_UseTargets( spawn, ent ); -    // select the highest weapon number available, after any -    // spawn given items have fired -    client->ps.weapon = 1; - -    for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- ) -    { -      if( BG_InventoryContainsWeapon( i, client->ps.stats ) ) -      { -        client->ps.weapon = i; -        break; -      } -    } +    client->ps.weapon = client->ps.stats[ STAT_WEAPON ];    }    // run a client frame to drop exactly to the floor, @@ -2025,15 +1543,16 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    ent->client->pers.cmd.serverTime = level.time;    ClientThink( ent-g_entities ); +  VectorCopy( ent->client->ps.viewangles, ent->r.currentAngles ); +  VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );    // positively link the client, even if the command times are weird -  if( client->sess.sessionTeam != TEAM_SPECTATOR ) +  if( client->sess.spectatorState == SPECTATOR_NOT )    {      BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); -    VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );      trap_LinkEntity( ent );    } -  //TA: must do this here so the number of active clients is calculated +  // must do this here so the number of active clients is calculated    CalculateRanks( );    // run the presend to set anything else @@ -2041,6 +1560,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles    // clear entity state values    BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + +  client->pers.infoChangeTime = level.time;  } @@ -2061,55 +1582,40 @@ void ClientDisconnect( int clientNum )    gentity_t *ent;    gentity_t *tent;    int       i; -  buildHistory_t *ptr;    ent = g_entities + clientNum; -  if( !ent->client ) +  if( !ent->client || ent->client->pers.connected == CON_DISCONNECTED )      return; -  // look through the bhist and readjust it if the referenced ent has left -  for( ptr = level.buildHistory; ptr; ptr = ptr->next ) -  { -    if( ptr->ent == ent ) -    { -      ptr->ent = NULL; -      Q_strncpyz( ptr->name, ent->client->pers.netname, MAX_NETNAME ); -    } -  } -   -  if ( ent->client->sess.invisible != qtrue ) -    G_admin_namelog_update( ent->client, qtrue );    G_LeaveTeam( ent ); +  G_namelog_disconnect( ent->client ); +  G_Vote( ent, TEAM_NONE, qfalse );    // stop any following clients    for( i = 0; i < level.maxclients; i++ )    {      // remove any /ignore settings for this clientNum -    BG_ClientListRemove( &level.clients[ i ].sess.ignoreList, clientNum ); +    Com_ClientListRemove( &level.clients[ i ].sess.ignoreList, clientNum );    }    // send effect if they were completely connected    if( ent->client->pers.connected == CON_CONNECTED && -      ent->client->sess.sessionTeam != TEAM_SPECTATOR ) +      ent->client->sess.spectatorState == SPECTATOR_NOT )    {      tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT );      tent->s.clientNum = ent->s.clientNum;    } -  if( ent->client->pers.connection ) -    ent->client->pers.connection->clientNum = -1; - -  G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s\"\n", clientNum, -   ent->client->pers.ip, ent->client->pers.guid, ent->client->pers.netname ); +  G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s^7\"\n", clientNum, +   ent->client->pers.ip.str, ent->client->pers.guid, ent->client->pers.netname );    trap_UnlinkEntity( ent ); -  ent->s.modelindex = 0;    ent->inuse = qfalse;    ent->classname = "disconnected";    ent->client->pers.connected = CON_DISCONNECTED; -  ent->client->ps.persistant[ PERS_TEAM ] = TEAM_FREE; -  ent->client->sess.sessionTeam = TEAM_FREE; +  ent->client->sess.spectatorState = +      ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_NOT;    trap_SetConfigstring( CS_PLAYERS + clientNum, ""); diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index c380b45..4dccf6a 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,59 +17,41 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */  #include "g_local.h" +static qboolean G_RoomForClassChange( gentity_t*, class_t, vec3_t ); +  /*  ==================  G_SanitiseString -Remove case and control characters from a player name +Remove color codes and non-alphanumeric characters from a string  ==================  */  void G_SanitiseString( char *in, char *out, int len )  { -  qboolean skip = qtrue; -  int spaces = 0; +  len--;    while( *in && len > 0 )    { -    // strip leading white space -    if( *in == ' ' ) -    { -      if( skip ) -      { -        in++; -        continue; -      } -      spaces++; -    } -    else -    { -      spaces = 0; -      skip = qfalse; -    } -          if( Q_IsColorString( in ) )      {        in += 2;    // skip color code        continue;      } -    if( *in < 32 ) +    if( isalnum( *in ) )      { -      in++; -      continue; +        *out++ = tolower( *in ); +        len--;      } - -    *out++ = tolower( *in++ ); -    len--; +    in++;    } -  out -= spaces;     *out = 0;  } @@ -77,36 +60,69 @@ void G_SanitiseString( char *in, char *out, int len )  G_ClientNumberFromString  Returns a player number for either a number or name string -Returns -1 if invalid +Returns -1 and optionally sets err if invalid or not exactly 1 match +err will have a trailing \n if set  ==================  */ -int G_ClientNumberFromString( gentity_t *to, char *s ) +int G_ClientNumberFromString( char *s, char *err, int len )  {    gclient_t *cl; -  int       idnum; -  char      s2[ MAX_STRING_CHARS ]; -  char      n2[ MAX_STRING_CHARS ]; +  int       i, found = 0, m = -1; +  char      s2[ MAX_NAME_LENGTH ]; +  char      n2[ MAX_NAME_LENGTH ]; +  char      *p = err; +  int       l, l2 = len; + +  if( !s[ 0 ] ) +  { +    if( p ) +      Q_strncpyz( p, "no player name or slot # provided\n", len ); + +    return -1; +  }    // numeric values are just slot numbers -  if( s[ 0 ] >= '0' && s[ 0 ] <= '9' ) +  for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); +  if( !s[ i ] )    { -    idnum = atoi( s ); +    i = atoi( s ); -    if( idnum < 0 || idnum >= level.maxclients ) +    if( i < 0 || i >= level.maxclients )        return -1; -    cl = &level.clients[ idnum ]; +    cl = &level.clients[ i ];      if( cl->pers.connected == CON_DISCONNECTED ) +    { +      if( p ) +        Q_strncpyz( p, "no player connected in that slot #\n", len ); +        return -1; +    } -    return idnum; +    return i;    } -  // check for a name match    G_SanitiseString( s, s2, sizeof( s2 ) ); +  if( !s2[ 0 ] ) +  { +    if( p ) +      Q_strncpyz( p, "no player name provided\n", len ); -  for( idnum = 0, cl = level.clients; idnum < level.maxclients; idnum++, cl++ ) +    return -1; +  } + +  if( p ) +  { +    Q_strncpyz( p, "more than one player name matches. " +                "be more specific or use the slot #:\n", l2 ); +    l = strlen( p ); +    p += l; +    l2 -= l; +  } + +  // check for a name match +  for( i = 0, cl = level.clients; i < level.maxclients; i++, cl++ )    {      if( cl->pers.connected == CON_DISCONNECTED )        continue; @@ -114,54 +130,29 @@ int G_ClientNumberFromString( gentity_t *to, char *s )      G_SanitiseString( cl->pers.netname, n2, sizeof( n2 ) );      if( !strcmp( n2, s2 ) ) -      return idnum; -  } - -  return -1; -} - - -/* -================== -G_MatchOnePlayer - -This is a companion function to G_ClientNumbersFromString() - -returns qtrue if the int array plist only has one client id, false otherwise -In the case of false, err will be populated with an error message. -================== -*/ -qboolean G_MatchOnePlayer( int *plist, char *err, int len ) -{ -  gclient_t *cl; -  int *p; -  char line[ MAX_NAME_LENGTH + 10 ] = {""}; +      return i; -  err[ 0 ] = '\0'; -  if( plist[ 0 ] == -1 ) -  { -    Q_strcat( err, len, "no connected player by that name or slot #" ); -    return qfalse; -  } -  if( plist[ 1 ] != -1 ) -  { -    Q_strcat( err, len, "more than one player name matches. " -            "be more specific or use the slot #:\n" ); -    for( p = plist; *p != -1; p++ ) +    if( strstr( n2, s2 ) )      { -      cl = &level.clients[ *p ]; -      if( cl->pers.connected == CON_CONNECTED ) +      if( p )        { -        Com_sprintf( line, sizeof( line ), "%2i - %s^7\n", -          *p, cl->pers.netname ); -        if( strlen( err ) + strlen( line ) > len ) -          break; -        Q_strcat( err, len, line ); +        l = Q_snprintf( p, l2, "%-2d - %s^7\n", i, cl->pers.netname ); +        p += l; +        l2 -= l;        } + +      found++; +      m = i;      } -    return qfalse;    } -  return qtrue; + +  if( found == 1 ) +    return m; + +  if( found == 0 && err ) +    Q_strncpyz( err, "no connected player by that name or slot #\n", len ); + +  return -1;  }  /* @@ -171,22 +162,27 @@ G_ClientNumbersFromString  Sets plist to an array of integers that represent client numbers that have  names that are a partial match for s. -Returns number of matching clientids up to MAX_CLIENTS. +Returns number of matching clientids up to max.  ==================  */ -int G_ClientNumbersFromString( char *s, int *plist) +int G_ClientNumbersFromString( char *s, int *plist, int max )  {    gclient_t *p;    int i, found = 0; +  char *endptr;    char n2[ MAX_NAME_LENGTH ] = {""};    char s2[ MAX_NAME_LENGTH ] = {""}; -  int max = MAX_CLIENTS; -  // if a number is provided, it might be a slot # -  for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); -  if( !s[ i ] ) +  if( max == 0 ) +    return 0; + +  if( !s[ 0 ] ) +    return 0; + +  // if a number is provided, it is a clientnum +  i = strtol( s, &endptr, 10 ); +  if( *endptr == '\0' )    { -    i = atoi( s );      if( i >= 0 && i < level.maxclients )      {        p = &level.clients[ i ]; @@ -197,15 +193,14 @@ int G_ClientNumbersFromString( char *s, int *plist)        }      }      // we must assume that if only a number is provided, it is a clientNum -    *plist = -1;      return 0;    }    // now look for name matches    G_SanitiseString( s, s2, sizeof( s2 ) ); -  if( strlen( s2 ) < 1 ) +  if( !s2[ 0 ] )      return 0; -  for( i = 0; i < level.maxclients && found <= max; i++ ) +  for( i = 0; i < level.maxclients && found < max; i++ )    {      p = &level.clients[ i ];      if( p->pers.connected == CON_DISCONNECTED ) @@ -219,7 +214,6 @@ int G_ClientNumbersFromString( char *s, int *plist)        found++;      }    } -  *plist = -1;    return found;  } @@ -254,15 +248,12 @@ void ScoreboardMessage( gentity_t *ent )      if( cl->pers.connected == CON_CONNECTING )        ping = -1; -    else if( cl->sess.spectatorState == SPECTATOR_FOLLOW ) -      ping = cl->pers.ping < 999 ? cl->pers.ping : 999;      else        ping = cl->ps.ping < 999 ? cl->ps.ping : 999; -    //If (loop) client is a spectator, they have nothing, so indicate such.  -    //Only send the client requesting the scoreboard the weapon/upgrades information for members of their team. If they are not on a team, send it all. -    if( cl->sess.sessionTeam != TEAM_SPECTATOR &&  -      (ent->client->pers.teamSelection == PTE_NONE || cl->pers.teamSelection == ent->client->pers.teamSelection ) ) +    if( cl->sess.spectatorState == SPECTATOR_NOT && +        ( ent->client->pers.teamSelection == TEAM_NONE || +          cl->pers.teamSelection == ent->client->pers.teamSelection ) )      {        weapon = cl->ps.weapon; @@ -286,19 +277,19 @@ void ScoreboardMessage( gentity_t *ent )      }      Com_sprintf( entry, sizeof( entry ), -      " %d %d %d %d %d %d", level.sortedClients[ i ], cl->pers.score, ping,  -      ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade ); +      " %d %d %d %d %d %d", level.sortedClients[ i ], cl->ps.persistant[ PERS_SCORE ], +      ping, ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade );      j = strlen( entry ); -    if( stringlength + j > 1024 ) +    if( stringlength + j >= sizeof( string ) )        break;      strcpy( string + stringlength, entry );      stringlength += j;    } -  trap_SendServerCommand( ent-g_entities, va( "scores %i %i %i%s", i, +  trap_SendServerCommand( ent-g_entities, va( "scores %i %i%s",      level.alienKills, level.humanKills, string ) );  } @@ -346,50 +337,120 @@ char *ConcatArgs( int start )  /*  ================== -G_Flood_Limited - -Determine whether a user is flood limited, and adjust their flood demerits +ConcatArgsPrintable +Duplicate of concatargs but enquotes things that need to be +Used to log command arguments in a way that preserves user intended tokenizing  ==================  */ +char *ConcatArgsPrintable( int start ) +{ +  int         i, c, tlen; +  static char line[ MAX_STRING_CHARS ]; +  int         len; +  char        arg[ MAX_STRING_CHARS + 2 ]; +  const char* printArg; -qboolean G_Flood_Limited( gentity_t *ent ) +  len = 0; +  c = trap_Argc( ); + +  for( i = start; i < c; i++ ) +  { +    printArg = arg; +    trap_Argv( i, arg, sizeof( arg ) ); +    if( strchr( arg, ' ' ) ) +      printArg = va( "\"%s\"", arg ); +    tlen = strlen( printArg ); + +    if( len + tlen >= MAX_STRING_CHARS - 1 ) +      break; + +    memcpy( line + len, printArg, tlen ); +    len += tlen; + +    if( len == MAX_STRING_CHARS - 1 ) +      break; + +    if( i != c - 1 ) +    { +      line[ len ] = ' '; +      len++; +    } +  } + +  line[ len ] = 0; + +  return line; +} + +static void Give_Class( gentity_t *ent, char *s )  { -  int millisSinceLastCommand; -  int maximumDemerits; +  class_t currentClass = ent->client->pers.classSelection; +  int clientNum = ent->client - level.clients; +  vec3_t infestOrigin; +  vec3_t oldVel; +  int oldBoostTime = -1; +  int newClass = BG_ClassByName( s )->number; -  // This shouldn't be called if g_floodMinTime isn't set, but handle it anyway. -  if( !g_floodMinTime.integer ) -    return qfalse; -   -  // Do not limit admins with no censor/flood flag -  if( G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) -   return qfalse; -   -  millisSinceLastCommand = level.time - ent->client->pers.lastFloodTime; -  if( millisSinceLastCommand < g_floodMinTime.integer ) -    ent->client->pers.floodDemerits += ( g_floodMinTime.integer - millisSinceLastCommand ); -  else +  if( newClass == PCL_NONE ) +    return; + +  if( !G_RoomForClassChange( ent, newClass, infestOrigin ) )    { -    ent->client->pers.floodDemerits -= ( millisSinceLastCommand - g_floodMinTime.integer ); -    if( ent->client->pers.floodDemerits < 0 ) -      ent->client->pers.floodDemerits = 0; +    ADMP("give: not enough room to evolve\n"); +    return;    } -  ent->client->pers.lastFloodTime = level.time; +  ent->client->pers.evolveHealthFraction  +      = (float)ent->client->ps.stats[ STAT_HEALTH ]  +      / (float)BG_Class( currentClass )->health; -  // If g_floodMaxDemerits == 0, then we go against g_floodMinTime^2. -   -  if( !g_floodMaxDemerits.integer ) -     maximumDemerits = g_floodMinTime.integer * g_floodMinTime.integer / 1000; -  else -     maximumDemerits = g_floodMaxDemerits.integer; +  if( ent->client->pers.evolveHealthFraction < 0.0f ) +    ent->client->pers.evolveHealthFraction = 0.0f; +  else if( ent->client->pers.evolveHealthFraction > 1.0f ) +    ent->client->pers.evolveHealthFraction = 1.0f; -  if( ent->client->pers.floodDemerits > maximumDemerits ) -     return qtrue; +  //remove credit +  //G_AddCreditToClient( ent->client, -cost, qtrue ); +  ent->client->pers.classSelection = newClass; +  ClientUserinfoChanged( clientNum, qfalse ); +  VectorCopy( infestOrigin, ent->s.pos.trBase ); +  VectorCopy( ent->client->ps.velocity, oldVel ); -  return qfalse; +  if( ent->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) +    oldBoostTime = ent->client->boostedTime; + +  ClientSpawn( ent, ent, ent->s.pos.trBase, ent->s.apos.trBase ); + +  VectorCopy( oldVel, ent->client->ps.velocity ); +  if( oldBoostTime > 0 ) +  { +    ent->client->boostedTime = oldBoostTime; +    ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; +  } +} + +static void Give_Gun( gentity_t *ent, char *s ) +{ +  int w = BG_WeaponByName( s )->number; + +  if ( w == WP_NONE ) +    return; + +  //if( !BG_Weapon( w )->purchasable ) +  //    return; + +  ent->client->ps.stats[ STAT_WEAPON ] = w; +  ent->client->ps.ammo = BG_Weapon( w )->maxAmmo; +  ent->client->ps.clips = BG_Weapon( w )->maxClips; +  G_ForceWeaponChange( ent, w ); +} + +static void Give_Upgrade( gentity_t *ent, char *s ) +{ +    int u = BG_UpgradeByName( s )->number; +    BG_AddUpgradeToInventory( u, ent->client->ps.stats );  } -   +  /*  ==================  Cmd_Give_f @@ -402,52 +463,157 @@ void Cmd_Give_f( gentity_t *ent )    char      *name;    qboolean  give_all = qfalse; +  if( trap_Argc( ) < 2 ) +  { +    ADMP( "^3give: ^7usage: give [what]\n" +          "health, funds <amount>, stamina, poison, gas, ammo, "  +          "^3level0, level1, level1upg, level2, level2upg, level3, level3upg, level4, builder, builderupg, " +          "human_base, human_bsuit, " +          "^5blaster, rifle, psaw, shotgun, lgun, mdriver, chaingun, flamer, prifle, grenade, lockblob, " +          "hive, teslagen, mgturret, abuild, abuildupg, portalgun, proximity, smokecan, " +          "^2larmour, helmet, medkit, battpak, jetpack, bsuit, gren \n" ); +    return; +  } +    name = ConcatArgs( 1 );    if( Q_stricmp( name, "all" ) == 0 )      give_all = qtrue;    if( give_all || Q_stricmp( name, "health" ) == 0 )    { -    if(!g_devmapNoGod.integer) +    ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ]; +    BG_AddUpgradeToInventory( UP_MEDKIT, ent->client->ps.stats ); +  } + +  if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) +  { +    float credits; + +    if( give_all || trap_Argc( ) < 3 ) +      credits = 30000.0f; +    else      { -     ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ]; -     BG_AddUpgradeToInventory( UP_MEDKIT, ent->client->ps.stats ); +      credits = atof( name + 6 ) * +        ( ent->client->pers.teamSelection ==  +          TEAM_ALIENS ? ALIEN_CREDITS_PER_KILL : 1.0f ); + +      // clamp credits manually, as G_AddCreditToClient() expects a short int +      if( credits > SHRT_MAX ) +        credits = 30000.0f; +      else if( credits < SHRT_MIN ) +        credits = -30000.0f;      } + +    G_AddCreditToClient( ent->client, (short)credits, qtrue );    } -  if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) +  if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 || +      ent->client->sess.spectatorState != SPECTATOR_NOT )    { -    int credits = give_all ? HUMAN_MAX_CREDITS : atoi( name + 6 ); -    G_AddCreditToClient( ent->client, credits, qtrue ); +    if( !( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) ) +      G_TriggerMenu( ent-g_entities, MN_CMD_ALIVE ); +    return; +  } + +  if( give_all || Q_stricmp( name, "health" ) == 0 ) +  { +    if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] ) +    { +      ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ]; +      ent->client->ps.stats[ STAT_HEALTH ] = ent->health; +    } +    BG_AddUpgradeToInventory( UP_MEDKIT, ent->client->ps.stats );    }    if( give_all || Q_stricmp( name, "stamina" ) == 0 ) -    ent->client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; +    ent->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; + +  // Adding guns +  Give_Gun(ent, name); + +  // Adding upgrades +  Give_Upgrade( ent, name); + +  // Change class- this allows you to be any alien class on TEAM_HUMAN and the +  // otherway round. +  Give_Class(ent, name);    if( Q_stricmp( name, "poison" ) == 0 )    { -    ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; -    ent->client->lastBoostedTime = level.time; +    if( ent->client->pers.teamSelection == TEAM_HUMANS ) +    { +      ent->client->ps.stats[ STAT_STATE ] |= SS_POISONED; +      ent->client->lastPoisonTime = level.time; +      ent->client->lastPoisonClient = ent; +    } +    else +    { +      ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; +      ent->client->boostedTime = level.time; +    } +  } + +  if( Q_stricmp( name, "gas" ) == 0 ) +  { +    ent->client->ps.eFlags |= EF_POISONCLOUDED; +    ent->client->lastPoisonCloudedTime = level.time; +    trap_SendServerCommand( ent->client->ps.clientNum, "poisoncloud" );    }    if( give_all || Q_stricmp( name, "ammo" ) == 0 )    { -    int maxAmmo, maxClips;      gclient_t *client = ent->client;      if( client->ps.weapon != WP_ALEVEL3_UPG && -        BG_FindInfinteAmmoForWeapon( client->ps.weapon ) ) +        BG_Weapon( client->ps.weapon )->infiniteAmmo )        return; -    BG_FindAmmoForWeapon( client->ps.weapon, &maxAmmo, &maxClips ); +    client->ps.ammo = BG_Weapon( client->ps.weapon )->maxAmmo; +    client->ps.clips = BG_Weapon( client->ps.weapon )->maxClips; -    if( BG_FindUsesEnergyForWeapon( client->ps.weapon ) && +    if( BG_Weapon( client->ps.weapon )->usesEnergy &&          BG_InventoryContainsUpgrade( UP_BATTPACK, client->ps.stats ) ) -      maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); +      client->ps.ammo = (int)( (float)client->ps.ammo * BATTPACK_MODIFIER ); +  } +} + +/* +Cmd_Drop_f +Drop a weapon onto the ground +*/ +void Cmd_Drop_f( gentity_t *ent ) +{ +  char t[ MAX_TOKEN_CHARS ]; +  char angle[ MAX_TOKEN_CHARS ]; +  float ang = 0.0f; +  int i; + +  if( trap_Argc( ) < 2 ) +  { +      ADMP("^3drop: ^7usage: drop <weapon> [angle]\n"); +      return; +  } -    client->ps.ammo = maxAmmo; -    client->ps.clips = maxClips; +  trap_Argv( 1, t, sizeof(t) ); + +  if ( trap_Argc() > 2 ) +  { +    trap_Argv( 2, angle, sizeof(angle) ); +    ang = atof( angle );    } + +  switch ((i = BG_WeaponByName( t )->number)) +  { +    case WP_NONE: +      ADMP("^3drop: ^7usage: drop <weapon> [angle]\n"); +      break; + +    default: +      G_DropWeapon( ent, i, ang ); +      break; +  }; + +  } @@ -464,19 +630,12 @@ void Cmd_God_f( gentity_t *ent )  {    char  *msg; - if( !g_devmapNoGod.integer ) - {    ent->flags ^= FL_GODMODE;    if( !( ent->flags & FL_GODMODE ) )      msg = "godmode OFF\n";    else      msg = "godmode ON\n"; - } - else - { -  msg = "Godmode has been disabled.\n"; - }    trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) );  } @@ -495,19 +654,12 @@ void Cmd_Notarget_f( gentity_t *ent )  {    char  *msg; - if( !g_devmapNoGod.integer ) - {    ent->flags ^= FL_NOTARGET;    if( !( ent->flags & FL_NOTARGET ) )      msg = "notarget OFF\n";    else      msg = "notarget ON\n"; - } - else - { -  msg = "Godmode has been disabled.\n"; - }    trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) );  } @@ -524,19 +676,22 @@ void Cmd_Noclip_f( gentity_t *ent )  {    char  *msg; - if( !g_devmapNoGod.integer ) - {    if( ent->client->noclip ) +  {      msg = "noclip OFF\n"; +    ent->r.contents = ent->client->cliprcontents; +  }    else +  {      msg = "noclip ON\n"; +    ent->client->cliprcontents = ent->r.contents; +    ent->r.contents = 0; +  }    ent->client->noclip = !ent->client->noclip; - }  - else - { -  msg = "Godmode has been disabled.\n"; - } + +  if( ent->r.linked ) +    trap_LinkEntity( ent );    trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) );  } @@ -565,15 +720,6 @@ Cmd_Kill_f  */  void Cmd_Kill_f( gentity_t *ent )  { -  if( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) -    return; - -  if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) -  { -    trap_SendServerCommand( ent-g_entities, "print \"Leave the hovel first (use your destroy key)\n\"" ); -    return; -  } -    if( g_cheats.integer )    {      ent->flags &= ~FL_GODMODE; @@ -589,730 +735,409 @@ void Cmd_Kill_f( gentity_t *ent )      }      else if( ent->suicideTime > level.time )      { -      trap_SendServerCommand( ent-g_entities, "print \"Suicide canceled\n\"" ); +      trap_SendServerCommand( ent-g_entities, "print \"Suicide cancelled\n\"" );        ent->suicideTime = 0;      }    }  }  /* -================== -G_LeaveTeam -================== +================= +Cmd_Team_f +=================  */ -void G_LeaveTeam( gentity_t *self ) +void Cmd_Team_f( gentity_t *ent )  { -  pTeam_t   team = self->client->pers.teamSelection; -  gentity_t *ent; -  int       i; +  team_t    team; +  team_t    oldteam = ent->client->pers.teamSelection; +  char      s[ MAX_TOKEN_CHARS ]; +  qboolean  force = G_admin_permission( ent, ADMF_FORCETEAMCHANGE ); +  int       aliens = level.numAlienClients; +  int       humans = level.numHumanClients; -  if( team == PTE_ALIENS ) -    G_RemoveFromSpawnQueue( &level.alienSpawnQueue, self->client->ps.clientNum ); -  else if( team == PTE_HUMANS ) -    G_RemoveFromSpawnQueue( &level.humanSpawnQueue, self->client->ps.clientNum ); -  else +  if( oldteam == TEAM_ALIENS ) +    aliens--; +  else if( oldteam == TEAM_HUMANS ) +    humans--; + +  // stop team join spam +  if( ent->client->pers.teamChangeTime &&  +      level.time - ent->client->pers.teamChangeTime < 1000 ) +    return; + +  // stop switching teams for gameplay exploit reasons by enforcing a long +  // wait before they can come back +  if( !force && !g_cheats.integer && ent->client->pers.secondsAlive && +      level.time - ent->client->pers.teamChangeTime < 30000 )    { -    if( self->client->sess.spectatorState == SPECTATOR_FOLLOW ) -    { -      G_StopFollowing( self ); -    } +    trap_SendServerCommand( ent-g_entities, +      va( "print \"You must wait another %d seconds before changing teams again\n\"", +        (int) ( ( 30000 - ( level.time - ent->client->pers.teamChangeTime ) ) / 1000.f ) ) );      return;    } -   -  // Cancel pending suicides -  self->suicideTime = 0; -  // stop any following clients -  G_StopFromFollowing( self ); +  trap_Argv( 1, s, sizeof( s ) ); -  for( i = 0; i < level.num_entities; i++ ) +  if( !s[ 0 ] )    { -    ent = &g_entities[ i ]; -    if( !ent->inuse ) -      continue; +    trap_SendServerCommand( ent-g_entities, va( "print \"team: %s\n\"", +      BG_TeamName( oldteam ) ) ); +    return; +  } -    // clean up projectiles -    if( ent->s.eType == ET_MISSILE && ent->r.ownerNum == self->s.number ) -      G_FreeEntity( ent ); -    if( ent->client && ent->client->pers.connected == CON_CONNECTED ) -    { -      // cure poison -      if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED && -          ent->client->lastPoisonCloudedClient == self ) -        ent->client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED; -      if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONED && -          ent->client->lastPoisonClient == self ) -        ent->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; -    } +  if( !Q_stricmp( s, "auto" ) ) +  { +    if( level.humanTeamLocked && level.alienTeamLocked ) +      team = TEAM_NONE; +    else if( level.humanTeamLocked || humans > aliens ) +      team = TEAM_ALIENS; + +    else if( level.alienTeamLocked || aliens > humans ) +      team = TEAM_HUMANS; +    else +      team = TEAM_ALIENS + rand( ) / ( RAND_MAX / 2 + 1 );    } -} +  else switch( G_TeamFromString( s ) ) +  { +    case TEAM_NONE: +      team = TEAM_NONE; +      break; -/* -================= -G_ChangeTeam -================= -*/ -void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam ) -{ -  pTeam_t oldTeam = ent->client->pers.teamSelection; -  qboolean isFixingImbalance=qfalse; +    case TEAM_ALIENS: +      if( level.alienTeamLocked ) +      { +        G_TriggerMenu( ent - g_entities, MN_A_TEAMLOCKED ); +        return; +      } +      else if( level.humanTeamLocked ) +        force = qtrue; -  if( oldTeam == newTeam ) -    return; +      if( !force && g_teamForceBalance.integer && aliens > humans ) +      { +        G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); +        return; +      } -  G_LeaveTeam( ent ); -  ent->client->pers.teamSelection = newTeam; +      team = TEAM_ALIENS; +      break; -  ent->client->pers.lastFreekillTime = level.time; +    case TEAM_HUMANS: +      if( level.humanTeamLocked ) +      { +        G_TriggerMenu( ent - g_entities, MN_H_TEAMLOCKED ); +        return; +      } +      else if( level.alienTeamLocked ) +        force = qtrue; -  // G_LeaveTeam() calls G_StopFollowing() which sets spec mode to free.  -  // Undo that in this case, or else people can freespec while in the spawn queue on their new team -  if( newTeam != PTE_NONE ) -  { -    ent->client->sess.spectatorState = SPECTATOR_LOCKED; -  } -   -   -  if ( ( level.numAlienClients - level.numHumanClients > 2 && oldTeam==PTE_ALIENS && newTeam == PTE_HUMANS && level.numHumanSpawns>0 ) || -       ( level.numHumanClients - level.numAlienClients > 2 && oldTeam==PTE_HUMANS && newTeam == PTE_ALIENS  && level.numAlienSpawns>0 ) )  -  { -    isFixingImbalance=qtrue; -  } +      if( !force && g_teamForceBalance.integer && humans > aliens ) +      { +        G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); +        return; +      } -  // under certain circumstances, clients can keep their kills and credits -  // when switching teams -  if( G_admin_permission( ent, ADMF_TEAMCHANGEFREE ) || -    ( g_teamImbalanceWarnings.integer && isFixingImbalance ) || -    ( ( oldTeam == PTE_HUMANS || oldTeam == PTE_ALIENS ) -    && ( level.time - ent->client->pers.teamChangeTime ) > 60000 ) ) -  { -    if( oldTeam == PTE_ALIENS ) -      ent->client->pers.credit *= (float)FREEKILL_HUMAN / FREEKILL_ALIEN; -    else if( newTeam == PTE_ALIENS ) -      ent->client->pers.credit *= (float)FREEKILL_ALIEN / FREEKILL_HUMAN; -  } -  else -  { -    ent->client->pers.credit = 0; -    ent->client->pers.score = 0; -  } -   -  ent->client->ps.persistant[ PERS_KILLED ] = 0; -  ent->client->pers.statscounters.kills = 0; -  ent->client->pers.statscounters.structskilled = 0; -  ent->client->pers.statscounters.assists = 0; -  ent->client->pers.statscounters.repairspoisons = 0; -  ent->client->pers.statscounters.headshots = 0; -  ent->client->pers.statscounters.hits = 0; -  ent->client->pers.statscounters.hitslocational = 0; -  ent->client->pers.statscounters.deaths = 0; -  ent->client->pers.statscounters.feeds = 0; -  ent->client->pers.statscounters.suicides = 0; -  ent->client->pers.statscounters.teamkills = 0; -  ent->client->pers.statscounters.dmgdone = 0; -  ent->client->pers.statscounters.structdmgdone = 0; -  ent->client->pers.statscounters.ffdmgdone = 0; -  ent->client->pers.statscounters.structsbuilt = 0; -  ent->client->pers.statscounters.timealive = 0; -  ent->client->pers.statscounters.timeinbase = 0; -  ent->client->pers.statscounters.dretchbasytime = 0; -  ent->client->pers.statscounters.jetpackusewallwalkusetime = 0; - -  if( G_admin_permission( ent, ADMF_DBUILDER ) ) -  { -    if( !ent->client->pers.designatedBuilder ) -    { -      ent->client->pers.designatedBuilder = qtrue; -      trap_SendServerCommand( ent-g_entities,  -        "print \"Your designation has been restored\n\"" ); -    } -  } -  else if( ent->client->pers.designatedBuilder ) -  { -    ent->client->pers.designatedBuilder = qfalse; -    trap_SendServerCommand( ent-g_entities,  -     "print \"You have lost designation due to teamchange\n\"" ); +      team = TEAM_HUMANS; +      break; + +    default: +      trap_SendServerCommand( ent-g_entities, +        va( "print \"Unknown team: %s\n\"", s ) ); +      return;    } -  ent->client->pers.classSelection = PCL_NONE; -  ClientSpawn( ent, NULL, NULL, NULL ); +  // stop team join spam +  if( oldteam == team ) +    return; -  ent->client->pers.joinedATeam = qtrue; -  ent->client->pers.teamChangeTime = level.time; +  if( team != TEAM_NONE && g_maxGameClients.integer && +    level.numPlayingClients >= g_maxGameClients.integer ) +  { +    G_TriggerMenu( ent - g_entities, MN_PLAYERLIMIT ); +    return; +  } -  //update ClientInfo -  ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); -  G_CheckDBProtection( ); +  // Apply the change +  G_ChangeTeam( ent, team );  }  /* -================= -Cmd_Team_f -================= +================== +G_CensorString +==================  */ -void Cmd_Team_f( gentity_t *ent ) +static char censors[ 20000 ]; +static int numcensors; + +void G_LoadCensors( void )  { -  pTeam_t team; -  pTeam_t oldteam = ent->client->pers.teamSelection; -  char    s[ MAX_TOKEN_CHARS ]; -  char buf[ MAX_STRING_CHARS ]; -  qboolean force = G_admin_permission(ent, ADMF_FORCETEAMCHANGE); -  int     aliens = level.numAlienClients; -  int     humans = level.numHumanClients; +  char *text_p, *token; +  char text[ 20000 ]; +  char *term; +  int  len; +  fileHandle_t f; -  // stop team join spam -  if( level.time - ent->client->pers.teamChangeTime < 1000 ) -    return; -  // Prevent invisible players from joining a team -  if ( ent->client->sess.invisible == qtrue ) -  { -    trap_SendServerCommand( ent-g_entities, -          va( "print \"You cannot join a team while invisible\n\"" ) ); +  numcensors = 0; + +  if( !g_censorship.string[ 0 ] )      return; -  } -  if( g_scrimMode.integer != 0 && !G_admin_permission( ent, ADMF_NOSCRIMRESTRICTION ) ) +  len = trap_FS_FOpenFile( g_censorship.string, &f, FS_READ ); +  if( len < 0 )    { -    trap_SendServerCommand( ent-g_entities, -                            va( "print \"You can't join a team when scrim mode is enabled\n\"" ) ); +    Com_Printf( S_COLOR_RED "ERROR: Censors file %s doesn't exist\n", +      g_censorship.string );      return;    } - -  if( oldteam == PTE_ALIENS ) -    aliens--; -  else if( oldteam == PTE_HUMANS ) -    humans--; - -  // do warm up -  if( g_doWarmup.integer && g_warmupMode.integer == 1 && -      level.time - level.startTime < g_warmup.integer * 1000 ) +  if( len == 0 || len >= sizeof( text ) - 1 )    { -    trap_SendServerCommand( ent - g_entities, va( "print \"team: you can't join" -      " a team during warm up (%d seconds remaining)\n\"", -      g_warmup.integer - ( level.time - level.startTime ) / 1000 ) ); +    trap_FS_FCloseFile( f ); +    Com_Printf( S_COLOR_RED "ERROR: Censors file %s is %s\n", +      g_censorship.string, len == 0 ? "empty" : "too long" );      return;    } +  trap_FS_Read( text, len, f ); +  trap_FS_FCloseFile( f ); +  text[ len ] = 0; -  trap_Argv( 1, s, sizeof( s ) ); +  term = censors; -  if( !strlen( s ) ) +  text_p = text; +  while( 1 )    { -    trap_SendServerCommand( ent-g_entities, va("print \"team: %i\n\"", -      oldteam ) ); -    return; +    token = COM_Parse( &text_p ); +    if( !*token || sizeof( censors ) - ( term - censors ) < 4 ) +      break; +    Q_strncpyz( term, token, sizeof( censors ) - ( term - censors ) ); +    Q_strlwr( term ); +    term += strlen( term ) + 1; +    if( sizeof( censors ) - ( term - censors ) == 0 ) +      break; +    token = COM_ParseExt( &text_p, qfalse ); +    Q_strncpyz( term, token, sizeof( censors ) - ( term - censors ) ); +    term += strlen( term ) + 1; +    numcensors++;    } +  G_Printf( "Parsed %d string replacements\n", numcensors ); +} -  if( Q_stricmpn( s, "spec", 4 ) ){ -    if(G_admin_level(ent)<g_minLevelToJoinTeam.integer){ -        trap_SendServerCommand( ent-g_entities,"print \"Sorry, but your admin level is only permitted to spectate.\n\"" );  -        return; -    } -  } -   -  if( !Q_stricmpn( s, "spec", 4 ) ) -    team = PTE_NONE; -  else if( !force && ent->client->pers.teamSelection == PTE_NONE && -           g_maxGameClients.integer && level.numPlayingClients >= -           g_maxGameClients.integer ) +void G_CensorString( char *out, const char *in, int len, gentity_t *ent ) +{ +  const char *s, *m; +  int  i; + +  if( !numcensors || G_admin_permission( ent, ADMF_NOCENSORFLOOD) )    { -    trap_SendServerCommand( ent - g_entities, va( "print \"The maximum number " -      "of playing clients has been reached (g_maxGameClients = %i)\n\"", -      g_maxGameClients.integer ) ); +    Q_strncpyz( out, in, len );      return;    } -  else if ( ent->client->pers.specExpires > level.time ) -  { -  trap_SendServerCommand( ent-g_entities, va( "print \"You can't join a team yet. Expires in %d seconds.\n\"", -                          ( ent->client->pers.specExpires - level.time ) / 1000 ) ); -  return; -  } -  else if( !Q_stricmpn( s, "alien", 5 ) ) -  { -    if( g_forceAutoSelect.integer && !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) ) -    { -      trap_SendServerCommand( ent-g_entities, "print \"You can only join teams using autoselect\n\"" ); -      return; -    } - -    if( level.alienTeamLocked && !force ) -    { -      trap_SendServerCommand( ent-g_entities, -        va( "print \"Alien team has been ^1LOCKED\n\"" ) ); -      return;  -    } -    else if( level.humanTeamLocked ) -    { -      // if only one team has been locked, let people join the other -      // regardless of balance -      force = qtrue; -    } -    if( !force && g_teamForceBalance.integer && aliens > humans ) -    { -      G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); -      return; -    } -     - -    team = PTE_ALIENS; -  } -  else if( !Q_stricmpn( s, "human", 5 ) ) +  len--; +  while( *in )    { -    if( g_forceAutoSelect.integer && !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) ) +    if( Q_IsColorString( in ) )      { -      trap_SendServerCommand( ent-g_entities, "print \"You can only join teams using autoselect\n\"" ); -      return; +      if( len < 2 ) +        break; +      *out++ = *in++; +      *out++ = *in++; +      len -= 2; +      continue;      } - -    if( level.humanTeamLocked && !force ) +    if( !isalnum( *in ) )      { -      trap_SendServerCommand( ent-g_entities, -        va( "print \"Human team has been ^1LOCKED\n\"" ) ); -      return;  +      if( len < 1 ) +        break; +      *out++ = *in++; +      len--; +      continue;      } -    else if( level.alienTeamLocked ) +    m = censors; +    for( i = 0; i < numcensors; i++, m++ )      { -      // if only one team has been locked, let people join the other -      // regardless of balance -      force = qtrue; +      s = in; +      while( *s && *m ) +      { +        if( Q_IsColorString( s ) ) +        { +          s += 2; +          continue; +        } +        if( !isalnum( *s ) ) +        { +          s++; +          continue; +        } +        if( tolower( *s ) != *m ) +          break; +        s++; +        m++; +      } +      // match +      if( !*m ) +      { +        in = s; +        m++; +        while( *m ) +        { +          if( len < 1 ) +            break; +          *out++ = *m++; +          len--; +        } +        break; +      } +      else +      { +        while( *m ) +          m++; +        m++; +        while( *m ) +          m++; +      }      } - -    if( !force && g_teamForceBalance.integer && humans > aliens ) +    if( len < 1 ) +      break; +    // no match +    if( i == numcensors )      { -      G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); -      return; +      *out++ = *in++; +      len--;      } - -    team = PTE_HUMANS;    } -  else if( !Q_stricmp( s, "auto" ) ) -  { -    if( level.humanTeamLocked && level.alienTeamLocked ) -      team = PTE_NONE; -    else if( humans > aliens ) -      team = PTE_ALIENS; -    else if( humans < aliens ) -      team = PTE_HUMANS; -    else -      team = PTE_ALIENS + ( rand( ) % 2 ); - -    if( team == PTE_ALIENS && level.alienTeamLocked ) -      team = PTE_HUMANS; -    else if( team == PTE_HUMANS && level.humanTeamLocked ) -      team = PTE_ALIENS; -  } -  else -  { -    trap_SendServerCommand( ent-g_entities, va( "print \"Unknown team: %s\n\"", s ) ); -    return; -  } - -  // stop team join spam -  if( oldteam == team ) -    return; - -  //guard against build timer exploit -  if( oldteam != PTE_NONE && ent->client->sess.sessionTeam != TEAM_SPECTATOR && -     ( ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || -       ent->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG || -       BG_InventoryContainsWeapon( WP_HBUILD, ent->client->ps.stats ) || -       BG_InventoryContainsWeapon( WP_HBUILD2, ent->client->ps.stats ) ) && -      ent->client->ps.stats[ STAT_MISC ] > 0 ) -  { -    trap_SendServerCommand( ent-g_entities, -        va( "print \"You cannot change teams until build timer expires\n\"" ) ); -    return; -  } - -   if (team != PTE_NONE) -   { -     char  namebuff[32]; -  -     Q_strncpyz (namebuff, ent->client->pers.netname, sizeof(namebuff)); -     Q_CleanStr (namebuff); -  -     if (!namebuff[0] || !Q_stricmp (namebuff, "UnnamedPlayer")) -     { -       trap_SendServerCommand( ent-g_entities, va( "print \"Please set your player name before joining a team. Press ESC and use the Options / Game menu  or use /name in the console\n\"") ); -       return; -     } -   } -  - -  G_ChangeTeam( ent, team ); -    -    - -   if( team == PTE_ALIENS ) { -     if ( oldteam == PTE_HUMANS ) -       Com_sprintf( buf, sizeof( buf ), "%s^7 abandoned humans and joined the aliens.", ent->client->pers.netname ); -     else -       Com_sprintf( buf, sizeof( buf ), "%s^7 joined the aliens.", ent->client->pers.netname ); -   } -   else if( team == PTE_HUMANS ) { -     if ( oldteam == PTE_ALIENS ) -       Com_sprintf( buf, sizeof( buf ), "%s^7 abandoned the aliens and joined the humans.", ent->client->pers.netname ); -     else -       Com_sprintf( buf, sizeof( buf ), "%s^7 joined the humans.", ent->client->pers.netname ); -   } -   else if( team == PTE_NONE ) { -     if ( oldteam == PTE_HUMANS ) -       Com_sprintf( buf, sizeof( buf ), "%s^7 left the humans.", ent->client->pers.netname ); -     else -       Com_sprintf( buf, sizeof( buf ), "%s^7 left the aliens.", ent->client->pers.netname ); -   } -   trap_SendServerCommand( -1, va( "print \"%s\n\"", buf ) ); -   G_LogOnlyPrintf("ClientTeam: %s\n",buf); +  *out = 0;  } -  /*  ==================  G_Say  ==================  */ -static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, const char *prefix ) +static qboolean G_SayTo( gentity_t *ent, gentity_t *other, saymode_t mode, const char *message )  { -  qboolean ignore = qfalse; -  qboolean specAllChat = qfalse; -    if( !other ) -    return; +    return qfalse;    if( !other->inuse ) -    return; +    return qfalse;    if( !other->client ) -    return; +    return qfalse;    if( other->client->pers.connected != CON_CONNECTED ) -    return; +    return qfalse; -  if( ( mode == SAY_TEAM || mode == SAY_ACTION_T ) && !OnSameTeam( ent, other ) ) -  { -    if( other->client->pers.teamSelection != PTE_NONE ) -      return; +  if( Com_ClientListContains( &other->client->sess.ignoreList, (int)( ent - g_entities ) ) ) +    return qfalse; -    specAllChat = G_admin_permission( other, ADMF_SPEC_ALLCHAT ); -    if( !specAllChat ) -      return; +  if( ( ent && !OnSameTeam( ent, other ) ) && +      ( mode == SAY_TEAM || mode == SAY_AREA || mode == SAY_TPRIVMSG ) ) +  { +    if( other->client->pers.teamSelection != TEAM_NONE ) +      return qfalse;      // specs with ADMF_SPEC_ALLCHAT flag can see team chat +    if( !G_admin_permission( other, ADMF_SPEC_ALLCHAT ) && mode != SAY_TPRIVMSG ) +      return qfalse;    } -  if( mode == SAY_ADMINS && -     (!G_admin_permission( other, ADMF_ADMINCHAT ) || other->client->pers.ignoreAdminWarnings || -      ( g_scrimMode.integer != 0 && !G_admin_permission( ent, ADMF_NOSCRIMRESTRICTION ) ) ) ) -     return; - -  if( mode == SAY_HADMINS && -     (!G_admin_permission( other, ADMF_HIGHADMINCHAT ) || other->client->pers.ignoreAdminWarnings ) ) -     return; - -  if( BG_ClientListTest( &other->client->sess.ignoreList, ent-g_entities ) ) -    ignore = qtrue; - -  if ( ignore && g_fullIgnore.integer ) -    return; +  trap_SendServerCommand( other-g_entities, va( "chat %d %d \"%s\"", +    (int)( ent ? ent-g_entities : -1 ), +    mode, +    message ) ); -  trap_SendServerCommand( other-g_entities, va( "%s \"%s%s%s%c%c%s\"", -    ( mode == SAY_TEAM || mode == SAY_ACTION_T ) ? "tchat" : "chat", -    ( ignore ) ? "[skipnotify]" : "", -    ( specAllChat ) ? prefix : "", -    name, Q_COLOR_ESCAPE, color, message ) ); +  return qtrue;  } -#define EC    "\x19" - -void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) +void G_Say( gentity_t *ent, saymode_t mode, const char *chatText )  {    int         j;    gentity_t   *other; -  int         color; -  const char  *prefix; -  char        name[ 64 ];    // don't let text be too long for malicious reasons    char        text[ MAX_SAY_TEXT ]; -  char        location[ 64 ]; - -  // Bail if the text is blank. -  if( ! chatText[0] ) -     return; - -  // Invisible players cannot use chat -  if( ent->client->sess.invisible == qtrue ) -  { -    if( !G_admin_cmd_check( ent, qtrue ) ) -      trap_SendServerCommand( ent-g_entities, "print \"You cannot chat while invisible\n\"" ); -    return; -  } -  if( ent && ent->client->pers.teamSelection == PTE_NONE && g_scrimMode.integer != 0 && !G_admin_permission( ent, ADMF_NOSCRIMRESTRICTION ) && mode != SAY_TEAM ) +  // check if blocked by g_specChat 0 +  if( ( !g_specChat.integer ) && ( mode != SAY_TEAM ) && +      ( ent ) && ( ent->client->pers.teamSelection == TEAM_NONE ) && +      ( !G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) )    { -      trap_SendServerCommand( ent-g_entities, "print \"You can't chat when scrim mode is enabled.\n\"" ); -      return; -  } - -  // Spam limit: If they said this message recently, ignore it. -  if( g_spamTime.integer ) -  { -    if ( ( level.time - ent->client->pers.lastMessageTime ) < ( g_spamTime.integer * 1000 ) && -         !Q_stricmp( ent->client->pers.lastMessage, chatText) && -         !G_admin_permission( ent, ADMF_NOCENSORFLOOD ) && -         ent->client->pers.floodDemerits <= g_floodMaxDemerits.integer ) -    { -      trap_SendServerCommand( ent-g_entities, "print \"Your message has been ignored to prevent spam\n\"" ); -      return; -    } -    else -    { -      ent->client->pers.lastMessageTime = level.time; - -      Q_strncpyz( ent->client->pers.lastMessage, chatText, -        sizeof( ent->client->pers.lastMessage ) ); -    } -  } - -  // Flood limit.  If they're talking too fast, determine that and return. -  if( g_floodMinTime.integer ) -    if ( G_Flood_Limited( ent ) ) -    { -      trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); -      return; -    } - -  if (g_chatTeamPrefix.integer && ent && ent->client ) -  { -    switch( ent->client->pers.teamSelection) -    { -      default: -      case PTE_NONE: -        prefix = "[^3S^7] "; -        break; - -      case PTE_ALIENS: -        prefix = "[^1A^7] "; -        break; - -      case PTE_HUMANS: -        prefix = "[^4H^7] "; -    } +    trap_SendServerCommand( ent-g_entities, "print \"say: Global chatting for " +      "spectators has been disabled. You may only use team chat.\n\"" ); +    mode = SAY_TEAM;    } -  else -    prefix = "";    switch( mode )    { -    default:      case SAY_ALL: -      G_LogPrintf( "say: %s^7: %s^7\n", ent->client->pers.netname, chatText ); -      Com_sprintf( name, sizeof( name ), "%s%s%c%c"EC": ", prefix, -                   ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); -      color = COLOR_GREEN; +      G_LogPrintf( "Say: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_GREEN "%s\n", +        (int)( ( ent ) ? ent - g_entities : -1 ), +        ( ent ) ? ent->client->pers.netname : "console", chatText );        break; -      case SAY_TEAM: -      G_LogPrintf( "sayteam: %s%s^7: %s^7\n", prefix, ent->client->pers.netname, chatText ); -      if( Team_GetLocationMsg( ent, location, sizeof( location ) ) ) -        Com_sprintf( name, sizeof( name ), EC"(%s%c%c"EC") (%s)"EC": ", -          ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); -      else -        Com_sprintf( name, sizeof( name ), EC"(%s%c%c"EC")"EC": ", -          ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); -       -      if( ent->client->pers.teamSelection == PTE_NONE ) -        color = COLOR_YELLOW; -      else -        color = COLOR_CYAN; -      break; - -    case SAY_TELL: -      if( target && OnSameTeam( target, ent ) && -          Team_GetLocationMsg( ent, location, sizeof( location ) ) ) -        Com_sprintf( name, sizeof( name ), EC"[%s%c%c"EC"] (%s)"EC": ", -          ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); -      else -        Com_sprintf( name, sizeof( name ), EC"[%s%c%c"EC"]"EC": ", -          ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); -      color = COLOR_MAGENTA; -      break; -       -    case SAY_ACTION: -      G_LogPrintf( "action: %s^7: %s^7\n", ent->client->pers.netname, chatText ); -      Com_sprintf( name, sizeof( name ), "^2%s^7%s%s%c%c"EC" ", g_actionPrefix.string, prefix, -                   ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); -      color = COLOR_WHITE; +      // console say_team is handled in g_svscmds, not here +      if( !ent || !ent->client ) +        Com_Error( ERR_FATAL, "SAY_TEAM by non-client entity" ); +      G_LogPrintf( "SayTeam: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_CYAN "%s\n", +        (int)( ent - g_entities ), ent->client->pers.netname, chatText );        break; - -    case SAY_ACTION_T: -      G_LogPrintf( "actionteam: %s%s^7: %s^7\n", prefix, ent->client->pers.netname, chatText ); -      if( Team_GetLocationMsg( ent, location, sizeof( location ) ) ) -        Com_sprintf( name, sizeof( name ), EC"^5%s^7%s%c%c"EC"(%s)"EC" ", g_actionPrefix.string,  -          ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); -      else -        Com_sprintf( name, sizeof( name ), EC"^5%s^7%s%c%c"EC""EC" ", g_actionPrefix.string,  -          ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); -      color = COLOR_WHITE; +    case SAY_RAW: +      if( ent ) +        Com_Error( ERR_FATAL, "SAY_RAW by client entity" ); +      G_LogPrintf( "Chat: -1 \"console\": %s\n", chatText ); +    default:        break; -       -      case SAY_ADMINS: -        if( G_admin_permission( ent, ADMF_ADMINCHAT ) ) //Differentiate between inter-admin chatter and user-admin alerts -        { -         G_LogPrintf( "say_admins: [ADMIN]%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText ); -         Com_sprintf( name, sizeof( name ), "%s[ADMIN]%s%c%c"EC": ", prefix, -                    ( ent ) ? ent->client->pers.netname : "console", Q_COLOR_ESCAPE, COLOR_WHITE ); -         color = COLOR_MAGENTA; -        } -        else -        { -          G_LogPrintf( "say_admins: [PLAYER]%s^7: %s^7\n", ent->client->pers.netname, chatText ); -          Com_sprintf( name, sizeof( name ), "%s[PLAYER]%s%c%c"EC": ", prefix, -            ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); -          color = COLOR_MAGENTA; -        } -        break; - -        case SAY_HADMINS: -        if( G_admin_permission( ent, ADMF_HIGHADMINCHAT ) ) //Differentiate between inter-high-admin chatter and lower-admin-high-admin-admin alerts and user-admin alerts -        { -          G_LogPrintf( "say_hadmins: ^5[^1HIGH ADMIN^5]^7%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText ); -          Com_sprintf( name, sizeof( name ), "%s^5[^1HIGH ADMIN^5]^7%s%c%c"EC": ", prefix, -                    ( ent ) ? ent->client->pers.netname : "console", Q_COLOR_ESCAPE, COLOR_WHITE ); -          color = COLOR_RED; -        } -        else if( G_admin_permission( ent, ADMF_ADMINCHAT ) )  -        { -         G_LogPrintf( "say_haadmins: ^1[^6LOWER ADMIN^1]^7%s^7: %s^7\n", ( ent ) ? ent->client->pers.netname : "console", chatText ); -         Com_sprintf( name, sizeof( name ), "%s[^6LOWER ADMIN^7]%s%c%c"EC": ", prefix, -                    ( ent ) ? ent->client->pers.netname : "console", Q_COLOR_ESCAPE, COLOR_WHITE ); -         color = COLOR_RED; -        } -        else -        { -          G_LogPrintf( "say_hadmins: [PLAYER]%s^7: %s^7\n", ent->client->pers.netname, chatText ); -          Com_sprintf( name, sizeof( name ), "%s[PLAYER]%s%c%c"EC": ", prefix, -            ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); -          color = COLOR_RED; -        } -        break; -} - - -  if( mode!=SAY_TEAM && ent && ent->client && ent->client->pers.teamSelection == PTE_NONE && G_admin_level(ent)<g_minLevelToSpecMM1.integer ) -  { -    trap_SendServerCommand( ent-g_entities,va( "print \"Sorry, but your admin level may only use teamchat while spectating.\n\"") );  -    return;    } -  Com_sprintf( text, sizeof( text ), "%s^7", chatText ); +  G_CensorString( text, chatText, sizeof( text ), ent ); -  if( ent && ent->client && g_aimbotAdvertBan.integer && ( Q_stricmp( text, "^1N^7ullify for ^1T^7remulous [beta] | Get it at CheatersUtopia.com^7" ) == 0 ) ) +  // send it to all the apropriate clients +  for( j = 0; j < level.maxclients; j++ )    { -    trap_SendConsoleCommand( 0, -                             va( "!ban %s %s %s\n", -                                 ent->client->pers.ip, -                                 ( Q_stricmp( g_aimbotAdvertBanTime.string, "0" ) == 1 ) ? g_aimbotAdvertBanTime.string : "" , -                                   g_aimbotAdvertBanReason.string ) ); -    Q_strncpyz( text, "^7has been caught hacking and will be dealt with.", sizeof( text ) ); +    other = &g_entities[ j ]; +    G_SayTo( ent, other, mode, text );    } - -  if( target ) -  { -    G_SayTo( ent, target, mode, color, name, text, prefix ); -    return; -  } -   - - -  // Ugly hax: if adminsayfilter is off, do the SAY first to prevent text from going out of order -  if( !g_adminSayFilter.integer ) -  { -    // send it to all the apropriate clients -    for( j = 0; j < level.maxclients; j++ ) -    { -      other = &g_entities[ j ]; -      G_SayTo( ent, other, mode, color, name, text, prefix ); -    } -  } -    -   if( g_adminParseSay.integer && ( mode== SAY_ALL || mode == SAY_TEAM ) ) -   { -     if( G_admin_cmd_check ( ent, qtrue ) && g_adminSayFilter.integer )  -     { -       return; -     } -   } - -  // if it's on, do it here, where it won't happen if it was an admin command -  if( g_adminSayFilter.integer ) -  { -    // send it to all the apropriate clients -    for( j = 0; j < level.maxclients; j++ ) -    { -      other = &g_entities[ j ]; -      G_SayTo( ent, other, mode, color, name, text, prefix ); -    } -  } -   -  } +/* +================== +Cmd_SayArea_f +================== +*/  static void Cmd_SayArea_f( gentity_t *ent )  {    int    entityList[ MAX_GENTITIES ];    int    num, i; -  int    color = COLOR_BLUE; -  const char  *prefix; -  vec3_t range = { HELMET_RANGE, HELMET_RANGE, HELMET_RANGE }; +  vec3_t range = { 1000.0f, 1000.0f, 1000.0f };    vec3_t mins, maxs; -  char   *msg = ConcatArgs( 1 ); -  char   name[ 64 ]; -   -   if( g_floodMinTime.integer ) -   if ( G_Flood_Limited( ent ) ) -   { -    trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); -    return; -   } -   -  if (g_chatTeamPrefix.integer) +  char   *msg; + +  if( trap_Argc( ) < 2 )    { -    switch( ent->client->pers.teamSelection) -    { -      default: -      case PTE_NONE: -        prefix = "[^3S^7] "; -        break; +    ADMP( "usage: say_area [message]\n" ); +    return; +  } -      case PTE_ALIENS: -        prefix = "[^1A^7] "; -        break; +  msg = ConcatArgs( 1 ); -      case PTE_HUMANS: -        prefix = "[^4H^7] "; -    } -  } -  else -    prefix = ""; +  for(i = 0; i < 3; i++ ) +    range[ i ] = g_sayAreaRange.value; -  G_LogPrintf( "sayarea: %s%s^7: %s\n", prefix, ent->client->pers.netname, msg ); -  Com_sprintf( name, sizeof( name ), EC"<%s%c%c"EC"> ", -    ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); +  G_LogPrintf( "SayArea: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_BLUE "%s\n", +    (int)( ent - g_entities ), ent->client->pers.netname, msg ); -  VectorAdd( ent->s.origin, range, maxs ); -  VectorSubtract( ent->s.origin, range, mins ); +  VectorAdd( ent->r.currentOrigin, range, maxs ); +  VectorSubtract( ent->r.currentOrigin, range, mins );    num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );    for( i = 0; i < num; i++ ) -    G_SayTo( ent, &g_entities[ entityList[ i ] ], SAY_TEAM, color, name, msg, prefix ); -   +    G_SayTo( ent, &g_entities[ entityList[ i ] ], SAY_AREA, msg ); +    //Send to ADMF_SPEC_ALLCHAT candidates    for( i = 0; i < level.maxclients; i++ )    { -    if( (&g_entities[ i ])->client->pers.teamSelection == PTE_NONE  && +    if( g_entities[ i ].client->pers.teamSelection == TEAM_NONE &&          G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) )      { -      G_SayTo( ent, &g_entities[ i ], SAY_TEAM, color, name, msg, prefix );    +      G_SayTo( ent, &g_entities[ i ], SAY_AREA, msg );      }    }  } @@ -1326,160 +1151,141 @@ Cmd_Say_f  static void Cmd_Say_f( gentity_t *ent )  {    char    *p; -  char    *args; -  int     mode = SAY_ALL; -  int     skipargs = 0; +  char    cmd[ MAX_TOKEN_CHARS ]; +  saymode_t mode = SAY_ALL; -  args = G_SayConcatArgs( 0 ); -  if( Q_stricmpn( args, "say_team ", 9 ) == 0 ) +  if( trap_Argc( ) < 2 ) +    return; + +  trap_Argv( 0, cmd, sizeof( cmd ) ); +  if( Q_stricmp( cmd, "say_team" ) == 0 )      mode = SAY_TEAM; -  if( Q_stricmpn( args, "say_admins ", 11 ) == 0 || Q_stricmpn( args, "a ", 2 ) == 0) -    mode = SAY_ADMINS; -  if( Q_stricmpn( args, "say_hadmins ", 12 ) == 0 || Q_stricmpn( args, "ha ", 3 ) == 0) -    mode = SAY_HADMINS; - -  // support parsing /m out of say text since some people have a hard -  // time figuring out what the console is. -  if( !Q_stricmpn( args, "say /m ", 7 ) || -      !Q_stricmpn( args, "say_team /m ", 12 ) ||  -      !Q_stricmpn( args, "say /mt ", 8 ) ||  -      !Q_stricmpn( args, "say_team /mt ", 13 ) ) + +  p = ConcatArgs( 1 ); + +  G_Say( ent, mode, p ); +} + +/* +================== +Cmd_VSay_f +================== +*/ +void Cmd_VSay_f( gentity_t *ent ) +{ +  char            arg[MAX_TOKEN_CHARS]; +  char            text[ MAX_TOKEN_CHARS ]; +  voiceChannel_t  vchan; +  voice_t         *voice; +  voiceCmd_t      *cmd; +  voiceTrack_t    *track; +  int             cmdNum = 0; +  int             trackNum = 0; +  char            voiceName[ MAX_VOICE_NAME_LEN ] = {"default"}; +  char            voiceCmd[ MAX_VOICE_CMD_LEN ] = {""}; +  char            vsay[ 12 ] = {""}; +  weapon_t        weapon; + +  if( !ent || !ent->client ) +    Com_Error( ERR_FATAL, "Cmd_VSay_f() called by non-client entity" ); + +  trap_Argv( 0, arg, sizeof( arg ) ); +  if( trap_Argc( ) < 2 )    { -    G_PrivateMessage( ent ); +    trap_SendServerCommand( ent-g_entities, va( +      "print \"usage: %s command [text] \n\"", arg ) );      return;    } - -  if( !Q_stricmpn( args, "say /a ", 7) || -       !Q_stricmpn( args, "say_team /a ", 12) || -       !Q_stricmpn( args, "say /say_admins ", 16) || -       !Q_stricmpn( args, "say_team /say_admins ", 21) ) -   { -       mode = SAY_ADMINS; -       skipargs=1; -   } - -   if( !Q_stricmpn( args, "say /ha ", 8) || -       !Q_stricmpn( args, "say_team /ha ", 13) || -       !Q_stricmpn( args, "say /say_hadmins ", 17) || -       !Q_stricmpn( args, "say_team /say_hadmins ", 22) ) -   { -       mode = SAY_HADMINS; -       skipargs=1; -   } - -   if( mode == SAY_ADMINS)   -   if(!G_admin_permission( ent, ADMF_ADMINCHAT ) ) -   { -     if( !g_publicSayadmins.integer ) -     { -      ADMP( "Sorry, but public use of say_admins has been disabled.\n" ); -      return; -     } -     else -     { -       ADMP( "Your message has been sent to any available admins and to the server logs.\n" ); -     } -   } - -   if( mode == SAY_HADMINS) -   if(!G_admin_permission( ent, ADMF_HIGHADMINCHAT ) ) -   { -     { -       ADMP( "You don't have permissions to see/use this channel.\n" ); -     } -   } - -  if(!Q_stricmpn( args, "say /me ", 8 ) ) +  if( !level.voices )    { -   if( g_actionPrefix.string[0] )  -   {  -    mode = SAY_ACTION; -    skipargs=1; -   } else return; +    trap_SendServerCommand( ent-g_entities, va( +      "print \"%s: voice system is not installed on this server\n\"", arg ) ); +    return;    } -  else if(!Q_stricmpn( args, "say_team /me ", 13 ) ) +  if( !g_voiceChats.integer )    { -   if( g_actionPrefix.string[0] )  -   {  -    mode = SAY_ACTION_T; -    skipargs=1; -   } else return; +    trap_SendServerCommand( ent-g_entities, va( +      "print \"%s: voice system administratively disabled on this server\n\"", +      arg ) ); +    return;    } -  else if( !Q_stricmpn( args, "me ", 3 ) ) +  if( !Q_stricmp( arg, "vsay" ) ) +    vchan = VOICE_CHAN_ALL; +  else if( !Q_stricmp( arg, "vsay_team" ) ) +    vchan = VOICE_CHAN_TEAM; +  else if( !Q_stricmp( arg, "vsay_local" ) ) +    vchan = VOICE_CHAN_LOCAL; +  else +    return; +  Q_strncpyz( vsay, arg, sizeof( vsay ) ); + +  if( ent->client->pers.voice[ 0 ] ) +    Q_strncpyz( voiceName, ent->client->pers.voice, sizeof( voiceName ) ); +  voice = BG_VoiceByName( level.voices, voiceName ); +  if( !voice )    { -   if( g_actionPrefix.string[0] )  -   {  -    mode = SAY_ACTION; -   } else return; +    trap_SendServerCommand( ent-g_entities, va( +      "print \"%s: voice '%s' not found\n\"", vsay, voiceName ) ); +    return;    } -  else if( !Q_stricmpn( args, "me_team ", 8 ) ) + +  trap_Argv( 1, voiceCmd, sizeof( voiceCmd ) ) ; +  cmd = BG_VoiceCmdFind( voice->cmds, voiceCmd, &cmdNum ); +  if( !cmd )    { -   if( g_actionPrefix.string[0] )  -   {  -    mode = SAY_ACTION_T; -   } else return; +    trap_SendServerCommand( ent-g_entities, va( +     "print \"%s: command '%s' not found in voice '%s'\n\"", +      vsay, voiceCmd, voiceName ) ); +    return;    } - -  if( g_allowShare.integer ) +  // filter non-spec humans by their primary weapon as well +  weapon = WP_NONE; +  if( ent->client->sess.spectatorState == SPECTATOR_NOT )    { -    args = G_SayConcatArgs(0); -    if( !Q_stricmpn( args, "say /share", 10 ) || -      !Q_stricmpn( args, "say_team /share", 15 ) ) -    { -      Cmd_Share_f( ent ); -      return; -    } -   if( !Q_stricmpn( args, "say /donate", 11 ) || -      !Q_stricmpn( args, "say_team /donate", 16 ) ) -    { -      Cmd_Donate_f( ent ); -      return; -    } +    weapon = ent->client->ps.stats[ STAT_WEAPON ];    } - -  if( trap_Argc( ) < 2 ) -    return; - -  p = G_SayConcatArgs( 1 + skipargs ); - -  G_Say( ent, NULL, mode, p ); -} - -/* -================== -Cmd_Tell_f -================== -*/ -static void Cmd_Tell_f( gentity_t *ent ) -{ -  int     targetNum; -  gentity_t *target; -  char    *p; -  char    arg[MAX_TOKEN_CHARS]; - -  if( trap_Argc( ) < 2 ) +  track = BG_VoiceTrackFind( cmd->tracks, ent->client->pers.teamSelection, +    ent->client->pers.classSelection, weapon, (int)ent->client->voiceEnthusiasm, +    &trackNum ); +  if( !track ) +  { +    trap_SendServerCommand( ent-g_entities, va( +      "print \"%s: no available track for command '%s', team %d, " +      "class %d, weapon %d, and enthusiasm %d in voice '%s'\n\"", +      vsay, voiceCmd, ent->client->pers.teamSelection, +      ent->client->pers.classSelection, weapon, +      (int)ent->client->voiceEnthusiasm, voiceName ) );      return; +  } -  trap_Argv( 1, arg, sizeof( arg ) ); -  targetNum = atoi( arg ); - -  if( targetNum < 0 || targetNum >= level.maxclients ) -    return; +  if( !Q_stricmp( ent->client->lastVoiceCmd, cmd->cmd ) ) +    ent->client->voiceEnthusiasm++; -  target = &g_entities[ targetNum ]; -  if( !target || !target->inuse || !target->client ) -    return; +  Q_strncpyz( ent->client->lastVoiceCmd, cmd->cmd, +    sizeof( ent->client->lastVoiceCmd ) ); -  p = ConcatArgs( 2 ); +  // optional user supplied text +  trap_Argv( 2, arg, sizeof( arg ) ); +  G_CensorString( text, arg, sizeof( text ), ent ); -  G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); -  G_Say( ent, target, SAY_TELL, p ); -  // don't tell to the player self if it was already directed to this player -  // also don't send the chat back to a bot -  if( ent != target ) -    G_Say( ent, ent, SAY_TELL, p ); +  switch( vchan ) +  { +    case VOICE_CHAN_ALL: +    case VOICE_CHAN_LOCAL: +      trap_SendServerCommand( -1, va( +        "voice %d %d %d %d \"%s\"\n", +        (int)( ent-g_entities ), vchan, cmdNum, trackNum, text ) ); +      break; +    case VOICE_CHAN_TEAM: +      G_TeamCommand( ent->client->pers.teamSelection, va( +        "voice %d %d %d %d \"%s\"\n", +        (int)( ent-g_entities ), vchan, cmdNum, trackNum, text ) ); +      break; +    default: +      break; +  }  }  /* @@ -1489,27 +1295,12 @@ Cmd_Where_f  */  void Cmd_Where_f( gentity_t *ent )  { -  trap_SendServerCommand( ent-g_entities, va( "print \"%s\n\"", vtos( ent->s.origin ) ) ); -} - - -static qboolean map_is_votable( const char *map ) -{ -  char maps[ MAX_CVAR_VALUE_STRING ]; -  char *token, *token_p; - -  if( !g_votableMaps.string[ 0 ] ) -    return qtrue; - -  Q_strncpyz( maps, g_votableMaps.string, sizeof( maps ) ); -  token_p = maps; -  while( *( token = COM_Parse( &token_p ) ) ) -  { -    if( !Q_stricmp( token, map ) ) -      return qtrue; -  } - -  return qfalse; +  if( !ent->client ) +    return; +  trap_SendServerCommand( ent - g_entities, +                          va( "print \"origin: %f %f %f\n\"", +                              ent->r.currentOrigin[ 0 ], ent->r.currentOrigin[ 1 ], +                              ent->r.currentOrigin[ 2 ] ) );  }  /* @@ -1519,1099 +1310,449 @@ Cmd_CallVote_f  */  void Cmd_CallVote_f( gentity_t *ent )  { -  int   i; -  char  arg1[ MAX_STRING_TOKENS ]; -  char  arg2[ MAX_STRING_TOKENS ]; -  int   clientNum = -1; -  char  name[ MAX_NETNAME ]; -  char *arg1plus; -  char *arg2plus; -  char  message[ MAX_STRING_CHARS ]; -  char targetname[ MAX_NAME_LENGTH] = ""; -  char reason[ MAX_STRING_CHARS ] = ""; -  char *ptr = NULL; - -  arg1plus = G_SayConcatArgs( 1 ); -  arg2plus = G_SayConcatArgs( 2 ); -   -  // Invisible players cannot call votes -  if( ent->client->sess.invisible == qtrue ) -  { -    trap_SendServerCommand( ent-g_entities, "print \"You cannot call votes while invisible\n\"" ); -    return; -  } - -  if( !g_allowVote.integer ) -  { -    trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here\n\"" ); -    return; -  } +  char   cmd[ MAX_TOKEN_CHARS ], +         vote[ MAX_TOKEN_CHARS ], +         arg[ MAX_TOKEN_CHARS ], +         extra[ MAX_TOKEN_CHARS ]; +  char   name[ MAX_NAME_LENGTH ] = ""; +  char   caller[ MAX_NAME_LENGTH ] = ""; +  char   reason[ MAX_TOKEN_CHARS ]; +  char   *creason; +  int    clientNum = -1; +  int    id = -1; +  team_t team; -  // Flood limit.  If they're talking too fast, determine that and return. -  if( g_floodMinTime.integer ) -    if ( G_Flood_Limited( ent ) ) -    { -      trap_SendServerCommand( ent-g_entities, "print \"Your /callvote attempt is flood-limited; wait before chatting again\n\"" ); -      return; -    } +  trap_Argv( 0, cmd, sizeof( cmd ) ); +  trap_Argv( 1, vote, sizeof( vote ) ); +  trap_Argv( 2, arg, sizeof( arg ) ); +  trap_Argv( 3, extra, sizeof( extra ) ); +  creason = ConcatArgs( 3 ); +  G_DecolorString( creason, reason, sizeof( reason ) ); + +  if( !Q_stricmp( cmd, "callteamvote" ) ) +    team = ent->client->pers.teamSelection; +  else +    team = TEAM_NONE; -  //see if they can vote -  if( G_admin_permission( ent, ADMF_NO_VOTE ) ) +  if( !g_allowVote.integer )    { -    trap_SendServerCommand( ent-g_entities, "print \"You have no voting rights\n\"" ); +    trap_SendServerCommand( ent-g_entities, +      va( "print \"%s: voting not allowed here\n\"", cmd ) );      return;    } -  if( g_voteMinTime.integer -    && ent->client->pers.firstConnect  -    && level.time - ent->client->pers.enterTime < g_voteMinTime.integer * 1000 -    && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT )  -    && (level.numPlayingClients > 0 && level.numConnectedClients>1) ) +  if( level.voteTime[ team ] )    { -    trap_SendServerCommand( ent-g_entities, va( -      "print \"You must wait %d seconds after connecting before calling a vote\n\"", -      g_voteMinTime.integer ) ); +    trap_SendServerCommand( ent-g_entities, +      va( "print \"%s: a vote is already in progress\n\"", cmd ) );      return;    } -  if( level.voteTime ) +  // protect against the dreaded exploit of '\n'-interpretation inside quotes +  if( strchr( arg, '\n' ) || strchr( arg, '\r' ) || +      strchr( extra, '\n' ) || strchr( extra, '\r' ) || +      strchr( creason, '\n' ) || strchr( creason, '\r' ) )    { -    trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress\n\"" ); +    trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" );      return;    } -  if( g_voteLimit.integer > 0 -    && ent->client->pers.voteCount >= g_voteLimit.integer -    && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) -  { -    trap_SendServerCommand( ent-g_entities, va( -      "print \"You have already called the maximum number of votes (%d)\n\"", -      g_voteLimit.integer ) ); -    return; -  } -   -  if( G_IsMuted( ent->client ) ) -  { -    trap_SendServerCommand( ent - g_entities, -      "print \"You are muted and cannot call votes\n\"" ); -    return; -  } +  if( level.voteExecuteTime[ team ] ) +    G_ExecuteVote( team ); -  if( !G_admin_permission( ent, ADMF_NOSCRIMRESTRICTION ) && g_scrimMode.integer != 0 && -      ent->client->pers.teamSelection == PTE_NONE ) -  { -    trap_SendServerCommand( ent - g_entities, -      "print \"You can't call votes when scrim mode is enabled\n\"" ); -	return; -  } - -  // make sure it is a valid command to vote on -  trap_Argv( 1, arg1, sizeof( arg1 ) ); -  trap_Argv( 2, arg2, sizeof( arg2 ) ); +  level.voteDelay[ team ] = 0; +  level.voteThreshold[ team ] = 50; -  if( strchr( arg1plus, ';' ) ) +  if( g_voteLimit.integer > 0 && +    ent->client->pers.namelog->voteCount >= g_voteLimit.integer && +    !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) )    { -    trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); +    trap_SendServerCommand( ent-g_entities, va( +      "print \"%s: you have already called the maximum number of votes (%d)\n\"", +      cmd, g_voteLimit.integer ) );      return;    } -  // if there is still a vote to be executed -  if( level.voteExecuteTime ) -  { -    if( !Q_stricmp( level.voteString, "map_restart" ) ) -    { -      G_admin_maplog_result( "r" ); -    } -    else if( !Q_stricmpn( level.voteString, "map", 3 ) ) -    { -      G_admin_maplog_result( "m" ); -    } - -    level.voteExecuteTime = 0; -    trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) ); -  } -   -  level.votePassThreshold=50; -   -  ptr = strstr(arg1plus, " -"); -  if( ptr ) +  // kick, mute, unmute, denybuild, allowbuild +  if( !Q_stricmp( vote, "kick" ) || +      !Q_stricmp( vote, "mute" ) || !Q_stricmp( vote, "unmute" ) || +      !Q_stricmp( vote, "denybuild" ) || !Q_stricmp( vote, "allowbuild" ) )    { -    *ptr = '\0'; -    ptr+=2;  - -    if( *ptr == 'r' || *ptr=='R' ) -    { -      ptr++; -      while( *ptr == ' ' ) -        ptr++; -      strcpy(reason, ptr); -    } -    else -    { -      trap_SendServerCommand( ent-g_entities, "print \"callvote: Warning: invalid argument specified \n\"" ); -    } -  } +    char err[ MAX_STRING_CHARS ]; -  // detect clientNum for partial name match votes -  if( !Q_stricmp( arg1, "kick" ) || -    !Q_stricmp( arg1, "spec" ) || -    !Q_stricmp( arg1, "mute" ) || -    !Q_stricmp( arg1, "unmute" ) ) -  { -    int clientNums[ MAX_CLIENTS ] = { -1 }; -    int numMatches=0; -    char err[ MAX_STRING_CHARS ] = ""; -     -    Q_strncpyz(targetname, arg2plus, sizeof(targetname)); -    ptr = strstr(targetname, " -"); -    if( ptr ) -      *ptr = '\0'; -     -    if( g_requireVoteReasons.integer && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) && !Q_stricmp( arg1, "kick" ) && reason[ 0 ]=='\0' ) -    { -       trap_SendServerCommand( ent-g_entities, "print \"callvote: You must specify a reason. Use /callvote kick [player] -r [reason] \n\"" ); -       return; -    } -     -    if( !targetname[ 0 ] ) +    if( !arg[ 0 ] )      {        trap_SendServerCommand( ent-g_entities, -        "print \"callvote: no target\n\"" ); +        va( "print \"%s: no target\n\"", cmd ) );        return;      } -    numMatches = G_ClientNumbersFromString( targetname, clientNums ); -    if( numMatches == 1 ) -    { -      // there was only one partial name match -      clientNum = clientNums[ 0 ];  -    } -    else -    { -      // look for an exact name match (sets clientNum to -1 if it fails)  -      clientNum = G_ClientNumberFromString( ent, targetname ); -    } -     -    if( clientNum==-1  && numMatches > 1 )  +    // with a little extra work only players from the right team are considered +    clientNum = G_ClientNumberFromString( arg, err, sizeof( err ) ); + +    if( clientNum == -1 )      { -      G_MatchOnePlayer( clientNums, err, sizeof( err ) ); -      ADMP( va( "^3callvote: ^7%s\n", err ) ); +      ADMP( va( "%s: %s", cmd, err ) );        return;      } -     -    if( clientNum != -1 && -        level.clients[ clientNum ].pers.connected != CON_CONNECTED ) -    { -      clientNum = -1; -    } -    if( clientNum != -1 ) +    G_DecolorString( level.clients[ clientNum ].pers.netname, name, sizeof( name ) ); +    id = level.clients[ clientNum ].pers.namelog->id; + +    if( !Q_stricmp( vote, "kick" ) || !Q_stricmp( vote, "mute" ) || +        !Q_stricmp( vote, "denybuild" ) )      { -      Q_strncpyz( name, level.clients[ clientNum ].pers.netname, -        sizeof( name ) ); -      Q_CleanStr( name ); -      if ( G_admin_permission ( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) +      if( G_admin_permission( g_entities + clientNum, ADMF_IMMUNITY ) )        { -    char reasonprint[ MAX_STRING_CHARS ] = ""; - -    if( reason[ 0 ] != '\0' ) -      Com_sprintf(reasonprint, sizeof(reasonprint), "With reason: %s", reason); +        trap_SendServerCommand( ent-g_entities, +          va( "print \"%s: admin is immune\n\"", cmd ) ); + +        G_AdminMessage( NULL, va( S_COLOR_WHITE "%s" S_COLOR_YELLOW " attempted %s %s" +                                " on immune admin " S_COLOR_WHITE "%s" S_COLOR_YELLOW +                                " for: %s", +                                ent->client->pers.netname, cmd, vote,  +                                g_entities[ clientNum ].client->pers.netname,  +                                reason[ 0 ] ? reason : "no reason" ) ); +        return; +      } -        Com_sprintf( message, sizeof( message ), "%s^7 attempted /callvote %s %s on immune admin %s^7 %s^7", -          ent->client->pers.netname, arg1, targetname, g_entities[ clientNum ].client->pers.netname, reasonprint ); +      if( team != TEAM_NONE && +          ( ent->client->pers.teamSelection != +            level.clients[ clientNum ].pers.teamSelection ) ) +      { +        trap_SendServerCommand( ent-g_entities, +          va( "print \"%s: player is not on your team\n\"", cmd ) ); +        return;        } -    } -    else -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callvote: invalid player\n\"" ); -      return; -    } -  } -  -  if( !Q_stricmp( arg1, "kick" ) ) -  { -    if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callvote: admin is immune from vote kick\n\"" ); -      G_AdminsPrintf("%s\n",message); -      G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse ); -      return; -    } -    // use ip in case this player disconnects before the vote ends -    Com_sprintf( level.voteString, sizeof( level.voteString ), -      "!ban %s \"%s\" vote kick", level.clients[ clientNum ].pers.ip, -      g_adminTempBan.string ); -    if ( reason[0]!='\0' ) -      Q_strcat( level.voteString, sizeof( level.voteDisplayString ), va( "(%s^7)", reason ) ); -    Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), -      "Kick player \'%s\'", name ); -  } -  else if( !Q_stricmp( arg1, "spec" ) ) -  { -    if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) -    { -          trap_SendServerCommand( ent-g_entities, "print \"callvote: admin is immune from vote spec\n\"" ); -          return; +      if( !reason[ 0 ] && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) +      { +        trap_SendServerCommand( ent-g_entities, +          va( "print \"%s: You must provide a reason\n\"", cmd ) ); +        return; +      }      } -    Com_sprintf( level.voteString, sizeof( level.voteString ), "!putteam %i s %s", clientNum, g_adminTempSpec.string ); -    Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "Spec player \'%s\'", name ); -    } -  else if( !Q_stricmp( arg1, "mute" ) ) -  { -    if( G_IsMuted( &level.clients[ clientNum ] ) ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callvote: player is already muted\n\"" ); -      return; -    } -    if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callvote: admin is immune from vote mute\n\"" ); -      G_AdminsPrintf("%s\n",message); -      G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse ); -      return; -    } -    Com_sprintf( level.voteString, sizeof( level.voteString ), -      "!mute %i %s", clientNum, g_adminTempMute.string ); -    Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), -      "Mute player \'%s\'", name ); -  } -  else if( !Q_stricmp( arg1, "unmute" ) ) +  if( !Q_stricmp( vote, "kick" ) )    { -    if( !G_IsMuted( &level.clients[ clientNum ] ) ) +    if( level.clients[ clientNum ].pers.localClient )      {        trap_SendServerCommand( ent-g_entities, -        "print \"callvote: player is not currently muted\n\"" ); -      return; -    } -    Com_sprintf( level.voteString, sizeof( level.voteString ), -      "!unmute %i", clientNum ); -    Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), -      "Un-Mute player \'%s\'", name ); -  } -  else if( !Q_stricmp( arg1, "map_restart" ) ) -  { -    if( g_mapvoteMaxTime.integer  -      && (( level.time - level.startTime ) >= g_mapvoteMaxTime.integer * 1000 ) -      && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT )  -      && (level.numPlayingClients > 0 && level.numConnectedClients>1) ) -    { -       trap_SendServerCommand( ent-g_entities, va( -         "print \"You cannot call for a restart after %d seconds\n\"", -         g_mapvoteMaxTime.integer ) ); -       G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse ); -       return; -    } -    Com_sprintf( level.voteString, sizeof( level.voteString ), "%s", arg1 ); -    Com_sprintf( level.voteDisplayString, -        sizeof( level.voteDisplayString ), "Restart current map" ); -    level.votePassThreshold = g_mapVotesPercent.integer; -  } -  else if( !Q_stricmp( arg1, "map" ) ) -  { -    if( g_mapvoteMaxTime.integer  -      && (( level.time - level.startTime ) >= g_mapvoteMaxTime.integer * 1000 ) -      && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT )  -      && (level.numPlayingClients > 0 && level.numConnectedClients>1) ) -    { -       trap_SendServerCommand( ent-g_entities, va( -         "print \"You cannot call for a mapchange after %d seconds\n\"", -         g_mapvoteMaxTime.integer ) ); -       G_admin_adminlog_log( ent, "vote", NULL, 0, qfalse ); -       return; -    } -   -    if( !G_MapExists( arg2 ) ) -    { -      trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " -        "'maps/%s.bsp' could not be found on the server\n\"", arg2 ) ); +        va( "print \"%s: admin is immune\n\"", cmd ) );        return;      } -    if( !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) && !map_is_votable( arg2 ) ) +    Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), +      "ban %s \"1s%s\" vote kick (%s)", level.clients[ clientNum ].pers.ip.str, +      g_adminTempBan.string, reason ); +    Com_sprintf( level.voteDisplayString[ team ], +      sizeof( level.voteDisplayString[ team ] ), "Kick player '%s'", name ); +    if( reason[ 0 ] )      { -      trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " -        "Only admins may call a vote for map: %s\n\"", arg2 ) ); -      return; +      Q_strcat( level.voteDisplayString[ team ], +        sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) );      } - -    Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 ); -    Com_sprintf( level.voteDisplayString, -        sizeof( level.voteDisplayString ), "Change to map '%s'", arg2 ); -    level.votePassThreshold = g_mapVotesPercent.integer;    } -  else if( !Q_stricmp( arg1, "nextmap" ) ) +  else if( team == TEAM_NONE )    { -    if( G_MapExists( g_nextMap.string ) ) -    { -      trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " -        "the next map is already set to '%s^7'\n\"", g_nextMap.string ) ); -      return; -    } - -    if( !arg2[ 0 ] ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callvote: you must specify a map\n\"" ); -      return; -    } - -    if( !G_MapExists( arg2 ) ) +    if( !Q_stricmp( vote, "mute" ) )      { -      trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " -        "'maps/%s^7.bsp' could not be found on the server\n\"", arg2 ) ); -      return; -    } +      if( level.clients[ clientNum ].pers.namelog->muted ) +      { +        trap_SendServerCommand( ent-g_entities, +          va( "print \"%s: player is already muted\n\"", cmd ) ); +        return; +      } -    if( !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) && !map_is_votable( arg2 ) ) -    { -      trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " -        "Only admins may call a vote for map: %s\n\"", arg2 ) ); -      return; +      Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), +        "mute %d", id ); +      Com_sprintf( level.voteDisplayString[ team ], +        sizeof( level.voteDisplayString[ team ] ), +        "Mute player '%s'", name ); +      if( reason[ 0 ] ) +      { +        Q_strcat( level.voteDisplayString[ team ], +          sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); +      }      } - -    Com_sprintf( level.voteString, sizeof( level.voteString ), -      "set g_nextMap %s", arg2 ); -    Com_sprintf( level.voteDisplayString, -        sizeof( level.voteDisplayString ), "Set the next map to '%s^7'", arg2 ); -    level.votePassThreshold = g_mapVotesPercent.integer; -  } -  else if( !Q_stricmp( arg1, "draw" ) ) -  { -    Com_sprintf( level.voteString, sizeof( level.voteString ), "evacuation" ); -    Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), -        "End match in a draw" ); -    level.votePassThreshold = g_mapVotesPercent.integer; -  } -   else if( !Q_stricmp( arg1, "poll" ) ) +    else if( !Q_stricmp( vote, "unmute" ) )      { -      if( arg2plus[ 0 ] == '\0' ) +      if( !level.clients[ clientNum ].pers.namelog->muted )        { -        trap_SendServerCommand( ent-g_entities, "print \"callvote: You forgot to specify what people should vote on.\n\"" ); +        trap_SendServerCommand( ent-g_entities, +          va( "print \"%s: player is not currently muted\n\"", cmd ) );          return;        } -      level.voteString[ 0 ] = '\0'; -      Com_sprintf( level.voteDisplayString, -          sizeof( level.voteDisplayString ), "[Poll] \'%s\'", arg2plus ); -   } -   else if( !Q_stricmp( arg1, "sudden_death" ) || -     !Q_stricmp( arg1, "suddendeath" ) ) -   { -     if(!g_suddenDeathVotePercent.integer) -     { -       trap_SendServerCommand( ent-g_entities, "print \"Sudden Death votes have been disabled\n\"" ); -       return; -     }  -     else if( g_suddenDeath.integer )  -     { -      trap_SendServerCommand( ent - g_entities, va( "print \"callvote: Sudden Death has already begun\n\"") ); -      return; -     } -     else if( G_TimeTilSuddenDeath() <= g_suddenDeathVoteDelay.integer * 1000 ) -     { -      trap_SendServerCommand( ent - g_entities, va( "print \"callvote: Sudden Death is already immenent\n\"") ); -      return; -     } -    else  -     { -       level.votePassThreshold = g_suddenDeathVotePercent.integer; -       Com_sprintf( level.voteString, sizeof( level.voteString ), "suddendeath" ); -       Com_sprintf( level.voteDisplayString, -           sizeof( level.voteDisplayString ), "Begin sudden death" ); - -       if( g_suddenDeathVoteDelay.integer ) -         Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( " in %d seconds", g_suddenDeathVoteDelay.integer ) ); - -     } -   } -   else if( !Q_stricmp( arg1, "extend" ) ) -   { -     if( !g_extendVotesPercent.integer ) -     { -       trap_SendServerCommand( ent-g_entities, "print \"Extend votes have been disabled\n\"" ); -       return; -     } -     if( g_extendVotesCount.integer -         && level.extend_vote_count >= g_extendVotesCount.integer ) -     { -       trap_SendServerCommand( ent-g_entities, -                               va( "print \"callvote: Maximum number of %d extend votes has been reached\n\"", -                                   g_extendVotesCount.integer ) ); -       return; -     } -     if( !g_timelimit.integer ) { -       trap_SendServerCommand( ent-g_entities, -                               "print \"This match has no timelimit so extend votes wont work\n\"" ); -       return; -     } -     if( level.time - level.startTime < -         ( g_timelimit.integer - g_extendVotesTime.integer / 2 ) * 60000 ) -     { -       trap_SendServerCommand( ent-g_entities, -                               va( "print \"callvote: Extend votes only allowed with less than %d minutes remaining\n\"", -                                   g_extendVotesTime.integer / 2 ) ); -       return; -     } -     level.extend_vote_count++; -     level.votePassThreshold = g_extendVotesPercent.integer;  -     Com_sprintf( level.voteString, sizeof( level.voteString ), -                  "timelimit %i", g_timelimit.integer + g_extendVotesTime.integer ); -     Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), -                  "Extend the timelimit by %d minutes", g_extendVotesTime.integer ); -   } -  else -  { -    qboolean match = qfalse; -    char customVoteKeys[ MAX_STRING_CHARS ]; -    customVoteKeys[ 0 ] = '\0'; -    if( g_customVotePercent.integer ) +      Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), +        "unmute %d", id ); +      Com_sprintf( level.voteDisplayString[ team ], +        sizeof( level.voteDisplayString[ team ] ), +        "Unmute player '%s'", name ); +    } +    else if( !Q_stricmp( vote, "map_restart" ) )      { -      char text[ MAX_STRING_CHARS ]; -      char *votekey, *votemsg, *votecmd, *voteperc; -      int votePValue; - -      text[ 0 ] = '\0'; -      for( i = 0; i < CUSTOM_VOTE_COUNT; i++ ) +      if( arg[ 0 ] )        { -        switch( i ) -        { -          case 0: -            Q_strncpyz( text, g_customVote1.string, sizeof( text ) ); -            break; -          case 1: -            Q_strncpyz( text, g_customVote2.string, sizeof( text ) ); -            break; -          case 2: -            Q_strncpyz( text, g_customVote3.string, sizeof( text ) ); -            break; -          case 3: -            Q_strncpyz( text, g_customVote4.string, sizeof( text ) ); -            break; -          case 4: -            Q_strncpyz( text, g_customVote5.string, sizeof( text ) ); -            break; -          case 5: -            Q_strncpyz( text, g_customVote6.string, sizeof( text ) ); -            break; -          case 6: -            Q_strncpyz( text, g_customVote7.string, sizeof( text ) ); -            break; -          case 7: -            Q_strncpyz( text, g_customVote8.string, sizeof( text ) ); -            break; -        } -        if ( text[ 0 ] == '\0' ) -          continue; +        char map[ MAX_QPATH ]; +        trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); -        // custom vote cvar format:  "callvote_name,Vote message string,vote success command[,percent]" -        votekey = text; -        votemsg = strchr( votekey, ',' ); -        if( !votemsg || *votemsg != ',' ) -          continue; -        *votemsg = '\0'; -        votemsg++; -        Q_strcat( customVoteKeys, sizeof( customVoteKeys ), -                  va( "%s%s", ( customVoteKeys[ 0 ] == '\0' ) ? "" : ", ", votekey ) ); -        votecmd = strchr( votemsg, ',' ); -        if( !votecmd || *votecmd != ',' ) -          continue; -        *votecmd = '\0'; -        votecmd++; - -        voteperc = strchr( votecmd, ',' ); -        if( !voteperc || *voteperc != ',' ) -          votePValue = g_customVotePercent.integer; -        else +        if( !G_LayoutExists( map, arg ) )          { -          *voteperc = '\0'; -          voteperc++; -          votePValue = atoi( voteperc ); -          if( !votePValue ) -            votePValue = g_customVotePercent.integer; +          trap_SendServerCommand( ent-g_entities, +            va( "print \"%s: layout '%s' does not exist for map '%s'\n\"", +              cmd, arg, map ) ); +          return;          } - -        if( Q_stricmp( arg1, votekey ) != 0 ) -          continue; -        if( votemsg[ 0 ] == '\0' || votecmd[ 0 ] == '\0' ) -          continue; - -        level.votePassThreshold = votePValue; -        Com_sprintf( level.voteString, sizeof( level.voteString ), "%s", votecmd ); -        Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", votemsg ); -        match = qtrue; -        break;        } -   } - -    if( !match ) -    { -      trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); -      trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: " -        "map, map_restart, draw, extend, nextmap, kick, spec, mute, unmute, poll, and sudden_death\n" ); -      if( customVoteKeys[ 0 ] != '\0' ) -        trap_SendServerCommand( ent-g_entities, -          va( "print \"Additional custom vote commands: %s\n\"", customVoteKeys ) ); -      return; -    } -  } -   -  if( level.votePassThreshold!=50 ) -  { -    Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( "^7 (Needs > %d percent)", level.votePassThreshold ) ); -  } -   -  if ( reason[0]!='\0' ) -    Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( " Reason: '%s^7'", reason ) ); -   -  G_admin_adminlog_log( ent, "vote", NULL, 0, qtrue ); - -  trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE -         " called a vote: %s" S_COLOR_WHITE "\n\"", ent->client->pers.netname, level.voteDisplayString ) ); -   -  G_LogPrintf("Vote: %s^7 called a vote: %s^7\n", ent->client->pers.netname, level.voteDisplayString ); -   -  Q_strcat( level.voteDisplayString, sizeof( level.voteDisplayString ), va( " Called by: '%s^7'", ent->client->pers.netname ) ); - -  ent->client->pers.voteCount++; - -  // start the voting, the caller autoamtically votes yes -  level.voteTime = level.time; -  level.voteNo = 0; - -  for( i = 0 ; i < level.maxclients ; i++ ) -    level.clients[i].ps.eFlags &= ~EF_VOTED; - -  if( !Q_stricmp( arg1, "poll" ) ) -  { -    level.voteYes = 0; -  } -  else -  { -   level.voteYes = 1; -   ent->client->ps.eFlags |= EF_VOTED; -  } - -  trap_SetConfigstring( CS_VOTE_TIME, va( "%i", level.voteTime ) ); -  trap_SetConfigstring( CS_VOTE_STRING, level.voteDisplayString ); -  trap_SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) ); -  trap_SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) ); -} - -/* -================== -Cmd_Vote_f -================== -*/ -void Cmd_Vote_f( gentity_t *ent ) -{ -  char msg[ 64 ]; - -  if ( level.intermissiontime || level.paused ) -  { -    if( level.mapRotationVoteTime ) -    { -      trap_Argv( 1, msg, sizeof( msg ) ); -      if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' ) -        G_IntermissionMapVoteCommand( ent, qfalse, qtrue ); +      Com_sprintf( level.voteDisplayString[ team ], sizeof( level.voteDisplayString[ team ] ), +        "Restart the current map%s", arg[ 0 ] ? va( " with layout '%s'", arg ) : "" ); +      Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), +        "set g_nextMap \"\" ; set g_nextLayout \"%s\" ; %s", arg, vote ); +      // map_restart comes with a default delay      } - -    return; -  } - -  if( !level.voteTime ) -  {  -    if( ent->client->pers.teamSelection != PTE_NONE ) +    else if( !Q_stricmp( vote, "map" ) )      { -      // If there is a teamvote going on but no global vote, forward this vote on as a teamvote -      // (ugly hack for 1.1 cgames + noobs who can't figure out how to use any command that isn't bound by default) -      int     cs_offset = 0; -      if( ent->client->pers.teamSelection == PTE_ALIENS ) -        cs_offset = 1; -     -      if( level.teamVoteTime[ cs_offset ] ) +      if( !G_MapExists( arg ) )        { -         if( !(ent->client->ps.eFlags & EF_TEAMVOTED ) ) -        { -          Cmd_TeamVote_f(ent);  -          return; -        } +        trap_SendServerCommand( ent-g_entities, +          va( "print \"%s: 'maps/%s.bsp' could not be found on the server\n\"", +            cmd, arg ) ); +        return;        } -    } -    trap_SendServerCommand( ent-g_entities, "print \"No vote in progress\n\"" ); -    return; -  } - -  if( ent->client->ps.eFlags & EF_VOTED ) -  { -    trap_SendServerCommand( ent-g_entities, "print \"Vote already cast\n\"" ); -    return; -  } - -  trap_SendServerCommand( ent-g_entities, "print \"Vote cast\n\"" ); -  ent->client->ps.eFlags |= EF_VOTED; - -  trap_Argv( 1, msg, sizeof( msg ) ); - -  if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' ) -  { -    level.voteYes++; -    trap_SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) ); -  } -  else -  { -    level.voteNo++; -    trap_SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) ); -  } - -  // a majority will be determined in G_CheckVote, which will also account -  // for players entering or leaving -} - -/* -================== -Cmd_CallTeamVote_f -================== -*/ -void Cmd_CallTeamVote_f( gentity_t *ent ) -{ -  int   i, team, cs_offset = 0; -  char  arg1[ MAX_STRING_TOKENS ]; -  char  arg2[ MAX_STRING_TOKENS ]; -  int   clientNum = -1; -  char  name[ MAX_NETNAME ]; -  char  message[ MAX_STRING_CHARS ]; -  char targetname[ MAX_NAME_LENGTH] = ""; -  char reason[ MAX_STRING_CHARS ] = ""; -  char *arg1plus; -  char *arg2plus; -  char *ptr = NULL; -  int numVoters = 0; - -  arg1plus = G_SayConcatArgs( 1 ); -  arg2plus = G_SayConcatArgs( 2 ); -   -  team = ent->client->pers.teamSelection; - -  if( team == PTE_ALIENS ) -    cs_offset = 1; - -  if(team==PTE_ALIENS) -    numVoters = level.numAlienClients; -  else if(team==PTE_HUMANS) -    numVoters = level.numHumanClients; - -  if( !g_allowVote.integer ) -  { -    trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here\n\"" ); -    return; -  } - -  if( level.teamVoteTime[ cs_offset ] ) -  { -    trap_SendServerCommand( ent-g_entities, "print \"A team vote is already in progress\n\"" ); -    return; -  } - -  //see if they can vote -  if( G_admin_permission( ent, ADMF_NO_VOTE ) ) -  { -    trap_SendServerCommand( ent-g_entities, "print \"You have no voting rights\n\"" ); -    return; -  } - -  if( g_voteLimit.integer > 0 -    && ent->client->pers.voteCount >= g_voteLimit.integer  -    && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) -  { -    trap_SendServerCommand( ent-g_entities, va( -      "print \"You have already called the maximum number of votes (%d)\n\"", -      g_voteLimit.integer ) ); -    return; -  } -   -  if( G_IsMuted( ent->client ) ) -  { -    trap_SendServerCommand( ent - g_entities, -      "print \"You are muted and cannot call teamvotes\n\"" ); -    return; -  } - -  if( g_voteMinTime.integer -    && ent->client->pers.firstConnect  -    && level.time - ent->client->pers.enterTime < g_voteMinTime.integer * 1000 -    && !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT )  -    && (level.numPlayingClients > 0 && level.numConnectedClients>1) ) -  { -    trap_SendServerCommand( ent-g_entities, va( -      "print \"You must wait %d seconds after connecting before calling a vote\n\"", -      g_voteMinTime.integer ) ); -    return; -  } - -  // make sure it is a valid command to vote on -  trap_Argv( 1, arg1, sizeof( arg1 ) ); -  trap_Argv( 2, arg2, sizeof( arg2 ) ); - -  if( strchr( arg1plus, ';' ) ) -  { -    trap_SendServerCommand( ent-g_entities, "print \"Invalid team vote string\n\"" ); -    return; -  } -   -  ptr = strstr(arg1plus, " -"); -  if( ptr ) -  { -    *ptr = '\0'; -    ptr+=2;  +      if( extra[ 0 ] && !G_LayoutExists( arg, extra ) ) +      { +        trap_SendServerCommand( ent-g_entities, +          va( "print \"%s: layout '%s' does not exist for map '%s'\n\"", +            cmd, extra, arg ) ); +        return; +      } -    if( *ptr == 'r' || *ptr=='R' ) -    { -      ptr++; -      while( *ptr == ' ' ) -        ptr++; -      strcpy(reason, ptr); +      Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), +        "set g_nextMap \"\" ; set g_nextLayout \"%s\" ; %s \"%s\"", extra, vote, arg ); +      Com_sprintf( level.voteDisplayString[ team ], sizeof( level.voteDisplayString[ team ] ), +        "Change to map '%s'%s", arg, extra[ 0 ] ? va( " with layout '%s'", extra ) : "" ); +      level.voteDelay[ team ] = 3000;      } -    else +    else if( !Q_stricmp( vote, "nextmap" ) )      { -      trap_SendServerCommand( ent-g_entities, "print \"callteamvote: Warning: invalid argument specified \n\"" ); -    } -  } -   -  // detect clientNum for partial name match votes -  if( !Q_stricmp( arg1, "kick" ) || -    !Q_stricmp( arg1, "denybuild" ) || -    !Q_stricmp( arg1, "allowbuild" ) ||  -    !Q_stricmp( arg1, "designate" ) ||  -    !Q_stricmp( arg1, "undesignate" ) ) -  { -    int clientNums[ MAX_CLIENTS ] = { -1 }; -    int numMatches=0; -    char err[ MAX_STRING_CHARS ]; -     -    Q_strncpyz(targetname, arg2plus, sizeof(targetname)); -    ptr = strstr(targetname, " -"); -    if( ptr ) -      *ptr = '\0'; -     -    if( g_requireVoteReasons.integer && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) && !Q_stricmp( arg1, "kick" ) && reason[ 0 ]=='\0' ) -    { -       trap_SendServerCommand( ent-g_entities, "print \"callvote: You must specify a reason. Use /callteamvote kick [player] -r [reason] \n\"" ); -       return; -    } -     +      if( G_MapExists( g_nextMap.string ) && +          ( !g_nextLayout.string[ 0 ] || G_LayoutExists( g_nextMap.string, g_nextLayout.string ) ) ) +      { +        trap_SendServerCommand( ent-g_entities, +          va( "print \"%s: the next map is already set to '%s'%s\n\"", +            cmd, g_nextMap.string, +            g_nextLayout.string[ 0 ] ? va( " with layout '%s'", g_nextLayout.string ) : "" ) ); +        return; +      } -    if( !arg2[ 0 ] ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: no target\n\"" ); -      return; -    } +      if( !G_MapExists( arg ) ) +      { +        trap_SendServerCommand( ent-g_entities, +          va( "print \"%s: 'maps/%s.bsp' could not be found on the server\n\"", +            cmd, arg ) ); +        return; +      } -    numMatches = G_ClientNumbersFromString( targetname, clientNums ); -    if( numMatches == 1 ) -    { -      // there was only one partial name match -      clientNum = clientNums[ 0 ];  -    } -    else -    { -      // look for an exact name match (sets clientNum to -1 if it fails)  -      clientNum = G_ClientNumberFromString( ent, targetname ); -    } -     -    if( clientNum==-1  && numMatches > 1 )  -    { -      G_MatchOnePlayer( clientNums, err, sizeof( err ) ); -      ADMP( va( "^3callteamvote: ^7%s\n", err ) ); -      return; -    } +      if( extra[ 0 ] && !G_LayoutExists( arg, extra ) ) +      { +        trap_SendServerCommand( ent-g_entities, +          va( "print \"%s: layout '%s' does not exist for map '%s'\n\"", +            cmd, extra, arg ) ); +        return; +      } -    // make sure this player is on the same team -    if( clientNum != -1 && level.clients[ clientNum ].pers.teamSelection != -      team ) -    { -      clientNum = -1; +      Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), +        "set g_nextMap \"%s\" ; set g_nextLayout \"%s\"", arg, extra ); +      Com_sprintf( level.voteDisplayString[ team ], sizeof( level.voteDisplayString[ team ] ), +        "Set the next map to '%s'%s", arg, extra[ 0 ] ? va( " with layout '%s'", extra ) : "" );      } -       -    if( clientNum != -1 && -      level.clients[ clientNum ].pers.connected == CON_DISCONNECTED ) +    else if( !Q_stricmp( vote, "draw" ) )      { -      clientNum = -1; +      strcpy( level.voteString[ team ], "evacuation" ); +      strcpy( level.voteDisplayString[ team ], "End match in a draw" ); +      level.voteDelay[ team ] = 3000;      } - -    if( clientNum != -1 ) +    else if( !Q_stricmp( vote, "sudden_death" ) )      { -      Q_strncpyz( name, level.clients[ clientNum ].pers.netname, -        sizeof( name ) ); -      Q_CleanStr( name ); -      if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) +      if(!g_suddenDeathVotePercent.integer)        { -       char reasonprint[ MAX_STRING_CHARS ] = ""; -       if( reason[ 0 ] != '\0' ) -        Com_sprintf(reasonprint, sizeof(reasonprint), "With reason: %s", reason); - -        Com_sprintf( message, sizeof( message ), "%s^7 attempted /callteamvote %s %s on immune admin %s^7 %s^7", -          ent->client->pers.netname, arg1, arg2, g_entities[ clientNum ].client->pers.netname, reasonprint ); +        trap_SendServerCommand( ent-g_entities, +              "print \"Sudden Death votes have been disabled\n\"" ); +        return; +      } +      if( G_TimeTilSuddenDeath( ) <= 0 ) +      { +        trap_SendServerCommand( ent - g_entities, +              va( "print \"callvote: Sudden Death has already begun\n\"") ); +        return;        } +      if( level.suddenDeathBeginTime > 0 && +          G_TimeTilSuddenDeath() <= g_suddenDeathVoteDelay.integer * 1000 ) +      { +        trap_SendServerCommand( ent - g_entities, +              va( "print \"callvote: Sudden Death is imminent\n\"") ); +        return; +      } +      level.voteThreshold[ team ] = g_suddenDeathVotePercent.integer; +      Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), +        "suddendeath %d", g_suddenDeathVoteDelay.integer ); +      Com_sprintf( level.voteDisplayString[ team ], +                   sizeof( level.voteDisplayString[ team ] ), +                   "Begin sudden death in %d seconds", +                   g_suddenDeathVoteDelay.integer );      }      else      { -      trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: invalid player\n\"" ); -      return; -    } -  } - -  if( !Q_stricmp( arg1, "kick" ) ) -  { -    if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: admin is immune from vote kick\n\"" ); -      G_AdminsPrintf("%s\n",message); -      G_admin_adminlog_log( ent, "teamvote", NULL, 0, qfalse ); +      trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); +      trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: " +        "map, nextmap, map_restart, draw, sudden_death, kick, mute and unmute\n" );        return;      } - - -    // use ip in case this player disconnects before the vote ends -    Com_sprintf( level.teamVoteString[ cs_offset ], -      sizeof( level.teamVoteString[ cs_offset ] ), -      "!ban %s \"%s\" team vote kick", level.clients[ clientNum ].pers.ip, -      g_adminTempBan.string ); -    Com_sprintf( level.teamVoteDisplayString[ cs_offset ], -        sizeof( level.teamVoteDisplayString[ cs_offset ] ), -        "Kick player '%s'", name );    } -  else if( !Q_stricmp( arg1, "denybuild" ) ) +  else if( !Q_stricmp( vote, "denybuild" ) )    { -    if( level.clients[ clientNum ].pers.denyBuild ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: player already lost building rights\n\"" ); -      return; -    } - -    if( G_admin_permission( &g_entities[ clientNum ], ADMF_IMMUNITY ) ) +    if( level.clients[ clientNum ].pers.namelog->denyBuild )      {        trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: admin is immune from denybuild\n\"" ); -      G_AdminsPrintf("%s\n",message); -      G_admin_adminlog_log( ent, "teamvote", NULL, 0, qfalse ); +        va( "print \"%s: player already lost building rights\n\"", cmd ) );        return;      } -    Com_sprintf( level.teamVoteString[ cs_offset ], -      sizeof( level.teamVoteString[ cs_offset ] ), "!denybuild %i", clientNum ); -    Com_sprintf( level.teamVoteDisplayString[ cs_offset ], -        sizeof( level.teamVoteDisplayString[ cs_offset ] ), -        "Take away building rights from '%s'", name ); -  } -  else if( !Q_stricmp( arg1, "allowbuild" ) ) -  { -    if( !level.clients[ clientNum ].pers.denyBuild ) +    Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), +      "denybuild %d", id ); +    Com_sprintf( level.voteDisplayString[ team ], +      sizeof( level.voteDisplayString[ team ] ), +      "Take away building rights from '%s'", name ); +    if( reason[ 0 ] )      { -      trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: player already has building rights\n\"" ); -      return; +      Q_strcat( level.voteDisplayString[ team ], +        sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) );      } - -    Com_sprintf( level.teamVoteString[ cs_offset ], -      sizeof( level.teamVoteString[ cs_offset ] ), "!allowbuild %i", clientNum ); -    Com_sprintf( level.teamVoteDisplayString[ cs_offset ], -        sizeof( level.teamVoteDisplayString[ cs_offset ] ), -        "Allow '%s' to build", name );    } -  else if( !Q_stricmp( arg1, "designate" ) ) +  else if( !Q_stricmp( vote, "allowbuild" ) )    { -    if( !g_designateVotes.integer ) +    if( !level.clients[ clientNum ].pers.namelog->denyBuild )      {        trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: Designate votes have been disabled.\n\"" ); +        va( "print \"%s: player already has building rights\n\"", cmd ) );        return;      } -    if( level.clients[ clientNum ].pers.designatedBuilder ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: player is already a designated builder\n\"" ); -      return; -    } -    Com_sprintf( level.teamVoteString[ cs_offset ], -      sizeof( level.teamVoteString[ cs_offset ] ), "!designate %i", clientNum ); -    Com_sprintf( level.teamVoteDisplayString[ cs_offset ], -        sizeof( level.teamVoteDisplayString[ cs_offset ] ), -        "Make '%s' a designated builder", name ); +    Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), +      "allowbuild %d", id ); +    Com_sprintf( level.voteDisplayString[ team ], +      sizeof( level.voteDisplayString[ team ] ), +      "Allow '%s' to build", name );    } -  else if( !Q_stricmp( arg1, "undesignate" ) ) +  else if( !Q_stricmp( vote, "admitdefeat" ) )    { - -    if( !g_designateVotes.integer ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: Designate votes have been disabled.\n\"" ); -      return; -    } - -    if( !level.clients[ clientNum ].pers.designatedBuilder ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: player is not currently a designated builder\n\"" ); -      return; -    } -    Com_sprintf( level.teamVoteString[ cs_offset ], -      sizeof( level.teamVoteString[ cs_offset ] ), "!undesignate %i", clientNum ); -    Com_sprintf( level.teamVoteDisplayString[ cs_offset ], -        sizeof( level.teamVoteDisplayString[ cs_offset ] ), -        "Remove designated builder status from '%s'", name ); +    Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), +      "admitdefeat %d", team ); +    strcpy( level.voteDisplayString[ team ], "Admit Defeat" ); +    level.voteDelay[ team ] = 3000;    } -  else if( !Q_stricmp( arg1, "admitdefeat" ) ) -  { -    if( numVoters <=1 ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"callteamvote: You cannot admitdefeat by yourself. Use /callvote draw.\n\"" ); -      return; -    } - -    Com_sprintf( level.teamVoteString[ cs_offset ], -      sizeof( level.teamVoteString[ cs_offset ] ), "admitdefeat %i", team ); -    Com_sprintf( level.teamVoteDisplayString[ cs_offset ], -        sizeof( level.teamVoteDisplayString[ cs_offset ] ), -        "Admit Defeat" ); -  } -   else if( !Q_stricmp( arg1, "poll" ) ) -   { -     if( arg2plus[ 0 ] == '\0' ) -     { -       trap_SendServerCommand( ent-g_entities, "print \"callteamvote: You forgot to specify what people should vote on.\n\"" ); -       return; -     } -     level.teamVoteString[ cs_offset ][ 0 ] = '\0'; -     Com_sprintf( level.teamVoteDisplayString[ cs_offset ], -         sizeof( level.voteDisplayString ), "[Poll] \'%s\'", arg2plus ); -   }    else    {      trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" );      trap_SendServerCommand( ent-g_entities,         "print \"Valid team vote commands are: " -       "kick, denybuild, allowbuild, poll, designate, undesignate, and admitdefeat\n\"" ); +       "kick, denybuild, allowbuild and admitdefeat\n\"" );      return;    } -  ent->client->pers.voteCount++; -  G_admin_adminlog_log( ent, "teamvote", arg1, 0, qtrue ); -   -  if ( reason[0]!='\0' ) -    Q_strcat( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), va( " Reason: '%s'^7", reason ) ); +  G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\": %s\n", +    team == TEAM_NONE ? "CallVote" : "CallTeamVote", +    (int)( ent - g_entities ), ent->client->pers.netname, level.voteString[ team ] ); -  for( i = 0 ; i < level.maxclients ; i++ ) +  if( team == TEAM_NONE )    { -    if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) -      continue; +    trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE +      " called a vote: %s\n\"", ent->client->pers.netname, +      level.voteDisplayString[ team ] ) ); +  } +  else +  { +    int i; -    if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == team ) -    { -      trap_SendServerCommand( i, va("print \"%s " S_COLOR_WHITE -            "called a team vote: %s^7 \n\"", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ) ); -    } -    else if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) &&  -             ( ( !Q_stricmp( arg1, "kick" ) || !Q_stricmp( arg1, "denybuild" ) ) ||  -             level.clients[ i ].pers.teamSelection == PTE_NONE ) ) +    for( i = 0 ; i < level.maxclients ; i++ )      { -      trap_SendServerCommand( i, va("print \"^6[Admins]^7 %s " S_COLOR_WHITE -            "called a team vote: %s^7 \n\"", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ) ); +      if( level.clients[ i ].pers.connected == CON_CONNECTED ) +      { +        if( level.clients[ i ].pers.teamSelection == team || +            ( level.clients[ i ].pers.teamSelection == TEAM_NONE && +            G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) +        { +          trap_SendServerCommand( i, va( "print \"%s" S_COLOR_WHITE +            " called a team vote: %s\n\"", ent->client->pers.netname, +            level.voteDisplayString[ team ] ) ); +        } +        else if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) +        { +          trap_SendServerCommand( i, va( "chat -1 %d \"" S_COLOR_YELLOW "%s"  +            S_COLOR_YELLOW " called a team vote (%ss): %s\"", SAY_ADMINS, +            ent->client->pers.netname, BG_TeamName( team ),  +            level.voteDisplayString[ team ] ) ); +        } +      }      }    } -   -  if(team==PTE_ALIENS) -    G_LogPrintf("Teamvote: %s^7 called a teamvote (aliens): %s^7\n", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ); -  else if(team==PTE_HUMANS) -    G_LogPrintf("Teamvote: %s^7 called a teamvote (humans): %s^7\n", ent->client->pers.netname, level.teamVoteDisplayString[ cs_offset ] ); -   -  Q_strcat( level.teamVoteDisplayString[ cs_offset ], sizeof( level.teamVoteDisplayString[ cs_offset ] ), va( " Called by: '%s^7'", ent->client->pers.netname ) ); -  // start the voting, the caller autoamtically votes yes -  level.teamVoteTime[ cs_offset ] = level.time; -  level.teamVoteNo[ cs_offset ] = 0; +  G_DecolorString( ent->client->pers.netname, caller, sizeof( caller ) ); -  for( i = 0 ; i < level.maxclients ; i++ ) -  { -    if( level.clients[ i ].ps.stats[ STAT_PTEAM ] == team ) -      level.clients[ i ].ps.eFlags &= ~EF_TEAMVOTED; -  } +  level.voteTime[ team ] = level.time; +  trap_SetConfigstring( CS_VOTE_TIME + team, +    va( "%d", level.voteTime[ team ] ) ); +  trap_SetConfigstring( CS_VOTE_STRING + team, +    level.voteDisplayString[ team ] ); +  trap_SetConfigstring( CS_VOTE_CALLER + team, +    caller ); -  if( !Q_stricmp( arg1, "poll" ) ) -  { -    level.teamVoteYes[ cs_offset ] = 0; -  } -  else -  { -   level.teamVoteYes[ cs_offset ] = 1; -   ent->client->ps.eFlags |= EF_TEAMVOTED; -  } - -  trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, va( "%i", level.teamVoteTime[ cs_offset ] ) ); -  trap_SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, level.teamVoteDisplayString[ cs_offset ] ); -  trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va( "%i", level.teamVoteYes[ cs_offset ] ) ); -  trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va( "%i", level.teamVoteNo[ cs_offset ] ) ); +  ent->client->pers.namelog->voteCount++; +  ent->client->pers.vote |= 1 << team; +  G_Vote( ent, team, qtrue );  } -  /*  ================== -Cmd_TeamVote_f +Cmd_Vote_f  ==================  */ -void Cmd_TeamVote_f( gentity_t *ent ) +void Cmd_Vote_f( gentity_t *ent )  { -  int     cs_offset = 0; -  char    msg[ 64 ]; +  char cmd[ MAX_TOKEN_CHARS ], vote[ MAX_TOKEN_CHARS ]; +  team_t team = ent->client->pers.teamSelection; -  if( ent->client->pers.teamSelection == PTE_ALIENS ) -    cs_offset = 1; +  trap_Argv( 0, cmd, sizeof( cmd ) ); +  if( Q_stricmp( cmd, "teamvote" ) ) +    team = TEAM_NONE; -  if( !level.teamVoteTime[ cs_offset ] ) +  if( !level.voteTime[ team ] )    { -    trap_SendServerCommand( ent-g_entities, "print \"No team vote in progress\n\"" ); +    trap_SendServerCommand( ent-g_entities, +      va( "print \"%s: no vote in progress\n\"", cmd ) );      return;    } -  if( ent->client->ps.eFlags & EF_TEAMVOTED ) +  if( ent->client->pers.voted & ( 1 << team ) )    { -    trap_SendServerCommand( ent-g_entities, "print \"Team vote already cast\n\"" ); +    trap_SendServerCommand( ent-g_entities, +      va( "print \"%s: vote already cast\n\"", cmd ) );      return;    } -  trap_SendServerCommand( ent-g_entities, "print \"Team vote cast\n\"" ); - -  ent->client->ps.eFlags |= EF_TEAMVOTED; - -  trap_Argv( 1, msg, sizeof( msg ) ); +  trap_SendServerCommand( ent-g_entities, +    va( "print \"%s: vote cast\n\"", cmd ) ); -  if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' ) -  { -    level.teamVoteYes[ cs_offset ]++; -    trap_SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va( "%i", level.teamVoteYes[ cs_offset ] ) ); -  } +  trap_Argv( 1, vote, sizeof( vote ) ); +  if( vote[ 0 ] == 'y' ) +    ent->client->pers.vote |= 1 << team;    else -  { -    level.teamVoteNo[ cs_offset ]++; -    trap_SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va( "%i", level.teamVoteNo[ cs_offset ] ) ); -  } - -  // a majority will be determined in TeamCheckVote, which will also account -  // for players entering or leaving +    ent->client->pers.vote &= ~( 1 << team ); +  G_Vote( ent, team, qtrue );  } @@ -2626,29 +1767,38 @@ void Cmd_SetViewpos_f( gentity_t *ent )    char    buffer[ MAX_TOKEN_CHARS ];    int     i; -  if( trap_Argc( ) != 5 ) +  if( trap_Argc( ) < 4 )    { -    trap_SendServerCommand( ent-g_entities, va( "print \"usage: setviewpos x y z yaw\n\"" ) ); +    trap_SendServerCommand( ent-g_entities, "print \"usage: setviewpos <x> <y> <z> [<yaw> [<pitch>]]\n\"" );      return;    } -  VectorClear( angles ); - -  for( i = 0 ; i < 3 ; i++ ) +  for( i = 0; i < 3; i++ )    {      trap_Argv( i + 1, buffer, sizeof( buffer ) );      origin[ i ] = atof( buffer );    } +  origin[ 2 ] -= ent->client->ps.viewheight; -  trap_Argv( 4, buffer, sizeof( buffer ) ); -  angles[ YAW ] = atof( buffer ); +  VectorCopy( ent->client->ps.viewangles, angles ); +  angles[ ROLL ] = 0; -  TeleportPlayer( ent, origin, angles ); +  if( trap_Argc() >= 5 ) { +    trap_Argv( 4, buffer, sizeof( buffer ) ); +    angles[ YAW ] = atof( buffer ); +    if( trap_Argc() >= 6 ) { +      trap_Argv( 5, buffer, sizeof( buffer ) ); +      angles[ PITCH ] = atof( buffer ); +    } +  } + +  TeleportPlayer( ent, origin, angles, 0.0f );  }  #define AS_OVER_RT3         ((ALIENSENSE_RANGE*0.5f)/M_ROOT3) -qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin ) +static qboolean G_RoomForClassChange( gentity_t *ent, class_t class, +                                      vec3_t newOrigin )  {    vec3_t    fromMins, fromMaxs;    vec3_t    toMins, toMaxs; @@ -2656,12 +1806,12 @@ qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin    trace_t   tr;    float     nudgeHeight;    float     maxHorizGrowth; -  pClass_t  oldClass = ent->client->ps.stats[ STAT_PCLASS ]; +  class_t   oldClass = ent->client->ps.stats[ STAT_CLASS ]; -  BG_FindBBoxForClass( oldClass, fromMins, fromMaxs, NULL, NULL, NULL ); -  BG_FindBBoxForClass( class, toMins, toMaxs, NULL, NULL, NULL ); +  BG_ClassBoundingBox( oldClass, fromMins, fromMaxs, NULL, NULL, NULL ); +  BG_ClassBoundingBox( class, toMins, toMaxs, NULL, NULL, NULL ); -  VectorCopy( ent->s.origin, newOrigin ); +  VectorCopy( ent->client->ps.origin, newOrigin );    // find max x/y diff    maxHorizGrowth = toMaxs[ 0 ] - fromMaxs[ 0 ]; @@ -2684,7 +1834,10 @@ qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin    }    // find what the new origin would be on a level surface -  newOrigin[ 2 ] += fabs( toMins[ 2 ] ) - fabs( fromMins[ 2 ] ); +  newOrigin[ 2 ] -= toMins[ 2 ] - fromMins[ 2 ]; + +  if( ent->client->noclip ) +    return qtrue;    //compute a place up in the air to start the real trace    VectorCopy( newOrigin, temp ); @@ -2702,10 +1855,7 @@ qboolean G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin      ent->s.number, MASK_PLAYERSOLID );    //check there is room to evolve -  if( !tr.startsolid && tr.fraction == 1.0f ) -    return qtrue; -  else -    return qfalse; +  return ( !tr.startsolid && tr.fraction == 1.0f );  }  /* @@ -2719,56 +1869,43 @@ void Cmd_Class_f( gentity_t *ent )    int       clientNum;    int       i;    vec3_t    infestOrigin; -  pClass_t  currentClass = ent->client->pers.classSelection; -  pClass_t  newClass; -  int       numLevels; +  class_t   currentClass = ent->client->pers.classSelection; +  class_t   newClass;    int       entityList[ MAX_GENTITIES ];    vec3_t    range = { AS_OVER_RT3, AS_OVER_RT3, AS_OVER_RT3 };    vec3_t    mins, maxs;    int       num;    gentity_t *other; -  qboolean  humanNear = qfalse; -  vec3_t    oldVel;    int       oldBoostTime = -1; +  vec3_t    oldVel;    clientNum = ent->client - level.clients;    trap_Argv( 1, s, sizeof( s ) ); -  newClass = BG_FindClassNumForName( s ); +  newClass = BG_ClassByName( s )->number; -  if( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) +  if( ent->client->sess.spectatorState != SPECTATOR_NOT )    {      if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )        G_StopFollowing( ent ); - -    if( ent->client->pers.teamSelection == PTE_ALIENS ) +    if( ent->client->pers.teamSelection == TEAM_ALIENS )      {        if( newClass != PCL_ALIEN_BUILDER0 &&            newClass != PCL_ALIEN_BUILDER0_UPG &&            newClass != PCL_ALIEN_LEVEL0 )        { -        trap_SendServerCommand( ent-g_entities, -          va( "print \"You cannot spawn with class %s\n\"", s ) ); -        return; -      }  -       -      if( !BG_ClassIsAllowed( newClass ) ) -      { -        trap_SendServerCommand( ent-g_entities, -          va( "print \"Class %s is not allowed\n\"", s ) ); +        G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTSPAWN, newClass );          return;        } -       -      if( !BG_FindStagesForClass( newClass, g_alienStage.integer ) ) + +      if( !BG_ClassIsAllowed( newClass ) )        { -        trap_SendServerCommand( ent-g_entities, -          va( "print \"Class %s not allowed at stage %d\n\"", -              s, g_alienStage.integer ) ); +        G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTALLOWED, newClass );          return;        } -       -      if( ent->client->pers.denyBuild && ( newClass==PCL_ALIEN_BUILDER0 || newClass==PCL_ALIEN_BUILDER0_UPG ) ) + +      if( !BG_ClassAllowedInStage( newClass, g_alienStage.integer ) )        { -        trap_SendServerCommand( ent-g_entities, "print \"Your building rights have been revoked\n\"" ); +        G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTATSTAGE, newClass );          return;        } @@ -2776,40 +1913,32 @@ void Cmd_Class_f( gentity_t *ent )        if( G_PushSpawnQueue( &level.alienSpawnQueue, clientNum ) )        {          ent->client->pers.classSelection = newClass; -        ent->client->ps.stats[ STAT_PCLASS ] = newClass; +        ent->client->ps.stats[ STAT_CLASS ] = newClass;        }      } -    else if( ent->client->pers.teamSelection == PTE_HUMANS ) +    else if( ent->client->pers.teamSelection == TEAM_HUMANS )      {        //set the item to spawn with -      if( !Q_stricmp( s, BG_FindNameForWeapon( WP_MACHINEGUN ) ) && +      if( !Q_stricmp( s, BG_Weapon( WP_MACHINEGUN )->name ) &&            BG_WeaponIsAllowed( WP_MACHINEGUN ) )        {          ent->client->pers.humanItemSelection = WP_MACHINEGUN;        } -      else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD ) ) && +      else if( !Q_stricmp( s, BG_Weapon( WP_HBUILD )->name ) &&                 BG_WeaponIsAllowed( WP_HBUILD ) )        {          ent->client->pers.humanItemSelection = WP_HBUILD;        } -      else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD2 ) ) && -               BG_WeaponIsAllowed( WP_HBUILD2 ) && -               BG_FindStagesForWeapon( WP_HBUILD2, g_humanStage.integer ) ) -      { -        ent->client->pers.humanItemSelection = WP_HBUILD2; -      }        else        { -        trap_SendServerCommand( ent-g_entities, -          "print \"Unknown starting item\n\"" ); +        G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNSPAWNITEM );          return;        }        // spawn from a telenode -      G_LogOnlyPrintf("ClientTeamClass: %i human %s\n", clientNum, s);        if( G_PushSpawnQueue( &level.humanSpawnQueue, clientNum ) )        {          ent->client->pers.classSelection = PCL_HUMAN; -        ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN; +        ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN;        }      }      return; @@ -2818,24 +1947,23 @@ void Cmd_Class_f( gentity_t *ent )    if( ent->health <= 0 )      return; -  if( ent->client->pers.teamSelection == PTE_ALIENS && -      !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) && -      !( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) ) +  if( ent->client->pers.teamSelection == TEAM_ALIENS )    {      if( newClass == PCL_NONE )      { -      trap_SendServerCommand( ent-g_entities, "print \"Unknown class\n\"" ); +      G_TriggerMenu( ent->client->ps.clientNum, MN_A_UNKNOWNCLASS );        return;      }      //if we are not currently spectating, we are attempting evolution      if( ent->client->pers.classSelection != PCL_NONE )      { -      if( ( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) || -          ( ent->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) +      int cost; + +      //check that we have an overmind +      if( !G_Overmind( ) )        { -        trap_SendServerCommand( ent-g_entities, -          "print \"You cannot evolve while wallwalking\n\"" ); +        G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE );          return;        } @@ -2848,64 +1976,42 @@ void Cmd_Class_f( gentity_t *ent )        {          other = &g_entities[ entityList[ i ] ]; -        if( ( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || -            ( other->s.eType == ET_BUILDABLE && other->biteam == BIT_HUMANS ) ) -        { -          humanNear = qtrue; -        } -        //If its the OM, then ignore all humans. -        if(other->s.eType == ET_BUILDABLE && other->s.modelindex == BA_A_OVERMIND) +        if( ( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) || +            ( other->s.eType == ET_BUILDABLE && other->buildableTeam == TEAM_HUMANS && +              other->powered ) )          { -          humanNear = qfalse; -          break; +          G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); +          return;          }        } -      if(humanNear == qtrue) { -        G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); -        return; -      } - -      if( !level.overmindPresent ) +      //check that we are not wallwalking +      if( ent->client->ps.eFlags & EF_WALLCLIMB )        { -        G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE ); +        G_TriggerMenu( clientNum, MN_A_EVOLVEWALLWALK );          return;        } -      // denyweapons -      if( newClass >= PCL_ALIEN_LEVEL1 && newClass <= PCL_ALIEN_LEVEL4 && -        ent->client->pers.denyAlienClasses & ( 1 << newClass ) ) -      { -        trap_SendServerCommand( ent-g_entities, va( "print \"You are denied from using this class\n\"" ) ); -        return; -      } - -      //guard against selling the HBUILD weapons exploit -       if( ent->client->sess.sessionTeam != TEAM_SPECTATOR && -           ( currentClass == PCL_ALIEN_BUILDER0 || +      if( ent->client->sess.spectatorState == SPECTATOR_NOT && +          ( currentClass == PCL_ALIEN_BUILDER0 ||              currentClass == PCL_ALIEN_BUILDER0_UPG ) &&            ent->client->ps.stats[ STAT_MISC ] > 0 )        { -        trap_SendServerCommand( ent-g_entities, -            va( "print \"You cannot evolve until build timer expires\n\"" ) ); +        G_TriggerMenu( ent->client->ps.clientNum, MN_A_EVOLVEBUILDTIMER );          return;        } -      numLevels = BG_ClassCanEvolveFromTo( currentClass, -                                           newClass, -                                           (short)ent->client->ps.persistant[ PERS_CREDIT ], 0 ); +      cost = BG_ClassCanEvolveFromTo( currentClass, newClass, +                                      ent->client->pers.credit, +                                      g_alienStage.integer, 0 );        if( G_RoomForClassChange( ent, newClass, infestOrigin ) )        { -        //...check we can evolve to that class -        if( numLevels >= 0 && -            BG_FindStagesForClass( newClass, g_alienStage.integer ) && -            BG_ClassIsAllowed( newClass ) ) +        if( cost >= 0 )          { -          G_LogOnlyPrintf("ClientTeamClass: %i alien %s\n", clientNum, s);            ent->client->pers.evolveHealthFraction = (float)ent->client->ps.stats[ STAT_HEALTH ] / -            (float)BG_FindHealthForClass( currentClass ); +            (float)BG_Class( currentClass )->health;            if( ent->client->pers.evolveHealthFraction < 0.0f )              ent->client->pers.evolveHealthFraction = 0.0f; @@ -2913,91 +2019,35 @@ void Cmd_Class_f( gentity_t *ent )              ent->client->pers.evolveHealthFraction = 1.0f;            //remove credit -          G_AddCreditToClient( ent->client, -(short)numLevels, qtrue ); +          G_AddCreditToClient( ent->client, -cost, qtrue );            ent->client->pers.classSelection = newClass;            ClientUserinfoChanged( clientNum, qfalse );            VectorCopy( infestOrigin, ent->s.pos.trBase );            VectorCopy( ent->client->ps.velocity, oldVel ); +            if( ent->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) -            oldBoostTime = ent->client->lastBoostedTime; +            oldBoostTime = ent->client->boostedTime; +            ClientSpawn( ent, ent, ent->s.pos.trBase, ent->s.apos.trBase ); +            VectorCopy( oldVel, ent->client->ps.velocity );            if( oldBoostTime > 0 )            { -            ent->client->lastBoostedTime = oldBoostTime; +            ent->client->boostedTime = oldBoostTime;              ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED;            } -          return;          }          else -        { -          trap_SendServerCommand( ent-g_entities, -               "print \"You cannot evolve from your current class\n\"" ); -          return; -        } +          G_TriggerMenuArgs( clientNum, MN_A_CANTEVOLVE, newClass );        }        else -      {          G_TriggerMenu( clientNum, MN_A_NOEROOM ); -        return; -      }      } -   else if( ent->client->pers.teamSelection == PTE_HUMANS ) -   { -    //humans cannot use this command whilst alive -    if( ent->client->pers.classSelection != PCL_NONE ) -    { -      trap_SendServerCommand( ent-g_entities, va( "print \"You must be dead to use the class command\n\"" ) ); -      return; -    } - -    ent->client->pers.classSelection = -      ent->client->ps.stats[ STAT_PCLASS ] = PCL_HUMAN; - -    //set the item to spawn with -    if( !Q_stricmp( s, BG_FindNameForWeapon( WP_MACHINEGUN ) ) && BG_WeaponIsAllowed( WP_MACHINEGUN ) ) -      ent->client->pers.humanItemSelection = WP_MACHINEGUN; -    else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD ) ) && BG_WeaponIsAllowed( WP_HBUILD ) ) -      ent->client->pers.humanItemSelection = WP_HBUILD; -    else if( !Q_stricmp( s, BG_FindNameForWeapon( WP_HBUILD2 ) ) && BG_WeaponIsAllowed( WP_HBUILD2 ) && -        BG_FindStagesForWeapon( WP_HBUILD2, g_humanStage.integer ) ) -      ent->client->pers.humanItemSelection = WP_HBUILD2; -    else -    { -      ent->client->pers.classSelection = PCL_NONE; -      trap_SendServerCommand( ent-g_entities, va( "print \"Unknown starting item\n\"" ) ); -      return; -    } - -    G_LogOnlyPrintf("ClientTeamClass: %i human %s\n", clientNum, s); - -    G_PushSpawnQueue( &level.humanSpawnQueue, clientNum );    } - } +  else if( ent->client->pers.teamSelection == TEAM_HUMANS ) +    G_TriggerMenu( clientNum, MN_H_DEADTOCLASS );  } -/* -================= -DBCommand - -Send command to all designated builders of selected team -================= -*/ -void DBCommand( pTeam_t team, const char *text ) -{ -  int i; -  gentity_t *ent; - -  for( i = 0, ent = g_entities + i; i < level.maxclients; i++, ent++ ) -  { -    if( !ent->client || ( ent->client->pers.connected != CON_CONNECTED ) || -        ( ent->client->pers.teamSelection != team ) || -        !ent->client->pers.designatedBuilder ) -      continue; - -    trap_SendServerCommand( i, text ); -  } -}  /*  ================= @@ -3006,16 +2056,16 @@ Cmd_Destroy_f  */  void Cmd_Destroy_f( gentity_t *ent )  { -  vec3_t      forward, end; +  vec3_t      viewOrigin, forward, end;    trace_t     tr;    gentity_t   *traceEnt;    char        cmd[ 12 ];    qboolean    deconstruct = qtrue; +  qboolean    lastSpawn = qfalse; -  if( ent->client->pers.denyBuild ) +  if( ent->client->pers.namelog->denyBuild )    { -    trap_SendServerCommand( ent-g_entities, -      "print \"Your building rights have been revoked\n\"" ); +    G_TriggerMenu( ent->client->ps.clientNum, MN_B_REVOKED );      return;    } @@ -3023,251 +2073,104 @@ void Cmd_Destroy_f( gentity_t *ent )    if( Q_stricmp( cmd, "destroy" ) == 0 )      deconstruct = qfalse; -  if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) +  BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); +  AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); +  VectorMA( viewOrigin, 100, forward, end ); + +  trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); +  traceEnt = &g_entities[ tr.entityNum ]; + +  if( tr.fraction < 1.0f && +      ( traceEnt->s.eType == ET_BUILDABLE ) && +      ( traceEnt->buildableTeam == ent->client->pers.teamSelection ) && +      ( ( ent->client->ps.weapon >= WP_ABUILD ) && +        ( ent->client->ps.weapon <= WP_HBUILD ) ) )    { -    if( ( ent->client->hovel->s.eFlags & EF_DBUILDER ) && -      !ent->client->pers.designatedBuilder ) +    // Always let the builder prevent the explosion +    if( traceEnt->health <= 0 )      { -      trap_SendServerCommand( ent-g_entities,  -        "print \"This structure is protected by designated builder\n\"" ); -      DBCommand( ent->client->pers.teamSelection, -        va( "print \"%s^3 has attempted to decon a protected structure!\n\"", -          ent->client->pers.netname ) ); +      G_QueueBuildPoints( traceEnt ); +      G_RewardAttackers( traceEnt ); +      G_FreeEntity( traceEnt );        return;      } -    G_Damage( ent->client->hovel, ent, ent, forward, ent->s.origin,  -      10000, 0, MOD_SUICIDE ); -  } -  if( !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) ) -  { -    AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); -    VectorMA( ent->client->ps.origin, 100, forward, end ); - -    trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); -    traceEnt = &g_entities[ tr.entityNum ]; - -    if( tr.fraction < 1.0f && -        ( traceEnt->s.eType == ET_BUILDABLE ) && -        ( traceEnt->biteam == ent->client->pers.teamSelection ) && -        ( ( ent->client->ps.weapon >= WP_ABUILD ) && -          ( ent->client->ps.weapon <= WP_HBUILD ) ) ) +    // Cancel deconstruction (unmark) +    if( deconstruct && g_markDeconstruct.integer && traceEnt->deconstruct )      { -      // Cancel deconstruction -      if( g_markDeconstruct.integer == 1 && traceEnt->deconstruct ) -      { -        traceEnt->deconstruct = qfalse; -        return; -      } -      if( ( traceEnt->s.eFlags & EF_DBUILDER ) && -        !ent->client->pers.designatedBuilder ) -      { -        trap_SendServerCommand( ent-g_entities,  -          "print \"This structure is protected by designated builder\n\"" ); -        DBCommand( ent->client->pers.teamSelection, -          va( "print \"%s^3 has attempted to decon a protected structure!\n\"", -            ent->client->pers.netname ) ); -        return; -      } -  -      // Check the minimum level to deconstruct -      if ( G_admin_level( ent ) < g_minDeconLevel.integer && !ent->client->pers.designatedBuilder ) -      { -        trap_SendServerCommand( ent-g_entities, -          "print \"You do not have deconstructuction rights.\n\"" ); -        return; -      } +      traceEnt->deconstruct = qfalse; +      return; +    } -      // Prevent destruction of the last spawn -      if( g_markDeconstruct.integer != 1 && !g_cheats.integer ) -      { -        if( ent->client->pers.teamSelection == PTE_ALIENS && -            traceEnt->s.modelindex == BA_A_SPAWN ) -        { -          if( level.numAlienSpawns <= 1 ) -            return; -        } -        else if( ent->client->pers.teamSelection == PTE_HUMANS && -                 traceEnt->s.modelindex == BA_H_SPAWN ) -        { -          if( level.numHumanSpawns <= 1 ) -            return; -        } -      } +    // Prevent destruction of the last spawn +    if( ent->client->pers.teamSelection == TEAM_ALIENS && +        traceEnt->s.modelindex == BA_A_SPAWN ) +    { +      if( level.numAlienSpawns <= 1 ) +        lastSpawn = qtrue; +    } +    else if( ent->client->pers.teamSelection == TEAM_HUMANS && +             traceEnt->s.modelindex == BA_H_SPAWN ) +    { +      if( level.numHumanSpawns <= 1 ) +        lastSpawn = qtrue; +    } -      // Don't allow destruction of hovel with granger inside -      if( traceEnt->s.modelindex == BA_A_HOVEL && traceEnt->active ) -        return; +    if( lastSpawn && !g_cheats.integer && +        !g_markDeconstruct.integer ) +    { +      G_TriggerMenu( ent->client->ps.clientNum, MN_B_LASTSPAWN ); +      return; +    } -      // Don't allow destruction of buildables that cannot be rebuilt -      if(g_suddenDeath.integer && traceEnt->health > 0 && -          ( ( g_suddenDeathMode.integer == SDMODE_SELECTIVE && -              !BG_FindReplaceableTestForBuildable( traceEnt->s.modelindex ) ) || -            ( g_suddenDeathMode.integer == SDMODE_BP && -              BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) || -            g_suddenDeathMode.integer == SDMODE_NO_BUILD  || -            g_suddenDeathMode.integer == SDMODE_NO_DECON ) ) -      { -        trap_SendServerCommand( ent-g_entities, -          "print \"During Sudden Death you can only decon buildings that " -          "can be rebuilt\n\"" ); -        return; -      } +    // Don't allow destruction of buildables that cannot be rebuilt +    if( G_TimeTilSuddenDeath( ) <= 0 ) +    { +      G_TriggerMenu( ent->client->ps.clientNum, MN_B_SUDDENDEATH ); +      return; +    } +    if( !g_markDeconstruct.integer || +        ( ent->client->pers.teamSelection == TEAM_HUMANS && +          !G_FindPower( traceEnt, qtrue ) ) ) +    {        if( ent->client->ps.stats[ STAT_MISC ] > 0 )        {          G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum );          return;        } - -      if( traceEnt->health > 0 || g_deconDead.integer ) -      { -        if( g_markDeconstruct.integer == 1 ) -        { -          traceEnt->deconstruct     = qtrue; // Mark buildable for deconstruction -          traceEnt->deconstructTime = level.time; -        } -        else -        { -          if( traceEnt->health > 0 ) -          { -            buildHistory_t *new; - -            new = G_Alloc( sizeof( buildHistory_t ) ); -            new->ID = ( ++level.lastBuildID > 1000 )  -                ? ( level.lastBuildID = 1 ) : level.lastBuildID; -            new->ent = ent; -            new->name[ 0 ] = 0; -            new->buildable = traceEnt->s.modelindex; -            VectorCopy( traceEnt->s.pos.trBase, new->origin ); -            VectorCopy( traceEnt->s.angles, new->angles ); -            VectorCopy( traceEnt->s.origin2, new->origin2 ); -            VectorCopy( traceEnt->s.angles2, new->angles2 ); -            new->fate = BF_DECONNED; -            new->next = NULL; -            new->marked = NULL; -            G_LogBuild( new ); - -            G_TeamCommand( ent->client->pers.teamSelection, -              va( "print \"%s ^3DECONSTRUCTED^7 by %s^7\n\"", -                BG_FindHumanNameForBuildable( traceEnt->s.modelindex ),  -                ent->client->pers.netname ) ); - -            G_LogPrintf( "Decon: %i %i 0: %s^7 deconstructed %s\n", -              ent->client->ps.clientNum, -              traceEnt->s.modelindex, -              ent->client->pers.netname,  -              BG_FindNameForBuildable( traceEnt->s.modelindex ) ); -        } - -          if( !deconstruct ) -            G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10000, 0, MOD_SUICIDE ); -          else -            G_FreeEntity( traceEnt ); - -          if( !g_cheats.integer ) -            ent->client->ps.stats[ STAT_MISC ] += -              BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2; -        } -      }      } -  } -} - -void Cmd_Mark_f( gentity_t *ent ) -{ -  vec3_t      forward, end; -  trace_t     tr; -  gentity_t   *traceEnt; -  if( g_markDeconstruct.integer != 2 ) -  { -    trap_SendServerCommand( ent-g_entities, -      "print \"Mark is disabled\n\"" ); -    return; -  } - -  if( ent->client->pers.denyBuild ) -  { -    trap_SendServerCommand( ent-g_entities, -      "print \"Your building rights have been revoked\n\"" ); -    return; -  } - -  // Check the minimum level to deconstruct -  if ( G_admin_level( ent ) < g_minDeconLevel.integer  && !ent->client->pers.designatedBuilder && g_minDeconAffectsMark.integer > 0 ) -  { -    trap_SendServerCommand( ent-g_entities, -      "print \"You do not have deconstructuction rights.\n\"" ); -    return; -  } - -  // can't mark when in hovel -  if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) -    return; - -  if( !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) ) -  { -    AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); -    VectorMA( ent->client->ps.origin, 100, forward, end ); - -    trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); -    traceEnt = &g_entities[ tr.entityNum ]; - -    if( tr.fraction < 1.0f && -        ( traceEnt->s.eType == ET_BUILDABLE ) && -        ( traceEnt->biteam == ent->client->pers.teamSelection ) && -        ( ( ent->client->ps.weapon >= WP_ABUILD ) && -          ( ent->client->ps.weapon <= WP_HBUILD ) ) ) +    if( traceEnt->health > 0 )      { -      if( ( traceEnt->s.eFlags & EF_DBUILDER ) && -        !ent->client->pers.designatedBuilder ) -      { -        trap_SendServerCommand( ent-g_entities, -          "print \"this structure is protected by a designated builder\n\"" ); -        return; -      } - -      // Cancel deconstruction -      if( traceEnt->deconstruct ) -      { -        traceEnt->deconstruct = qfalse; - -        trap_SendServerCommand( ent-g_entities, -          va( "print \"%s no longer marked for deconstruction\n\"", -          BG_FindHumanNameForBuildable( traceEnt->s.modelindex ) ) ); -        return; -      } - -      // Don't allow marking of buildables that cannot be rebuilt -      if(g_suddenDeath.integer && traceEnt->health > 0 && -          ( ( g_suddenDeathMode.integer == SDMODE_SELECTIVE && -              !BG_FindReplaceableTestForBuildable( traceEnt->s.modelindex ) ) || -            ( g_suddenDeathMode.integer == SDMODE_BP && -              BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) || -            g_suddenDeathMode.integer == SDMODE_NO_BUILD || -            g_suddenDeathMode.integer == SDMODE_NO_DECON ) ) +      if( !deconstruct )        { -        trap_SendServerCommand( ent-g_entities, -          "print \"During Sudden Death you can only mark buildings that " -          "can be rebuilt\n\"" ); -        return; +        G_Damage( traceEnt, ent, ent, forward, tr.endpos, +                  traceEnt->health, 0, MOD_SUICIDE );        } - -      if( traceEnt->health > 0 ) +      else if( g_markDeconstruct.integer && +               ( ent->client->pers.teamSelection != TEAM_HUMANS || +                 G_FindPower( traceEnt , qtrue ) || lastSpawn ) )        {          traceEnt->deconstruct     = qtrue; // Mark buildable for deconstruction          traceEnt->deconstructTime = level.time; - -        trap_SendServerCommand( ent-g_entities, -          va( "print \"%s marked for deconstruction\n\"", -          BG_FindHumanNameForBuildable( traceEnt->s.modelindex ) ) ); +      } +      else +      { +        if( !g_cheats.integer ) // add a bit to the build timer +        { +            ent->client->ps.stats[ STAT_MISC ] += +              BG_Buildable( traceEnt->s.modelindex )->buildTime / 4; +        } +        G_Damage( traceEnt, ent, ent, forward, tr.endpos, +                  traceEnt->health, 0, MOD_DECONSTRUCT ); +        G_RemoveRangeMarkerFrom( traceEnt ); +        G_FreeEntity( traceEnt );        }      }    }  } -  /*  =================  Cmd_ActivateItem_f @@ -3281,13 +2184,28 @@ void Cmd_ActivateItem_f( gentity_t *ent )    int   upgrade, weapon;    trap_Argv( 1, s, sizeof( s ) ); -  upgrade = BG_FindUpgradeNumForName( s ); -  weapon = BG_FindWeaponNumForName( s ); + +  // "weapon" aliased to whatever weapon you have +  if( !Q_stricmp( "weapon", s ) ) +  { +    if( ent->client->ps.weapon == WP_BLASTER && +        BG_PlayerCanChangeWeapon( &ent->client->ps ) ) +      G_ForceWeaponChange( ent, WP_NONE ); +    return; +  } + +  upgrade = BG_UpgradeByName( s )->number; +  weapon = BG_WeaponByName( s )->number;    if( upgrade != UP_NONE && BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )      BG_ActivateUpgrade( upgrade, ent->client->ps.stats ); -  else if( weapon != WP_NONE && BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) -    G_ForceWeaponChange( ent, weapon ); +  else if( weapon != WP_NONE && +           BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) +  { +    if( ent->client->ps.weapon != weapon && +        BG_PlayerCanChangeWeapon( &ent->client->ps ) ) +      G_ForceWeaponChange( ent, weapon ); +  }    else      trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) );  } @@ -3302,11 +2220,11 @@ Deactivate an item  */  void Cmd_DeActivateItem_f( gentity_t *ent )  { -  char  s[ MAX_TOKEN_CHARS ]; -  int   upgrade; +  char      s[ MAX_TOKEN_CHARS ]; +  upgrade_t upgrade;    trap_Argv( 1, s, sizeof( s ) ); -  upgrade = BG_FindUpgradeNumForName( s ); +  upgrade = BG_UpgradeByName( s )->number;    if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )      BG_DeactivateUpgrade( upgrade, ent->client->ps.stats ); @@ -3322,38 +2240,25 @@ Cmd_ToggleItem_f  */  void Cmd_ToggleItem_f( gentity_t *ent )  { -  char  s[ MAX_TOKEN_CHARS ]; -  int   upgrade, weapon, i; +  char      s[ MAX_TOKEN_CHARS ]; +  weapon_t  weapon; +  upgrade_t upgrade;    trap_Argv( 1, s, sizeof( s ) ); -  upgrade = BG_FindUpgradeNumForName( s ); -  weapon = BG_FindWeaponNumForName( s ); +  upgrade = BG_UpgradeByName( s )->number; +  weapon = BG_WeaponByName( s )->number;    if( weapon != WP_NONE )    { +    if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) +      return; +      //special case to allow switching between      //the blaster and the primary weapon -      if( ent->client->ps.weapon != WP_BLASTER )        weapon = WP_BLASTER;      else -    { -      //find a held weapon which isn't the blaster -      for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) -      { -        if( i == WP_BLASTER ) -          continue; - -        if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) ) -        { -          weapon = i; -          break; -        } -      } - -      if( i == WP_NUM_WEAPONS ) -        weapon = WP_BLASTER; -    } +      weapon = WP_NONE;      G_ForceWeaponChange( ent, weapon );    } @@ -3375,60 +2280,32 @@ Cmd_Buy_f  */  void Cmd_Buy_f( gentity_t *ent )  { -  char      s[ MAX_TOKEN_CHARS ]; -  int       i; -  int       weapon, upgrade, numItems = 0; -  int       maxAmmo, maxClips; -  qboolean  buyingEnergyAmmo = qfalse; -  qboolean  hasEnergyWeapon = qfalse; - -  for( i = UP_NONE; i < UP_NUM_UPGRADES; i++ ) -  { -    if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) ) -      numItems++; -  } - -  for( i = WP_NONE; i < WP_NUM_WEAPONS; i++ ) -  { -    if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) ) -    { -      if( BG_FindUsesEnergyForWeapon( i ) ) -        hasEnergyWeapon = qtrue; -      numItems++; -    } -  } +  char s[ MAX_TOKEN_CHARS ]; +  weapon_t  weapon; +  upgrade_t upgrade; +  qboolean  energyOnly;    trap_Argv( 1, s, sizeof( s ) ); -  weapon = BG_FindWeaponNumForName( s ); -  upgrade = BG_FindUpgradeNumForName( s ); - -  //special case to keep norf happy -  if( weapon == WP_NONE && upgrade == UP_AMMO ) -  { -    buyingEnergyAmmo = hasEnergyWeapon; -  } - -  if( buyingEnergyAmmo ) -  { -    //no armoury nearby -    if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) && -        !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) && -        !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) -    { -      trap_SendServerCommand( ent-g_entities, va( -        "print \"You must be near a reactor, repeater or armoury\n\"" ) ); -      return; -    } -  } +  weapon = BG_WeaponByName( s )->number; +  upgrade = BG_UpgradeByName( s )->number; + +  // Only give energy from reactors or repeaters +  if( G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) +    energyOnly = qfalse; +  else if( upgrade == UP_AMMO && +           BG_Weapon( ent->client->ps.stats[ STAT_WEAPON ] )->usesEnergy && +           ( G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) || +             G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) ) ) +    energyOnly = qtrue;    else    { -    //no armoury nearby -    if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) -    { -      trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) ); -      return; -    } +    if( upgrade == UP_AMMO && +        BG_Weapon( ent->client->ps.weapon )->usesEnergy ) +      G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOENERGYAMMOHERE ); +    else +      G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); +    return;    }    if( weapon != WP_NONE ) @@ -3440,59 +2317,52 @@ void Cmd_Buy_f( gentity_t *ent )        return;      } -    // denyweapons -    if( weapon >= WP_PAIN_SAW && weapon <= WP_GRENADE && -        ent->client->pers.denyHumanWeapons & ( 1 << (weapon - WP_BLASTER) ) ) +    // Only humans can buy stuff +    if( BG_Weapon( weapon )->team != TEAM_HUMANS )      { -      trap_SendServerCommand( ent-g_entities, va( "print \"You are denied from buying this weapon\n\"" ) ); +      trap_SendServerCommand( ent-g_entities, "print \"You can't buy alien items\n\"" );        return;      } -    //can afford this? -    if( BG_FindPriceForWeapon( weapon ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] ) +    //are we /allowed/ to buy this? +    if( !BG_Weapon( weapon )->purchasable )      { -      G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); +      trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" );        return;      } -    //have space to carry this? -    if( BG_FindSlotsForWeapon( weapon ) & ent->client->ps.stats[ STAT_SLOTS ] ) +    //are we /allowed/ to buy this? +    if( !BG_WeaponAllowedInStage( weapon, g_humanStage.integer ) || !BG_WeaponIsAllowed( weapon ) )      { -      G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS ); +      trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" );        return;      } -    if( BG_FindTeamForWeapon( weapon ) != WUT_HUMANS ) +    //can afford this? +    if( BG_Weapon( weapon )->price > (short)ent->client->pers.credit )      { -      //shouldn't need a fancy dialog -      trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy alien items\n\"" ) ); +      G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS );        return;      } -    //are we /allowed/ to buy this? -    if( !BG_FindPurchasableForWeapon( weapon ) ) +    //have space to carry this? +    if( BG_Weapon( weapon )->slots & BG_SlotsForInventory( ent->client->ps.stats ) )      { -      trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) ); +      G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS );        return;      } -    //are we /allowed/ to buy this? -    if( !BG_FindStagesForWeapon( weapon, g_humanStage.integer ) || !BG_WeaponIsAllowed( weapon ) ) -    { -      trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) ); +    // In some instances, weapons can't be changed +    if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) )        return; -    } -    //add to inventory -    BG_AddWeaponToInventory( weapon, ent->client->ps.stats ); -    BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips ); +    ent->client->ps.stats[ STAT_WEAPON ] = weapon; +    ent->client->ps.ammo = BG_Weapon( weapon )->maxAmmo; +    ent->client->ps.clips = BG_Weapon( weapon )->maxClips; -    if( BG_FindUsesEnergyForWeapon( weapon ) && +    if( BG_Weapon( weapon )->usesEnergy &&          BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) -      maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); - -    ent->client->ps.ammo = maxAmmo; -    ent->client->ps.clips = maxClips; +      ent->client->ps.ammo *= BATTPACK_MODIFIER;      G_ForceWeaponChange( ent, weapon ); @@ -3500,7 +2370,7 @@ void Cmd_Buy_f( gentity_t *ent )      ent->client->ps.stats[ STAT_MISC ] = 0;      //subtract from funds -    G_AddCreditToClient( ent->client, -(short)BG_FindPriceForWeapon( weapon ), qfalse ); +    G_AddCreditToClient( ent->client, -(short)BG_Weapon( weapon )->price, qfalse );    }    else if( upgrade != UP_NONE )    { @@ -3511,60 +2381,60 @@ void Cmd_Buy_f( gentity_t *ent )        return;      } -    // denyweapons -    if( upgrade == UP_GRENADE && -        ent->client->pers.denyHumanWeapons & ( 1 << (WP_GRENADE - WP_BLASTER) ) ) -    { -      trap_SendServerCommand( ent-g_entities, va( "print \"You are denied from buying this upgrade\n\"" ) ); -      return; -    } -      //can afford this? -    if( BG_FindPriceForUpgrade( upgrade ) > (short)ent->client->ps.persistant[ PERS_CREDIT ] ) +    if( BG_Upgrade( upgrade )->price > (short)ent->client->pers.credit )      {        G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS );        return;      }      //have space to carry this? -    if( BG_FindSlotsForUpgrade( upgrade ) & ent->client->ps.stats[ STAT_SLOTS ] ) +    if( BG_Upgrade( upgrade )->slots & BG_SlotsForInventory( ent->client->ps.stats ) )      {        G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS );        return;      } -    if( BG_FindTeamForUpgrade( upgrade ) != WUT_HUMANS ) +    // Only humans can buy stuff +    if( BG_Upgrade( upgrade )->team != TEAM_HUMANS )      { -      //shouldn't need a fancy dialog -      trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy alien items\n\"" ) ); +      trap_SendServerCommand( ent-g_entities, "print \"You can't buy alien items\n\"" );        return;      }      //are we /allowed/ to buy this? -    if( !BG_FindPurchasableForUpgrade( upgrade ) ) +    if( !BG_Upgrade( upgrade )->purchasable )      { -      trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) ); +      trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" );        return;      }      //are we /allowed/ to buy this? -    if( !BG_FindStagesForUpgrade( upgrade, g_humanStage.integer ) || !BG_UpgradeIsAllowed( upgrade ) ) +    if( !BG_UpgradeAllowedInStage( upgrade, g_humanStage.integer ) || !BG_UpgradeIsAllowed( upgrade ) )      { -      trap_SendServerCommand( ent-g_entities, va( "print \"You can't buy this item\n\"" ) ); -      return; -    } - -    if( upgrade == UP_BATTLESUIT && ent->client->ps.pm_flags & PMF_DUCKED ) -    { -      trap_SendServerCommand( ent-g_entities, -        va( "print \"You can't buy this item while crouching\n\"" ) ); +      trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" );        return;      }      if( upgrade == UP_AMMO ) -      G_GiveClientMaxAmmo( ent, buyingEnergyAmmo ); +      G_GiveClientMaxAmmo( ent, energyOnly );      else      { +      if( upgrade == UP_BATTLESUIT ) +      { +        vec3_t newOrigin; + +        if( !G_RoomForClassChange( ent, PCL_HUMAN_BSUIT, newOrigin ) ) +        { +          G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITON ); +          return; +        } +        VectorCopy( newOrigin, ent->client->ps.origin ); +        ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN_BSUIT; +        ent->client->pers.classSelection = PCL_HUMAN_BSUIT; +        ent->client->ps.eFlags ^= EF_TELEPORT_BIT; +      } +        //add to inventory        BG_AddUpgradeToInventory( upgrade, ent->client->ps.stats );      } @@ -3573,30 +2443,17 @@ void Cmd_Buy_f( gentity_t *ent )        G_GiveClientMaxAmmo( ent, qtrue );      //subtract from funds -    G_AddCreditToClient( ent->client, -(short)BG_FindPriceForUpgrade( upgrade ), qfalse ); +    G_AddCreditToClient( ent->client, -(short)BG_Upgrade( upgrade )->price, qfalse );    }    else    { -    trap_SendServerCommand( ent-g_entities, va( "print \"Unknown item\n\"" ) ); -  } - -  if( trap_Argc( ) >= 2 ) -  { -    trap_Argv( 2, s, sizeof( s ) ); - -    //retrigger the armoury menu -    if( !Q_stricmp( s, "retrigger" ) ) -      ent->client->retriggerArmouryMenu = level.framenum + RAM_FRAMES; -  } -  if( ent->client->pers.paused ) -  { -    trap_SendServerCommand( ent-g_entities, -      "print \"You may not deconstruct while paused\n\"" ); +    G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNITEM );      return;    }    //update ClientInfo    ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); +  ent->client->pers.infoChangeTime = level.time;  } @@ -3609,26 +2466,36 @@ void Cmd_Sell_f( gentity_t *ent )  {    char      s[ MAX_TOKEN_CHARS ];    int       i; -  int       weapon, upgrade; +  weapon_t  weapon; +  upgrade_t upgrade;    trap_Argv( 1, s, sizeof( s ) );    //no armoury nearby    if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) )    { -    trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a powered armoury\n\"" ) ); +    G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE );      return;    } -  weapon = BG_FindWeaponNumForName( s ); -  upgrade = BG_FindUpgradeNumForName( s ); +  if( !Q_stricmpn( s, "weapon", 6 ) ) +    weapon = ent->client->ps.stats[ STAT_WEAPON ]; +  else +    weapon = BG_WeaponByName( s )->number; + +  upgrade = BG_UpgradeByName( s )->number;    if( weapon != WP_NONE )    { +    weapon_t selected = BG_GetPlayerWeapon( &ent->client->ps ); + +    if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) +      return; +      //are we /allowed/ to sell this? -    if( !BG_FindPurchasableForWeapon( weapon ) ) +    if( !BG_Weapon( weapon )->purchasable )      { -      trap_SendServerCommand( ent-g_entities, va( "print \"You can't sell this weapon\n\"" ) ); +      trap_SendServerCommand( ent-g_entities, "print \"You can't sell this weapon\n\"" );        return;      } @@ -3636,67 +2503,59 @@ void Cmd_Sell_f( gentity_t *ent )      if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) )      {        //guard against selling the HBUILD weapons exploit -      if( ( weapon == WP_HBUILD || weapon == WP_HBUILD2 ) && -          ent->client->ps.stats[ STAT_MISC ] > 0 ) +      if( weapon == WP_HBUILD && ent->client->ps.stats[ STAT_MISC ] > 0 )        { -        trap_SendServerCommand( ent-g_entities, va( "print \"Cannot sell until build timer expires\n\"" ) ); +        G_TriggerMenu( ent->client->ps.clientNum, MN_H_ARMOURYBUILDTIMER );          return;        } -      BG_RemoveWeaponFromInventory( weapon, ent->client->ps.stats ); +      ent->client->ps.stats[ STAT_WEAPON ] = WP_NONE; +      // Cancel ghost buildables +      ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;        //add to funds -      G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( weapon ), qfalse ); +      G_AddCreditToClient( ent->client, (short)BG_Weapon( weapon )->price, qfalse );      }      //if we have this weapon selected, force a new selection -    if( weapon == ent->client->ps.weapon ) -      G_ForceWeaponChange( ent, WP_NONE ); +    if( weapon == selected ) +      G_ForceWeaponChange( ent, WP_BLASTER );    }    else if( upgrade != UP_NONE )    {      //are we /allowed/ to sell this? -    if( !BG_FindPurchasableForUpgrade( upgrade ) ) +    if( !BG_Upgrade( upgrade )->purchasable )      { -      trap_SendServerCommand( ent-g_entities, va( "print \"You can't sell this item\n\"" ) ); +      trap_SendServerCommand( ent-g_entities, "print \"You can't sell this item\n\"" );        return;      }      //remove upgrade if carried      if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) )      { +      // shouldn't really need to test for this, but just to be safe +      if( upgrade == UP_BATTLESUIT ) +      { +        vec3_t newOrigin; + +        if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) +        { +          G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); +          return; +        } +        VectorCopy( newOrigin, ent->client->ps.origin ); +        ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; +        ent->client->pers.classSelection = PCL_HUMAN; +        ent->client->ps.eFlags ^= EF_TELEPORT_BIT; +      } + +      //add to inventory        BG_RemoveUpgradeFromInventory( upgrade, ent->client->ps.stats );        if( upgrade == UP_BATTPACK )          G_GiveClientMaxAmmo( ent, qtrue );        //add to funds -      G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( upgrade ), qfalse ); -    } -  } -  else if( !Q_stricmp( s, "weapons" ) ) -  { -    for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) -    { -      //guard against selling the HBUILD weapons exploit -      if( ( i == WP_HBUILD || i == WP_HBUILD2 ) && -          ent->client->ps.stats[ STAT_MISC ] > 0 ) -      { -        trap_SendServerCommand( ent-g_entities, va( "print \"Cannot sell until build timer expires\n\"" ) ); -        continue; -      } - -      if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) && -          BG_FindPurchasableForWeapon( i ) ) -      { -        BG_RemoveWeaponFromInventory( i, ent->client->ps.stats ); - -        //add to funds -        G_AddCreditToClient( ent->client, (short)BG_FindPriceForWeapon( i ), qfalse ); -      } - -      //if we have this weapon selected, force a new selection -      if( i == ent->client->ps.weapon ) -        G_ForceWeaponChange( ent, WP_NONE ); +      G_AddCreditToClient( ent->client, (short)BG_Upgrade( upgrade )->price, qfalse );      }    }    else if( !Q_stricmp( s, "upgrades" ) ) @@ -3705,47 +2564,44 @@ void Cmd_Sell_f( gentity_t *ent )      {        //remove upgrade if carried        if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) && -          BG_FindPurchasableForUpgrade( i ) ) +          BG_Upgrade( i )->purchasable )        { -        BG_RemoveUpgradeFromInventory( i, ent->client->ps.stats ); -        if( i == UP_BATTPACK ) +        // shouldn't really need to test for this, but just to be safe +        if( i == UP_BATTLESUIT )          { -          int j; +          vec3_t newOrigin; -          //remove energy -          for( j = WP_NONE; j < WP_NUM_WEAPONS; j++ ) +          if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) )            { -            if( BG_InventoryContainsWeapon( j, ent->client->ps.stats ) && -                BG_FindUsesEnergyForWeapon( j ) && -                !BG_FindInfinteAmmoForWeapon( j ) ) -            { -              // GH FIXME -              ent->client->ps.ammo = 0; -              ent->client->ps.clips = 0; -            } +            G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); +            continue;            } +          VectorCopy( newOrigin, ent->client->ps.origin ); +          ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; +          ent->client->pers.classSelection = PCL_HUMAN; +          ent->client->ps.eFlags ^= EF_TELEPORT_BIT;          } +        BG_RemoveUpgradeFromInventory( i, ent->client->ps.stats ); + +        if( i == UP_BATTPACK ) +          G_GiveClientMaxAmmo( ent, qtrue ); +          //add to funds -        G_AddCreditToClient( ent->client, (short)BG_FindPriceForUpgrade( i ), qfalse ); +        G_AddCreditToClient( ent->client, (short)BG_Upgrade( i )->price, qfalse );        }      }    }    else -    trap_SendServerCommand( ent-g_entities, va( "print \"Unknown item\n\"" ) ); - -  if( trap_Argc( ) >= 2 )    { -    trap_Argv( 2, s, sizeof( s ) ); - -    //retrigger the armoury menu -    if( !Q_stricmp( s, "retrigger" ) ) -      ent->client->retriggerArmouryMenu = level.framenum + RAM_FRAMES; +    G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNITEM ); +    return;    }    //update ClientInfo    ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); +  ent->client->pers.infoChangeTime = level.time;  } @@ -3759,207 +2615,126 @@ void Cmd_Build_f( gentity_t *ent )    char          s[ MAX_TOKEN_CHARS ];    buildable_t   buildable;    float         dist; -  vec3_t        origin; -  pTeam_t       team; +  vec3_t        origin, normal; +  int           groundEntNum; +  team_t        team; -  if( ent->client->pers.denyBuild ) +  if( ent->client->pers.namelog->denyBuild )    { -    trap_SendServerCommand( ent-g_entities, -      "print \"Your building rights have been revoked\n\"" ); +    G_TriggerMenu( ent->client->ps.clientNum, MN_B_REVOKED );      return;    } -  if( ent->client->pers.paused ) + +  if( ent->client->pers.teamSelection == level.surrenderTeam )    { -    trap_SendServerCommand( ent-g_entities, -      "print \"You may not mark while paused\n\"" ); +    G_TriggerMenu( ent->client->ps.clientNum, MN_B_SURRENDER );      return;    }    trap_Argv( 1, s, sizeof( s ) ); -  buildable = BG_FindBuildNumForName( s ); +  buildable = BG_BuildableByName( s )->number; +  team = ent->client->ps.stats[ STAT_TEAM ]; +  if( buildable == BA_NONE || !BG_BuildableIsAllowed( buildable ) || +      !( ( 1 << ent->client->ps.weapon ) & BG_Buildable( buildable )->buildWeapon ) || +      ( team == TEAM_ALIENS && !BG_BuildableAllowedInStage( buildable, g_alienStage.integer ) ) || +      ( team == TEAM_HUMANS && !BG_BuildableAllowedInStage( buildable, g_humanStage.integer ) ) ) +  { +    G_TriggerMenu( ent->client->ps.clientNum, MN_B_CANNOT ); +    return; +  } -  if( g_suddenDeath.integer) +  if( G_TimeTilSuddenDeath( ) <= 0 )    { -    if( g_suddenDeathMode.integer == SDMODE_SELECTIVE ) -    { -      if( !BG_FindReplaceableTestForBuildable( buildable ) ) -      { -        trap_SendServerCommand( ent-g_entities, -          "print \"This building type cannot be rebuilt during Sudden Death\n\"" ); -        return; -      } -      if( G_BuildingExists( buildable ) ) -      { -        trap_SendServerCommand( ent-g_entities, -          "print \"You can only rebuild one of each type of rebuildable building during Sudden Death.\n\"" ); -        return; -      } -    } -    else if( g_suddenDeathMode.integer == SDMODE_NO_BUILD ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"Building is not allowed during Sudden Death\n\"" ); -      return; -    } +    G_TriggerMenu( ent->client->ps.clientNum, MN_B_SUDDENDEATH ); +    return;    } -  team = ent->client->ps.stats[ STAT_PTEAM ]; +  ent->client->ps.stats[ STAT_BUILDABLE ] = buildable; -  if( buildable != BA_NONE && -      ( ( 1 << ent->client->ps.weapon ) & BG_FindBuildWeaponForBuildable( buildable ) ) && -      !( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) && -      !( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) && -      BG_BuildableIsAllowed( buildable ) && -      ( ( team == PTE_ALIENS && BG_FindStagesForBuildable( buildable, g_alienStage.integer ) ) || -        ( team == PTE_HUMANS && BG_FindStagesForBuildable( buildable, g_humanStage.integer ) ) ) ) +  if( 1 )    { -    dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); +    dynMenu_t err; +    dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist;      //these are the errors displayed when the builder first selects something to use -    switch( G_CanBuild( ent, buildable, dist, origin ) ) +    switch( G_CanBuild( ent, buildable, dist, origin, normal, &groundEntNum ) )      { +      // can place right away, set the blueprint and the valid togglebit        case IBE_NONE:        case IBE_TNODEWARN: -      case IBE_RPTWARN: -      case IBE_RPTWARN2: +      case IBE_RPTNOREAC: +      case IBE_RPTPOWERHERE:        case IBE_SPWNWARN: -      case IBE_NOROOM: +        err = MN_NONE; +        ent->client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT; +        break; + +      // can't place yet but maybe soon: start with valid togglebit off        case IBE_NORMAL: -      case IBE_HOVELEXIT: -        ent->client->ps.stats[ STAT_BUILDABLE ] = ( buildable | SB_VALID_TOGGLEBIT ); +        err = MN_B_NORMAL;          break; -      case IBE_NOASSERT: -        G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT ); +      case IBE_NOCREEP: +        err = MN_A_NOCREEP; +        break; + +      case IBE_NOROOM: +        err = MN_B_NOROOM;          break;        case IBE_NOOVERMIND: -        G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOOVMND ); +        err = MN_A_NOOVMND;          break; -      case IBE_OVERMIND: -        G_TriggerMenu( ent->client->ps.clientNum, MN_A_OVERMIND ); +      case IBE_NOPOWERHERE: +        err = MN_NONE;          break; -      case IBE_REACTOR: -        G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR ); +      // more serious errors just pop a menu +      case IBE_NOALIENBP: +        err = MN_A_NOBP;          break; -      case IBE_REPEATER: -        G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER ); +      case IBE_ONEOVERMIND: +        err = MN_A_ONEOVERMIND;          break; -      case IBE_NOPOWER: -        G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER ); +      case IBE_ONEREACTOR: +        err = MN_H_ONEREACTOR;          break; -      case IBE_NOCREEP: -        G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOCREEP ); +      case IBE_NOHUMANBP: +        err = MN_H_NOBP;          break;        case IBE_NODCC: -        G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC ); +        err = MN_H_NODCC;          break; -      default: +      case IBE_PERMISSION: +        err = MN_B_NORMAL;          break; -    } -  } -  else -    trap_SendServerCommand( ent-g_entities, va( "print \"Cannot build this item\n\"" ) ); -} - - -/* -================= -Cmd_Boost_f -================= -*/ -void Cmd_Boost_f( gentity_t *ent ) -{ -  if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) && -      BG_UpgradeIsActive( UP_JETPACK, ent->client->ps.stats ) ) -    return; - -  if( ent->client->pers.cmd.buttons & BUTTON_WALKING ) -    return; - -  if( ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) && -      ( ent->client->ps.stats[ STAT_STAMINA ] > 0 ) ) -    ent->client->ps.stats[ STAT_STATE ] |= SS_SPEEDBOOST; -} - -/* -================= -Cmd_Protect_f -================= -*/ -void Cmd_Protect_f( gentity_t *ent ) -{ -  vec3_t      forward, end; -  trace_t     tr; -  gentity_t   *traceEnt; -  if( !ent->client->pers.designatedBuilder ) -  { -    trap_SendServerCommand( ent-g_entities, "print \"Only designated" -        " builders can toggle structure protection.\n\"" ); -    return; -  } - -  AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); -  VectorMA( ent->client->ps.origin, 100, forward, end ); - -  trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, -    MASK_PLAYERSOLID ); -  traceEnt = &g_entities[ tr.entityNum ]; +      case IBE_LASTSPAWN: +        err = MN_B_LASTSPAWN; +        break; -  if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) && -      ( traceEnt->biteam == ent->client->pers.teamSelection ) ) -  { -    if( traceEnt->s.eFlags & EF_DBUILDER ) -    { -      trap_SendServerCommand( ent-g_entities, -        "print \"Structure protection removed\n\"" ); -      traceEnt->s.eFlags &= ~EF_DBUILDER; +      default: +        err = -1; // stop uninitialised warning +        break;      } -    else -    { -      trap_SendServerCommand( ent-g_entities,  -        "print \"Structure protection applied\n\"" ); -      traceEnt->s.eFlags |= EF_DBUILDER; -      // adding protection turns off deconstruction mark -      traceEnt->deconstruct = qfalse; -    } +    if( err == MN_NONE || ent->client->pers.disableBlueprintErrors ) +      ent->client->ps.stats[ STAT_BUILDABLE ] |= buildable; +    else +      G_TriggerMenu( ent->client->ps.clientNum, err );    } +  else +    G_TriggerMenu( ent->client->ps.clientNum, MN_B_CANNOT );  } - /* - ================= - Cmd_Resign_f - ================= - */ - void Cmd_Resign_f( gentity_t *ent ) - { -   if( !ent->client->pers.designatedBuilder ) -   { -     trap_SendServerCommand( ent-g_entities, -       "print \"You are not a designated builder\n\"" ); -     return; -   } -  -   ent->client->pers.designatedBuilder = qfalse; -   trap_SendServerCommand( -1, va( -     "print \"%s" S_COLOR_WHITE " has resigned\n\"", -     ent->client->pers.netname ) ); -   G_CheckDBProtection( ); - } -  -  -  /*  =================  Cmd_Reload_f @@ -3967,347 +2742,29 @@ Cmd_Reload_f  */  void Cmd_Reload_f( gentity_t *ent )  { -  if( ( ent->client->ps.weapon >= WP_ABUILD ) && -    ( ent->client->ps.weapon <= WP_HBUILD ) ) -  { -    if( ent->client->pers.designatedBuilder ) -      Cmd_Protect_f( ent ); -    else -      Cmd_Mark_f( ent ); -  } -  else if( ent->client->ps.weaponstate != WEAPON_RELOADING ) -    ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD; -} - - -/* -================= -Cmd_MyStats_f -================= -*/ -void Cmd_MyStats_f( gentity_t *ent ) -{ - -   if(!ent) return; - - -   if( !level.intermissiontime && ent->client->pers.statscounters.timeLastViewed && (level.time - ent->client->pers.statscounters.timeLastViewed) <60000 )  -   {    -     ADMP( "You may only check your stats once per minute and during intermission.\n"); -     return; -   } -    -   if( !g_myStats.integer ) -   { -    ADMP( "myStats has been disabled\n"); -    return; -   } -    -   ADMP( G_statsString( &ent->client->pers.statscounters, &ent->client->pers.teamSelection ) ); -   ent->client->pers.statscounters.timeLastViewed = level.time; -   -  return; -} - -char *G_statsString( statsCounters_t *sc, pTeam_t *pt ) -{ -  char *s; -   -  int percentNearBase=0; -  int percentJetpackWallwalk=0; -  int percentHeadshots=0; -  double avgTimeAlive=0; -  int avgTimeAliveMins = 0; -  int avgTimeAliveSecs = 0; - -  if( sc->timealive ) -   percentNearBase = (int)(100 *  (float) sc->timeinbase / ((float) (sc->timealive ) ) ); - -  if( sc->timealive && sc->deaths ) -  { -    avgTimeAlive = sc->timealive / sc->deaths; -  } - -  avgTimeAliveMins = (int) (avgTimeAlive / 60.0f); -  avgTimeAliveSecs = (int) (avgTimeAlive - (60.0f * avgTimeAliveMins)); -   -  if( *pt == PTE_ALIENS ) -  { -    if( sc->dretchbasytime > 0 ) -     percentJetpackWallwalk = (int)(100 *  (float) sc->jetpackusewallwalkusetime / ((float) ( sc->dretchbasytime) ) ); -     -    if( sc->hitslocational ) -      percentHeadshots = (int)(100 * (float) sc->headshots / ((float) (sc->hitslocational) ) ); -     -    s = va( "^3Kills:^7 %3i ^3StructKills:^7 %3i ^3Assists:^7 %3i^7 ^3Poisons:^7 %3i ^3Headshots:^7 %3i (%3i)\n^3Deaths:^7 %3i ^3Feeds:^7 %3i ^3Suicides:^7 %3i ^3TKs:^7 %3i ^3Avg Lifespan:^7 %4d:%02d\n^3Damage to:^7 ^3Enemies:^7 %5i ^3Structs:^7 %5i ^3Friendlies:^7 %3i \n^3Structs Built:^7 %3i ^3Time Near Base:^7 %3i ^3Time wallwalking:^7 %3i\n", -     sc->kills, -     sc->structskilled, -     sc->assists, -     sc->repairspoisons, -     sc->headshots, -     percentHeadshots, -     sc->deaths, -     sc->feeds, -     sc->suicides, -     sc->teamkills, -     avgTimeAliveMins, -     avgTimeAliveSecs, -     sc->dmgdone, -     sc->structdmgdone, -     sc->ffdmgdone, -     sc->structsbuilt, -     percentNearBase, -     percentJetpackWallwalk -         ); -  } -  else if( *pt == PTE_HUMANS ) -  { -    if( sc->timealive ) -     percentJetpackWallwalk = (int)(100 *  (float) sc->jetpackusewallwalkusetime / ((float) ( sc->timealive ) ) ); -    s = va( "^3Kills:^7 %3i ^3StructKills:^7 %3i ^3Assists:^7 %3i \n^3Deaths:^7 %3i ^3Feeds:^7 %3i ^3Suicides:^7 %3i ^3TKs:^7 %3i ^3Avg Lifespan:^7 %4d:%02d\n^3Damage to:^7 ^3Enemies:^7 %5i ^3Structs:^7 %5i ^3Friendlies:^7 %3i \n^3Structs Built:^7 %3i ^3Repairs:^7 %4i ^3Time Near Base:^7 %3i ^3Time Jetpacking:^7 %3i\n", -     sc->kills, -     sc->structskilled, -     sc->assists, -     sc->deaths, -     sc->feeds, -     sc->suicides, -     sc->teamkills, -     avgTimeAliveMins, -     avgTimeAliveSecs, -     sc->dmgdone, -     sc->structdmgdone, -     sc->ffdmgdone, -     sc->structsbuilt, -     sc->repairspoisons, -     percentNearBase, -     percentJetpackWallwalk -         ); -  } -  else s="No stats available\n"; - -  return s; -} - - /* - ================= - Cmd_AllStats_f - ================= - */ - void Cmd_AllStats_f( gentity_t *ent ) - { -    int i; -    int NextViewTime; -    int NumResults = 0; -    int Teamcolor = 3; -    gentity_t *tmpent; -  -    //check if ent exists -    if(!ent) return; -  -    NextViewTime = ent->client->pers.statscounters.AllstatstimeLastViewed + g_AllStatsTime.integer * 1000; -    //check if you can use the cmd at this time -    if( !level.intermissiontime && level.time < NextViewTime) -    { -      ADMP( va("You may only check your stats every %i Seconds and during intermission. Next valid time is %d:%02d\n",( g_AllStatsTime.integer ) ? ( g_AllStatsTime.integer ) : 60, ( NextViewTime / 60000 ), ( NextViewTime / 1000 ) % 60 ) ); -      return; -    } -    //see if allstats is enabled -    if( !g_AllStats.integer ) -    { -     ADMP( "AllStats has been disabled\n"); -     return; -    } -    ADMP("^3K^2=^7Kills ^3A^2=^7Assists ^3SK^2=^7StructKills\n^3D^2=^7Deaths ^3F^2=^7Feeds ^3S^2=^7Suicides ^3TK^2=^7Teamkills\n^3DD^2=^7Damage done ^3TDD^2=^7Team Damage done\n^3SB^2=^7Structs Built\n\n" ); -    //display a header describing the data -    ADMP( "^3 #|  K   A  SK|  D   F   S  TK|   DD   TDD| SB| Name\n" ); +  playerState_t *ps = &ent->client->ps; +  int ammo; -    //loop through the clients that are connected -    for( i = 0; i < level.numConnectedClients; i++ )  -    { -       //assign a tempent 4 the hell of it -       tmpent = &g_entities[ level.sortedClients[ i ] ]; -  -       //check for what mode we are working in and display relevent data -       if( g_AllStats.integer == 1 ) -       { -            //check if client is connected and on same team -            if( tmpent->client && tmpent->client->pers.connected == CON_CONNECTED && tmpent->client->pers.teamSelection == ent->client->pers.teamSelection && tmpent->client->pers.teamSelection != PTE_NONE ) -            { -               NumResults++; -               if( tmpent->client->pers.teamSelection == PTE_ALIENS ) Teamcolor = 1; -               if( tmpent->client->pers.teamSelection == PTE_HUMANS ) Teamcolor = 5; -               ADMP( va( "^%i%2i^3|^%i%3i %3i %3i^3|^%i%3i %3i %3i %3i^3|^%i%5i %5i^3|^%i%3i^3|^7 %s\n", -               Teamcolor, -               NumResults, -               Teamcolor, -               ( tmpent->client->pers.statscounters.kills ) ? tmpent->client->pers.statscounters.kills : 0, -               ( tmpent->client->pers.statscounters.assists ) ? tmpent->client->pers.statscounters.assists : 0, -               ( tmpent->client->pers.statscounters.structskilled ) ? tmpent->client->pers.statscounters.structskilled : 0, -               Teamcolor, -               ( tmpent->client->pers.statscounters.deaths ) ? tmpent->client->pers.statscounters.deaths : 0, -               ( tmpent->client->pers.statscounters.feeds ) ? tmpent->client->pers.statscounters.feeds : 0, -               ( tmpent->client->pers.statscounters.suicides ) ? tmpent->client->pers.statscounters.suicides : 0, -               ( tmpent->client->pers.statscounters.teamkills ) ? tmpent->client->pers.statscounters.teamkills : 0, -               Teamcolor, -               ( tmpent->client->pers.statscounters.dmgdone ) ? tmpent->client->pers.statscounters.dmgdone : 0, -               ( tmpent->client->pers.statscounters.ffdmgdone ) ? tmpent->client->pers.statscounters.ffdmgdone : 0, -               Teamcolor, -               ( tmpent->client->pers.statscounters.structsbuilt ) ? tmpent->client->pers.statscounters.structsbuilt : 0, -               ( tmpent->client->pers.netname ) ? tmpent->client->pers.netname : "Unknown" ) ); -            } -       } -       else if( g_AllStats.integer == 2 ) -       { -            //check if client is connected and has some stats or atleast is on a team -            if( tmpent->client && tmpent->client->pers.connected == CON_CONNECTED && ( tmpent->client->pers.teamSelection != PTE_NONE ) ) -            { -               NumResults++; -               if( tmpent->client->pers.teamSelection == PTE_ALIENS ) Teamcolor = 1; -               if( tmpent->client->pers.teamSelection == PTE_HUMANS ) Teamcolor = 5; -               ADMP( va( "^%i%2i^3|^%i%3i %3i %3i^3|^%i%3i %3i %3i %3i^3|^%i%5i %5i^3|^%i%3i^3|^7 %s\n", -               Teamcolor, -               NumResults, -               Teamcolor, -               ( tmpent->client->pers.statscounters.kills ) ? tmpent->client->pers.statscounters.kills : 0, -               ( tmpent->client->pers.statscounters.assists ) ? tmpent->client->pers.statscounters.assists : 0, -               ( tmpent->client->pers.statscounters.structskilled ) ? tmpent->client->pers.statscounters.structskilled : 0, -               Teamcolor, -               ( tmpent->client->pers.statscounters.deaths ) ? tmpent->client->pers.statscounters.deaths : 0, -               ( tmpent->client->pers.statscounters.feeds ) ? tmpent->client->pers.statscounters.feeds : 0, -               ( tmpent->client->pers.statscounters.suicides ) ? tmpent->client->pers.statscounters.suicides : 0, -               ( tmpent->client->pers.statscounters.teamkills ) ? tmpent->client->pers.statscounters.teamkills : 0, -               Teamcolor, -               ( tmpent->client->pers.statscounters.dmgdone ) ? tmpent->client->pers.statscounters.dmgdone : 0, -               ( tmpent->client->pers.statscounters.ffdmgdone ) ? tmpent->client->pers.statscounters.ffdmgdone : 0, -               Teamcolor, -               ( tmpent->client->pers.statscounters.structsbuilt ) ? tmpent->client->pers.statscounters.structsbuilt : 0, -               ( tmpent->client->pers.netname ) ? tmpent->client->pers.netname : "Unknown" ) ); -            } -       } -    } -    if( NumResults == 0 ) { -       ADMP( "   ^3EMPTY!\n" ); -    } else { -       ADMP( va( "^7 %i Players found!\n", NumResults ) ); -    } -    //update time last viewed - -    ent->client->pers.statscounters.AllstatstimeLastViewed = level.time; +  // weapon doesn't ever need reloading +  if( BG_Weapon( ps->weapon )->infiniteAmmo )      return; -} -/* -================= -Cmd_TeamStatus_f -================= -*/ -void Cmd_TeamStatus_f( gentity_t *ent ) -{ -  char multiple[ 12 ]; -  int builders = 0; -  int arm = 0, mediboost = 0; -  int omrccount = 0, omrchealth = 0; -  qboolean omrcbuild = qfalse; -  gentity_t *tmp; -  int i; - -  if( !g_teamStatus.integer ) -  { -    trap_SendServerCommand( ent - g_entities, -      "print \"teamstatus is disabled.\n\"" ); +  if( ps->clips <= 0 )      return; -  } -  if( G_IsMuted( ent->client ) ) -  { -    trap_SendServerCommand( ent - g_entities, -      "print \"You are muted and cannot use message commands.\n\"" ); -    return; -  } +  if( BG_Weapon( ps->weapon )->usesEnergy && +      BG_InventoryContainsUpgrade( UP_BATTPACK, ps->stats ) ) +    ammo = BG_Weapon( ps->weapon )->maxAmmo * BATTPACK_MODIFIER; +  else +    ammo = BG_Weapon( ps->weapon )->maxAmmo; -  if( ent->client->pers.lastTeamStatus &&  -      ( level.time - ent->client->pers.lastTeamStatus) < g_teamStatus.integer * 1000 ) -  { -    ADMP( va("You may only check your team's status once every %i seconds.\n", -          g_teamStatus.integer  )); +  // don't reload when full +  if( ps->ammo >= ammo )      return; -  } -  ent->client->pers.lastTeamStatus = level.time; - -  tmp = &g_entities[ 0 ]; -  for ( i = 0; i < level.num_entities; i++, tmp++ ) -  { -    if( i < MAX_CLIENTS ) -    { -      if( tmp->client && -          tmp->client->pers.connected == CON_CONNECTED && -          tmp->client->pers.teamSelection == ent->client->pers.teamSelection && -          tmp->health > 0 && -          ( tmp->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 || -            tmp->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG || -            BG_InventoryContainsWeapon( WP_HBUILD, tmp->client->ps.stats ) || -            BG_InventoryContainsWeapon( WP_HBUILD2, tmp->client->ps.stats ) ) ) -        builders++; -      continue; -    } - -    if( tmp->s.eType == ET_BUILDABLE ) -    { -      if( tmp->biteam != ent->client->pers.teamSelection || -          tmp->health <= 0 ) -        continue; - -      switch( tmp->s.modelindex ) -      { -        case BA_H_REACTOR: -        case BA_A_OVERMIND: -          omrccount++; -          if( tmp->health > omrchealth ) -            omrchealth = tmp->health; -          if( !omrcbuild ) -            omrcbuild = tmp->spawned; -          break; -        case BA_H_ARMOURY: -          arm++; -          break; -        case BA_H_MEDISTAT: -        case BA_A_BOOSTER: -          mediboost++; -          break; -        default: -          break; -      } -    } -  } - -  if( omrccount > 1 ) -    Com_sprintf( multiple, sizeof( multiple ), "^7[x%d]", omrccount ); -  else -    multiple[ 0 ] = '\0'; - -  if( ent->client->pers.teamSelection == PTE_ALIENS ) -  { -    G_Say( ent, NULL, SAY_TEAM, -      va( "^3OM: %s(%d)%s ^3Spawns: ^5%d ^3Builders: ^5%d ^3Boosters: ^5%d^7" , -      ( !omrccount ) ? "^1Down" : ( omrcbuild ) ? "^2Up" : "^5Building", -      omrchealth * 100 / OVERMIND_HEALTH, -      multiple, -      level.numAlienSpawns, -      builders, -      mediboost ) ); -  } -  else -  { -    G_Say( ent, NULL, SAY_TEAM, -      va( "^3RC: %s(%d)%s ^3Spawns: ^5%d ^3Builders: ^5%d ^3Armouries: ^5%d ^3Medistations: ^5%d^7" , -      ( !omrccount ) ? "^1Down" : ( omrcbuild ) ? "^2Up" : "^5Building", -      omrchealth * 100 / REACTOR_HEALTH, -      multiple, -      level.numHumanSpawns, -      builders, -      arm, mediboost ) ); -  } +  // the animation, ammo refilling etc. is handled by PM_Weapon +  if( ent->client->ps.weaponstate != WEAPON_RELOADING ) +    ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD;  }  /* @@ -4325,7 +2782,7 @@ void G_StopFromFollowing( gentity_t *ent )    for( i = 0; i < level.maxclients; i++ )    {      if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && -        level.clients[ i ].sess.spectatorClient == ent-g_entities ) +        level.clients[ i ].sess.spectatorClient == ent->client->ps.clientNum )      {        if( !G_FollowNewClient( &g_entities[ i ], 1 ) )          G_StopFollowing( &g_entities[ i ] ); @@ -4343,44 +2800,86 @@ to free floating spectator mode  */  void G_StopFollowing( gentity_t *ent )  { -  ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; -  ent->client->sess.sessionTeam = TEAM_SPECTATOR; -  ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection; +  ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; -  if( ent->client->pers.teamSelection == PTE_NONE ) +  if( ent->client->pers.teamSelection == TEAM_NONE )    { -    ent->client->sess.spectatorState = SPECTATOR_FREE; -    ent->client->ps.pm_type = PM_SPECTATOR; -    ent->client->ps.stats[ STAT_HEALTH ] = 100; // hacky server-side fix to prevent cgame from viewlocking a freespec +    ent->client->sess.spectatorState = +      ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_FREE;    }    else    { -    vec3_t   spawn_origin, spawn_angles; +    vec3_t spawn_origin, spawn_angles; -    ent->client->sess.spectatorState = SPECTATOR_LOCKED; -    if( ent->client->pers.teamSelection == PTE_ALIENS ) +    ent->client->sess.spectatorState = +      ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_LOCKED; + +    if( ent->client->pers.teamSelection == TEAM_ALIENS )        G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); -    else if( ent->client->pers.teamSelection == PTE_HUMANS ) +    else if( ent->client->pers.teamSelection == TEAM_HUMANS )        G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); +      G_SetOrigin( ent, spawn_origin );      VectorCopy( spawn_origin, ent->client->ps.origin );      G_SetClientViewAngle( ent, spawn_angles );    }    ent->client->sess.spectatorClient = -1;    ent->client->ps.pm_flags &= ~PMF_FOLLOW; +  ent->client->ps.groundEntityNum = ENTITYNUM_NONE; +  ent->client->ps.stats[ STAT_STATE ] = 0; +  ent->client->ps.stats[ STAT_VIEWLOCK ] = 0; +  ent->client->ps.eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); +  ent->client->ps.viewangles[ PITCH ] = 0.0f; +  ent->client->ps.clientNum = ent - g_entities; +  ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.credit; + +  if( ent->client->pers.teamSelection == TEAM_NONE ) +  { +    vec3_t viewOrigin, angles; + +    BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); +    VectorCopy( ent->client->ps.viewangles, angles ); +    angles[ ROLL ] = 0; +    TeleportPlayer( ent, viewOrigin, angles, qfalse ); +  } + +  CalculateRanks( ); +} + +/* +================= +G_FollowLockView -  // Prevent spawning with bsuit in rare case -  if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) ) -    BG_RemoveUpgradeFromInventory( UP_BATTLESUIT, ent->client->ps.stats ); +Client is still following a player, but that player has gone to spectator +mode and cannot be followed for the moment +================= +*/ +void G_FollowLockView( gentity_t *ent ) +{ +  vec3_t spawn_origin, spawn_angles; +  int clientNum; +  clientNum = ent->client->sess.spectatorClient; +  ent->client->sess.spectatorState = +    ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_FOLLOW; +  ent->client->ps.clientNum = clientNum; +  ent->client->ps.pm_flags &= ~PMF_FOLLOW; +  ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection;    ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; -  ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; -  ent->client->ps.eFlags &= ~EF_WALLCLIMB; +  ent->client->ps.stats[ STAT_VIEWLOCK ] = 0; +  ent->client->ps.eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); +  ent->client->ps.eFlags ^= EF_TELEPORT_BIT;    ent->client->ps.viewangles[ PITCH ] = 0.0f; -  ent->client->ps.clientNum = ent - g_entities; +  // Put the view at the team spectator lock position +  if( level.clients[ clientNum ].pers.teamSelection == TEAM_ALIENS ) +    G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); +  else if( level.clients[ clientNum ].pers.teamSelection == TEAM_HUMANS ) +    G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); -  CalculateRanks( ); +  G_SetOrigin( ent, spawn_origin ); +  VectorCopy( spawn_origin, ent->client->ps.origin ); +  G_SetClientViewAngle( ent, spawn_angles );  }  /* @@ -4403,7 +2902,7 @@ qboolean G_FollowNewClient( gentity_t *ent, int dir )    else if( dir == 0 )      return qtrue; -  if( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) +  if( ent->client->sess.spectatorState == SPECTATOR_NOT )      return qfalse;    // select any if no target exists @@ -4423,36 +2922,41 @@ qboolean G_FollowNewClient( gentity_t *ent, int dir )      if( clientnum < 0 )        clientnum = level.maxclients - 1; +    // can't follow self +    if( &g_entities[ clientnum ] == ent ) +      continue; +      // avoid selecting existing follow target      if( clientnum == original && !selectAny )        continue; //effectively break; -    // can't follow self -    if( &level.clients[ clientnum ] == ent->client ) -      continue; -      // can only follow connected clients      if( level.clients[ clientnum ].pers.connected != CON_CONNECTED )        continue; -    // can't follow another spectator -     if( level.clients[ clientnum ].pers.teamSelection == PTE_NONE ) -       continue; -      -      // can only follow teammates when dead and on a team -     if( ent->client->pers.teamSelection != PTE_NONE &&  -         ( level.clients[ clientnum ].pers.teamSelection !=  -           ent->client->pers.teamSelection ) ) -       continue; -      -     // cannot follow a teammate who is following you -     if( level.clients[ clientnum ].sess.spectatorState == SPECTATOR_FOLLOW &&  -         ( level.clients[ clientnum ].sess.spectatorClient == ent->s.number ) ) -       continue; +    // can't follow a spectator +    if( level.clients[ clientnum ].pers.teamSelection == TEAM_NONE ) +      continue; + +    // if stickyspec is disabled, can't follow someone in queue either +    if( !ent->client->pers.stickySpec && +        level.clients[ clientnum ].sess.spectatorState != SPECTATOR_NOT ) +      continue; + +    // can only follow teammates when dead and on a team +    if( ent->client->pers.teamSelection != TEAM_NONE && +        ( level.clients[ clientnum ].pers.teamSelection != +          ent->client->pers.teamSelection ) ) +      continue;      // this is good, we can use it      ent->client->sess.spectatorClient = clientnum;      ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + +    // if this client is in the spawn queue, we need to do something special +    if( level.clients[ clientnum ].sess.spectatorState != SPECTATOR_NOT ) +      G_FollowLockView( ent ); +      return qtrue;    } while( clientnum != original ); @@ -4467,12 +2971,6 @@ G_ToggleFollow  */  void G_ToggleFollow( gentity_t *ent )  { -  if( level.mapRotationVoteTime ) -  { -    G_IntermissionMapVoteCommand( ent, qfalse, qtrue ); -    return; -  } -    if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )      G_StopFollowing( ent );    else @@ -4487,20 +2985,11 @@ Cmd_Follow_f  void Cmd_Follow_f( gentity_t *ent )  {    int   i; -  int   pids[ MAX_CLIENTS ]; -  char  arg[ MAX_TOKEN_CHARS ]; +  char  arg[ MAX_NAME_LENGTH ]; -  if( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) -  { -    trap_SendServerCommand( ent - g_entities, "print \"follow: You cannot follow unless you are dead or on the spectators.\n\"" ); -    return; -  } -  if( ent->client->pers.paused ) -  { -    trap_SendServerCommand( ent-g_entities, -      "print \"You may not build while paused\n\"" ); +  // won't work unless spectating +  if( ent->client->sess.spectatorState == SPECTATOR_NOT )      return; -  }    if( trap_Argc( ) != 2 )    { @@ -4508,45 +2997,32 @@ void Cmd_Follow_f( gentity_t *ent )    }    else    { +    char err[ MAX_STRING_CHARS ];      trap_Argv( 1, arg, sizeof( arg ) ); -    if( G_ClientNumbersFromString( arg, pids ) == 1 ) -    { -      i = pids[ 0 ]; -    } -    else -    { -      i = G_ClientNumberFromString( ent, arg ); -      if( i == -1 ) -      { -        trap_SendServerCommand( ent - g_entities, -          "print \"follow: invalid player\n\"" ); -        return; -      } +    i = G_ClientNumberFromString( arg, err, sizeof( err ) ); + +    if( i == -1 ) +    { +      trap_SendServerCommand( ent - g_entities, +        va( "print \"follow: %s\"", err ) ); +      return;      }      // can't follow self      if( &level.clients[ i ] == ent->client ) -    { -      trap_SendServerCommand( ent - g_entities, "print \"follow: You cannot follow yourself.\n\"" );        return; -    } -    // can't follow another spectator -    if( level.clients[ i ].pers.teamSelection == PTE_NONE) -    { -      trap_SendServerCommand( ent - g_entities, "print \"follow: You cannot follow another spectator.\n\"" ); +    // can't follow another spectator if sticky spec is off +    if( !ent->client->pers.stickySpec && +        level.clients[ i ].sess.spectatorState != SPECTATOR_NOT )        return; -    } -    // can only follow teammates when dead and on a team -    if( ent->client->pers.teamSelection != PTE_NONE &&  -        ( level.clients[ i ].pers.teamSelection !=  +    // if not on team spectator, you can only follow teammates +    if( ent->client->pers.teamSelection != TEAM_NONE && +        ( level.clients[ i ].pers.teamSelection !=            ent->client->pers.teamSelection ) ) -    { -      trap_SendServerCommand( ent - g_entities, "print \"follow: You can only follow teammates, and only when you are dead.\n\"" );        return; -    }      ent->client->sess.spectatorState = SPECTATOR_FOLLOW;      ent->client->sess.spectatorClient = i; @@ -4568,126 +3044,10 @@ void Cmd_FollowCycle_f( gentity_t *ent )      dir = -1;    // won't work unless spectating -   if( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) -     return; -   if( ent->client->sess.spectatorState == SPECTATOR_NOT ) -     return; -  G_FollowNewClient( ent, dir ); -} - -/* -================= -Cmd_PTRCVerify_f - -Check a PTR code is valid -================= -*/ -void Cmd_PTRCVerify_f( gentity_t *ent ) -{ -  connectionRecord_t  *connection; -  char                s[ MAX_TOKEN_CHARS ] = { 0 }; -  int                 code; - -  if( ent->client->pers.connection ) -    return; - -  trap_Argv( 1, s, sizeof( s ) ); - -  if( !strlen( s ) ) +  if( ent->client->sess.spectatorState == SPECTATOR_NOT )      return; -  code = atoi( s ); - -  connection = G_FindConnectionForCode( code ); -  if( connection && connection->clientNum == -1 ) -  { -    // valid code -    if( connection->clientTeam != PTE_NONE ) -      trap_SendServerCommand( ent->client->ps.clientNum, "ptrcconfirm" ); - -    // restore mapping -    ent->client->pers.connection = connection; -    connection->clientNum = ent->client->ps.clientNum; -  } -  else -  { -    // invalid code -- generate a new one -    connection = G_GenerateNewConnection( ent->client ); - -    if( connection ) -    { -      trap_SendServerCommand( ent->client->ps.clientNum, -        va( "ptrcissue %d", connection->ptrCode ) ); -    } -  } -} - -/* -================= -Cmd_PTRCRestore_f - -Restore against a PTR code -================= -*/ -void Cmd_PTRCRestore_f( gentity_t *ent ) -{ -  char                s[ MAX_TOKEN_CHARS ] = { 0 }; -  int                 code; -  connectionRecord_t  *connection; - -  if( ent->client->pers.joinedATeam ) -  { -    trap_SendServerCommand( ent - g_entities, -      "print \"You cannot use a PTR code after joining a team\n\"" ); -    return; -  } - -  trap_Argv( 1, s, sizeof( s ) ); - -  if( !strlen( s ) ) -    return; - -  code = atoi( s ); - -  connection = ent->client->pers.connection; -  if( connection && connection->ptrCode == code ) -  { -    // Set the correct team -    if( !( ent->client->pers.specExpires > level.time ) ) -    { -        // Check if the alien team is full -        if( connection->clientTeam == PTE_ALIENS && -            !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) && -            g_teamForceBalance.integer && -            level.numAlienClients > level.numHumanClients ) -        { -            G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); -        } -        // Check if the human team is full -        else if( connection->clientTeam == PTE_HUMANS && -                 !G_admin_permission(ent, ADMF_FORCETEAMCHANGE) && -                 g_teamForceBalance.integer && -                 level.numHumanClients > level.numAlienClients ) -        { -            G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); -        } -        else -        { -            G_ChangeTeam( ent, connection->clientTeam ); -        } -    } - -    // set the correct credit etc. -    ent->client->ps.persistant[ PERS_CREDIT ] = 0; -    G_AddCreditToClient( ent->client, connection->clientCredit, qtrue ); -    ent->client->pers.score = connection->clientScore; -    ent->client->pers.enterTime = connection->clientEnterTime; -  } -  else -  { -    trap_SendServerCommand( ent - g_entities, -      va( "print \"\"%d\" is not a valid PTR code\n\"", code ) ); -  } +  G_FollowNewClient( ent, dir );  }  static void Cmd_Ignore_f( gentity_t *ent ) @@ -4706,12 +3066,12 @@ static void Cmd_Ignore_f( gentity_t *ent )    if( trap_Argc() < 2 )    {      trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" -      "%s: usage \\%s [clientNum | partial name match]\n\"", cmd, cmd ) ); +      "usage: %s [clientNum | partial name match]\n\"", cmd ) );      return;    }    Q_strncpyz( name, ConcatArgs( 1 ), sizeof( name ) ); -  matches = G_ClientNumbersFromString( name, pids );  +  matches = G_ClientNumbersFromString( name, pids, MAX_CLIENTS );    if( matches < 1 )    {      trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" @@ -4723,9 +3083,9 @@ static void Cmd_Ignore_f( gentity_t *ent )    {      if( ignore )      { -      if( !BG_ClientListTest( &ent->client->sess.ignoreList, pids[ i ] ) ) +      if( !Com_ClientListContains( &ent->client->sess.ignoreList, pids[ i ] ) )        { -        BG_ClientListAdd( &ent->client->sess.ignoreList, pids[ i ] ); +        Com_ClientListAdd( &ent->client->sess.ignoreList, pids[ i ] );          ClientUserinfoChanged( ent->client->ps.clientNum, qfalse );          trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]"            "ignore: added %s^7 to your ignore list\n\"", @@ -4740,9 +3100,9 @@ static void Cmd_Ignore_f( gentity_t *ent )      }      else      { -      if( BG_ClientListTest( &ent->client->sess.ignoreList, pids[ i ] ) ) +      if( Com_ClientListContains( &ent->client->sess.ignoreList, pids[ i ] ) )        { -        BG_ClientListRemove( &ent->client->sess.ignoreList, pids[ i ] ); +        Com_ClientListRemove( &ent->client->sess.ignoreList, pids[ i ] );          ClientUserinfoChanged( ent->client->ps.clientNum, qfalse );          trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]"            "unignore: removed %s^7 from your ignore list\n\"", @@ -4758,396 +3118,356 @@ static void Cmd_Ignore_f( gentity_t *ent )    }  } - /* - ================= - Cmd_Share_f - ================= - */ - void Cmd_Share_f( gentity_t *ent ) - { -   int   i, clientNum = 0, creds = 0, skipargs = 0; -   int   clientNums[ MAX_CLIENTS ] = { -1 }; -   char  cmd[ 12 ]; -   char  arg1[ MAX_STRING_TOKENS ]; -   char  arg2[ MAX_STRING_TOKENS ]; -   pTeam_t team; - -   if( !ent || !ent->client || ( ent->client->pers.teamSelection == PTE_NONE ) ) -   { -     return; -   } - -   if( !g_allowShare.integer ) -   { -     trap_SendServerCommand( ent-g_entities, "print \"Share has been disabled.\n\"" ); -     return; -   } - -   if( g_floodMinTime.integer ) -   if ( G_Flood_Limited( ent ) ) -   { -    trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); +/* +================= +Cmd_ListMaps_f + +List all maps on the server +================= +*/ + +static int SortMaps( const void *a, const void *b ) +{ +  return strcmp( *(char **)a, *(char **)b ); +} + +#define MAX_MAPLIST_MAPS 256 +#define MAX_MAPLIST_ROWS 9 +void Cmd_ListMaps_f( gentity_t *ent ) +{ +  char search[ 16 ] = {""}; +  char fileList[ 4096 ] = {""}; +  char *fileSort[ MAX_MAPLIST_MAPS ]; +  char *filePtr, *p; +  int  numFiles; +  int  fileLen = 0; +  int  shown = 0; +  int  count = 0; +  int  page = 0; +  int  pages; +  int  row, rows; +  int  start, i, j; + +  if( trap_Argc( ) > 1 ) +  { +    trap_Argv( 1, search, sizeof( search ) ); +    for( p = search; ( *p ) && isdigit( *p ); p++ ); +    if( !( *p ) ) +    { +      page = atoi( search ); +      search[ 0 ] = '\0'; +    } +    else if( trap_Argc( ) > 2 ) +    { +      char lp[ 8 ]; +      trap_Argv( 2, lp, sizeof( lp ) ); +      page = atoi( lp ); +    } + +    if( page > 0 ) +      page--; +    else if( page < 0 ) +      page = 0; +  } + +  numFiles = trap_FS_GetFileList( "maps/", ".bsp", +                                  fileList, sizeof( fileList ) ); +  filePtr = fileList; +  for( i = 0; i < numFiles && count < MAX_MAPLIST_MAPS; i++, filePtr += fileLen + 1 ) +  { +    fileLen = strlen( filePtr ); +    if ( fileLen < 5 ) +      continue; + +    filePtr[ fileLen - 4 ] = '\0'; + +    if( search[ 0 ] && !strstr( filePtr, search ) ) +      continue; + +    fileSort[ count ] = filePtr; +    count++; +  } +  qsort( fileSort, count, sizeof( fileSort[ 0 ] ), SortMaps ); + +  rows = ( count + 2 ) / 3; +  pages = MAX( 1, ( rows + MAX_MAPLIST_ROWS - 1 ) / MAX_MAPLIST_ROWS ); +  if( page >= pages ) +    page = pages - 1; + +  start = page * MAX_MAPLIST_ROWS * 3; +  if( count < start + ( 3 * MAX_MAPLIST_ROWS ) ) +    rows = ( count - start + 2 ) / 3; +  else +    rows = MAX_MAPLIST_ROWS; + +  ADMBP_begin( ); +  for( row = 0; row < rows; row++ ) +  { +    for( i = start + row, j = 0; i < count && j < 3; i += rows, j++ ) +    { +      ADMBP( va( "^7 %-20s", fileSort[ i ] ) ); +      shown++; +    } +    ADMBP( "\n" ); +  } +  if ( search[ 0 ] ) +    ADMBP( va( "^3listmaps: ^7found %d maps matching '%s^7'", count, search ) ); +  else +    ADMBP( va( "^3listmaps: ^7listing %d of %d maps", shown, count ) ); +  if( pages > 1 ) +    ADMBP( va( ", page %d of %d", page + 1, pages ) ); +  if( page + 1 < pages ) +    ADMBP( va( ", use 'listmaps %s%s%d' to see more", +               search, ( search[ 0 ] ) ? " ": "", page + 2 ) ); +  ADMBP( ".\n" ); +  ADMBP_end( ); +} + +/* +================= +Cmd_ListVoices_f +================= +*/ +void Cmd_ListVoices_f( gentity_t *ent ) +{ +  if ( !level.voices ) { +    ADMP( "^3listvoices: ^7voice system is not installed on this server\n" );      return; -   } - -   team = ent->client->pers.teamSelection; - -   G_SayArgv( 0, cmd, sizeof( cmd ) ); -   if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) ) -   { -     skipargs = 1; -     G_SayArgv( 1, cmd, sizeof( cmd ) ); -   } - -   // target player name is in arg1 -   G_SayArgv( 1+skipargs, arg1, sizeof( arg1 ) ); -   // amount to be shared is in arg2 -   G_SayArgv( 2+skipargs, arg2, sizeof( arg2 ) ); - -   if( arg1[0] && !strchr( arg1, ';' ) && Q_stricmp( arg1, "target_in_aim" ) ) -   { -     //check arg1 is a number -     for( i = 0; arg1[ i ]; i++ ) -     { -       if( arg1[ i ] < '0' || arg1[ i ] > '9' ) -       { -         clientNum = -1; -         break; -       } -     } - -     if( clientNum >= 0 ) -     { -       clientNum = atoi( arg1 ); -     } -     else if( G_ClientNumbersFromString( arg1, clientNums ) == 1 ) -     { -       // there was one partial name match -       clientNum = clientNums[ 0 ];  -     } -     else -     { -       // look for an exact name match before bailing out -       clientNum = G_ClientNumberFromString( ent, arg1 ); -       if( clientNum == -1 ) -       { -         trap_SendServerCommand( ent-g_entities, -           "print \"share: invalid player name specified.\n\"" ); -         return; -       } -     } -   } -   else // arg1 not set -   { -     vec3_t      forward, end; -     trace_t     tr; -     gentity_t   *traceEnt; - -     // trace a teammate -     AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); -     VectorMA( ent->client->ps.origin, 8192 * 16, forward, end ); - -     trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); -     traceEnt = &g_entities[ tr.entityNum ]; - -     if( tr.fraction < 1.0f && traceEnt->client && -       ( traceEnt->client->pers.teamSelection == team ) ) -     { -       clientNum = traceEnt - g_entities; -     } -     else -     { -       trap_SendServerCommand( ent-g_entities, -         va( "print \"share: aim at a teammate to share %s.\n\"", -         ( team == PTE_HUMANS ) ? "credits" : "evolvepoints" ) ); -       return; -     } -   } - -   // verify target player team -   if( ( clientNum < 0 ) || ( clientNum >= level.maxclients ) || -       ( level.clients[ clientNum ].pers.teamSelection != team ) ) -   { -     trap_SendServerCommand( ent-g_entities, -       "print \"share: not a valid player of your team.\n\"" ); -     return; -   } - -   if( !arg2[0] || strchr( arg2, ';' ) ) -   { -     // default credit count -     if( team == PTE_HUMANS ) -     { -       creds = FREEKILL_HUMAN; -     } -     else if( team == PTE_ALIENS ) -     { -       creds = FREEKILL_ALIEN; -     } -   } -   else -   { -     //check arg2 is a number -     for( i = 0; arg2[ i ]; i++ ) -     { -       if( arg2[ i ] < '0' || arg2[ i ] > '9' ) -       { -         trap_SendServerCommand( ent-g_entities, -           "print \"usage: share [name|slot#] [amount]\n\"" ); -         return; -       } -     } - -     // credit count from parameter -     creds = atoi( arg2 ); -   } - -   // player specified "0" to transfer -   if( creds <= 0 ) -   { -     trap_SendServerCommand( ent-g_entities, -       "print \"Ooh, you are a generous one, indeed!\n\"" ); -     return; -   } - -   // transfer only credits the player really has -   if( creds > ent->client->pers.credit ) -   { -     creds = ent->client->pers.credit; -   } - -   // player has no credits -   if( creds <= 0 ) -   { -     trap_SendServerCommand( ent-g_entities, -       "print \"Earn some first, lazy gal!\n\"" ); -     return; -   } - -   // allow transfers only up to the credit/evo limit -   if( ( team == PTE_HUMANS ) &&  -       ( creds > HUMAN_MAX_CREDITS - level.clients[ clientNum ].pers.credit ) ) -   { -     creds = HUMAN_MAX_CREDITS - level.clients[ clientNum ].pers.credit; -   } -   else if( ( team == PTE_ALIENS ) &&  -       ( creds > ALIEN_MAX_KILLS - level.clients[ clientNum ].pers.credit ) ) -   { -     creds = ALIEN_MAX_KILLS - level.clients[ clientNum ].pers.credit; -   } - -   // target cannot take any more credits -   if( creds <= 0 ) -   { -     trap_SendServerCommand( ent-g_entities, -       va( "print \"share: player cannot receive any more %s.\n\"", -         ( team == PTE_HUMANS ) ? "credits" : "evolvepoints" ) ); -     return; -   } - -   // transfer credits -   G_AddCreditToClient( ent->client, -creds, qfalse ); -   trap_SendServerCommand( ent-g_entities, -     va( "print \"share: transferred %d %s to %s^7.\n\"", creds, -       ( team == PTE_HUMANS ) ? "credits" : "evolvepoints", -       level.clients[ clientNum ].pers.netname ) ); -   G_AddCreditToClient( &(level.clients[ clientNum ]), creds, qtrue ); -   trap_SendServerCommand( clientNum, -     va( "print \"You have received %d %s from %s^7.\n\"", creds, -       ( team == PTE_HUMANS ) ? "credits" : "evolvepoints", -       ent->client->pers.netname ) ); - -   G_LogPrintf( "Share: %i %i %i %d: %s^7 transferred %d%s to %s^7\n", -     ent->client->ps.clientNum, -     clientNum, -     team, -     creds, -     ent->client->pers.netname, -     creds, -     ( team == PTE_HUMANS ) ? "c" : "e", -     level.clients[ clientNum ].pers.netname ); - } - - /* - ================= - Cmd_Donate_f - - Alms for the poor - ================= - */ - void Cmd_Donate_f( gentity_t *ent ) { -   char s[ MAX_TOKEN_CHARS ] = "", *type = "evo(s)"; -   int i, value, divisor, portion, new_credits, total=0, -     max = ALIEN_MAX_KILLS, *amounts, *totals; -   qboolean donated = qtrue; - -   if( !ent->client ) return; - -   if( !g_allowShare.integer ) -   { -     trap_SendServerCommand( ent-g_entities, "print \"Donate has been disabled.\n\"" ); -     return; -   } - -  if( g_floodMinTime.integer ) -   if ( G_Flood_Limited( ent ) ) -   { -    trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); +  } + +  if ( !g_voiceChats.integer ) { +    ADMP( "^3listvoices: ^7voice system administratively disabled on this server\n" );      return; -   } - -   if( ent->client->pers.teamSelection == PTE_ALIENS ) -     divisor = level.numAlienClients-1; -   else if( ent->client->pers.teamSelection == PTE_HUMANS ) { -     divisor = level.numHumanClients-1; -     max = HUMAN_MAX_CREDITS; -     type = "credit(s)"; -   } else { -     trap_SendServerCommand( ent-g_entities, -       va( "print \"donate: spectators cannot be so gracious\n\"" ) ); -     return; -   } - -   if( divisor < 1 ) { -     trap_SendServerCommand( ent-g_entities, -       "print \"donate: get yourself some teammates first\n\"" ); -     return; -   } - -   trap_Argv( 1, s, sizeof( s ) ); -   value = atoi(s); -   if( value <= 0 ) { -     trap_SendServerCommand( ent-g_entities, -       "print \"donate: very funny\n\"" ); -     return; -   } -   if( value > ent->client->pers.credit) -     value = ent->client->pers.credit; - -   // allocate memory for distribution amounts -   amounts = G_Alloc( level.maxclients * sizeof( int ) ); -   totals = G_Alloc( level.maxclients * sizeof( int ) ); -   for( i = 0; i < level.maxclients; i++ ) { -     amounts[ i ] = 0; -     totals[ i ] = 0; -   } - -   // determine donation amounts for each client -   total = value; -   while( donated && value ) { -     donated = qfalse; -     portion = value / divisor; -     if( portion < 1 ) portion = 1; -     for( i = 0; i < level.maxclients; i++ ) -       if( level.clients[ i ].pers.connected == CON_CONNECTED && -            ent->client != level.clients + i && -            level.clients[ i ].pers.teamSelection == -            ent->client->pers.teamSelection  && -            level.clients[ i ].pers.credit < max  ) { -         new_credits = level.clients[ i ].pers.credit + portion; -         amounts[ i ] = portion; -         totals[ i ] += portion; -         if( new_credits > max ) { -           amounts[ i ] -= new_credits - max; -           totals[ i ] -= new_credits - max; -           new_credits = max; -         } -         if( amounts[ i ] ) { -           G_AddCreditToClient( &(level.clients[ i ]), amounts[ i ], qtrue ); -           donated = qtrue; -           value -= amounts[ i ]; -           if( value < portion ) break; -         } -       } -   } - -   // transfer funds -   G_AddCreditToClient( ent->client, value - total, qtrue ); -   for( i = 0; i < level.maxclients; i++ ) -     if( totals[ i ] ) { -       trap_SendServerCommand( i, -         va( "print \"%s^7 donated %d %s to you, don't forget to say 'thank you'!\n\"", -         ent->client->pers.netname, totals[ i ], type ) ); -     } - -   G_Free( amounts ); -   G_Free( totals ); - -   trap_SendServerCommand( ent-g_entities, -     va( "print \"Donated %d %s to the cause.\n\"", -     total-value, type ) ); - } +  } -commands_t cmds[ ] = { -  // normal commands -  { "team", 0, Cmd_Team_f }, -  { "vote", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Vote_f }, -  { "ignore", 0, Cmd_Ignore_f }, -  { "unignore", 0, Cmd_Ignore_f }, +  if ( trap_Argc() < 2 ) +  { +    voice_t *v; +    int i = 0; + +    ADMBP_begin(); +    for( v = level.voices; v; v = v->next ) +    { +      ADMBP(va("%d - %s\n", i+1, v->name)); +      i++; +    } +    ADMBP(va("^3listvoices: ^7showing %d voices\n", i)); +    ADMBP("^3listvoices: ^7run 'listvoices <voice>' to see available commands.\n"); +    ADMBP_end(); +    return; +  } +  else if ( trap_Argc() >= 2 ) +  { +    voice_t *v; +    voiceCmd_t *c; +    int i = 0; + +    char name[ MAX_VOICE_NAME_LEN ]; +    trap_Argv(1, name, sizeof(name)); + +    v = BG_VoiceByName(level.voices, name); +    if ( !v ) +    { +      ADMP(va("^3listvoices: ^7no matching voice \"%s\"\n", name)); +      return; +    } + +    ADMBP_begin(); +    for ( c = v->cmds; c; c = c->next ) +    { +      ADMBP(va("%d - %s\n", i+1, c->cmd)); +      i++; +    } +    ADMBP(va("^3listvoices: ^7showing %d voice commands for %s\n", i, v->name)); +    ADMBP_end(); +  } +} + +/* +================= +Cmd_ListModels_f + +List all the available player models installed on the server. +================= +*/ +void Cmd_ListModels_f( gentity_t *ent ) +{ +    int i; + +    ADMBP_begin(); +    for (i = 0; i < level.playerModelCount; i++) +    { +        ADMBP(va("%d - %s\n", i+1, level.playerModel[i])); +    } +    ADMBP(va("^3listmodels: ^7showing %d player models\n", level.playerModelCount)); +    ADMBP_end(); + +} + +/* +================= +Cmd_ListSkins_f +================= +*/ +void Cmd_ListSkins_f( gentity_t *ent ) +{ +    char modelname[ 64 ]; +    char skins[ MAX_PLAYER_MODEL ][ 64 ]; +    int numskins; +    int i; + +    if ( trap_Argc() < 2 ) +    { +        ADMP("^3listskins: ^7usage: listskins <model>\n"); +        return; +    } + +    trap_Argv(1, modelname, sizeof(modelname)); + +    G_GetPlayerModelSkins(modelname, skins, MAX_PLAYER_MODEL, &numskins); + +    ADMBP_begin(); +    for (i = 0; i < numskins; i++) +    { +        ADMBP(va("%d - %s\n", i+1, skins[i])); +    } +    ADMBP(va("^3listskins: ^7default skin ^2%s\n", GetSkin(modelname, "default"))); +    ADMBP(va("^3listskins: ^7showing %d skins for %s\n", numskins, modelname)); +    ADMBP_end(); +} + + +/* +================= +Cmd_Test_f +================= +*/ +void Cmd_Test_f( gentity_t *humanPlayer ) +{ +} + +/* +================= +Cmd_Damage_f + +Deals damage to you (for testing), arguments: [damage] [dx] [dy] [dz] +The dx/dy arguments describe the damage point's offset from the entity origin +================= +*/ +void Cmd_Damage_f( gentity_t *ent ) +{ +  vec3_t point; +  char arg[ 16 ]; +  float dx = 0.0f, dy = 0.0f, dz = 100.0f; +  int damage = 100; +  qboolean nonloc = qtrue; + +  if( trap_Argc() > 1 ) +  { +    trap_Argv( 1, arg, sizeof( arg ) ); +    damage = atoi( arg ); +  } +  if( trap_Argc() > 4 ) +  { +    trap_Argv( 2, arg, sizeof( arg ) ); +    dx = atof( arg ); +    trap_Argv( 3, arg, sizeof( arg ) ); +    dy = atof( arg ); +    trap_Argv( 4, arg, sizeof( arg ) ); +    dz = atof( arg ); +    nonloc = qfalse; +  } +  VectorCopy( ent->r.currentOrigin, point ); +  point[ 0 ] += dx; +  point[ 1 ] += dy; +  point[ 2 ] += dz; +  G_Damage( ent, NULL, NULL, NULL, point, damage, +            ( nonloc ? DAMAGE_NO_LOCDAMAGE : 0 ), MOD_TARGET_LASER ); +} + +/* +================== +G_FloodLimited + +Determine whether a user is flood limited, and adjust their flood demerits +Print them a warning message if they are over the limit +Return is time in msec until the user can speak again +================== +*/ +int G_FloodLimited( gentity_t *ent ) +{ +  int deltatime, ms; -  // communication commands -  { "tell", CMD_MESSAGE, Cmd_Tell_f }, +  if( g_floodMinTime.integer <= 0 ) +    return 0; + +  // handles !ent +  if( G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) +    return 0; + +  deltatime = level.time - ent->client->pers.floodTime; + +  ent->client->pers.floodDemerits += g_floodMinTime.integer - deltatime; +  if( ent->client->pers.floodDemerits < 0 ) +    ent->client->pers.floodDemerits = 0; +  ent->client->pers.floodTime = level.time; + +  ms = ent->client->pers.floodDemerits - g_floodMaxDemerits.integer; +  if( ms <= 0 ) +    return 0; +  trap_SendServerCommand( ent - g_entities, va( "print \"You are flooding: " +                          "please wait %d second%s before trying again\n", +                          ( ms + 999 ) / 1000, ( ms > 1000 ) ? "s" : "" ) ); +  return ms; +} + +commands_t cmds[ ] = { +  { "a", CMD_MESSAGE|CMD_INTERMISSION, Cmd_AdminMessage_f }, +  { "build", CMD_TEAM|CMD_ALIVE, Cmd_Build_f }, +  { "buy", CMD_HUMAN|CMD_ALIVE, Cmd_Buy_f }, +  { "callteamvote", CMD_MESSAGE|CMD_TEAM, Cmd_CallVote_f },    { "callvote", CMD_MESSAGE, Cmd_CallVote_f }, -  { "callteamvote", CMD_MESSAGE|CMD_TEAM, Cmd_CallTeamVote_f }, -  { "say_area", CMD_MESSAGE|CMD_TEAM, Cmd_SayArea_f }, -  // can be used even during intermission +  { "class", CMD_TEAM, Cmd_Class_f }, +  { "damage", CMD_CHEAT|CMD_ALIVE, Cmd_Damage_f }, +  { "deconstruct", CMD_TEAM|CMD_ALIVE, Cmd_Destroy_f }, +  { "destroy", CMD_CHEAT|CMD_TEAM|CMD_ALIVE, Cmd_Destroy_f }, +  { "drop", CMD_HUMAN|CMD_CHEAT, Cmd_Drop_f }, +  { "follow", CMD_SPEC, Cmd_Follow_f }, +  { "follownext", CMD_SPEC, Cmd_FollowCycle_f }, +  { "followprev", CMD_SPEC, Cmd_FollowCycle_f }, +  { "give", CMD_CHEAT|CMD_TEAM, Cmd_Give_f }, +  { "god", CMD_CHEAT, Cmd_God_f }, +  { "ignore", 0, Cmd_Ignore_f }, +  { "itemact", CMD_HUMAN|CMD_ALIVE, Cmd_ActivateItem_f }, +  { "itemdeact", CMD_HUMAN|CMD_ALIVE, Cmd_DeActivateItem_f }, +  { "itemtoggle", CMD_HUMAN|CMD_ALIVE, Cmd_ToggleItem_f }, +  { "kill", CMD_TEAM|CMD_ALIVE, Cmd_Kill_f }, +  { "listmaps", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListMaps_f }, +  { "listmodels", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListModels_f }, +  { "listskins", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListSkins_f }, +  { "listvoices", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListVoices_f }, +  { "m", CMD_MESSAGE|CMD_INTERMISSION, Cmd_PrivateMessage_f }, +  { "mt", CMD_MESSAGE|CMD_INTERMISSION, Cmd_PrivateMessage_f }, +  { "noclip", CMD_CHEAT_TEAM, Cmd_Noclip_f }, +  { "notarget", CMD_CHEAT|CMD_TEAM|CMD_ALIVE, Cmd_Notarget_f }, +  { "reload", CMD_HUMAN|CMD_ALIVE, Cmd_Reload_f },    { "say", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, +  { "say_area", CMD_MESSAGE|CMD_TEAM|CMD_ALIVE, Cmd_SayArea_f },    { "say_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, -  { "say_admins", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, -  { "say_hadmins", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, -  { "a", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, -  { "ha", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, -  { "m", CMD_MESSAGE|CMD_INTERMISSION, G_PrivateMessage }, -  { "mt", CMD_MESSAGE|CMD_INTERMISSION, G_PrivateMessage }, -  { "me", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, -  { "me_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, -    { "score", CMD_INTERMISSION, ScoreboardMessage }, -  { "mystats", CMD_TEAM|CMD_INTERMISSION, Cmd_MyStats_f }, -  { "allstats", 0|CMD_INTERMISSION, Cmd_AllStats_f }, -  { "teamstatus", CMD_TEAM, Cmd_TeamStatus_f }, - -  // cheats -  { "give", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Give_f }, -  { "god", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_God_f }, -  { "notarget", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Notarget_f }, -  { "noclip", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Noclip_f }, -  { "levelshot", CMD_CHEAT, Cmd_LevelShot_f }, -  { "setviewpos", CMD_CHEAT, Cmd_SetViewpos_f }, -  { "destroy", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, - -  { "kill", CMD_TEAM|CMD_LIVING, Cmd_Kill_f }, - -  // game commands -  { "ptrcverify", CMD_NOTEAM, Cmd_PTRCVerify_f }, -  { "ptrcrestore", CMD_NOTEAM, Cmd_PTRCRestore_f }, - -  { "follow", 0, Cmd_Follow_f }, -  { "follownext", 0, Cmd_FollowCycle_f }, -  { "followprev", 0, Cmd_FollowCycle_f }, - -  { "where", CMD_TEAM, Cmd_Where_f }, -  { "teamvote", CMD_TEAM, Cmd_TeamVote_f }, -  { "class", CMD_TEAM, Cmd_Class_f }, - -  { "build", CMD_TEAM|CMD_LIVING, Cmd_Build_f }, -  { "deconstruct", CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, -  { "mark", CMD_TEAM|CMD_LIVING, Cmd_Mark_f }, - -  { "buy", CMD_HUMAN|CMD_LIVING, Cmd_Buy_f }, -  { "sell", CMD_HUMAN|CMD_LIVING, Cmd_Sell_f }, -  { "itemact", CMD_HUMAN|CMD_LIVING, Cmd_ActivateItem_f }, -  { "itemdeact", CMD_HUMAN|CMD_LIVING, Cmd_DeActivateItem_f }, -  { "itemtoggle", CMD_HUMAN|CMD_LIVING, Cmd_ToggleItem_f }, -  { "reload", CMD_TEAM|CMD_LIVING, Cmd_Reload_f }, -  { "boost", 0, Cmd_Boost_f }, -  { "share", CMD_TEAM, Cmd_Share_f }, -  { "donate", CMD_TEAM, Cmd_Donate_f }, -  { "protect", CMD_TEAM|CMD_LIVING, Cmd_Protect_f }, -  { "resign", CMD_TEAM, Cmd_Resign_f }, -  { "builder", 0, Cmd_Builder_f } +  { "sell", CMD_HUMAN|CMD_ALIVE, Cmd_Sell_f }, +  { "setviewpos", CMD_CHEAT_TEAM, Cmd_SetViewpos_f }, +  { "team", 0, Cmd_Team_f }, +  { "teamvote", CMD_TEAM, Cmd_Vote_f }, +  { "test", CMD_CHEAT, Cmd_Test_f }, +  { "unignore", 0, Cmd_Ignore_f }, +  { "vote", 0, Cmd_Vote_f }, +  { "vsay", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, +  { "vsay_local", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, +  { "vsay_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, +  { "where", 0, Cmd_Where_f }  }; -static int numCmds = sizeof( cmds ) / sizeof( cmds[ 0 ] ); +static size_t numCmds = ARRAY_LEN( cmds );  /*  ================= @@ -5156,25 +3476,21 @@ ClientCommand  */  void ClientCommand( int clientNum )  { -  gentity_t *ent; -  char      cmd[ MAX_TOKEN_CHARS ]; -  int       i; +  gentity_t  *ent; +  char       cmd[ MAX_TOKEN_CHARS ]; +  commands_t *command;    ent = g_entities + clientNum; -  if( !ent->client ) +  if( !ent->client || ent->client->pers.connected != CON_CONNECTED )      return;   // not fully in game yet    trap_Argv( 0, cmd, sizeof( cmd ) ); -  for( i = 0; i < numCmds; i++ ) -  { -    if( Q_stricmp( cmd, cmds[ i ].cmdName ) == 0 ) -      break; -  } +  command = bsearch( cmd, cmds, numCmds, sizeof( cmds[ 0 ] ), cmdcmp ); -  if( i == numCmds ) +  if( !command )    { -    if( !G_admin_cmd_check( ent, qfalse ) ) +    if( !G_admin_cmd_check( ent ) )        trap_SendServerCommand( clientNum,          va( "print \"Unknown command %s\n\"", cmd ) );      return; @@ -5182,622 +3498,232 @@ void ClientCommand( int clientNum )    // do tests here to reduce the amount of repeated code -  if( !( cmds[ i ].cmdFlags & CMD_INTERMISSION ) && ( level.intermissiontime || level.paused ) ) +  if( !( command->cmdFlags & CMD_INTERMISSION ) && +      ( level.intermissiontime || level.pausedTime ) )      return; -  if( cmds[ i ].cmdFlags & CMD_CHEAT && !g_cheats.integer ) +  if( command->cmdFlags & CMD_CHEAT && !g_cheats.integer )    { -    trap_SendServerCommand( clientNum, -      "print \"Cheats are not enabled on this server\n\"" ); +    G_TriggerMenu( clientNum, MN_CMD_CHEAT );      return;    } -  if( cmds[ i ].cmdFlags & CMD_MESSAGE && G_IsMuted( ent->client ) ) -  { -    trap_SendServerCommand( clientNum, -      "print \"You are muted and cannot use message commands.\n\"" ); +  if( command->cmdFlags & CMD_MESSAGE && ( ent->client->pers.namelog->muted || +      G_FloodLimited( ent ) ) )      return; -  } -  if( cmds[ i ].cmdFlags & CMD_TEAM && -      ent->client->pers.teamSelection == PTE_NONE ) +  if( command->cmdFlags & CMD_TEAM && +      ent->client->pers.teamSelection == TEAM_NONE )    { -    trap_SendServerCommand( clientNum, "print \"Join a team first\n\"" ); +    G_TriggerMenu( clientNum, MN_CMD_TEAM );      return;    } -  if( cmds[ i ].cmdFlags & CMD_NOTEAM && -      ent->client->pers.teamSelection != PTE_NONE ) +  if( command->cmdFlags & CMD_CHEAT_TEAM && !g_cheats.integer && +      ent->client->pers.teamSelection != TEAM_NONE )    { -    trap_SendServerCommand( clientNum, -      "print \"Cannot use this command when on a team\n\"" ); +    G_TriggerMenu( clientNum, MN_CMD_CHEAT_TEAM );      return;    } -  if( cmds[ i ].cmdFlags & CMD_ALIEN && -      ent->client->pers.teamSelection != PTE_ALIENS ) +  if( command->cmdFlags & CMD_SPEC && +      ent->client->sess.spectatorState == SPECTATOR_NOT )    { -    trap_SendServerCommand( clientNum, -      "print \"Must be alien to use this command\n\"" ); +    G_TriggerMenu( clientNum, MN_CMD_SPEC );      return;    } -  if( cmds[ i ].cmdFlags & CMD_HUMAN && -      ent->client->pers.teamSelection != PTE_HUMANS ) +  if( command->cmdFlags & CMD_ALIEN && +      ent->client->pers.teamSelection != TEAM_ALIENS )    { -    trap_SendServerCommand( clientNum, -      "print \"Must be human to use this command\n\"" ); +    G_TriggerMenu( clientNum, MN_CMD_ALIEN );      return;    } -  if( cmds[ i ].cmdFlags & CMD_LIVING && -    ( ent->client->ps.stats[ STAT_HEALTH ] <= 0 || -      ent->client->sess.sessionTeam == TEAM_SPECTATOR ) ) +  if( command->cmdFlags & CMD_HUMAN && +      ent->client->pers.teamSelection != TEAM_HUMANS )    { -    trap_SendServerCommand( clientNum, -      "print \"Must be living to use this command\n\"" ); +    G_TriggerMenu( clientNum, MN_CMD_HUMAN );      return;    } -  // Disallow a large class of commands if a player is restricted. -  if( G_admin_is_restricted( ent, qtrue ) && -      ( !Q_stricmp( cmd, "team" ) || -      ( cmds[ i ].cmdFlags & ( CMD_MESSAGE | CMD_TEAM | CMD_NOTEAM ) ) ) ) +  if( command->cmdFlags & CMD_ALIVE && +    ( ent->client->ps.stats[ STAT_HEALTH ] <= 0 || +      ent->client->sess.spectatorState != SPECTATOR_NOT ) )    { +    G_TriggerMenu( clientNum, MN_CMD_ALIVE );      return;    } -  cmds[ i ].cmdHandler( ent ); +  command->cmdHandler( ent );  } -int G_SayArgc( void ) +void G_ListCommands( gentity_t *ent )  { -  int c = 0; -  char *s; +  int   i; +  char  out[ MAX_STRING_CHARS ] = ""; +  int   len, outlen; -  s = ConcatArgs( 0 ); -  while( 1 ) +  outlen = 0; + +  for( i = 0; i < numCmds; i++ )    { -    while( *s == ' ' ) -      s++; -    if( !*s ) -      break; -    c++; -    while( *s && *s != ' ' ) -      s++; -  } -  return c; -} +    // never advertise cheats +    if( cmds[ i ].cmdFlags & CMD_CHEAT ) +      continue; -qboolean G_SayArgv( int n, char *buffer, int bufferLength ) -{ -  int bc = 0; -  int c = 0; -  char *s; +    len = strlen( cmds[ i ].cmdName ) + 1; +    if( len + outlen >= sizeof( out ) - 1 ) +    { +      trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); +      outlen = 0; +    } -  if( bufferLength < 1 ) -    return qfalse; -  if( n < 0 ) -    return qfalse; -  s = ConcatArgs( 0 ); -  while( c < n ) -  { -    while( *s == ' ' ) -      s++; -    if( !*s ) -      break; -    c++; -    while( *s && *s != ' ' ) -      s++; +    strcpy( out + outlen, va( " %s", cmds[ i ].cmdName ) ); +    outlen += len;    } -  if( c < n ) -    return qfalse; -  while( *s == ' ' ) -    s++; -  if( !*s ) -    return qfalse; -  //memccpy( buffer, s, ' ', bufferLength ); -  while( bc < bufferLength - 1 && *s && *s != ' ' ) -    buffer[ bc++ ] = *s++; -  buffer[ bc ] = 0; -  return qtrue; + +  trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); +  G_admin_cmdlist( ent );  } -char *G_SayConcatArgs( int start ) +void G_DecolorString( const char *in, char *out, int len )  { -  char *s; -  int c = 0; +  qboolean decolor = qtrue; -  s = ConcatArgs( 0 ); -  while( c < start ) -  { -    while( *s == ' ' ) -      s++; -    if( !*s ) -      break; -    c++; -    while( *s && *s != ' ' ) -      s++; -  } -  while( *s == ' ' ) -    s++; -  return s; -} +  len--; -void G_DecolorString( char *in, char *out ) -{    -  while( *in ) { -    if( *in == 27 || *in == '^' ) { +  while( *in && len > 0 ) { +    if( *in == DECOLOR_OFF || *in == DECOLOR_ON ) +    { +      decolor = ( *in == DECOLOR_ON );        in++; -      if( *in ) -        in++; +      continue; +    } +    if( Q_IsColorString( in ) && decolor ) { +      in += 2;        continue;      }      *out++ = *in++; +    len--;    }    *out = '\0';  } -void G_ParseEscapedString( char *buffer ) +void G_UnEscapeString( char *in, char *out, int len )  { -  int i = 0; -  int j = 0; +  len--; -  while( buffer[i] ) +  while( *in && len > 0 )    { -    if(!buffer[i]) break; - -    if(buffer[i] == '\\') +    if( *in >= ' ' || *in == '\n' )      { -      if(buffer[i + 1] == '\\') -        buffer[j] = buffer[++i]; -      else if(buffer[i + 1] == 'n') -      { -        buffer[j] = '\n'; -        i++; -      } -      else -        buffer[j] = buffer[i]; +      *out++ = *in; +      len--;      } -    else -      buffer[j] = buffer[i]; - -    i++; -    j++; +    in++;    } -  buffer[j] = 0; -} - -void G_WordWrap( char *buffer, int maxwidth ) -{ -  char out[ MAX_STRING_CHARS ]; -  int i = 0; -  int j = 0; -  int k; -  int linecount = 0; -  int currentcolor = 7; - -  while ( buffer[ j ]!='\0' ) -  { -     if( i == ( MAX_STRING_CHARS - 1 ) ) -       break; - -     //If it's the start of a new line, copy over the color code, -     //but not if we already did it, or if the text at the start of the next line is also a color code -     if( linecount == 0 && i>2 && out[ i-2 ] != Q_COLOR_ESCAPE && out[ i-1 ] != Q_COLOR_ESCAPE ) -     { -       out[ i ] = Q_COLOR_ESCAPE; -       out[ i + 1 ] = '0' + currentcolor;  -       i+=2; -       continue; -     } - -     if( linecount < maxwidth ) -     { -       out[ i ] = buffer[ j ]; -       if( out[ i ] == '\n' )  -       { -         linecount = 0; -       } -       else if( Q_IsColorString( &buffer[j] ) ) -       { -         currentcolor = buffer[j+1] - '0'; -       } -       else -         linecount++; -        -       //If we're at a space and getting close to a line break, look ahead and make sure that there isn't already a \n or a closer space coming. If not, break here. -      if( out[ i ] == ' ' && linecount >= (maxwidth - 10 ) )  -      { -        qboolean foundbreak = qfalse; -        for( k = i+1; k < maxwidth; k++ ) -        { -          if( !buffer[ k ] ) -            continue; -          if( buffer[ k ] == '\n' || buffer[ k ] == ' ' ) -            foundbreak = qtrue; -        } -        if( !foundbreak ) -        { -          out [ i ] = '\n'; -          linecount = 0; -        } -      } -        -      i++; -      j++; -     } -     else -     { -       out[ i ] = '\n'; -       i++; -       linecount = 0; -     } -  } -  out[ i ] = '\0'; - - -  strcpy( buffer, out ); +  *out = '\0';  } -void G_PrivateMessage( gentity_t *ent ) +void Cmd_PrivateMessage_f( gentity_t *ent )  {    int pids[ MAX_CLIENTS ]; -  int ignoreids[ MAX_CLIENTS ];    char name[ MAX_NAME_LENGTH ];    char cmd[ 12 ]; -  char str[ MAX_STRING_CHARS ]; +  char text[ MAX_STRING_CHARS ];    char *msg;    char color; -  int pcount, matches, ignored = 0; -  int i; -  int skipargs = 0; +  int i, pcount; +  int count = 0;    qboolean teamonly = qfalse; -  gentity_t *tmpent; +  char recipients[ MAX_STRING_CHARS ] = "";    if( !g_privateMessages.integer && ent )    {      ADMP( "Sorry, but private messages have been disabled\n" );      return;    } -   -  if( g_floodMinTime.integer ) -   if ( G_Flood_Limited( ent ) ) -   { -    trap_SendServerCommand( ent-g_entities, "print \"Your chat is flood-limited; wait before chatting again\n\"" ); -    return; -   } -  G_SayArgv( 0, cmd, sizeof( cmd ) ); -  if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) ) -  { -    skipargs = 1; -    G_SayArgv( 1, cmd, sizeof( cmd ) ); -  } -  if( G_SayArgc( ) < 3+skipargs ) +  trap_Argv( 0, cmd, sizeof( cmd ) ); +  if( trap_Argc( ) < 3 )    {      ADMP( va( "usage: %s [name|slot#] [message]\n", cmd ) );      return;    } -  if( !Q_stricmp( cmd, "mt" ) || !Q_stricmp( cmd, "/mt" ) ) +  if( !Q_stricmp( cmd, "mt" ) )      teamonly = qtrue; -  G_SayArgv( 1+skipargs, name, sizeof( name ) ); -  msg = G_SayConcatArgs( 2+skipargs ); -  pcount = G_ClientNumbersFromString( name, pids ); +  trap_Argv( 1, name, sizeof( name ) ); +  msg = ConcatArgs( 2 ); +  pcount = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); -  if( ent ) -  { -    int count = 0; +  G_CensorString( text, msg, sizeof( text ), ent ); -    for( i=0; i < pcount; i++ ) +  // send the message +  for( i = 0; i < pcount; i++ ) +  { +    if( G_SayTo( ent, &g_entities[ pids[ i ] ], +        teamonly ? SAY_TPRIVMSG : SAY_PRIVMSG, text ) )      { -      tmpent = &g_entities[ pids[ i ] ]; - -      if( teamonly && !OnSameTeam( ent, tmpent ) ) -        continue; -       -      // Ignore sending to invisible players -      if( tmpent->client->sess.invisible == qtrue && !G_admin_permission( ent, "invisible" ) ) -        continue; -       -      // Ignore sending to non-invisible-capable players while invisible -      if( ent->client->sess.invisible == qtrue && !G_admin_permission( tmpent, "invisible" ) ) -        continue; -       -      if( BG_ClientListTest( &tmpent->client->sess.ignoreList, -        ent-g_entities ) ) -      { -        ignoreids[ ignored++ ] = pids[ i ]; -        continue; -      } - -      pids[ count ] = pids[ i ];        count++; +      Q_strcat( recipients, sizeof( recipients ), va( "%s" S_COLOR_WHITE ", ", +        level.clients[ pids[ i ] ].pers.netname ) );      } -    matches = count; -  } -  else -  { -    matches = pcount;    } +  // report the results    color = teamonly ? COLOR_CYAN : COLOR_YELLOW; -  if( !Q_stricmp( name, "console" ) ) -  { -    ADMP( va( "^%cPrivate message: ^7%s\n", color, msg ) ); -    ADMP( va( "^%csent to Console.\n", color ) ); - -    G_LogPrintf( "privmsg: %s^7: Console: ^6%s^7\n", -      ( ent ) ? ent->client->pers.netname : "Console", msg ); - -    return; -  } - -  Q_strncpyz( str, -    va( "^%csent to %i player%s: ^7", color, matches, -      ( matches == 1 ) ? "" : "s" ), -    sizeof( str ) ); - -  for( i=0; i < matches; i++ ) -  { -    tmpent = &g_entities[ pids[ i ] ]; - -    if( i > 0 ) -      Q_strcat( str, sizeof( str ), "^7, " ); -    Q_strcat( str, sizeof( str ), tmpent->client->pers.netname ); -    trap_SendServerCommand( pids[ i ], va( -      "chat \"%s^%c -> ^7%s^7: (%d recipients): ^%c%s^7\" %i", -      ( ent ) ? ent->client->pers.netname : "console", -      color, -      name, -      matches, -      color, -      msg, -      ent ? ent-g_entities : -1 ) ); - -    trap_SendServerCommand( pids[ i ], va(  -      "cp \"^%cprivate message from ^7%s^7\"", color, -      ( ent ) ? ent->client->pers.netname : "console" ) ); -  } - -  if( !matches ) +  if( !count )      ADMP( va( "^3No player matching ^7\'%s^7\' ^3to send message to.\n",        name ) );    else    { -    if( ent ) -      ADMP( va( "^%cPrivate message: ^7%s\n", color, msg ) ); +    ADMP( va( "^%cPrivate message: ^7%s\n", color, text ) ); +    // remove trailing ", " +    recipients[ strlen( recipients ) - 2 ] = '\0'; +    ADMP( va( "^%csent to %i player%s: " S_COLOR_WHITE "%s\n", color, count, +      count == 1 ? "" : "s", recipients ) ); -    ADMP( va( "%s\n", str ) ); - -    G_LogPrintf( "%s: %s^7: %s^7: %s\n", -      ( teamonly ) ? "tprivmsg" : "privmsg", +    G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\" \"%s\": ^%c%s\n", +      ( teamonly ) ? "TPrivMsg" : "PrivMsg", +      (int)( ( ent ) ? ent - g_entities : -1 ),        ( ent ) ? ent->client->pers.netname : "console", -      name, msg ); +      name, color, msg );    } - -  if( ignored ) -  { -    Q_strncpyz( str, va( "^%cignored by %i player%s: ^7", color, ignored, -      ( ignored == 1 ) ? "" : "s" ), sizeof( str ) ); -    for( i=0; i < ignored; i++ ) -    { -      tmpent = &g_entities[ ignoreids[ i ] ]; -      if( i > 0 ) -        Q_strcat( str, sizeof( str ), "^7, " ); -      Q_strcat( str, sizeof( str ), tmpent->client->pers.netname ); -    } -    ADMP( va( "%s\n", str ) ); -  } -} - - /* - ================= - Cmd_Builder_f - ================= - */ - void Cmd_Builder_f( gentity_t *ent ) - { -   vec3_t      forward, right, up; -   vec3_t      start, end; -   trace_t     tr; -   gentity_t   *traceEnt; -   char bdnumbchr[21]; -  -   AngleVectors( ent->client->ps.viewangles, forward, right, up ); -   if( ent->client->pers.teamSelection != PTE_NONE ) -     CalcMuzzlePoint( ent, forward, right, up, start ); -   else -     VectorCopy( ent->client->ps.origin, start ); -   VectorMA( start, 1000, forward, end ); -  -   trap_Trace( &tr, start, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); -   traceEnt = &g_entities[ tr.entityNum ]; -  -   Com_sprintf( bdnumbchr, sizeof(bdnumbchr), "%i", traceEnt->bdnumb ); -  -   if( tr.fraction < 1.0f && ( traceEnt->s.eType == ET_BUILDABLE ) ) -   { -     if( G_admin_permission( ent, "buildlog" ) ) { -      trap_SendServerCommand( ent-g_entities, va( -        "print \"^5/builder:^7 ^3Building:^7 %s ^3Built By:^7 %s^7 ^3Buildlog Number:^7 %s^7\n\"", -        BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), -        (traceEnt->bdnumb != -1) ? G_FindBuildLogName( traceEnt->bdnumb ) : "<world>", -        (traceEnt->bdnumb != -1) ? bdnumbchr : "none" ) ); -     } -     else -     { -      trap_SendServerCommand( ent-g_entities, va( -        "print \"^5/builder:^7 ^3Building:^7 %s ^3Built By:^7 %s^7\n\"", -         BG_FindHumanNameForBuildable( traceEnt->s.modelindex ),      -        (traceEnt->bdnumb != -1) ? G_FindBuildLogName( traceEnt->bdnumb ) : "<world>" ) ); -     } -   } -   else -   { -      trap_SendServerCommand( ent-g_entities, "print \"^5/builder:^7 No structure found in your crosshair. Please face a structure and try again.\n\"" ); -   } - } - -void G_CP( gentity_t *ent ) -{ -   int i; -   char buffer[MAX_STRING_CHARS]; -   char prefixes[MAX_STRING_CHARS] = ""; -   char wrappedtext[ MAX_STRING_CHARS ] = ""; -   char *ptr; -   char *text; -   qboolean sendAliens = qtrue; -   qboolean sendHumans = qtrue; -   qboolean sendSpecs = qtrue; -   Q_strncpyz( buffer, ConcatArgs( 1 ), sizeof( buffer ) ); -   G_ParseEscapedString( buffer ); - -   if( strstr( buffer, "!cp" ) ) -   { -     ptr = buffer; -     while( *ptr != '!' ) -       ptr++; -     ptr+=4; -      -     Q_strncpyz( buffer, ptr, sizeof(buffer) ); -   } - -   text = buffer; - -   ptr = buffer; -   while( *ptr == ' ' ) -     ptr++; -   if( *ptr == '-' ) -   { -      sendAliens = qfalse; -      sendHumans = qfalse; -      sendSpecs = qfalse; -      Q_strcat( prefixes, sizeof( prefixes ), " " ); -      ptr++; - -      while( *ptr && *ptr != ' ' ) -      { -        if( !sendAliens && ( *ptr == 'a' || *ptr == 'A' ) ) -        { -          sendAliens = qtrue; -          Q_strcat( prefixes, sizeof( prefixes ), "[^1A^7]" ); -        } -        if( !sendHumans && ( *ptr == 'h' || *ptr == 'H' ) ) -        { -          sendHumans = qtrue; -          Q_strcat( prefixes, sizeof( prefixes ), "[^4H^7]" ); -        } -        if( !sendSpecs && ( *ptr == 's' || *ptr == 'S' ) ) -        { -          sendSpecs = qtrue; -          Q_strcat( prefixes, sizeof( prefixes ), "[^3S^7]" ); -        } -        ptr++; -      } -      if( *ptr ) text = ptr+1; -      else text = ptr; -   } -   -  strcpy( wrappedtext, text ); - -  if( strlen( text ) == 0 ) return; - -  G_WordWrap( wrappedtext, 50 ); - -  for( i = 0; i < level.maxclients; i++ ) -  { -    if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) -      continue; - -    if( ( !sendAliens && level.clients[ i ].pers.teamSelection == PTE_ALIENS ) || -         ( !sendHumans && level.clients[ i ].pers.teamSelection == PTE_HUMANS ) || -         ( !sendSpecs && level.clients[ i ].pers.teamSelection == PTE_NONE ) ) -    { -      if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) -      { -        trap_SendServerCommand( i, va("print \"^6[Admins]^7 CP to other team%s: %s \n\"", prefixes, text ) ); -      } -      continue; -    } - -      trap_SendServerCommand( i, va( "cp \"%s\"", wrappedtext ) ); -      trap_SendServerCommand( i, va( "print \"%s^7 CP%s: %s\n\"", ( ent ? G_admin_adminPrintName( ent ) : "console" ), prefixes, text ) ); -    } - -     G_Printf( "cp: %s\n", ConcatArgs( 1 ) );  }  /*  ================= -G_IsMuted +Cmd_AdminMessage_f -Check if a player is muted +Send a message to all active admins  =================  */ -qboolean G_IsMuted( gclient_t *client ) +void Cmd_AdminMessage_f( gentity_t *ent )  { -  qboolean muteState = qfalse; - -  //check if mute has expired -  if( client->pers.muteExpires ) { -    if( client->pers.muteExpires < level.time ) +  // Check permissions and add the appropriate user [prefix] +  if( !G_admin_permission( ent, ADMF_ADMINCHAT ) ) +  { +    if( !g_publicAdminMessages.integer ) +    { +      ADMP( "Sorry, but use of /a by non-admins has been disabled.\n" ); +      return; +    } +    else      { -      client->pers.muted = qfalse; -      client->pers.muteExpires = 0; +      ADMP( "Your message has been sent to any available admins " +            "and to the server logs.\n" );      }    } -  if( client->pers.muted ) -    muteState = qtrue; - -  return muteState; -} - -/* -================== -G_TeamKill_Repent - -Determine whether a players team kill activity is high -================== -*/ - -qboolean G_TeamKill_Repent( gentity_t *ent ) -{ -  int millisSinceLastTeamKill; - -  // Doesn't work if g_teamKillThreshold isn't set -  if( !g_teamKillThreshold.integer || -       g_teamKillThreshold.integer == 0 ) -    return qfalse; - -  // Doesn't work when game is paused -  if( level.paused ) -    return qfalse; - -  millisSinceLastTeamKill = level.time - ent->client->pers.lastTeamKillTime; -  if( millisSinceLastTeamKill < 30000 ) -    ent->client->pers.teamKillDemerits++; -  else +  if( trap_Argc( ) < 2 )    { -    ent->client->pers.teamKillDemerits--; -    if( ent->client->pers.teamKillDemerits < 0 ) -      ent->client->pers.teamKillDemerits = 0; +    ADMP( "usage: a [message]\n" ); +    return;    } -  ent->client->pers.lastTeamKillTime = level.time; - -  if ( ent->client->pers.teamKillDemerits >= ( g_teamKillThreshold.integer + 2 ) ) -    trap_SendConsoleCommand( 0, va( "!ban %s 30m team killing\n", ent->client->pers.ip ) ); -  else if ( ent->client->pers.teamKillDemerits == ( g_teamKillThreshold.integer + 1 ) ) -    trap_SendConsoleCommand( 0, va( "!warn %i team killing\n", ent->client->ps.clientNum ) ); -  else if ( ent->client->pers.teamKillDemerits == g_teamKillThreshold.integer ) -    G_AdminsPrintf( "Team killer %s^7 has team killed ^6%i^7 times.\n", -                    ent->client->pers.netname, -                    ent->client->pers.statscounters.teamkills ); - -  return qfalse; +  G_AdminMessage( ent, ConcatArgs( 1 ) );  } diff --git a/src/game/g_combat.c b/src/game/g_combat.c index 5350895..cb15147 100644 --- a/src/game/g_combat.c +++ b/src/game/g_combat.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,24 +17,24 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */  #include "g_local.h" -damageRegion_t  g_damageRegions[ PCL_NUM_CLASSES ][ MAX_LOCDAMAGE_REGIONS ]; +damageRegion_t  g_damageRegions[ PCL_NUM_CLASSES ][ MAX_DAMAGE_REGIONS ];  int             g_numDamageRegions[ PCL_NUM_CLASSES ]; -armourRegion_t  g_armourRegions[ UP_NUM_UPGRADES ][ MAX_ARMOUR_REGIONS ]; +damageRegion_t  g_armourRegions[ UP_NUM_UPGRADES ][ MAX_DAMAGE_REGIONS ];  int             g_numArmourRegions[ UP_NUM_UPGRADES ];  /*  ============  AddScore -Adds score to both the client and his team +Adds score to the client  ============  */  void AddScore( gentity_t *ent, int score ) @@ -41,6 +42,15 @@ void AddScore( gentity_t *ent, int score )    if( !ent->client )      return; +  // make alien and human scores equivalent  +  if ( ent->client->pers.teamSelection == TEAM_ALIENS ) +  { +    score = rint( ((float)score) / 2.0f ); +  } + +  // scale values down to fit the scoreboard better +  score = rint( ((float)score) / 50.0f ); +    ent->client->ps.persistant[ PERS_SCORE ] += score;    CalculateRanks( );  } @@ -52,19 +62,13 @@ LookAtKiller  */  void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker )  { -  vec3_t    dir;    if ( attacker && attacker != self ) -    VectorSubtract( attacker->s.pos.trBase, self->s.pos.trBase, dir ); +    self->client->ps.stats[ STAT_VIEWLOCK ] = attacker - g_entities;    else if( inflictor && inflictor != self ) -    VectorSubtract( inflictor->s.pos.trBase, self->s.pos.trBase, dir ); +    self->client->ps.stats[ STAT_VIEWLOCK ] = inflictor - g_entities;    else -  { -    self->client->ps.stats[ STAT_VIEWLOCK ] = self->s.angles[ YAW ]; -    return; -  } - -  self->client->ps.stats[ STAT_VIEWLOCK ] = vectoyaw( dir ); +    self->client->ps.stats[ STAT_VIEWLOCK ] = self - g_entities;  }  // these are just for logging, the client prints its own messages @@ -104,7 +108,8 @@ char *modNames[ ] =    "MOD_LEVEL2_CLAW",    "MOD_LEVEL2_ZAP",    "MOD_LEVEL4_CLAW", -  "MOD_LEVEL4_CHARGE", +  "MOD_LEVEL4_TRAMPLE", +  "MOD_LEVEL4_CRUSH",    "MOD_SLOWBLOB",    "MOD_POISON", @@ -118,11 +123,112 @@ char *modNames[ ] =    "MOD_ASPAWN",    "MOD_ATUBE",    "MOD_OVERMIND", -  "MOD_SLAP" +  "MOD_DECONSTRUCT", +  "MOD_REPLACE", +  "MOD_NOCREEP"  };  /*  ================== +G_RewardAttackers + +Function to distribute rewards to entities that killed this one. +Returns the total damage dealt. +================== +*/ +float G_RewardAttackers( gentity_t *self ) +{ +  float     value, totalDamage = 0; +  int       team, i, maxHealth = 0; +  int       alienCredits = 0, humanCredits = 0; +  gentity_t *player; + +  // Total up all the damage done by non-teammates +  for( i = 0; i < level.maxclients; i++ ) +  { +    player = g_entities + i; + +    if( !OnSameTeam( self, player ) ||  +        self->buildableTeam != player->client->ps.stats[ STAT_TEAM ] ) +      totalDamage += (float)self->credits[ i ]; +  } + +  if( totalDamage <= 0.0f ) +    return 0.0f; + +  // Only give credits for killing players and buildables +  if( self->client ) +  { +    value = BG_GetValueOfPlayer( &self->client->ps ); +    team = self->client->pers.teamSelection; +    maxHealth = self->client->ps.stats[ STAT_MAX_HEALTH ]; +  } +  else if( self->s.eType == ET_BUILDABLE ) +  { +    value = BG_Buildable( self->s.modelindex )->value; + +    // only give partial credits for a buildable not yet completed +    if( !self->spawned ) +    { +      value *= (float)( level.time - self->buildTime ) / +        BG_Buildable( self->s.modelindex )->buildTime; +    } + +    team = self->buildableTeam; +    maxHealth = BG_Buildable( self->s.modelindex )->health; +  } +  else +    return totalDamage; + +  // Give credits and empty the array +  for( i = 0; i < level.maxclients; i++ ) +  { +    int stageValue = value * self->credits[ i ] / totalDamage; +    player = g_entities + i; + +    if( player->client->pers.teamSelection != team ) +    { +      if( totalDamage < maxHealth ) +        stageValue *= totalDamage / maxHealth; + +      if( !self->credits[ i ] || player->client->ps.stats[ STAT_TEAM ] == team ) +        continue; + +      AddScore( player, stageValue ); + +      // killing buildables earns score, but not credits +      if( self->s.eType != ET_BUILDABLE ) +      { +        G_AddCreditToClient( player->client, stageValue, qtrue ); + +        // add to stage counters +        if( player->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) +          alienCredits += stageValue; +        else if( player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) +          humanCredits += stageValue; +      } +    } +    self->credits[ i ] = 0; +  } + +  if( alienCredits ) +  { +    trap_Cvar_Set( "g_alienCredits", +      va( "%d", g_alienCredits.integer + alienCredits ) ); +    trap_Cvar_Update( &g_alienCredits ); +  } +  if( humanCredits ) +  { +    trap_Cvar_Set( "g_humanCredits", +      va( "%d", g_humanCredits.integer + humanCredits ) ); +    trap_Cvar_Update( &g_humanCredits ); +  } +   +  return totalDamage; +} + +/* +==================  player_die  ==================  */ @@ -131,18 +237,11 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int    gentity_t *ent;    int       anim;    int       killer; -  int       i, j; -  char      *killerName, *obit; -  float     totalTK = 0; -  float     totalDamage = 0.0f; -  float     percentDamage = 0.0f; -  gentity_t *player; -  qboolean  tk = qfalse; - +  int       i; +  const char      *killerName, *obit;    if( self->client->ps.pm_type == PM_DEAD )      return; -      if( level.intermissiontime )      return; @@ -155,27 +254,9 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int      killer = attacker->s.number;      if( attacker->client ) -    {        killerName = attacker->client->pers.netname; -      tk = ( attacker != self && attacker->client->ps.stats[ STAT_PTEAM ]  -        == self->client->ps.stats[ STAT_PTEAM ] ); - -      if( attacker != self && attacker->client->ps.stats[ STAT_PTEAM ]  == self->client->ps.stats[ STAT_PTEAM ] )  -      { -        attacker->client->pers.statscounters.teamkills++; -        if( attacker->client->pers.teamSelection == PTE_ALIENS )  -        { -          level.alienStatsCounters.teamkills++; -        } -        else if( attacker->client->pers.teamSelection == PTE_HUMANS ) -        { -          level.humanStatsCounters.teamkills++; -        } -      } - -    }      else -      killerName = "<non-client>"; +      killerName = "<world>";    }    else    { @@ -183,395 +264,62 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int      killerName = "<world>";    } -  if( killer < 0 || killer >= MAX_CLIENTS ) -  { -    killer = ENTITYNUM_WORLD; -    killerName = "<world>"; -  } - -  if( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) -    obit = "<bad obituary>"; +  if( meansOfDeath < 0 || meansOfDeath >= ARRAY_LEN( modNames ) ) +    // fall back on the number +    obit = va( "%d", meansOfDeath );    else      obit = modNames[ meansOfDeath ]; -  G_LogPrintf("Kill: %i %i %i: %s^7 killed %s^7 by %s\n", -    killer, self->s.number, meansOfDeath, killerName, -    self->client->pers.netname, obit ); +  G_LogPrintf( "Die: %d %d %s: %s" S_COLOR_WHITE " killed %s\n", +    killer, +    (int)( self - g_entities ), +    obit, +    killerName, +    self->client->pers.netname ); -  //TA: deactivate all upgrades +  // deactivate all upgrades    for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )      BG_DeactivateUpgrade( i, self->client->ps.stats ); -  if( meansOfDeath == MOD_SLAP ) -  { -    trap_SendServerCommand( -1, -      va( "print \"%s^7 felt %s^7's authority\n\"", -      self->client->pers.netname, killerName ) ); -    goto finish_dying; -  } -    // broadcast the death event to everyone -  if( !tk ) -  { -    ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); -    ent->s.eventParm = meansOfDeath; -    ent->s.otherEntityNum = self->s.number; -    ent->s.otherEntityNum2 = killer; -    ent->r.svFlags = SVF_BROADCAST; // send to everyone -  } -  else if( attacker && attacker->client ) -  { -    // tjw: obviously this is a hack and belongs in the client, but -    //      this works as a temporary fix. -    trap_SendServerCommand( -1, -      va( "print \"%s^7 was killed by ^1TEAMMATE^7 %s^7 (Did %d damage to %d max)\n\"", -      self->client->pers.netname, attacker->client->pers.netname, self->client->tkcredits[ attacker->s.number ], self->client->ps.stats[ STAT_MAX_HEALTH ] ) ); -    trap_SendServerCommand( attacker - g_entities, -      va( "cp \"You killed ^1TEAMMATE^7 %s\"", self->client->pers.netname ) ); -    G_LogOnlyPrintf("%s^7 was killed by ^1TEAMMATE^7 %s^7 (Did %d damage to %d max)\n", -      self->client->pers.netname, attacker->client->pers.netname, self->client->tkcredits[ attacker->s.number ], self->client->ps.stats[ STAT_MAX_HEALTH ] ); -    G_TeamKill_Repent( attacker ); -  } +  ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); +  ent->s.eventParm = meansOfDeath; +  ent->s.otherEntityNum = self->s.number; +  ent->s.otherEntityNum2 = killer; +  ent->r.svFlags = SVF_BROADCAST; // send to everyone    self->enemy = attacker; -    self->client->ps.persistant[ PERS_KILLED ]++; -  self->client->pers.statscounters.deaths++; -  if( self->client->pers.teamSelection == PTE_ALIENS )  -  { -    level.alienStatsCounters.deaths++; -  } -  else if( self->client->pers.teamSelection == PTE_HUMANS ) -  { -     level.humanStatsCounters.deaths++; -  }    if( attacker && attacker->client )    {      attacker->client->lastkilled_client = self->s.number; -   if( g_killerHP.integer || -       ( g_devmapKillerHP.integer && g_cheats.integer ) ) -   { -     trap_SendServerCommand( self-g_entities, -       va( "print \"Your killer, %s^7, had %3i HP.\n\"", -         killerName, attacker->health ) ); -   } - -    if( attacker == self || OnSameTeam( self, attacker ) ) -    { -      AddScore( attacker, -1 ); - -      // Normal teamkill penalty -      if( !g_retribution.integer ) -      { -        if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -          G_AddCreditToClient( attacker->client, -FREEKILL_ALIEN, qtrue ); -        else if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -          G_AddCreditToClient( attacker->client, -FREEKILL_HUMAN, qtrue ); -      } -    } -    else -    { -      AddScore( attacker, 1 ); - -      if( g_gradualFreeFunds.integer < 2 ) -        attacker->client->pers.lastFreekillTime = level.time; -      attacker->client->pers.statscounters.kills++; -      if( attacker->client->pers.teamSelection == PTE_ALIENS )  -      { -        level.alienStatsCounters.kills++; -      } -      else if( attacker->client->pers.teamSelection == PTE_HUMANS ) -      { -         level.humanStatsCounters.kills++; -      } -     } -     -    if( attacker == self ) +    if( ( attacker == self || OnSameTeam( self, attacker ) ) && meansOfDeath != MOD_HSPAWN )      { -      attacker->client->pers.statscounters.suicides++; -      if( attacker->client->pers.teamSelection == PTE_ALIENS )  +      //punish team kills and suicides +      if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )        { -        level.alienStatsCounters.suicides++; +        G_AddCreditToClient( attacker->client, -ALIEN_TK_SUICIDE_PENALTY, qtrue ); +        AddScore( attacker, -ALIEN_TK_SUICIDE_PENALTY );        } -      else if( attacker->client->pers.teamSelection == PTE_HUMANS ) +      else if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )        { -        level.humanStatsCounters.suicides++; +        G_AddCreditToClient( attacker->client, -HUMAN_TK_SUICIDE_PENALTY, qtrue ); +        AddScore( attacker, -HUMAN_TK_SUICIDE_PENALTY );        }      }    }    else if( attacker->s.eType != ET_BUILDABLE ) -    AddScore( self, -1 ); - -  //total up all the damage done by every client -  for( i = 0; i < MAX_CLIENTS; i++ )    { -    totalDamage += (float)self->credits[ i ]; -    totalTK += (float)self->client->tkcredits[ i ]; +    if( self->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) +      AddScore( self, -ALIEN_TK_SUICIDE_PENALTY ); +    else if( self->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) +      AddScore( self, -HUMAN_TK_SUICIDE_PENALTY );    } -  // punish players for damaging teammates -  if ( g_retribution.integer && totalTK ) -  { -    int totalPrice; -    int max = HUMAN_MAX_CREDITS; - -    if ( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -    { -      totalPrice = BG_ClassCanEvolveFromTo( PCL_ALIEN_LEVEL0, self->client->ps.stats[ STAT_PCLASS ], ALIEN_MAX_KILLS, 0 ); -      max = ALIEN_MAX_KILLS; -    } -    else -    { -      totalPrice = BG_GetValueOfEquipment( &self->client->ps ); -    } - -    if ( self->client->ps.persistant[ PERS_CREDIT ] + totalPrice > max ) -      totalPrice = max - self->client->ps.persistant[ PERS_CREDIT ]; -    if ( totalPrice > 0 ) -    { -      totalTK += totalDamage; -      if( totalTK < self->client->ps.stats[ STAT_MAX_HEALTH ] ) -        totalTK = self->client->ps.stats[ STAT_MAX_HEALTH ]; - -      if ( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -      { -        for ( i = 0; i < MAX_CLIENTS; i++ ) -        { -          int price; -          // no retribution if self damage or enemy damage or building damage or no damage from this client -          if ( i == self - g_entities || !g_entities[ i ].client || -               !OnSameTeam( &g_entities[ i ], self ) || -               !self->client->tkcredits[ i ] ) -            continue; - -          // calculate retribution price (rounded up) -          price = ( totalPrice * self->client->tkcredits[ i ] ) / totalTK + 0.5f; -          self->client->tkcredits[ i ] = 0; - -          // check for enough credits -          if ( g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] < price ) -            price = g_entities[ i ].client->ps.persistant[ PERS_CREDIT ]; -          if ( price ) -          { -            G_AddCreditToClient( self->client, price, qtrue ); -            G_AddCreditToClient( g_entities[ i ].client, -price, qtrue ); - -            trap_SendServerCommand( self->client->ps.clientNum, -              va( "print \"Received ^3%d credits ^7from %s ^7in retribution.\n\"", -                price, g_entities[ i ].client->pers.netname ) ); -            trap_SendServerCommand( g_entities[ i ].client->ps.clientNum, -              va( "print \"Transfered ^3%d credits ^7to %s ^7in retribution.\n\"", -                price, self->client->pers.netname ) ); -          } -        } -      } -      else -      { -        int toPay[ MAX_CLIENTS ] = { 0 }; -        int frags = totalPrice; -        int damageForEvo = totalTK / totalPrice; -        for ( i = 0; i < MAX_CLIENTS; i++ ) -        { -          // no retribution if self damage or enemy damage or building damage or no damage from this client -          if ( i == self - g_entities || !g_entities[ i ].client || -               !OnSameTeam( &g_entities[ i ], self ) || -               !self->client->tkcredits[ i ] ) -            continue; - -          // find out how many full evos this client needs to pay -          toPay[ i ] = ( totalPrice * self->client->tkcredits[ i ] ) / totalTK; -          if ( toPay[ i ] > g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] ) -            toPay[ i ] = g_entities[ i ].client->ps.persistant[ PERS_CREDIT ]; -          frags -= toPay[ i ]; -          self->client->tkcredits[ i ] -= damageForEvo * toPay[ i ]; -        } - -        // if we have not met the evo count, continue stealing evos -        while ( 1 ) -        { -          int maximum = 0; -          int topClient = 0; -          for ( i = 0; i < MAX_CLIENTS; i++ ) -          { -            if ( self->client->tkcredits[ i ] > maximum && g_entities[ i ].client->ps.persistant[ PERS_CREDIT ] ) -            { -              maximum = self->client->tkcredits[ i ]; -              topClient = i; -            } -          } -          if ( !maximum ) -            break; -          toPay[ topClient ]++; -          self->client->tkcredits[ topClient ] = 0; -          frags--; -          if ( !frags ) -           break; -        } -         -        // now move the evos around -        for ( i = 0; i < MAX_CLIENTS; i++ ) -        { -          if ( !toPay[ i ] ) -            continue; -           -          G_AddCreditToClient( self->client, toPay[ i ], qtrue ); -          G_AddCreditToClient( g_entities[ i ].client, -toPay[ i ], qtrue ); - -          trap_SendServerCommand( self->client->ps.clientNum, -            va( "print \"Received ^3%d ^7evos from %s ^7in retribution.\n\"", -              toPay[ i ], g_entities[ i ].client->pers.netname ) ); -          trap_SendServerCommand( g_entities[ i ].client->ps.clientNum, -            va( "print \"Transfered ^3%d ^7evos to %s ^7in retribution.\n\"", -              toPay[ i ], self->client->pers.netname ) ); -        } -      } -    } -  } - -  // if players did more than DAMAGE_FRACTION_FOR_KILL increment the stage counters -  if( !OnSameTeam( self, attacker ) && totalDamage >= ( self->client->ps.stats[ STAT_MAX_HEALTH ] * DAMAGE_FRACTION_FOR_KILL ) ) -  { -    if( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )  -    { -      trap_Cvar_Set( "g_alienKills", va( "%d", g_alienKills.integer + 1 ) ); -      if( g_alienStage.integer < 2 ) -      { -        self->client->pers.statscounters.feeds++; -        level.humanStatsCounters.feeds++; -      } -    } -    else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -    { -      trap_Cvar_Set( "g_humanKills", va( "%d", g_humanKills.integer + 1 ) ); -      if( g_humanStage.integer < 2 ) -      { -        self->client->pers.statscounters.feeds++; -        level.alienStatsCounters.feeds++; -      } -    } -  } - -  if( totalDamage > 0.0f ) -  { -    if( self->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -    { -      //nice simple happy bouncy human land -      float classValue = BG_FindValueOfClass( self->client->ps.stats[ STAT_PCLASS ] ); - -      for( i = 0; i < MAX_CLIENTS; i++ ) -      { -        player = g_entities + i; - -        if( !player->client ) -          continue; - -        if( player->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) -          continue; - -        if( !self->credits[ i ] ) -          continue; - -        percentDamage = (float)self->credits[ i ] / totalDamage; -        if( percentDamage > 0 && percentDamage < 1) -        { -          player->client->pers.statscounters.assists++; -          level.humanStatsCounters.assists++; -        } - -        //add credit -        G_AddCreditToClient( player->client, -            (int)( classValue * percentDamage ), qtrue ); -      } -    } -    else if( self->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -    { -      //horribly complex nasty alien land -      float humanValue = BG_GetValueOfHuman( &self->client->ps ); -      int   frags; -      int   unclaimedFrags = (int)humanValue; - -      for( i = 0; i < MAX_CLIENTS; i++ ) -      { -        player = g_entities + i; - -        if( !player->client ) -          continue; - -        if( player->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) -          continue; - -        //this client did no damage -        if( !self->credits[ i ] ) -          continue; - -        //nothing left to claim -        if( !unclaimedFrags ) -          break; - -        percentDamage = (float)self->credits[ i ] / totalDamage; -         if( percentDamage > 0 && percentDamage < 1) -         { -            player->client->pers.statscounters.assists++; -            level.alienStatsCounters.assists++; -         } -     -        frags = (int)floor( humanValue * percentDamage); - -        if( frags > 0 ) -        { -          //add kills -          G_AddCreditToClient( player->client, frags, qtrue ); - -          //can't revist this account later -          self->credits[ i ] = 0; - -          //reduce frags left to be claimed -          unclaimedFrags -= frags; -        } -      } - -      //there are frags still to be claimed -      if( unclaimedFrags ) -      { -        //the clients remaining at this point do not -        //have enough credit to claim even one frag -        //so simply give the top <unclaimedFrags> clients -        //a frag each - -        for( i = 0; i < unclaimedFrags; i++ ) -        { -          int maximum = 0; -          int topClient = 0; - -          for( j = 0; j < MAX_CLIENTS; j++ ) -          { -            //this client did no damage -            if( !self->credits[ j ] ) -              continue; - -            if( self->credits[ j ] > maximum ) -            { -              maximum = self->credits[ j ]; -              topClient = j; -            } -          } - -          if( maximum > 0 ) -          { -            player = g_entities + topClient; - -            //add kills -            G_AddCreditToClient( player->client, 1, qtrue ); - -            //can't revist this account again -            self->credits[ topClient ] = 0; -          } -        } -      } -    } -  } +  // give credits for killing this player +  G_RewardAttackers( self );    ScoreboardMessage( self );    // show scores @@ -585,29 +333,28 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int      if( client->pers.connected != CON_CONNECTED )        continue; -    if( client->sess.sessionTeam != TEAM_SPECTATOR ) +    if( client->sess.spectatorState == SPECTATOR_NOT )        continue;      if( client->sess.spectatorClient == self->s.number )        ScoreboardMessage( g_entities + i );    } -finish_dying: // from MOD_SLAP - -  VectorCopy( self->s.origin, self->client->pers.lastDeathLocation ); +  VectorCopy( self->r.currentOrigin, self->client->pers.lastDeathLocation );    self->takedamage = qfalse; // can still be gibbed    self->s.weapon = WP_NONE; -  self->r.contents = CONTENTS_CORPSE; +  if( self->client->noclip ) +    self->client->cliprcontents = CONTENTS_CORPSE; +  else +    self->r.contents = CONTENTS_CORPSE; -  self->s.angles[ PITCH ] = 0; -  self->s.angles[ ROLL ] = 0; -  self->s.angles[ YAW ] = self->s.apos.trBase[ YAW ]; +  self->client->ps.viewangles[ PITCH ] = 0; // zomg +  self->client->ps.viewangles[ YAW ] = self->s.apos.trBase[ YAW ]; +  self->client->ps.viewangles[ ROLL ] = 0;    LookAtKiller( self, inflictor, attacker ); -  VectorCopy( self->s.angles, self->client->ps.viewangles ); -    self->s.loopSound = 0;    self->r.maxs[ 2 ] = -8; @@ -674,57 +421,55 @@ finish_dying: // from MOD_SLAP    }    trap_LinkEntity( self ); -} - -////////TA: locdamage +  self->client->pers.infoChangeTime = level.time; +}  /*  =============== -G_ParseArmourScript +G_ParseDmgScript  ===============  */ -void G_ParseArmourScript( char *buf, int upgrade ) +static int G_ParseDmgScript( damageRegion_t *regions, char *buf )  {    char  *token; +  float angleSpan, heightSpan;    int   count; -  count = 0; - -  while( 1 ) +  for( count = 0; ; count++ )    {      token = COM_Parse( &buf ); - -    if( !token[0] ) +    if( !token[ 0 ] )        break;      if( strcmp( token, "{" ) )      { -      G_Printf( "Missing { in armour file\n" ); +      COM_ParseError( "Missing {" );        break;      } -    if( count == MAX_ARMOUR_REGIONS ) +    if( count >= MAX_DAMAGE_REGIONS )      { -      G_Printf( "Max armour regions exceeded in locdamage file\n" ); +      COM_ParseError( "Max damage regions exceeded" );        break;      } -    //default -    g_armourRegions[ upgrade ][ count ].minHeight = 0.0; -    g_armourRegions[ upgrade ][ count ].maxHeight = 1.0; -    g_armourRegions[ upgrade ][ count ].minAngle = 0; -    g_armourRegions[ upgrade ][ count ].maxAngle = 360; -    g_armourRegions[ upgrade ][ count ].modifier = 1.0; -    g_armourRegions[ upgrade ][ count ].crouch = qfalse; +    // defaults +    regions[ count ].name[ 0 ] = '\0'; +    regions[ count ].minHeight = 0.0f; +    regions[ count ].maxHeight = 1.0f; +    regions[ count ].minAngle = 0.0f; +    regions[ count ].maxAngle = 360.0f; +    regions[ count ].modifier = 1.0f; +    regions[ count ].crouch = qfalse;      while( 1 )      {        token = COM_ParseExt( &buf, qtrue ); -      if( !token[0] ) +      if( !token[ 0 ] )        { -        G_Printf( "Unexpected end of armour file\n" ); +        COM_ParseError( "Unexpected end of file" );          break;        } @@ -732,172 +477,260 @@ void G_ParseArmourScript( char *buf, int upgrade )        {          break;        } +      else if( !strcmp( token, "name" ) ) +      { +        token = COM_ParseExt( &buf, qfalse ); +        if( token[ 0 ] ) +          Q_strncpyz( regions[ count ].name, token, +                      sizeof( regions[ count ].name ) ); +      }        else if( !strcmp( token, "minHeight" ) )        {          token = COM_ParseExt( &buf, qfalse ); - -        if ( !token[0] ) +        if( !token[ 0 ] )            strcpy( token, "0" ); - -        g_armourRegions[ upgrade ][ count ].minHeight = atof( token ); +        regions[ count ].minHeight = atof( token );        }        else if( !strcmp( token, "maxHeight" ) )        {          token = COM_ParseExt( &buf, qfalse ); - -        if ( !token[0] ) +        if( !token[ 0 ] )            strcpy( token, "100" ); - -        g_armourRegions[ upgrade ][ count ].maxHeight = atof( token ); +        regions[ count ].maxHeight = atof( token );        }        else if( !strcmp( token, "minAngle" ) )        {          token = COM_ParseExt( &buf, qfalse ); - -        if ( !token[0] ) +        if( !token[ 0 ] )            strcpy( token, "0" ); - -        g_armourRegions[ upgrade ][ count ].minAngle = atoi( token ); +        regions[ count ].minAngle = atoi( token );        }        else if( !strcmp( token, "maxAngle" ) )        {          token = COM_ParseExt( &buf, qfalse ); - -        if ( !token[0] ) +        if( !token[ 0 ] )            strcpy( token, "360" ); - -        g_armourRegions[ upgrade ][ count ].maxAngle = atoi( token ); +        regions[ count ].maxAngle = atoi( token );        }        else if( !strcmp( token, "modifier" ) )        {          token = COM_ParseExt( &buf, qfalse ); - -        if ( !token[0] ) +        if( !token[ 0 ] )            strcpy( token, "1.0" ); - -        g_armourRegions[ upgrade ][ count ].modifier = atof( token ); +        regions[ count ].modifier = atof( token );        }        else if( !strcmp( token, "crouch" ) )        { -        g_armourRegions[ upgrade ][ count ].crouch = qtrue; +        regions[ count ].crouch = qtrue; +      } +      else +      { +        COM_ParseWarning("Unknown token \"%s\"", token);        }      } - -    g_numArmourRegions[ upgrade ]++; -    count++; +     +    // Angle portion covered +    angleSpan = regions[ count ].maxAngle - regions[ count ].minAngle; +    if( angleSpan < 0.0f ) +      angleSpan += 360.0f; +    angleSpan /= 360.0f; +           +    // Height portion covered +    heightSpan = regions[ count ].maxHeight - regions[ count ].minHeight; +    if( heightSpan < 0.0f ) +      heightSpan = -heightSpan; +    if( heightSpan > 1.0f ) +      heightSpan = 1.0f; + +    regions[ count ].area = angleSpan * heightSpan; +    if( !regions[ count ].area ) +      regions[ count ].area = 0.00001f;    } +   +  return count;  } -  /* -=============== -G_ParseDmgScript -=============== +============ +GetRegionDamageModifier +============  */ -void G_ParseDmgScript( char *buf, int class ) +static float GetRegionDamageModifier( gentity_t *targ, int class, int piece )  { -  char  *token; -  int   count; - -  count = 0; - -  while( 1 ) +  damageRegion_t *regions, *overlap; +  float modifier = 0.0f, areaSum = 0.0f; +  int j, i; +  qboolean crouch; +         +  crouch = targ->client->ps.pm_flags & PMF_DUCKED; +  overlap = &g_damageRegions[ class ][ piece ]; + +  if( g_debugDamage.integer > 2 ) +    G_Printf( "GetRegionDamageModifier():\n" +              ".   bodyRegion = [%d %d %f %f] (%s)\n" +              ".   modifier = %f\n", +              overlap->minAngle, overlap->maxAngle, +              overlap->minHeight, overlap->maxHeight, +              overlap->name, overlap->modifier ); + +  // Find the armour layer modifier, assuming that none of the armour regions +  // overlap and that any areas that are not covered have a modifier of 1.0 +  for( j = UP_NONE + 1; j < UP_NUM_UPGRADES; j++ )    { -    token = COM_Parse( &buf ); - -    if( !token[0] ) -      break; - -    if( strcmp( token, "{" ) ) -    { -      G_Printf( "Missing { in locdamage file\n" ); -      break; -    } - -    if( count == MAX_LOCDAMAGE_REGIONS ) -    { -      G_Printf( "Max damage regions exceeded in locdamage file\n" ); -      break; -    } - -    //default -    g_damageRegions[ class ][ count ].minHeight = 0.0; -    g_damageRegions[ class ][ count ].maxHeight = 1.0; -    g_damageRegions[ class ][ count ].minAngle = 0; -    g_damageRegions[ class ][ count ].maxAngle = 360; -    g_damageRegions[ class ][ count ].modifier = 1.0; -    g_damageRegions[ class ][ count ].crouch = qfalse; - -    while( 1 ) +    if( !BG_InventoryContainsUpgrade( j, targ->client->ps.stats ) || +        !g_numArmourRegions[ j ] ) +      continue; +    regions = g_armourRegions[ j ]; +       +    for( i = 0; i < g_numArmourRegions[ j ]; i++ )      { -      token = COM_ParseExt( &buf, qtrue ); - -      if( !token[0] ) +      float overlapMaxA, regionMinA, regionMaxA, angleSpan, heightSpan, area; +     +      if( regions[ i ].crouch != crouch ) +        continue; + +      // Convert overlap angle to 0 to max     +      overlapMaxA = overlap->maxAngle - overlap->minAngle; +      if( overlapMaxA < 0.0f ) +        overlapMaxA += 360.0f; + +      // Convert region angles to match overlap +      regionMinA = regions[ i ].minAngle - overlap->minAngle; +      if( regionMinA < 0.0f ) +        regionMinA += 360.0f; +      regionMaxA = regions[ i ].maxAngle - overlap->minAngle; +      if( regionMaxA < 0.0f ) +        regionMaxA += 360.0f; + +      // Overlapping Angle portion +      if( regionMinA <= regionMaxA )        { -        G_Printf( "Unexpected end of locdamage file\n" ); -        break; +        angleSpan = 0.0f; +        if( regionMinA < overlapMaxA ) +        { +          if( regionMaxA > overlapMaxA ) +            regionMaxA = overlapMaxA; +          angleSpan = regionMaxA - regionMinA; +        }        } - -      if( !Q_stricmp( token, "}" ) ) +      else        { -        break; +        if( regionMaxA > overlapMaxA ) +          regionMaxA = overlapMaxA; +        angleSpan = regionMaxA; +        if( regionMinA < overlapMaxA ) +          angleSpan += overlapMaxA - regionMinA;        } -      else if( !strcmp( token, "minHeight" ) ) -      { -        token = COM_ParseExt( &buf, qfalse ); +      angleSpan /= 360.0f; +       +      // Overlapping height portion +      heightSpan = MIN( overlap->maxHeight, regions[ i ].maxHeight ) - +                   MAX( overlap->minHeight, regions[ i ].minHeight ); +      if( heightSpan < 0.0f ) +        heightSpan = 0.0f; +      if( heightSpan > 1.0f ) +        heightSpan = 1.0f; +       +      if( g_debugDamage.integer > 2 ) +        G_Printf( ".   armourRegion = [%d %d %f %f] (%s)\n" +                  ".   .   modifier = %f\n" +                  ".   .   angleSpan = %f\n" +                  ".   .   heightSpan = %f\n", +                  regions[ i ].minAngle, regions[ i ].maxAngle, +                  regions[ i ].minHeight, regions[ i ].maxHeight, +                  regions[ i ].name, regions[ i ].modifier, +                  angleSpan, heightSpan ); +             +      areaSum += area = angleSpan * heightSpan; +      modifier += regions[ i ].modifier * area; +    } +  } -        if ( !token[0] ) -          strcpy( token, "0" ); +  if( g_debugDamage.integer > 2 ) +    G_Printf( ".   areaSum = %f\n" +              ".   armourModifier = %f\n", areaSum, modifier ); -        g_damageRegions[ class ][ count ].minHeight = atof( token ); -      } -      else if( !strcmp( token, "maxHeight" ) ) -      { -        token = COM_ParseExt( &buf, qfalse ); +  return overlap->modifier * ( overlap->area + modifier - areaSum ); +} -        if ( !token[0] ) -          strcpy( token, "100" ); +/* +============ +GetNonLocDamageModifier +============ +*/ +static float GetNonLocDamageModifier( gentity_t *targ, int class ) +{ +  float modifier = 0.0f, area = 0.0f, scale = 0.0f; +  int i; +  qboolean crouch; + +  // For every body region, use stretch-armor formula to apply armour modifier +  // for any overlapping area that armour shares with the body region +  crouch = targ->client->ps.pm_flags & PMF_DUCKED; +  for( i = 0; i < g_numDamageRegions[ class ]; i++ ) +  { +    damageRegion_t *region; -        g_damageRegions[ class ][ count ].maxHeight = atof( token ); -      } -      else if( !strcmp( token, "minAngle" ) ) -      { -        token = COM_ParseExt( &buf, qfalse ); +    region = &g_damageRegions[ class ][ i ]; -        if ( !token[0] ) -          strcpy( token, "0" ); +    if( region->crouch != crouch ) +      continue; -        g_damageRegions[ class ][ count ].minAngle = atoi( token ); -      } -      else if( !strcmp( token, "maxAngle" ) ) -      { -        token = COM_ParseExt( &buf, qfalse ); +    modifier += GetRegionDamageModifier( targ, class, i ); -        if ( !token[0] ) -          strcpy( token, "360" ); +    scale += region->modifier * region->area; +    area += region->area; -        g_damageRegions[ class ][ count ].maxAngle = atoi( token ); -      } -      else if( !strcmp( token, "modifier" ) ) -      { -        token = COM_ParseExt( &buf, qfalse ); +  } -        if ( !token[0] ) -          strcpy( token, "1.0" ); +  modifier = !scale ? 1.0f : 1.0f + ( modifier / scale - 1.0f ) * area; +   +  if( g_debugDamage.integer > 1 ) +    G_Printf( "GetNonLocDamageModifier() modifier:%f, area:%f, scale:%f\n", +              modifier, area, scale ); +       +  return modifier; +} -        g_damageRegions[ class ][ count ].modifier = atof( token ); -      } -      else if( !strcmp( token, "crouch" ) ) -      { -        g_damageRegions[ class ][ count ].crouch = qtrue; -      } -    } +/* +============ +GetPointDamageModifier + +Returns the damage region given an angle and a height proportion +============ +*/ +static float GetPointDamageModifier( gentity_t *targ, damageRegion_t *regions, +                                     int len, float angle, float height ) +{ +  float modifier = 1.0f; +  int i; -    g_numDamageRegions[ class ]++; -    count++; +  for( i = 0; i < len; i++ ) +  { +    if( regions[ i ].crouch != ( targ->client->ps.pm_flags & PMF_DUCKED ) ) +      continue; + +    // Angle must be within range +    if( ( regions[ i ].minAngle <= regions[ i ].maxAngle && +          ( angle < regions[ i ].minAngle || +            angle > regions[ i ].maxAngle ) ) || +        ( regions[ i ].minAngle > regions[ i ].maxAngle && +          angle > regions[ i ].maxAngle && angle < regions[ i ].minAngle ) ) +      continue; +     +    // Height must be within range +    if( height < regions[ i ].minHeight || height > regions[ i ].maxHeight ) +      continue;       +       +    modifier *= regions[ i ].modifier;    } -} +  if( g_debugDamage.integer ) +    G_Printf( "GetDamageRegionModifier(angle = %f, height = %f): %f\n", +              angle, height, modifier ); + +  return modifier; +}  /*  ============ @@ -906,34 +739,33 @@ G_CalcDamageModifier  */  static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *attacker, int class, int dflags )  { -  vec3_t  targOrigin; -  vec3_t  bulletPath; -  vec3_t  bulletAngle; -  vec3_t  pMINUSfloor, floor, normal; - -  float   clientHeight, hitRelative, hitRatio; -  int     bulletRotation, clientRotation, hitRotation; -  float   modifier = 1.0f; -  int     i, j; +  vec3_t  targOrigin, bulletPath, bulletAngle, pMINUSfloor, floor, normal; +  float   clientHeight, hitRelative, hitRatio, modifier; +  int     hitRotation, i;    if( point == NULL )      return 1.0f; +  // Don't need to calculate angles and height for non-locational damage +  if( dflags & DAMAGE_NO_LOCDAMAGE ) +    return GetNonLocDamageModifier( targ, class ); +   +  // Get the point location relative to the floor under the target    if( g_unlagged.integer && targ->client && targ->client->unlaggedCalc.used )      VectorCopy( targ->client->unlaggedCalc.origin, targOrigin );    else      VectorCopy( targ->r.currentOrigin, targOrigin ); -  clientHeight = targ->r.maxs[ 2 ] - targ->r.mins[ 2 ]; - -  if( targ->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) -    VectorCopy( targ->client->ps.grapplePoint, normal ); -  else -    VectorSet( normal, 0, 0, 1 ); - +  BG_GetClientNormal( &targ->client->ps, normal );    VectorMA( targOrigin, targ->r.mins[ 2 ], normal, floor );    VectorSubtract( point, floor, pMINUSfloor ); +  // Get the proportion of the target height where the hit landed +  clientHeight = targ->r.maxs[ 2 ] - targ->r.mins[ 2 ]; + +  if( !clientHeight ) +    clientHeight = 1.0f; +    hitRelative = DotProduct( normal, pMINUSfloor ) / VectorLength( normal );    if( hitRelative < 0.0f ) @@ -944,105 +776,25 @@ static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *att    hitRatio = hitRelative / clientHeight; -  VectorSubtract( targOrigin, point, bulletPath ); +  // Get the yaw of the attack relative to the target's view yaw +  VectorSubtract( point, targOrigin, bulletPath );    vectoangles( bulletPath, bulletAngle ); -  clientRotation = targ->client->ps.viewangles[ YAW ]; -  bulletRotation = bulletAngle[ YAW ]; - -  hitRotation = abs( clientRotation - bulletRotation ); +  hitRotation = AngleNormalize360( targ->client->ps.viewangles[ YAW ] - +                                   bulletAngle[ YAW ] ); -  hitRotation = hitRotation % 360; // Keep it in the 0-359 range +  // Get modifiers from the target's damage regions +  modifier = GetPointDamageModifier( targ, g_damageRegions[ class ], +                                     g_numDamageRegions[ class ], +                                     hitRotation, hitRatio ); -  if( dflags & DAMAGE_NO_LOCDAMAGE ) +  for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )    { -    for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) +    if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) )      { -      float totalModifier = 0.0f; -      float averageModifier = 1.0f; - -      //average all of this upgrade's armour regions together -      if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) ) -      { -        for( j = 0; j < g_numArmourRegions[ i ]; j++ ) -          totalModifier += g_armourRegions[ i ][ j ].modifier; - -        if( g_numArmourRegions[ i ] ) -          averageModifier = totalModifier / g_numArmourRegions[ i ]; -        else -          averageModifier = 1.0f; -      } - -      modifier *= averageModifier; -    } -  } -  else -  { -    if( attacker && attacker->client ) -    { -      attacker->client->pers.statscounters.hitslocational++; -      level.alienStatsCounters.hitslocational++; -    } -    for( i = 0; i < g_numDamageRegions[ class ]; i++ ) -    { -      qboolean rotationBound; - -      if( g_damageRegions[ class ][ i ].minAngle > -          g_damageRegions[ class ][ i ].maxAngle ) -      { -        rotationBound = ( hitRotation >= g_damageRegions[ class ][ i ].minAngle && -                          hitRotation <= 360 ) || ( hitRotation >= 0 && -                          hitRotation <= g_damageRegions[ class ][ i ].maxAngle ); -      } -      else -      { -        rotationBound = ( hitRotation >= g_damageRegions[ class ][ i ].minAngle && -                          hitRotation <= g_damageRegions[ class ][ i ].maxAngle ); -      } - -      if( rotationBound && -          hitRatio >= g_damageRegions[ class ][ i ].minHeight && -          hitRatio <= g_damageRegions[ class ][ i ].maxHeight && -          ( g_damageRegions[ class ][ i ].crouch == -            ( targ->client->ps.pm_flags & PMF_DUCKED ) ) ) -        modifier *= g_damageRegions[ class ][ i ].modifier; -    }     -     -    if( attacker && attacker->client && modifier == 2 ) -    { -      attacker->client->pers.statscounters.headshots++; -      level.alienStatsCounters.headshots++; -    } - -    for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) -    { -      if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) ) -      { -        for( j = 0; j < g_numArmourRegions[ i ]; j++ ) -        { -          qboolean rotationBound; - -          if( g_armourRegions[ i ][ j ].minAngle > -              g_armourRegions[ i ][ j ].maxAngle ) -          { -            rotationBound = ( hitRotation >= g_armourRegions[ i ][ j ].minAngle && -                              hitRotation <= 360 ) || ( hitRotation >= 0 && -                              hitRotation <= g_armourRegions[ i ][ j ].maxAngle ); -          } -          else -          { -            rotationBound = ( hitRotation >= g_armourRegions[ i ][ j ].minAngle && -                              hitRotation <= g_armourRegions[ i ][ j ].maxAngle ); -          } - -          if( rotationBound && -              hitRatio >= g_armourRegions[ i ][ j ].minHeight && -              hitRatio <= g_armourRegions[ i ][ j ].maxHeight && -              ( g_armourRegions[ i ][ j ].crouch == -                ( targ->client->ps.pm_flags & PMF_DUCKED ) ) ) -            modifier *= g_armourRegions[ i ][ j ].modifier; -        } -      } +      modifier *= GetPointDamageModifier( targ, g_armourRegions[ i ], +                                          g_numArmourRegions[ i ], +                                          hitRotation, hitRatio );      }    } @@ -1062,37 +814,41 @@ void G_InitDamageLocations( void )    int           i;    int           len;    fileHandle_t  fileHandle; -  char          buffer[ MAX_LOCDAMAGE_TEXT ]; +  char          buffer[ MAX_DAMAGE_REGION_TEXT ];    for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )    { -    modelName = BG_FindModelNameForClass( i ); -    Com_sprintf( filename, sizeof( filename ), "models/players/%s/locdamage.cfg", modelName ); +    modelName = BG_ClassConfig( i )->modelName; +    Com_sprintf( filename, sizeof( filename ), +                 "models/players/%s/locdamage.cfg", modelName );      len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ );      if ( !fileHandle )      { -      G_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); +      G_Printf( S_COLOR_RED "file not found: %s\n", filename );        continue;      } -    if( len >= MAX_LOCDAMAGE_TEXT ) +    if( len >= MAX_DAMAGE_REGION_TEXT )      { -      G_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_LOCDAMAGE_TEXT ) ); +      G_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", +                filename, len, MAX_DAMAGE_REGION_TEXT );        trap_FS_FCloseFile( fileHandle );        continue;      } +    COM_BeginParseSession( filename ); +      trap_FS_Read( buffer, len, fileHandle );      buffer[len] = 0;      trap_FS_FCloseFile( fileHandle ); -    G_ParseDmgScript( buffer, i ); +    g_numDamageRegions[ i ] = G_ParseDmgScript( g_damageRegions[ i ], buffer );    }    for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )    { -    modelName = BG_FindNameForUpgrade( i ); +    modelName = BG_Upgrade( i )->name;      Com_sprintf( filename, sizeof( filename ), "armour/%s.armour", modelName );      len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ ); @@ -1101,23 +857,24 @@ void G_InitDamageLocations( void )      if ( !fileHandle )        continue; -    if( len >= MAX_LOCDAMAGE_TEXT ) +    if( len >= MAX_DAMAGE_REGION_TEXT )      { -      G_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_LOCDAMAGE_TEXT ) ); +      G_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", +                filename, len, MAX_DAMAGE_REGION_TEXT );        trap_FS_FCloseFile( fileHandle );        continue;      } +    COM_BeginParseSession( filename ); +      trap_FS_Read( buffer, len, fileHandle );      buffer[len] = 0;      trap_FS_FCloseFile( fileHandle ); -    G_ParseArmourScript( buffer, i ); +    g_numArmourRegions[ i ] = G_ParseDmgScript( g_armourRegions[ i ], buffer );    }  } -////////TA: locdamage -  /*  ============ @@ -1138,16 +895,16 @@ inflictor, attacker, dir, and point can be NULL for environmental effects  dflags    these flags are used to control how T_Damage works    DAMAGE_RADIUS     damage was indirect (from a nearby explosion)    DAMAGE_NO_ARMOR     armor does not protect from this damage -  DAMAGE_KNOCKBACK      affect velocity, not just view angles -  DAMAGE_NO_PROTECTION  kills godmode, armor, everything +  DAMAGE_NO_KNOCKBACK   do not affect velocity, just view angles +  DAMAGE_NO_PROTECTION  kills everything except godmode  ============  */ -//TA: team is the team that is immune to this damage +// team is the team that is immune to this damage  void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,           vec3_t dir, vec3_t point, int damage, int dflags, int mod, int team )  { -  if( targ->client && ( team != targ->client->ps.stats[ STAT_PTEAM ] ) ) +  if( targ->client && ( team != targ->client->ps.stats[ STAT_TEAM ] ) )      G_Damage( targ, inflictor, attacker, dir, point, damage, dflags, mod );  } @@ -1156,18 +913,11 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,  {    gclient_t *client;    int     take; -  int     save;    int     asave = 0; -  int     knockback = 0; -  float damagemodifier=0.0; -  int takeNoOverkill; - -  if( !targ->takedamage ) -    return; +  int     knockback; -  // the intermission has allready been qualified for, so don't -  // allow any extra scoring -  if( level.intermissionQueued ) +  // Can't deal damage sometimes +  if( !targ->takedamage || targ->health <= 0 || level.intermissionQueued )      return;    if( !inflictor ) @@ -1176,9 +926,6 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,    if( !attacker )      attacker = &g_entities[ ENTITYNUM_WORLD ]; -  if( attacker->client && attacker->client->pers.paused ) -    return; -    // shootable doors / buttons don't actually have any health    if( targ->s.eType == ET_MOVER )    { @@ -1190,42 +937,41 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,    }    client = targ->client; - -  if( client ) -  { -    if( client->noclip && !g_devmapNoGod.integer) -      return; -    if( client->pers.paused ) -      return; -  } +  if( client && client->noclip ) +    return;    if( !dir ) -    dflags &= ~DAMAGE_KNOCKBACK; +    dflags |= DAMAGE_NO_KNOCKBACK;    else      VectorNormalize( dir ); -  if( dflags & DAMAGE_KNOCKBACK ) +  knockback = damage; + +  if( inflictor->s.weapon != WP_NONE ) +  { +    knockback = (int)( (float)knockback * +      BG_Weapon( inflictor->s.weapon )->knockbackScale ); +  } + +  if( targ->client )    { -    knockback = damage; +    knockback = (int)( (float)knockback * +      BG_Class( targ->client->ps.stats[ STAT_CLASS ] )->knockbackScale ); +  } -    if( inflictor->s.weapon != WP_NONE ) -    { -      knockback = (int)( (float)knockback * -        BG_FindKnockbackScaleForWeapon( inflictor->s.weapon ) ); -    } +  // Too much knockback from falling really far makes you "bounce" and  +  //  looks silly. However, none at all also looks bad. Cap it. +  if( mod == MOD_FALLING && knockback > 50 )  +    knockback = 50; -    if( targ->client ) -    { -      knockback = (int)( (float)knockback * -        BG_FindKnockbackScaleForClass( targ->client->ps.stats[ STAT_PCLASS ] ) ); -    } +  if( knockback > 200 ) +    knockback = 200; -    if( knockback > 200 ) -      knockback = 200; +  if( targ->flags & FL_NO_KNOCKBACK ) +    knockback = 0; -    if( targ->flags & FL_NO_KNOCKBACK ) -      knockback = 0; -  } +  if( dflags & DAMAGE_NO_KNOCKBACK ) +    knockback = 0;    // figure momentum add, even if the damage won't be taken    if( knockback && targ->client ) @@ -1256,6 +1002,18 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,      }    } +  // check for godmode +  if( targ->flags & FL_GODMODE ) +    return; + +  // don't do friendly fire on movement attacks +  if( ( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE || +        mod == MOD_LEVEL4_CRUSH ) && +      targ->s.eType == ET_BUILDABLE && targ->buildableTeam == TEAM_ALIENS ) +  { +    return; +  } +    // check for completely getting out of the damage    if( !( dflags & DAMAGE_NO_PROTECTION ) )    { @@ -1264,8 +1022,14 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,      // if the attacker was on the same team      if( targ != attacker && OnSameTeam( targ, attacker ) )      { +      // don't do friendly fire on movement attacks +      if( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE || +          mod == MOD_LEVEL4_CRUSH ) +        return; + +      // if dretchpunt is enabled and this is a dretch, do dretchpunt instead of damage        if( g_dretchPunt.integer && -        targ->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL0 ) +          targ->client->ps.stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL0 )        {          vec3_t dir, push; @@ -1275,67 +1039,33 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,          push[2] = 64.0f;          VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity );          return; -      }  -      else if(mod == MOD_LEVEL4_CHARGE || mod == MOD_LEVEL3_POUNCE ) -      { // don't do friendly fire on movement attacks -        if( g_friendlyFireMovementAttacks.value <= 0 || ( g_friendlyFire.value<=0 && g_friendlyFireAliens.value<=0 ) ) -          return; -        else if( g_friendlyFireMovementAttacks.value > 0 && g_friendlyFireMovementAttacks.value < 1 ) -         damage =(int)(0.5 + g_friendlyFireMovementAttacks.value * (float) damage); -      } -      else if( g_friendlyFire.value <=0) -      { -        if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -        { -          if(g_friendlyFireHumans.value<=0) -            return; -          else if( g_friendlyFireHumans.value > 0 && g_friendlyFireHumans.value < 1 ) -            damage =(int)(0.5 + g_friendlyFireHumans.value * (float) damage);        -        } -        if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -        { -          if(g_friendlyFireAliens.value==0) -            return; -          else if( g_friendlyFireAliens.value > 0 && g_friendlyFireAliens.value < 1 ) -           damage =(int)(0.5 + g_friendlyFireAliens.value * (float) damage); -        }        } -      else if( g_friendlyFire.value > 0 && g_friendlyFire.value < 1 ) + +      // check if friendly fire has been disabled +      if( !g_friendlyFire.integer )        { -        damage =(int)(0.5 + g_friendlyFire.value * (float) damage); +        return;        }      } -    // If target is buildable on the same team as the attacking client      if( targ->s.eType == ET_BUILDABLE && attacker->client && -        targ->biteam == attacker->client->pers.teamSelection ) +        mod != MOD_DECONSTRUCT && mod != MOD_SUICIDE && +        mod != MOD_REPLACE && mod != MOD_NOCREEP )      { -      if(mod == MOD_LEVEL4_CHARGE || mod == MOD_LEVEL3_POUNCE )  -      { -         if(g_friendlyFireMovementAttacks.value <= 0) -           return; -         else if(g_friendlyFireMovementAttacks.value > 0 && g_friendlyFireMovementAttacks.value < 1) -           damage =(int)(0.5 + g_friendlyFireMovementAttacks.value * (float) damage); -      } -      if( g_friendlyBuildableFire.value <= 0 ) +      if( targ->buildableTeam == attacker->client->pers.teamSelection && +        !g_friendlyBuildableFire.integer )        {          return;        } -      else if( g_friendlyBuildableFire.value > 0 && g_friendlyBuildableFire.value < 1 ) + +      // base is under attack warning if DCC'd +      if( targ->buildableTeam == TEAM_HUMANS && G_FindDCC( targ ) && +          level.time > level.humanBaseAttackTimer )        { -         damage =(int)(0.5 + g_friendlyBuildableFire.value * (float) damage); +        level.humanBaseAttackTimer = level.time + DC_ATTACK_PERIOD; +        G_BroadcastEvent( EV_DCC_ATTACK, 0 );        }      } - -    // check for godmode -    if ( targ->flags & FL_GODMODE && !g_devmapNoGod.integer) -      return; - -    if( level.paused ) -      return; -     -    if(targ->s.eType == ET_BUILDABLE && g_cheats.integer && g_devmapNoStructDmg.integer) -      return;    }    // add to the attacker's hit counter @@ -1350,7 +1080,6 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,    }    take = damage; -  save = 0;    // add to the damage inflicted on a player this frame    // the total will be turned into screen blends and view angle kicks @@ -1380,23 +1109,21 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,      // set the last client who damaged the target      targ->client->lasthurt_client = attacker->s.number;      targ->client->lasthurt_mod = mod; -     -    damagemodifier = G_CalcDamageModifier( point, targ, attacker, client->ps.stats[ STAT_PCLASS ], dflags ); -    take = (int)( (float)take * damagemodifier ); +    take = (int)( take * G_CalcDamageModifier( point, targ, attacker, +                                               client->ps.stats[ STAT_CLASS ], +                                               dflags ) + 0.5f );      //if boosted poison every attack      if( attacker->client && attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED )      { -      if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && -          !( targ->client->ps.stats[ STAT_STATE ] & SS_POISONED ) && -          mod != MOD_LEVEL2_ZAP && -          targ->client->poisonImmunityTime < level.time ) +      if( targ->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && +          mod != MOD_LEVEL2_ZAP && mod != MOD_POISON && +          mod != MOD_LEVEL1_PCLOUD && mod != MOD_HSPAWN && +          mod != MOD_ASPAWN && targ->client->poisonImmunityTime < level.time )        {          targ->client->ps.stats[ STAT_STATE ] |= SS_POISONED;          targ->client->lastPoisonTime = level.time;          targ->client->lastPoisonClient = attacker; -        attacker->client->pers.statscounters.repairspoisons++; -        level.alienStatsCounters.repairspoisons++;        }      }    } @@ -1410,90 +1137,23 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,        targ->health, take, asave );    } -  takeNoOverkill = take; -  if( takeNoOverkill > targ->health )  -  { -    if(targ->health > 0) -      takeNoOverkill = targ->health; -    else -      takeNoOverkill = 0; -  } - +  // do the damage    if( take )    { -    //Increment some stats counters -    if( attacker && attacker->client ) -    { -      if( targ->biteam == attacker->client->pers.teamSelection || OnSameTeam( targ, attacker ) )  -      { -        attacker->client->pers.statscounters.ffdmgdone += takeNoOverkill; -        if( attacker->client->pers.teamSelection == PTE_ALIENS )  -        { -          level.alienStatsCounters.ffdmgdone+=takeNoOverkill; -        } -        else if( attacker->client->pers.teamSelection == PTE_HUMANS ) -        { -          level.humanStatsCounters.ffdmgdone+=takeNoOverkill; -        } -      } -      else if( targ->s.eType == ET_BUILDABLE ) -      { -        attacker->client->pers.statscounters.structdmgdone += takeNoOverkill; -             -        if( attacker->client->pers.teamSelection == PTE_ALIENS )  -        { -          level.alienStatsCounters.structdmgdone+=takeNoOverkill; -        } -        else if( attacker->client->pers.teamSelection == PTE_HUMANS ) -        { -          level.humanStatsCounters.structdmgdone+=takeNoOverkill; -        } -             -        if( targ->health > 0 && ( targ->health - take ) <=0 ) -        { -          attacker->client->pers.statscounters.structskilled++; -          if( attacker->client->pers.teamSelection == PTE_ALIENS )  -          { -            level.alienStatsCounters.structskilled++; -          } -          else if( attacker->client->pers.teamSelection == PTE_HUMANS ) -          { -            level.humanStatsCounters.structskilled++; -          } -        } -      } -      else if( targ->client ) -      { -        attacker->client->pers.statscounters.dmgdone +=takeNoOverkill; -        attacker->client->pers.statscounters.hits++; -        if( attacker->client->pers.teamSelection == PTE_ALIENS )  -        { -          level.alienStatsCounters.dmgdone+=takeNoOverkill; -        } -        else if( attacker->client->pers.teamSelection == PTE_HUMANS ) -        { -          level.humanStatsCounters.dmgdone+=takeNoOverkill; -        } -      } -    } - -     -    //Do the damage      targ->health = targ->health - take;      if( targ->client ) +    {        targ->client->ps.stats[ STAT_HEALTH ] = targ->health; +      targ->client->pers.infoChangeTime = level.time; +    }      targ->lastDamageTime = level.time; +    targ->nextRegenTime = level.time + ALIEN_REGEN_DAMAGE_TIME; -    //TA: add to the attackers "account" on the target -    if( targ->client && attacker->client ) -    { -      if( attacker != targ && !OnSameTeam( targ, attacker ) ) -        targ->credits[ attacker->client->ps.clientNum ] += take; -      else if( attacker != targ && OnSameTeam( targ, attacker ) ) -        targ->client->tkcredits[ attacker->client->ps.clientNum ] += takeNoOverkill; -    } +    // add to the attackers "account" on the target +    if( attacker->client && attacker != targ ) +      targ->credits[ attacker->client->ps.clientNum ] += take;      if( targ->health <= 0 )      { @@ -1501,7 +1161,11 @@ void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,          targ->flags |= FL_NO_KNOCKBACK;        if( targ->health < -999 ) +      {          targ->health = -999; +        if( targ->client ) +          targ->client->ps.stats[ STAT_HEALTH ] = -999; +      }        targ->enemy = attacker;        targ->die( targ, inflictor, attacker, take, mod ); @@ -1570,8 +1234,6 @@ qboolean CanDamage( gentity_t *targ, vec3_t origin )    return qfalse;  } - -//TA:  /*  ============  G_SelectiveRadiusDamage @@ -1611,6 +1273,9 @@ qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float dama      if( !ent->takedamage )        continue; +    if( ent->flags & FL_NOTARGET ) +      continue; +      // find the distance from the edge of the bounding box      for( i = 0 ; i < 3 ; i++ )      { @@ -1628,14 +1293,16 @@ qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float dama      points = damage * ( 1.0 - dist / radius ); -    if( CanDamage( ent, origin ) ) +    if( CanDamage( ent, origin ) && ent->client && +        ent->client->ps.stats[ STAT_TEAM ] != team )      {        VectorSubtract( ent->r.currentOrigin, origin, dir );        // push the center of mass higher than the origin so players        // get knocked into the air more        dir[ 2 ] += 24; -      G_SelectiveDamage( ent, NULL, attacker, dir, origin, -          (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod, team ); +      hitClient = qtrue; +      G_Damage( ent, NULL, attacker, dir, origin, +          (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod );      }    } @@ -1649,7 +1316,7 @@ G_RadiusDamage  ============  */  qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, -                         float radius, gentity_t *ignore, int dflags, int mod ) +                         float radius, gentity_t *ignore, int mod )  {    float     points, dist;    gentity_t *ent; @@ -1705,8 +1372,9 @@ qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage,        // push the center of mass higher than the origin so players        // get knocked into the air more        dir[ 2 ] += 24; +      hitClient = qtrue;        G_Damage( ent, NULL, attacker, dir, origin, -          (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE|dflags, mod ); +          (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod );      }    } @@ -1714,46 +1382,71 @@ qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage,  }  /* -============ -G_Knockback -============ +================ +G_LogDestruction + +Log deconstruct/destroy events +================  */ -void G_Knockback( gentity_t *targ, vec3_t dir, int knockback ) +void G_LogDestruction( gentity_t *self, gentity_t *actor, int mod )  { -  if( knockback && targ->client ) -  { -    vec3_t  kvel; -    float   mass; +  buildFate_t fate; -    mass = 200; - -    // Halve knockback for bsuits -    if( targ->client && -        targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && -        BG_InventoryContainsUpgrade( UP_BATTLESUIT, targ->client->ps.stats ) ) -      mass += 400; - -    // Halve knockback for crouching players -    if(targ->client->ps.pm_flags&PMF_DUCKED) knockback /= 2; - -    VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel ); -    VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity ); +  switch( mod ) +  { +    case MOD_DECONSTRUCT: +      fate = BF_DECONSTRUCT; +      break; +    case MOD_REPLACE: +      fate = BF_REPLACE; +      break; +    case MOD_NOCREEP: +      fate = ( actor->client ) ? BF_UNPOWER : BF_AUTO; +      break; +    default: +      if( actor->client ) +      { +        if( actor->client->pers.teamSelection ==  +            BG_Buildable( self->s.modelindex )->team ) +        { +          fate = BF_TEAMKILL; +        } +        else +          fate = BF_DESTROY; +      } +      else +        fate = BF_AUTO; +      break; +  } +  G_BuildLogAuto( actor, self, fate ); -    // set the timer so that the other client can't cancel -    // out the movement immediately -    if( !targ->client->ps.pm_time ) -    { -      int   t; +  // don't log when marked structures are removed +  if( mod == MOD_REPLACE ) +    return; -      t = knockback * 2; -      if( t < 50 ) -        t = 50; +  G_LogPrintf( S_COLOR_YELLOW "Deconstruct: %d %d %s %s: %s %s by %s\n", +    (int)( actor - g_entities ), +    (int)( self - g_entities ), +    BG_Buildable( self->s.modelindex )->name, +    modNames[ mod ], +    BG_Buildable( self->s.modelindex )->humanName, +    mod == MOD_DECONSTRUCT ? "deconstructed" : "destroyed", +    actor->client ? actor->client->pers.netname : "<world>" ); + +  // No-power deaths for humans come after some minutes and it's confusing +  //  when the messages appear attributed to the deconner. Just don't print them. +  if( mod == MOD_NOCREEP && actor->client &&  +      actor->client->pers.teamSelection == TEAM_HUMANS ) +    return; -      if( t > 200 ) -        t = 200; -      targ->client->ps.pm_time = t; -      targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; -    } +  if( actor->client && actor->client->pers.teamSelection == +    BG_Buildable( self->s.modelindex )->team ) +  { +    G_TeamCommand( actor->client->ps.stats[ STAT_TEAM ], +      va( "print \"%s ^3%s^7 by %s\n\"", +        BG_Buildable( self->s.modelindex )->humanName, +        mod == MOD_DECONSTRUCT ? "DECONSTRUCTED" : "DESTROYED", +        actor->client->pers.netname ) );    } -} +} diff --git a/src/game/g_local.h b/src/game/g_local.h index df16a84..f894ec2 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,14 +17,14 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */  // g_local.h -- local definitions for game module -#include "../qcommon/q_shared.h" +#include "qcommon/q_shared.h"  #include "bg_public.h"  #include "g_public.h" @@ -37,11 +38,8 @@ typedef struct gclient_s gclient_t;  #define INFINITE      1000000  #define FRAMETIME     100         // msec -#define CARNAGE_REWARD_TIME 3000 -#define REWARD_SPRITE_TIME  2000  #define INTERMISSION_DELAY_TIME 1000 -#define SP_INTERMISSION_DELAY_TIME 5000  // gentity->flags  #define FL_GODMODE        0x00000010 @@ -53,19 +51,6 @@ typedef struct gclient_s gclient_t;  #define FL_NO_HUMANS      0x00004000  // spawn point just for bots  #define FL_FORCE_GESTURE  0x00008000  // spawn point just for bots -typedef struct -{ -  qboolean	isNB; -  float		Area; -  float		Height; -} noBuild_t; - -typedef struct -{ -  gentity_t	*Marker; -  vec3_t	Origin; -} nbMarkers_t; -  // movers are things like doors, plats, buttons, etc  typedef enum  { @@ -87,6 +72,12 @@ typedef enum  #define SP_PODIUM_MODEL   "models/mapobjects/podium/podium4.md3" +typedef struct gitem_s +{ +  int  ammo; // ammo held +  int  clips; // clips held +} gitem_t; +  //============================================================================  struct gentity_s @@ -99,6 +90,7 @@ struct gentity_s    //================================    struct gclient_s  *client;        // NULL if not a client +  gitem_t    item;    qboolean          inuse; @@ -134,7 +126,6 @@ struct gentity_s    int               soundLoop;    gentity_t         *parent;    gentity_t         *nextTrain; -  gentity_t         *prevTrain;    vec3_t            pos1, pos2;    float             rotatorAngle;    gentity_t         *clipBrush;     // clipping brush for model doors @@ -143,7 +134,6 @@ struct gentity_s    int               timestamp;      // body queue sinking, etc -  float             angle;          // set in editor, -1 = up, -2 = down    char              *target;    char              *targetname;    char              *team; @@ -172,7 +162,6 @@ struct gentity_s    void              (*die)( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod );    int               pain_debounce_time; -  int               fly_sound_debounce_time;  // wind tunnel    int               last_move_time;    int               health; @@ -181,12 +170,10 @@ struct gentity_s    qboolean          takedamage;    int               damage; -  int               dflags;    int               splashDamage; // quad will increase this without increasing radius    int               splashRadius;    int               methodOfDeath;    int               splashMethodOfDeath; -  int               chargeRepeat;    int               count; @@ -205,18 +192,18 @@ struct gentity_s    float             wait;    float             random; -  pTeam_t           stageTeam; +  team_t            stageTeam;    stage_t           stageStage; -  int               biteam;             // buildable item team +  team_t            buildableTeam;      // buildable item team    gentity_t         *parentNode;        // for creep and defence/spawn dependencies +  gentity_t         *rangeMarker;    qboolean          active;             // for power repeater, but could be useful elsewhere    qboolean          powered;            // for human buildables -  int               builtBy;            // clientNum of person that built this -  gentity_t         *dccNode;           // controlling dcc -  gentity_t         *overmindNode;      // controlling overmind -  qboolean          dcced;              // controlled by a dcc or not? +  struct namelog_s  *builtBy;           // person who built this +  int               dcc;                // number of controlling dccs    qboolean          spawned;            // whether or not this buildable has finished spawning +  int               shrunkTime;         // time when a barricade shrunk or zero    int               buildTime;          // when this buildable was built    int               animTime;           // last animation change    int               time1000;           // timer evaluated every second @@ -228,25 +215,20 @@ struct gentity_s    int               nextPhysicsTime;    // buildables don't need to check what they're sitting on                                          // every single frame.. so only do it periodically    int               clientSpawnTime;    // the time until this spawn can spawn a client -  qboolean          lev1Grabbed;        // for turrets interacting with lev1s -  int               lev1GrabTime;       // for turrets interacting with lev1s -  int               spawnBlockTime;    int               credits[ MAX_CLIENTS ];     // human credits for each client -  qboolean          creditsHash[ MAX_CLIENTS ]; // track who has claimed credit    int               killedBy;                   // clientNum of killer -  gentity_t         *targeted;          // true if the player is currently a valid target of a turret    vec3_t            turretAim;          // aim vector for turrets +  vec3_t            turretAimRate;      // track turn speed for norfenturrets +  int               turretSpinupTime;   // spinup delay for norfenturrets    vec4_t            animation;          // animated map objects -  gentity_t         *builder;           // occupant of this hovel -    qboolean          nonSegModel;        // this entity uses a nonsegmented player model    buildable_t       bTriggers[ BA_NUM_BUILDABLES ]; // which buildables are triggers -  pClass_t          cTriggers[ PCL_NUM_CLASSES ];   // which classes are triggers +  class_t           cTriggers[ PCL_NUM_CLASSES ];   // which classes are triggers    weapon_t          wTriggers[ WP_NUM_WEAPONS ];    // which weapons are triggers    upgrade_t         uTriggers[ UP_NUM_UPGRADES ];   // which upgrades are triggers @@ -255,11 +237,12 @@ struct gentity_s    int               suicideTime;                    // when the client will suicide    int               lastDamageTime; -   -  int               bdnumb;     // buildlog entry ID -   -  // For nobuild! -  noBuild_t	    noBuild; +  int               nextRegenTime; + +  qboolean          pointAgainstWorld;              // don't use the bbox for map collisions + +  int               buildPointZone;                 // index for zone +  int               usesBuildPointZone;             // does it use a zone?  };  typedef enum @@ -269,125 +252,46 @@ typedef enum    CON_CONNECTED  } clientConnected_t; -typedef enum -{ -  SPECTATOR_NOT, -  SPECTATOR_FREE, -  SPECTATOR_LOCKED, -  SPECTATOR_FOLLOW, -  SPECTATOR_SCOREBOARD -} spectatorState_t; - -typedef enum -{ -  TEAM_BEGIN,   // Beginning a team game, spawn at base -  TEAM_ACTIVE   // Now actively playing -} playerTeamStateState_t; - -typedef struct -{ -  playerTeamStateState_t  state; - -  int     location; - -  int     captures; -  int     basedefense; -  int     carrierdefense; -  int     flagrecovery; -  int     fragcarrier; -  int     assists; - -  float   lasthurtcarrier; -  float   lastreturnedflag; -  float   flagsince; -  float   lastfraggedcarrier; -} playerTeamState_t; - -// the auto following clients don't follow a specific client -// number, but instead follow the first two active players -#define FOLLOW_ACTIVE1  -1 -#define FOLLOW_ACTIVE2  -2 -  // client data that stays across multiple levels or tournament restarts  // this is achieved by writing all the data to cvar strings at game shutdown  // time and reading them back at connection time.  Anything added here  // MUST be dealt with in G_InitSessionData() / G_ReadSessionData() / G_WriteSessionData()  typedef struct  { -  team_t            sessionTeam; -  pTeam_t         restartTeam; //for !restart keepteams and !restart switchteams    int               spectatorTime;    // for determining next-in-line to play    spectatorState_t  spectatorState;    int               spectatorClient;  // for chasecam and follow mode -  int               wins, losses;     // tournament stats -  qboolean          invisible;       // for being invisible on the server - ghosts! -  qboolean          teamLeader;       // true when this client is a team leader +  team_t            restartTeam; //for !restart keepteams and !restart switchteams    clientList_t      ignoreList;  } clientSession_t; -#define MAX_NETNAME       36 - -// data to store details of clients that have abnormally disconnected -typedef struct connectionRecord_s +// namelog +#define MAX_NAMELOG_NAMES 5 +#define MAX_NAMELOG_ADDRS 5 +typedef struct namelog_s  { -  int       clientNum; -  pTeam_t   clientTeam; -  int       clientCredit; -  int       clientScore; -  int       clientEnterTime; +  struct namelog_s  *next; +  char              name[ MAX_NAMELOG_NAMES ][ MAX_NAME_LENGTH ]; +  addr_t            ip[ MAX_NAMELOG_ADDRS ]; +  char              guid[ 33 ]; +  qboolean          guidless; +  int               slot; +  qboolean          banned; -  int       ptrCode; -} connectionRecord_t; +  int               nameOffset; +  int               nameChangeTime; +  int               nameChanges; +  int               voteCount; -typedef struct -{ -  short kills; -  short deaths; -  short feeds; -  short suicides; -  short assists; -  int dmgdone; -  int ffdmgdone; -  int structdmgdone; -  short structsbuilt; -  short repairspoisons; -  short structskilled; -  int timealive; -  int timeinbase; -  short headshots; -  int hits; -  int hitslocational; -  short teamkills; -  int dretchbasytime; -  int jetpackusewallwalkusetime; -  int timeLastViewed; -  int AllstatstimeLastViewed; -  int spreebleeds; -} statsCounters_t; +  qboolean          muted; +  qboolean          denyBuild; -typedef struct -{ -  int kills; -  int deaths; -  int feeds; -  int suicides; -  int assists; -  long dmgdone; -  long ffdmgdone; -  long structdmgdone; -  int structsbuilt; -  int repairspoisons; -  int structskilled; -  long timealive; -  long timeinbase; -  int headshots; -  long hits; -  long hitslocational; -  int teamkills; -  long dretchbasytime; -  long jetpackusewallwalkusetime; -  long timeLastViewed; -} statsCounters_level; +  int               score; +  int               credits; +  team_t            team; + +  int               id; +} namelog_t;  // client data that stays across multiple respawns, but is cleared  // on each level change or team change at ClientBegin() @@ -396,66 +300,51 @@ typedef struct    clientConnected_t   connected;    usercmd_t           cmd;                // we would lose angles if not persistant    qboolean            localClient;        // true if "ip" info key is "localhost" -  qboolean            initialSpawn;       // the first spawn should be at a cool location -  qboolean            predictItemPickup;  // based on cg_predictItems userinfo +  qboolean            stickySpec;         // don't stop spectating a player after they get killed    qboolean            pmoveFixed;         // -  char                netname[ MAX_NETNAME ]; -  int                 maxHealth;          // for handicapping +  char                netname[ MAX_NAME_LENGTH ];    int                 enterTime;          // level.time the client entered the game -  playerTeamState_t   teamState;          // status in teamplay games -  int                 voteCount;          // to prevent people from constantly calling votes -  qboolean            teamInfo;           // send team overlay updates? +  int                 location;           // player locations +  int                 teamInfo;           // level.time of team overlay update (disabled = 0) +  float               flySpeed;           // for spectator/noclip moves +  qboolean            disableBlueprintErrors; // should the buildable blueprint never be hidden from the players? +  int                 buildableRangeMarkerMask; -  pClass_t            classSelection;     // player class (copied to ent->client->ps.stats[ STAT_PCLASS ] once spawned) +  class_t             classSelection;     // player class (copied to ent->client->ps.stats[ STAT_CLASS ] once spawned)    float               evolveHealthFraction;    weapon_t            humanItemSelection; // humans have a starting item -  pTeam_t             teamSelection;      // player team (copied to ps.stats[ STAT_PTEAM ]) +  team_t              teamSelection;      // player team (copied to ps.stats[ STAT_TEAM ])    int                 teamChangeTime;     // level.time of last team change -  qboolean            joinedATeam;        // used to tell when a PTR code is valid -  connectionRecord_t  *connection; +  namelog_t           *namelog; +  g_admin_admin_t     *admin; -  int                 nameChangeTime; -  int                 nameChanges; +  int                 secondsAlive;       // time player has been alive in seconds +  qboolean            hasHealed;          // has healed a player (basi regen aura) in the last 10sec (for score use) -  // used to save playerState_t values while in SPECTATOR_FOLLOW mode -  int                 score; +  // used to save persistant[] values while in SPECTATOR_FOLLOW mode    int                 credit; -  int                 ping; - -  int                 lastTeamStatus; -  int                 lastFreekillTime; -  int                 lastFloodTime;         // level.time of last flood-limited command -  int                 floodDemerits;         // number of flood demerits accumulated +  int                 voted; +  int                 vote; -  char                lastMessage[ MAX_SAY_TEXT ];  // last message said by this player -  int                 lastMessageTime;              // level.time of last message said by this player - -  int                 lastTeamKillTime;      // level.time of last team kill -  int                 teamKillDemerits;      // number of team kill demerits accumulated +  // flood protection +  int                 floodDemerits; +  int                 floodTime;    vec3_t              lastDeathLocation; +  int                 alternateProtocol;    char                guid[ 33 ]; -  char                ip[ 16 ]; -  qboolean            paused; -  qboolean            muted; -  int                 muteExpires;           // level.time at which a player is unmuted -  qboolean            ignoreAdminWarnings; -  qboolean            denyBuild; -  int                 specExpires;          // level.time at which a player can join a team again after being forced into spectator -  int                 denyHumanWeapons; -  int                 denyAlienClasses; -  int                 adminLevel; -  char                adminName[ MAX_NETNAME ]; -  qboolean            designatedBuilder; -  qboolean            firstConnect;        // This is the first map since connect +  qboolean            guidless; +  addr_t              ip; +  char                voice[ MAX_VOICE_NAME_LEN ];    qboolean            useUnlagged; -  statsCounters_t     statscounters; -  int                 bubbleTime; + +  // level.time when teamoverlay info changed so we know to tell other players +  int                 infoChangeTime;  } clientPersistant_t; -#define MAX_UNLAGGED_MARKERS 10 +#define MAX_UNLAGGED_MARKERS 256  typedef struct unlagged_s {    vec3_t      origin;    vec3_t      mins; @@ -463,6 +352,7 @@ typedef struct unlagged_s {    qboolean    used;  } unlagged_t; +#define MAX_TRAMPLE_BUILDABLES_TRACKED 20  // this structure is cleared on each ClientSpawn(),  // except for 'client->pers' and 'client->sess'  struct gclient_s @@ -480,6 +370,7 @@ struct gclient_s    qboolean            readyToExit;    // wishes to leave the intermission    qboolean            noclip; +  int                 cliprcontents;  // the backup layer of ent->r.contents for when noclipping    int                 lastCmdTime;    // level.time of last usercmd_t, for EF_CONNECTION                                        // we can't just use pers.lastCommand.time, because @@ -508,6 +399,7 @@ struct gclient_s    int                 inactivityTime;   // kick players when time > this    qboolean            inactivityWarning;// qtrue if the five seoond warning has been given    int                 rewardTime;       // clear the EF_AWARD_IMPRESSIVE, etc when time > this +  int                 boostedTime;      // last time we touched a booster    int                 airOutTime; @@ -517,53 +409,43 @@ struct gclient_s    int                 switchTeamTime;   // time the player switched teams -  // timeResidual is used to handle events that happen every second -  // like health / armor countdowns and regeneration -  // two timers, one every 100 msecs, another every sec -  int                 time100; -  int                 time1000; -  int                 time10000; +  int                 time100;          // timer for 100ms interval events +  int                 time1000;         // timer for one second interval events +  int                 time10000;        // timer for ten second interval events    char                *areabits; -  gentity_t           *hovel; -    int                 lastPoisonTime;    int                 poisonImmunityTime;    gentity_t           *lastPoisonClient;    int                 lastPoisonCloudedTime; -  gentity_t           *lastPoisonCloudedClient;    int                 grabExpiryTime;    int                 lastLockTime;    int                 lastSlowTime; -  int                 lastBoostedTime;    int                 lastMedKitTime;    int                 medKitHealthToRestore;    int                 medKitIncrementTime;    int                 lastCreepSlowTime;    // time until creep can be removed -  qboolean            allowedToPounce; -    qboolean            charging; -  float               jetpackfuel; - -  vec3_t              hovelOrigin;          // player origin before entering hovel -    int                 lastFlameBall;        // s.number of the last flame ball fired -#define RAM_FRAMES  1                       // number of frames to wait before retriggering -  int                 retriggerArmouryMenu; // frame number to retrigger the armoury menu -    unlagged_t          unlaggedHist[ MAX_UNLAGGED_MARKERS ];    unlagged_t          unlaggedBackup;    unlagged_t          unlaggedCalc;    int                 unlaggedTime; -   -  int               tkcredits[ MAX_CLIENTS ]; -}; +  float               voiceEnthusiasm; +  char                lastVoiceCmd[ MAX_VOICE_CMD_LEN ]; +  int                 lcannonStartTime; +  int                 trampleBuildablesHitPos; +  int                 trampleBuildablesHit[ MAX_TRAMPLE_BUILDABLES_TRACKED ]; + +  int                 lastCrushTime;        // Tyrant crush +  int                 lastDropTime;         // Weapon drop with /drop +};  typedef struct spawnQueue_s  { @@ -583,35 +465,30 @@ qboolean  G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum );  qboolean  G_PushSpawnQueue( spawnQueue_t *sq, int clientNum );  qboolean  G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum );  int       G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum ); +void      G_PrintSpawnQueue( spawnQueue_t *sq ); -#define MAX_LOCDAMAGE_TEXT    8192 -#define MAX_LOCDAMAGE_REGIONS 16 +#define MAX_DAMAGE_REGION_TEXT    8192 +#define MAX_DAMAGE_REGIONS 16 -// store locational damage regions -typedef struct damageRegion_s +// build point zone +typedef struct  { -  float     minHeight, maxHeight; -  int       minAngle, maxAngle; +  int active; -  float     modifier; +  int totalBuildPoints; +  int queuedBuildPoints; +  int nextQueueTime; +} buildPointZone_t; -  qboolean  crouch; -} damageRegion_t; - -#define MAX_ARMOUR_TEXT    8192 -#define MAX_ARMOUR_REGIONS 16 - -// store locational armour regions -typedef struct armourRegion_s +// store locational damage regions +typedef struct damageRegion_s  { -  float     minHeight, maxHeight; +  char      name[ 32 ]; +  float     area, modifier, minHeight, maxHeight;    int       minAngle, maxAngle; - -  float     modifier; -    qboolean  crouch; -} armourRegion_t; +} damageRegion_t;  //status of the warning of certain events  typedef enum @@ -621,37 +498,43 @@ typedef enum    TW_PASSED  } timeWarning_t; +// fate of a buildable  typedef enum  { -  BF_BUILT, -  BF_DECONNED, -  BF_DESTROYED, -  BF_TEAMKILLED -} buildableFate_t; - -// record all changes to the buildable layout - build, decon, destroy - and -// enough information to revert that change -typedef struct buildHistory_s buildHistory_t; -struct buildHistory_s +  BF_CONSTRUCT, +  BF_DECONSTRUCT, +  BF_REPLACE, +  BF_DESTROY, +  BF_TEAMKILL, +  BF_UNPOWER, +  BF_AUTO +} buildFate_t; + +// data needed to revert a change in layout +typedef struct  { -  int ID; // persistent ID to aid in specific reverting -  gentity_t *ent; // who, NULL if they've disconnected (or aren't an ent) -  char name[ MAX_NETNAME ]; // who, saves name if ent is NULL -  int buildable; // what -  vec3_t origin; // where -  vec3_t angles; // which way round -  vec3_t origin2; // I don't know what the hell these are, but layoutsave saves -  vec3_t angles2; // them so I will do the same -  buildableFate_t fate; // was it built, destroyed or deconned -  buildHistory_t *next; // next oldest change -  buildHistory_t *marked; // linked list of markdecon buildings taken -}; +  int          time; +  buildFate_t  fate; +  namelog_t    *actor; +  namelog_t    *builtBy; +  buildable_t  modelindex; +  qboolean     deconstruct; +  int          deconstructTime; +  vec3_t       origin; +  vec3_t       angles; +  vec3_t       origin2; +  vec3_t       angles2; +  buildable_t  powerSource; +  int          powerValue; +} buildLog_t;  //  // this structure is cleared as each map is entered  //  #define MAX_SPAWN_VARS      64  #define MAX_SPAWN_VARS_CHARS  4096 +#define MAX_BUILDLOG          128 +#define MAX_PLAYER_MODEL      256  typedef struct  { @@ -659,7 +542,9 @@ typedef struct    struct gentity_s  *gentities;    int               gentitySize; -  int               num_entities;   // current number, <= MAX_GENTITIES +  int               num_entities;   // MAX_CLIENTS <= num_entities <= ENTITYNUM_MAX_NORMAL + +  int               warmupTime;     // restart match at this time    fileHandle_t      logFile; @@ -673,7 +558,7 @@ typedef struct    int               startTime;                    // level.time the map was started -  int               teamScores[ TEAM_NUM_TEAMS ]; +  int               teamScores[ NUM_TEAMS ];    int               lastTeamLocationTime;         // last time of client team location update    qboolean          newSession;                   // don't use any old session data, because @@ -686,27 +571,20 @@ typedef struct    int               numPlayingClients;            // connected, non-spectators    int               sortedClients[MAX_CLIENTS];   // sorted by score -  int               numNewbies;                   // number of UnnamedPlayers who have been renamed this round. -    int               snd_fry;                      // sound index for standing in lava +  int               warmupModificationCount;      // for detecting if g_warmup is changed +    // voting state -  char              voteString[MAX_STRING_CHARS]; -  char              voteDisplayString[MAX_STRING_CHARS]; -  int               votePassThreshold; -  int               voteTime;                     // level.time vote was called -  int               voteExecuteTime;              // time the vote is executed -  int               voteYes; -  int               voteNo; -  int               numVotingClients;             // set by CalculateRanks - -  // team voting state -  char              teamVoteString[ 2 ][ MAX_STRING_CHARS ]; -  char              teamVoteDisplayString[ 2 ][ MAX_STRING_CHARS ]; -  int               teamVoteTime[ 2 ];            // level.time vote was called -  int               teamVoteYes[ 2 ]; -  int               teamVoteNo[ 2 ]; -  int               numteamVotingClients[ 2 ];    // set by CalculateRanks +  int               voteThreshold[ NUM_TEAMS ];   // need at least this percent to pass +  char              voteString[ NUM_TEAMS ][ MAX_STRING_CHARS ]; +  char              voteDisplayString[ NUM_TEAMS ][ MAX_STRING_CHARS ]; +  int               voteTime[ NUM_TEAMS ];        // level.time vote was called +  int               voteExecuteTime[ NUM_TEAMS ]; // time the vote is executed +  int               voteDelay[ NUM_TEAMS ];       // it doesn't make sense to always delay vote execution +  int               voteYes[ NUM_TEAMS ]; +  int               voteNo[ NUM_TEAMS ]; +  int               numVotingClients[ NUM_TEAMS ];// set by CalculateRanks    // spawn variables    qboolean          spawning;                     // the G_Spawn*() functions are valid @@ -716,6 +594,7 @@ typedef struct    char              spawnVarChars[ MAX_SPAWN_VARS_CHARS ];    // intermission state +  qboolean          exited;    int               intermissionQueued;           // intermission was qualified, but                                                    // wait INTERMISSION_DELAY_TIME before                                                    // actually going there so the last @@ -728,7 +607,6 @@ typedef struct    vec3_t            intermission_origin;          // also used for spectator spawns    vec3_t            intermission_angle; -  qboolean          locationLinked;               // target_locations get linked    gentity_t         *locationHead;                // head of the location list    int               numAlienSpawns; @@ -742,12 +620,17 @@ typedef struct    float             averageNumHumanClients;    int               numHumanSamples; -  int               numLiveAlienClients; -  int               numLiveHumanClients; +  int               numAlienClientsAlive; +  int               numHumanClientsAlive;    int               alienBuildPoints; +  int               alienBuildPointQueue; +  int               alienNextQueueTime;    int               humanBuildPoints; -  int               humanBuildPointsPowered; +  int               humanBuildPointQueue; +  int               humanNextQueueTime; + +  buildPointZone_t  *buildPointZones;    gentity_t         *markedBuildables[ MAX_GENTITIES ];    int               numBuildablesForRemoval; @@ -755,21 +638,15 @@ typedef struct    int               alienKills;    int               humanKills; -  qboolean          reactorPresent; -  qboolean          overmindPresent;    qboolean          overmindMuted;    int               humanBaseAttackTimer; -  pTeam_t           lastWin; +  team_t            lastWin; -  int               suddenDeathABuildPoints; -  int               suddenDeathHBuildPoints; -  qboolean          suddenDeath;    int               suddenDeathBeginTime;    timeWarning_t     suddenDeathWarning;    timeWarning_t     timelimitWarning; -  int               extend_vote_count;    spawnQueue_t      alienSpawnQueue;    spawnQueue_t      humanSpawnQueue; @@ -783,48 +660,41 @@ typedef struct    qboolean          uncondHumanWin;    qboolean          alienTeamLocked;    qboolean          humanTeamLocked; -  qboolean          paused; -  int               pauseTime; -  float             pause_speed; -  float             pause_gravity; -  float             pause_knockback; -  int               pause_ff; -  int               pause_ffb; - -  int               lastCreditedAlien; -  int               lastCreditedHuman; +  int               pausedTime;    int unlaggedIndex;    int unlaggedTimes[ MAX_UNLAGGED_MARKERS ];    char              layout[ MAX_QPATH ]; -  pTeam_t           surrenderTeam; -  buildHistory_t    *buildHistory; -  int               lastBuildID; -  int               lastTeamUnbalancedTime; -  int               numTeamWarnings;   -  int               lastMsgTime; -  int               mapRotationVoteTime; -   -  statsCounters_level alienStatsCounters; -  statsCounters_level humanStatsCounters; -   -  qboolean	    noBuilding; -  float		    nbArea; -  float		    nbHeight; +  team_t            surrenderTeam; +  int               lastTeamImbalancedTime; +  int               numTeamImbalanceWarnings; + +  voice_t           *voices; + +  emoticon_t        emoticons[ MAX_EMOTICONS ]; +  int               emoticonCount; -  nbMarkers_t	    nbMarkers[ MAX_GENTITIES ]; +  char              *playerModel[ MAX_PLAYER_MODEL ]; +  int               playerModelCount; + +  namelog_t         *namelogs; + +  buildLog_t        buildLog[ MAX_BUILDLOG ]; +  int               buildId; +  int               numBuildLogs;  } level_locals_t; -#define CMD_CHEAT         0x01 -#define CMD_MESSAGE       0x02 // sends message to others (skip when muted) -#define CMD_TEAM          0x04 // must be on a team -#define CMD_NOTEAM        0x08 // must not be on a team -#define CMD_ALIEN         0x10 -#define CMD_HUMAN         0x20 -#define CMD_LIVING        0x40 -#define CMD_INTERMISSION  0x80 // valid during intermission +#define CMD_CHEAT         0x0001 +#define CMD_CHEAT_TEAM    0x0002 // is a cheat when used on a team +#define CMD_MESSAGE       0x0004 // sends message to others (skip when muted) +#define CMD_TEAM          0x0008 // must be on a team +#define CMD_SPEC          0x0010 // must be a spectator +#define CMD_ALIEN         0x0020 +#define CMD_HUMAN         0x0040 +#define CMD_ALIVE         0x0080 +#define CMD_INTERMISSION  0x0100 // valid during intermission  typedef struct  { @@ -847,33 +717,31 @@ char      *G_NewString( const char *string );  //  // g_cmds.c  // -void      Cmd_Score_f( gentity_t *ent ); -qboolean  G_RoomForClassChange( gentity_t *ent, pClass_t class, vec3_t newOrigin ); -void      G_StopFromFollowing( gentity_t *ent ); + +#define DECOLOR_OFF '\16' +#define DECOLOR_ON  '\17' +  void      G_StopFollowing( gentity_t *ent ); +void      G_StopFromFollowing( gentity_t *ent ); +void      G_FollowLockView( gentity_t *ent );  qboolean  G_FollowNewClient( gentity_t *ent, int dir );  void      G_ToggleFollow( gentity_t *ent ); -qboolean  G_MatchOnePlayer( int *plist, char *err, int len ); -int       G_ClientNumbersFromString( char *s, int *plist ); -void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ); -int       G_SayArgc( void ); -qboolean  G_SayArgv( int n, char *buffer, int bufferLength ); -char      *G_SayConcatArgs( int start ); -void      G_DecolorString( char *in, char *out ); -void      G_ParseEscapedString( char *buffer ); -void      G_LeaveTeam( gentity_t *self ); -void      G_ChangeTeam( gentity_t *ent, pTeam_t newTeam ); +int       G_ClientNumberFromString( char *s, char *err, int len ); +int       G_ClientNumbersFromString( char *s, int *plist, int max ); +char      *ConcatArgs( int start ); +char      *ConcatArgsPrintable( int start ); +void      G_Say( gentity_t *ent, saymode_t mode, const char *chatText ); +void      G_DecolorString( const char *in, char *out, int len ); +void      G_UnEscapeString( char *in, char *out, int len );  void      G_SanitiseString( char *in, char *out, int len ); -void      G_PrivateMessage( gentity_t *ent ); -char      *G_statsString( statsCounters_t *sc, pTeam_t *pt ); -void      Cmd_Share_f( gentity_t *ent ); -void      Cmd_Donate_f( gentity_t *ent ); -void      Cmd_TeamVote_f( gentity_t *ent );  -void      Cmd_Builder_f( gentity_t *ent ); -void      G_WordWrap( char *buffer, int maxwidth ); -void      G_CP( gentity_t *ent ); -qboolean  G_IsMuted( gclient_t *ent ); -qboolean  G_TeamKill_Repent( gentity_t *ent ); +void      Cmd_PrivateMessage_f( gentity_t *ent ); +void      Cmd_ListMaps_f( gentity_t *ent ); +void      Cmd_Test_f( gentity_t *ent ); +void      Cmd_AdminMessage_f( gentity_t *ent ); +int       G_FloodLimited( gentity_t *ent ); +void      G_ListCommands( gentity_t *ent ); +void      G_LoadCensors( void ); +void      G_CensorString( char *out, const char *in, int len, gentity_t *ent );  //  // g_physics.c @@ -891,69 +759,80 @@ typedef enum    IBE_NONE,    IBE_NOOVERMIND, -  IBE_OVERMIND, -  IBE_NOASSERT, -  IBE_SPWNWARN, +  IBE_ONEOVERMIND, +  IBE_NOALIENBP, +  IBE_SPWNWARN, // not currently used    IBE_NOCREEP, -  IBE_HOVEL, -  IBE_HOVELEXIT, - -  IBE_REACTOR, -  IBE_REPEATER, -  IBE_TNODEWARN, -  IBE_RPTWARN, -  IBE_RPTWARN2, -  IBE_NOPOWER, + +  IBE_ONEREACTOR, +  IBE_NOPOWERHERE, +  IBE_TNODEWARN, // not currently used +  IBE_RPTNOREAC, +  IBE_RPTPOWERHERE, +  IBE_NOHUMANBP,    IBE_NODCC, -  IBE_NORMAL, +  IBE_NORMAL, // too steep    IBE_NOROOM,    IBE_PERMISSION, +  IBE_LASTSPAWN,    IBE_MAXERRORS  } itemBuildError_t; -qboolean          AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit ); -gentity_t         *G_CheckSpawnPoint( int spawnNum, vec3_t origin, vec3_t normal, -                    buildable_t spawn, vec3_t spawnOrigin ); +gentity_t         *G_CheckSpawnPoint( int spawnNum, const vec3_t origin, +                                      const vec3_t normal, buildable_t spawn, +                                      vec3_t spawnOrigin ); -qboolean          G_IsPowered( vec3_t origin ); +buildable_t       G_IsPowered( vec3_t origin );  qboolean          G_IsDCCBuilt( void ); -qboolean          G_IsOvermindBuilt( void ); +int               G_FindDCC( gentity_t *self ); +gentity_t         *G_Reactor( void ); +gentity_t         *G_Overmind( void ); +qboolean          G_FindCreep( gentity_t *self );  void              G_BuildableThink( gentity_t *ent, int msec );  qboolean          G_BuildableRange( vec3_t origin, float r, buildable_t buildable ); -itemBuildError_t  G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ); -qboolean G_BuildingExists( int bclass ) ; +void              G_ClearDeconMarks( void ); +itemBuildError_t  G_CanBuild( gentity_t *ent, buildable_t buildable, int distance, +                              vec3_t origin, vec3_t normal, int *groundEntNum );  qboolean          G_BuildIfValid( gentity_t *ent, buildable_t buildable );  void              G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force );  void              G_SetIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim );  void              G_SpawnBuildable(gentity_t *ent, buildable_t buildable);  void              FinishSpawningBuildable( gentity_t *ent ); -void              G_CheckDBProtection( void );  void              G_LayoutSave( char *name );  int               G_LayoutList( const char *map, char *list, int len );  void              G_LayoutSelect( void ); -void              G_LayoutLoad( void ); -void              G_BaseSelfDestruct( pTeam_t team ); -gentity_t         *G_InstantBuild( buildable_t buildable, vec3_t origin, vec3_t angles, vec3_t origin2, vec3_t angles2 ); -void              G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark ); -void              G_CommitRevertedBuildable( gentity_t *ent ); -qboolean          G_RevertCanFit( buildHistory_t *bh ); -int               G_LogBuild( buildHistory_t *new ); -int               G_CountBuildLog( void ); -char             *G_FindBuildLogName( int id ); -void		  G_NobuildSave( void ); -void 		  G_NobuildLoad( void ); +void              G_LayoutLoad( char *lstr ); +void              G_BaseSelfDestruct( team_t team ); +int               G_NextQueueTime( int queuedBP, int totalBP, int queueBaseRate ); +void              G_QueueBuildPoints( gentity_t *self ); +int               G_GetBuildPoints( const vec3_t pos, team_t team ); +int               G_GetMarkedBuildPoints( const vec3_t pos, team_t team ); +qboolean          G_FindPower( gentity_t *self, qboolean searchUnspawned ); +gentity_t         *G_PowerEntityForPoint( const vec3_t origin ); +gentity_t         *G_PowerEntityForEntity( gentity_t *ent ); +gentity_t         *G_RepeaterEntityForPoint( vec3_t origin ); +gentity_t         *G_InPowerZone( gentity_t *self ); +buildLog_t        *G_BuildLogNew( gentity_t *actor, buildFate_t fate ); +void              G_BuildLogSet( buildLog_t *log, gentity_t *ent ); +void              G_BuildLogAuto( gentity_t *actor, gentity_t *buildable, buildFate_t fate ); +void              G_BuildLogRevert( int id ); +void              G_RemoveRangeMarkerFrom( gentity_t *self ); +void              G_UpdateBuildableRangeMarkers( void );  //  // g_utils.c  // -int         G_ParticleSystemIndex( char *name ); -int         G_ShaderIndex( char *name ); -int         G_ModelIndex( char *name ); -int         G_SoundIndex( char *name ); -void        G_TeamCommand( pTeam_t team, char *cmd ); +//addr_t in g_admin.h for g_admin_ban_t +qboolean    G_AddressParse( const char *str, addr_t *addr ); +qboolean    G_AddressCompare( const addr_t *a, const addr_t *b ); + +int         G_ParticleSystemIndex( const char *name ); +int         G_ShaderIndex( const char *name ); +int         G_ModelIndex( const char *name ); +int         G_SoundIndex( const char *name );  void        G_KillBox (gentity_t *ent);  gentity_t   *G_Find (gentity_t *from, int fieldofs, const char *match);  gentity_t   *G_PickTarget (char *targetname); @@ -962,15 +841,14 @@ void        G_SetMovedir ( vec3_t angles, vec3_t movedir);  void        G_InitGentity( gentity_t *e );  gentity_t   *G_Spawn( void ); -gentity_t   *G_TempEntity( vec3_t origin, int event ); +gentity_t   *G_TempEntity( const vec3_t origin, int event );  void        G_Sound( gentity_t *ent, int channel, int soundIndex );  void        G_FreeEntity( gentity_t *e ); +void        G_RemoveEntity( gentity_t *ent );  qboolean    G_EntitiesFree( void );  void        G_TouchTriggers( gentity_t *ent ); -void        G_TouchSolids( gentity_t *ent ); -float       *tv( float x, float y, float z );  char        *vtos( const vec3_t v );  float       vectoyaw( const vec3_t vec ); @@ -978,7 +856,7 @@ float       vectoyaw( const vec3_t vec );  void        G_AddPredictableEvent( gentity_t *ent, int event, int eventParm );  void        G_AddEvent( gentity_t *ent, int event, int eventParm );  void        G_BroadcastEvent( int event, int eventParm ); -void        G_SetOrigin( gentity_t *ent, vec3_t origin ); +void        G_SetOrigin( gentity_t *ent, const vec3_t origin );  void        AddRemap(const char *oldShader, const char *newShader, float timeOffset);  const char  *BuildShaderStateConfig( void ); @@ -986,9 +864,10 @@ const char  *BuildShaderStateConfig( void );  qboolean    G_ClientIsLagging( gclient_t *client );  void        G_TriggerMenu( int clientNum, dynMenu_t menu ); +void        G_TriggerMenuArgs( int clientNum, dynMenu_t menu, int arg );  void        G_CloseMenus( int clientNum ); -qboolean    G_Visible( gentity_t *ent1, gentity_t *ent2 ); +qboolean    G_Visible( gentity_t *ent1, gentity_t *ent2, int contents );  gentity_t   *G_ClosestEnt( vec3_t origin, gentity_t **entities, int numEntities );  // @@ -1000,31 +879,32 @@ void      G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker,  void      G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir,                               vec3_t point, int damage, int dflags, int mod, int team );  qboolean  G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius, -                          gentity_t *ignore, int dflags, int mod ); +                          gentity_t *ignore, int mod );  qboolean  G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float damage, float radius,                                     gentity_t *ignore, int mod, int team ); -void      G_Knockback( gentity_t *targ, vec3_t dir, int knockback ); -void      body_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ); +float     G_RewardAttackers( gentity_t *self );  void      AddScore( gentity_t *ent, int score ); +void      G_LogDestruction( gentity_t *self, gentity_t *actor, int mod );  void      G_InitDamageLocations( void );  // damage flags  #define DAMAGE_RADIUS         0x00000001  // damage was indirect  #define DAMAGE_NO_ARMOR       0x00000002  // armour does not protect from this damage -#define DAMAGE_KNOCKBACK      0x00000004  // affect velocity, not just view angles -#define DAMAGE_NO_PROTECTION  0x00000008  // armor, shields, invulnerability, and godmode have no effect +#define DAMAGE_NO_KNOCKBACK   0x00000004  // do not affect velocity, just view angles +#define DAMAGE_NO_PROTECTION  0x00000008  // kills everything except godmode  #define DAMAGE_NO_LOCDAMAGE   0x00000010  // do not apply locational damage  //  // g_missile.c  // +void      G_BounceMissile( gentity_t *ent, trace_t *trace );  void      G_RunMissile( gentity_t *ent );  gentity_t *fire_flamer( gentity_t *self, vec3_t start, vec3_t aimdir );  gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir );  gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir ); -gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius ); +gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius, int speed );  gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir );  gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir );  gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir ); @@ -1044,35 +924,44 @@ void manualTriggerSpectator( gentity_t *trigger, gentity_t *player );  // g_trigger.c  //  void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace ); -void G_Checktrigger_stages( pTeam_t team, stage_t stage ); +void G_Checktrigger_stages( team_t team, stage_t stage );  //  // g_misc.c  // -void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ); -void ShineTorch( gentity_t *self ); +void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles, float speed );  // -// g_weapon.c +// g_playermodel.c  // +void G_InitPlayerModel(void); +qboolean G_IsValidPlayerModel(const char *model); +void G_FreePlayerModel(void); +void G_GetPlayerModelSkins( const char *modelname, char skins[][ 64 ], int maxskins, int *numskins ); +char *GetSkin( char *modelname, char *wish ); -#define MAX_ZAP_TARGETS LEVEL2_AREAZAP_MAX_TARGETS +// +// g_weapon.c +//  typedef struct zap_s  {    qboolean      used;    gentity_t     *creator; -  gentity_t     *targets[ MAX_ZAP_TARGETS ]; +  gentity_t     *targets[ LEVEL2_AREAZAP_MAX_TARGETS ];    int           numTargets; +  float         distances[ LEVEL2_AREAZAP_MAX_TARGETS ];    int           timeToLive; -  int           damageUsed;    gentity_t     *effectChannel;  } zap_t; +#define MAX_ZAPS MAX_CLIENTS +extern zap_t zaps[ MAX_ZAPS ]; +  void      G_ForceWeaponChange( gentity_t *ent, weapon_t weapon );  void      G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo );  void      CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint ); @@ -1080,35 +969,34 @@ void      SnapVectorTowards( vec3_t v, vec3_t to );  qboolean  CheckVenomAttack( gentity_t *ent );  void      CheckGrabAttack( gentity_t *ent );  qboolean  CheckPounceAttack( gentity_t *ent ); -void      ChargeAttack( gentity_t *ent, gentity_t *victim ); +void      CheckCkitRepair( gentity_t *ent ); +void      G_ChargeAttack( gentity_t *ent, gentity_t *victim ); +void      G_CrushAttack( gentity_t *ent, gentity_t *victim );  void      G_UpdateZaps( int msec ); +void      G_ClearPlayerZapEffects( gentity_t *player );  //  // g_client.c  //  void      G_AddCreditToClient( gclient_t *client, short credit, qboolean cap ); -team_t    TeamCount( int ignoreClientNum, int team ); -void      G_SetClientViewAngle( gentity_t *ent, vec3_t angle ); -gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles ); +void      G_SetClientViewAngle( gentity_t *ent, const vec3_t angle ); +gentity_t *G_SelectTremulousSpawnPoint( team_t team, vec3_t preference, vec3_t origin, vec3_t angles );  gentity_t *G_SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles );  gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles );  gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles ); -void      SpawnCorpse( gentity_t *ent );  void      respawn( gentity_t *ent );  void      BeginIntermission( void ); -void      ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ); +void      ClientSpawn( gentity_t *ent, gentity_t *spawn, const vec3_t origin, const vec3_t angles );  void      player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod );  qboolean  SpotWouldTelefrag( gentity_t *spot ); -char     *G_NextNewbieName( gentity_t *ent ); -void      G_LogAutobahn( gentity_t *ent, const char *userinfo, int rating, qboolean onConnect );  //  // g_svcmds.c  //  qboolean  ConsoleCommand( void ); -void      G_ProcessIPBans( void ); -qboolean  G_FilterPacket( char *from ); +void      G_RegisterCommands( void ); +void      G_UnregisterCommands( void );  //  // g_weapon.c @@ -1118,8 +1006,11 @@ void FireWeapon2( gentity_t *ent );  void FireWeapon3( gentity_t *ent );  // -// g_cmds.c +// g_weapondrop.c  // +gentity_t *LaunchWeapon( gentity_t *client, weapon_t weap, vec3_t origin, vec3_t velocity ); +gentity_t *G_DropWeapon( gentity_t *ent, weapon_t w, float angle ); +void G_RunWeaponDrop(gentity_t *ent);  //  // g_main.c @@ -1130,27 +1021,22 @@ void G_MapConfigs( const char *mapname );  void CalculateRanks( void );  void FindIntermissionPoint( void );  void G_RunThink( gentity_t *ent ); -void QDECL G_LogPrintf( const char *fmt, ... ); -void QDECL G_LogPrintfColoured( const char *fmt, ... ); -void QDECL G_LogOnlyPrintf( const char *fmt, ... ); -void QDECL G_AdminsPrintf( const char *fmt, ... ); -void QDECL G_WarningsPrintf( char *flag, const char *fmt, ... ); -void QDECL G_LogOnlyPrintf( const char *fmt, ... ); +void G_AdminMessage( gentity_t *ent, const char *string ); +void QDECL G_LogPrintf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2)));  void SendScoreboardMessageToAllClients( void ); -void QDECL G_Printf( const char *fmt, ... ); -void QDECL G_Error( const char *fmt, ... ); -void CheckVote( void ); -void CheckTeamVote( int teamnum ); +void QDECL G_Printf( const char *fmt, ... ) __attribute__ ((format (printf, 1, 2))); +void QDECL G_Error( const char *fmt, ... ) __attribute__ ((noreturn, format (printf, 1, 2))); +void G_Vote( gentity_t *ent, team_t team, qboolean voting ); +void G_ExecuteVote( team_t team ); +void G_CheckVote( team_t team );  void LogExit( const char *string );  int  G_TimeTilSuddenDeath( void ); -void CheckMsgTimer( void ); -qboolean G_Flood_Limited( gentity_t *ent );  //  // g_client.c  // -char *ClientConnect( int clientNum, qboolean firstTime ); -void ClientUserinfoChanged( int clientNum, qboolean forceName ); +const char *ClientConnect( int clientNum, qboolean firstTime ); +char *ClientUserinfoChanged( int clientNum, qboolean forceName );  void ClientDisconnect( int clientNum );  void ClientBegin( int clientNum );  void ClientCommand( int clientNum ); @@ -1170,20 +1056,15 @@ void G_RunClient( gentity_t *ent );  //  // g_team.c  // +team_t    G_TeamFromString( char *str ); +void      G_TeamCommand( team_t team, const char *cmd );  qboolean  OnSameTeam( gentity_t *ent1, gentity_t *ent2 ); +void      G_LeaveTeam( gentity_t *self ); +void      G_ChangeTeam( gentity_t *ent, team_t newTeam );  gentity_t *Team_GetLocation( gentity_t *ent ); -qboolean  Team_GetLocationMsg( gentity_t *ent, char *loc, int loclen );  void      TeamplayInfoMessage( gentity_t *ent );  void      CheckTeamStatus( void ); - -// -// g_mem.c -// -void *G_Alloc( int size ); -void G_InitMemory( void ); -void G_Free( void *ptr ); -void G_DefragmentMemory( void ); -void Svcmd_GameMem_f( void ); +void      G_UpdateTeamConfigStrings( void );  //  // g_session.c @@ -1195,100 +1076,28 @@ void G_WriteSessionData( void );  //  // g_maprotation.c  // -#define MAX_MAP_ROTATIONS       64 -#define MAX_MAP_ROTATION_MAPS   64 -#define MAX_MAP_COMMANDS        16 -#define MAX_MAP_ROTATION_CONDS  8 - -#define NOT_ROTATING          -1 - -typedef enum -{ -  MCV_ERR, -  MCV_RANDOM, -  MCV_NUMCLIENTS, -  MCV_LASTWIN, -  MCV_VOTE, -  MCV_SELECTEDRANDOM -} mapConditionVariable_t; - -typedef enum -{ -  MCO_LT, -  MCO_EQ, -  MCO_GT -} mapConditionOperator_t; - -typedef enum -{ -  MCT_ERR, -  MCT_MAP, -  MCT_ROTATION -} mapConditionType_t; - -typedef struct mapRotationCondition_s -{ -  char                    dest[ MAX_QPATH ]; - -  qboolean                unconditional; - -  mapConditionVariable_t  lhs; -  mapConditionOperator_t  op; - -  int                     numClients; -  pTeam_t                 lastWin; -} mapRotationCondition_t; - -typedef struct mapRotationEntry_s -{ -  char                    name[ MAX_QPATH ]; - -  char                    postCmds[ MAX_MAP_COMMANDS ][ MAX_STRING_CHARS ]; -  char                    layouts[ MAX_CVAR_VALUE_STRING ]; -  int                     numCmds; - -  mapRotationCondition_t  conditions[ MAX_MAP_ROTATION_CONDS ]; -  int                     numConditions; -} mapRotationEntry_t; - -typedef struct mapRotation_s -{ -  char                name[ MAX_QPATH ]; - -  mapRotationEntry_t  maps[ MAX_MAP_ROTATION_MAPS ]; -  int                 numMaps; -  int                 currentMap; -} mapRotation_t; - -typedef struct mapRotations_s -{ -  mapRotation_t   rotations[ MAX_MAP_ROTATIONS ]; -  int             numRotations; -} mapRotations_t; -  void      G_PrintRotations( void ); -qboolean  G_AdvanceMapRotation( void ); -qboolean  G_StartMapRotation( char *name, qboolean changeMap ); +void      G_AdvanceMapRotation( int depth ); +qboolean  G_StartMapRotation( char *name, qboolean advance, +                              qboolean putOnStack, qboolean reset_index, int depth );  void      G_StopMapRotation( void );  qboolean  G_MapRotationActive( void );  void      G_InitMapRotations( void ); -qboolean  G_MapExists( char *name ); -int       G_GetCurrentMap( int rotation ); - -qboolean G_CheckMapRotationVote( void ); -qboolean G_IntermissionMapVoteWinner( void ); -void G_IntermissionMapVoteMessage( gentity_t *ent ); -void G_IntermissionMapVoteMessageAll( void ); -void G_IntermissionMapVoteCommand( gentity_t *ent, qboolean next, qboolean choose ); +void      G_ShutdownMapRotations( void ); +qboolean  G_MapExists( const char *name ); +qboolean  G_LayoutExists( const char *map, const char *layout ); +void      G_ClearRotationStack( void );  // -// g_ptr.c +// g_namelog.c  // -void                G_UpdatePTRConnection( gclient_t *client ); -connectionRecord_t  *G_GenerateNewConnection( gclient_t *client ); -void                G_ResetPTRConnections( void ); -connectionRecord_t  *G_FindConnectionForCode( int code ); +void G_namelog_connect( gclient_t *client ); +void G_namelog_disconnect( gclient_t *client ); +void G_namelog_restore( gclient_t *client ); +void G_namelog_update_score( gclient_t *client ); +void G_namelog_update_name( gclient_t *client ); +void G_namelog_cleanup( void );  //some maxs  #define MAX_FILEPATH      144 @@ -1296,7 +1105,7 @@ connectionRecord_t  *G_FindConnectionForCode( int code );  extern  level_locals_t  level;  extern  gentity_t       g_entities[ MAX_GENTITIES ]; -#define FOFS(x) ((int)&(((gentity_t *)0)->x)) +#define FOFS(x) ((size_t)&(((gentity_t *)0)->x))  extern  vmCvar_t  g_dedicated;  extern  vmCvar_t  g_cheats; @@ -1304,94 +1113,54 @@ extern  vmCvar_t  g_maxclients;     // allow this many total, including spectato  extern  vmCvar_t  g_maxGameClients;   // allow this many active  extern  vmCvar_t  g_restarted;  extern  vmCvar_t  g_lockTeamsAtStart; -extern  vmCvar_t  g_minCommandPeriod;  extern  vmCvar_t  g_minNameChangePeriod;  extern  vmCvar_t  g_maxNameChanges; -extern  vmCvar_t  g_newbieNumbering; -extern  vmCvar_t  g_newbieNamePrefix;  extern  vmCvar_t  g_timelimit;  extern  vmCvar_t  g_suddenDeathTime; -extern  vmCvar_t  g_suddenDeath; -extern  vmCvar_t  g_suddenDeathMode;  extern  vmCvar_t  g_friendlyFire; -extern  vmCvar_t  g_friendlyFireHumans; -extern  vmCvar_t  g_friendlyFireAliens; -extern  vmCvar_t  g_retribution; -extern  vmCvar_t  g_friendlyFireMovementAttacks;  extern  vmCvar_t  g_friendlyBuildableFire; +extern  vmCvar_t  g_dretchPunt;  extern  vmCvar_t  g_password;  extern  vmCvar_t  g_needpass;  extern  vmCvar_t  g_gravity;  extern  vmCvar_t  g_speed;  extern  vmCvar_t  g_knockback; -extern  vmCvar_t  g_quadfactor;  extern  vmCvar_t  g_inactivity;  extern  vmCvar_t  g_debugMove; -extern  vmCvar_t  g_debugAlloc;  extern  vmCvar_t  g_debugDamage; -extern  vmCvar_t  g_weaponRespawn; -extern  vmCvar_t  g_weaponTeamRespawn;  extern  vmCvar_t  g_synchronousClients;  extern  vmCvar_t  g_motd;  extern  vmCvar_t  g_warmup; -extern  vmCvar_t  g_warmupMode;  extern  vmCvar_t  g_doWarmup; -extern  vmCvar_t  g_blood;  extern  vmCvar_t  g_allowVote; -extern  vmCvar_t  g_requireVoteReasons;  extern  vmCvar_t  g_voteLimit;  extern  vmCvar_t  g_suddenDeathVotePercent;  extern  vmCvar_t  g_suddenDeathVoteDelay; -extern  vmCvar_t  g_extendVotesPercent; -extern  vmCvar_t  g_extendVotesTime; -extern  vmCvar_t  g_extendVotesCount; -extern  vmCvar_t  g_kickVotesPercent; -extern  vmCvar_t  g_customVote1; -extern  vmCvar_t  g_customVote2; -extern  vmCvar_t  g_customVote3; -extern  vmCvar_t  g_customVote4; -extern  vmCvar_t  g_customVote5; -extern  vmCvar_t  g_customVote6; -extern  vmCvar_t  g_customVote7; -extern  vmCvar_t  g_customVote8; -#define CUSTOM_VOTE_COUNT 8 -extern  vmCvar_t  g_customVotePercent; -extern  vmCvar_t  g_mapVotesPercent; -extern  vmCvar_t  g_mapRotationVote; -extern  vmCvar_t  g_extendVotesPercent; -extern  vmCvar_t  g_extendVotesTime; -extern  vmCvar_t  g_extendVotesCount; -extern  vmCvar_t  g_readyPercent; -extern  vmCvar_t  g_designateVotes; -extern  vmCvar_t  g_teamAutoJoin;  extern  vmCvar_t  g_teamForceBalance; -extern  vmCvar_t  g_banIPs; -extern  vmCvar_t  g_filterBan;  extern  vmCvar_t  g_smoothClients; -extern  vmCvar_t  g_outdatedClientMessage;  extern  vmCvar_t  pmove_fixed;  extern  vmCvar_t  pmove_msec; -extern  vmCvar_t  g_rankings; -extern  vmCvar_t  g_allowShare; -extern  vmCvar_t  g_creditOverflow; -extern  vmCvar_t  g_enableDust; -extern  vmCvar_t  g_enableBreath; -extern  vmCvar_t  g_singlePlayer; -extern  vmCvar_t  g_humanBuildPoints;  extern  vmCvar_t  g_alienBuildPoints; +extern  vmCvar_t  g_alienBuildQueueTime; +extern  vmCvar_t  g_humanBuildPoints; +extern  vmCvar_t  g_humanBuildQueueTime; +extern  vmCvar_t  g_humanRepeaterBuildPoints; +extern  vmCvar_t  g_humanRepeaterBuildQueueTime; +extern  vmCvar_t  g_humanRepeaterMaxZones;  extern  vmCvar_t  g_humanStage; -extern  vmCvar_t  g_humanKills; +extern  vmCvar_t  g_humanCredits;  extern  vmCvar_t  g_humanMaxStage;  extern  vmCvar_t  g_humanStage2Threshold;  extern  vmCvar_t  g_humanStage3Threshold;  extern  vmCvar_t  g_alienStage; -extern  vmCvar_t  g_alienKills; +extern  vmCvar_t  g_alienCredits;  extern  vmCvar_t  g_alienMaxStage;  extern  vmCvar_t  g_alienStage2Threshold;  extern  vmCvar_t  g_alienStage3Threshold;  extern  vmCvar_t  g_teamImbalanceWarnings; +extern  vmCvar_t  g_freeFundPeriod;  extern  vmCvar_t  g_unlagged; @@ -1400,120 +1169,60 @@ extern  vmCvar_t  g_disabledClasses;  extern  vmCvar_t  g_disabledBuildables;  extern  vmCvar_t  g_markDeconstruct; -extern  vmCvar_t  g_markDeconstructMode; -extern  vmCvar_t  g_deconDead;  extern  vmCvar_t  g_debugMapRotation;  extern  vmCvar_t  g_currentMapRotation; -extern  vmCvar_t  g_currentMap; +extern  vmCvar_t  g_mapRotationNodes; +extern  vmCvar_t  g_mapRotationStack;  extern  vmCvar_t  g_nextMap;  extern  vmCvar_t  g_initialMapRotation; -extern  vmCvar_t  g_chatTeamPrefix; -extern  vmCvar_t  g_actionPrefix; +extern  vmCvar_t  g_sayAreaRange; + +extern  vmCvar_t  g_debugVoices; +extern  vmCvar_t  g_voiceChats; +  extern  vmCvar_t  g_floodMaxDemerits;  extern  vmCvar_t  g_floodMinTime; -extern  vmCvar_t  g_spamTime;  extern  vmCvar_t  g_shove;  extern  vmCvar_t  g_mapConfigs; -extern  vmCvar_t  g_layouts; +extern  vmCvar_t  g_nextLayout; +extern  vmCvar_t  g_layouts[ 9 ];  extern  vmCvar_t  g_layoutAuto; +extern  vmCvar_t  g_emoticonsAllowedInNames; +  extern  vmCvar_t  g_admin; -extern  vmCvar_t  g_adminLog; -extern  vmCvar_t  g_adminParseSay; -extern  vmCvar_t  g_adminSayFilter; -extern  vmCvar_t  g_adminNameProtect; -extern  vmCvar_t  g_adminTempMute;  extern  vmCvar_t  g_adminTempBan;  extern  vmCvar_t  g_adminMaxBan; -extern  vmCvar_t  g_adminTempSpec; -extern  vmCvar_t  g_adminMapLog; -extern  vmCvar_t  g_minLevelToJoinTeam; -extern  vmCvar_t  g_minDeconLevel; -extern  vmCvar_t  g_minDeconAffectsMark; -extern  vmCvar_t  g_forceAutoSelect; -extern  vmCvar_t  g_minLevelToSpecMM1; -extern  vmCvar_t  g_banNotice; - -extern  vmCvar_t  g_devmapKillerHP; -extern  vmCvar_t  g_killerHP;  extern  vmCvar_t  g_privateMessages; -extern  vmCvar_t  g_fullIgnore; -extern  vmCvar_t  g_decolourLogfiles; -extern  vmCvar_t  g_publicSayadmins; -extern  vmCvar_t  g_myStats; -extern  vmCvar_t  g_teamStatus; -extern  vmCvar_t  g_antiSpawnBlock; - -extern  vmCvar_t  g_dretchPunt; - -extern  vmCvar_t  g_devmapNoGod; -extern  vmCvar_t  g_devmapNoStructDmg; - -extern  vmCvar_t  g_slapKnockback; -extern  vmCvar_t  g_slapDamage; - -extern  vmCvar_t  g_voteMinTime; -extern  vmCvar_t  g_mapvoteMaxTime; -extern  vmCvar_t  g_votableMaps; - -extern  vmCvar_t  g_msg; -extern  vmCvar_t  g_msgTime; -extern  vmCvar_t  g_welcomeMsg; -extern  vmCvar_t  g_welcomeMsgTime; -extern  vmCvar_t  g_deconBanTime; +extern  vmCvar_t  g_specChat; +extern  vmCvar_t  g_publicAdminMessages; +extern  vmCvar_t  g_allowTeamOverlay; -extern  vmCvar_t  g_buildLogMaxLength; +extern  vmCvar_t  g_censorship; -extern vmCvar_t  g_AllStats; -extern vmCvar_t  g_AllStatsTime; - -extern  vmCvar_t  mod_jetpackFuel; -extern  vmCvar_t  mod_jetpackConsume; -extern  vmCvar_t  mod_jetpackRegen; - -extern  vmCvar_t  g_adminExpireTime; - -extern  vmCvar_t  g_autoGhost; - -extern  vmCvar_t  g_teamKillThreshold; - -extern  vmCvar_t  g_aimbotAdvertBan; -extern  vmCvar_t  g_aimbotAdvertBanTime; -extern  vmCvar_t  g_aimbotAdvertBanReason; - -extern  vmCvar_t  g_Bubbles; -extern  vmCvar_t  g_scrimMode; -extern  vmCvar_t  g_gradualFreeFunds; -extern  vmCvar_t  g_bleedingSpree; -extern  vmCvar_t  g_schachtmeisterClearThreshold; -extern  vmCvar_t  g_schachtmeisterAutobahnThreshold; -extern  vmCvar_t  g_schachtmeisterAutobahnMessage; -extern  vmCvar_t  g_adminAutobahnNotify; - -void      trap_Printf( const char *fmt ); -void      trap_Error( const char *fmt ); +void      trap_Print( const char *fmt ); +void      trap_Error( const char *fmt ) __attribute__((noreturn));  int       trap_Milliseconds( void );  int       trap_RealTime( qtime_t *qtime );  int       trap_Argc( void );  void      trap_Argv( int n, char *buffer, int bufferLength );  void      trap_Args( char *buffer, int bufferLength ); -int       trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +int       trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode );  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 +int       trap_FS_Seek( fileHandle_t f, long offset, enum FS_Mode origin ); // fsOrigin_t  void      trap_SendConsoleCommand( int exec_when, const char *text );  void      trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags );  void      trap_Cvar_Update( vmCvar_t *cvar );  void      trap_Cvar_Set( const char *var_name, const char *value );  int       trap_Cvar_VariableIntegerValue( const char *var_name ); -float     trap_Cvar_VariableValue( const char *var_name );  void      trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );  void      trap_LocateGameData( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t,                                 playerState_t *gameClients, int sizeofGameClient ); @@ -1521,6 +1230,7 @@ void      trap_DropClient( int clientNum, const char *reason );  void      trap_SendServerCommand( int clientNum, const char *text );  void      trap_SetConfigstring( int num, const char *string );  void      trap_GetConfigstring( int num, char *buffer, int bufferSize ); +void      trap_SetConfigstringRestrictions( int num, const clientList_t *clientList );  void      trap_GetUserinfo( int num, char *buffer, int bufferSize );  void      trap_SetUserinfo( int num, const char *buffer );  void      trap_GetServerinfo( char *buffer, int bufferSize ); @@ -1536,10 +1246,11 @@ void      trap_LinkEntity( gentity_t *ent );  void      trap_UnlinkEntity( gentity_t *ent );  int       trap_EntitiesInBox( const vec3_t mins, const vec3_t maxs, int *entityList, int maxcount );  qboolean  trap_EntityContact( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); -int       trap_BotAllocateClient( void ); -void      trap_BotFreeClient( int clientNum );  void      trap_GetUsercmd( int clientNum, usercmd_t *cmd );  qboolean  trap_GetEntityToken( char *buffer, int bufferSize );  void      trap_SnapVector( float *v ); -void      trap_SendGameStat( const char *data ); + +void      trap_AddCommand( const char *cmdName ); +void      trap_RemoveCommand( const char *cmdName ); +int       trap_FS_GetFilteredFiles( const char *path, const char *extension, const char *filter, char *listbuf, int bufsize ); diff --git a/src/game/g_main.c b/src/game/g_main.c index 6623959..f0f2158 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,44 +17,38 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */  #include "g_local.h" -#define QVM_NAME       "AA-QVM" -#define QVM_VERSIONNUM      "MULTIPROTOCOL" -  level_locals_t  level;  typedef struct  {    vmCvar_t  *vmCvar; -  char    *cvarName; -  char    *defaultString; -  int     cvarFlags; -  int     modificationCount;  // for tracking changes -  qboolean  trackChange;  // track this variable, and announce if changed -  qboolean teamShader;        // track and if changed, update shader state +  char      *cvarName; +  char      *defaultString; +  int       cvarFlags; +  int       modificationCount; // for tracking changes +  qboolean  trackChange;       // track this variable, and announce if changed +  /* certain cvars can be set in worldspawn, but we don't want those values to +     persist, so keep track of non-worldspawn changes and restore that on map +     end. unfortunately, if the server crashes, the value set in worldspawn may +     persist */ +  char      *explicit;  } cvarTable_t;  gentity_t   g_entities[ MAX_GENTITIES ];  gclient_t   g_clients[ MAX_CLIENTS ]; -vmCvar_t  g_fraglimit;  vmCvar_t  g_timelimit;  vmCvar_t  g_suddenDeathTime; -vmCvar_t  g_suddenDeath; -vmCvar_t  g_suddenDeathMode; -vmCvar_t  g_capturelimit;  vmCvar_t  g_friendlyFire; -vmCvar_t  g_friendlyFireAliens; -vmCvar_t  g_friendlyFireHumans; -vmCvar_t  g_friendlyFireMovementAttacks; -vmCvar_t  g_retribution;  vmCvar_t  g_friendlyBuildableFire; +vmCvar_t  g_dretchPunt;  vmCvar_t  g_password;  vmCvar_t  g_needpass;  vmCvar_t  g_maxclients; @@ -63,79 +58,47 @@ vmCvar_t  g_speed;  vmCvar_t  g_gravity;  vmCvar_t  g_cheats;  vmCvar_t  g_knockback; -vmCvar_t  g_quadfactor;  vmCvar_t  g_inactivity;  vmCvar_t  g_debugMove;  vmCvar_t  g_debugDamage; -vmCvar_t  g_debugAlloc; -vmCvar_t  g_weaponRespawn; -vmCvar_t  g_weaponTeamRespawn;  vmCvar_t  g_motd;  vmCvar_t  g_synchronousClients;  vmCvar_t  g_warmup; -vmCvar_t  g_warmupMode;  vmCvar_t  g_doWarmup;  vmCvar_t  g_restarted;  vmCvar_t  g_lockTeamsAtStart;  vmCvar_t  g_logFile;  vmCvar_t  g_logFileSync; -vmCvar_t  g_blood; -vmCvar_t  g_podiumDist; -vmCvar_t  g_podiumDrop;  vmCvar_t  g_allowVote; -vmCvar_t  g_requireVoteReasons;  vmCvar_t  g_voteLimit;  vmCvar_t  g_suddenDeathVotePercent;  vmCvar_t  g_suddenDeathVoteDelay; -vmCvar_t  g_extendVotesPercent; -vmCvar_t  g_extendVotesTime; -vmCvar_t  g_extendVotesCount; -vmCvar_t  g_kickVotesPercent; -vmCvar_t  g_customVote1; -vmCvar_t  g_customVote2; -vmCvar_t  g_customVote3; -vmCvar_t  g_customVote4; -vmCvar_t  g_customVote5; -vmCvar_t  g_customVote6; -vmCvar_t  g_customVote7; -vmCvar_t  g_customVote8; -vmCvar_t  g_customVotePercent; -vmCvar_t  g_mapVotesPercent; -vmCvar_t  g_extendVotesPercent; -vmCvar_t  g_extendVotesTime; -vmCvar_t  g_extendVotesCount; -vmCvar_t  g_mapRotationVote; -vmCvar_t  g_readyPercent; -vmCvar_t  g_designateVotes; -vmCvar_t  g_teamAutoJoin;  vmCvar_t  g_teamForceBalance; -vmCvar_t  g_banIPs; -vmCvar_t  g_filterBan;  vmCvar_t  g_smoothClients; -vmCvar_t  g_outdatedClientMessage;  vmCvar_t  pmove_fixed;  vmCvar_t  pmove_msec; -vmCvar_t  g_rankings; -vmCvar_t  g_listEntity; -vmCvar_t  g_minCommandPeriod;  vmCvar_t  g_minNameChangePeriod;  vmCvar_t  g_maxNameChanges; -vmCvar_t  g_newbieNumbering; -vmCvar_t  g_newbieNamePrefix; -vmCvar_t  g_humanBuildPoints;  vmCvar_t  g_alienBuildPoints; +vmCvar_t  g_alienBuildQueueTime; +vmCvar_t  g_humanBuildPoints; +vmCvar_t  g_humanBuildQueueTime; +vmCvar_t  g_humanRepeaterBuildPoints; +vmCvar_t  g_humanRepeaterBuildQueueTime; +vmCvar_t  g_humanRepeaterMaxZones;  vmCvar_t  g_humanStage; -vmCvar_t  g_humanKills; +vmCvar_t  g_humanCredits;  vmCvar_t  g_humanMaxStage;  vmCvar_t  g_humanStage2Threshold;  vmCvar_t  g_humanStage3Threshold;  vmCvar_t  g_alienStage; -vmCvar_t  g_alienKills; +vmCvar_t  g_alienCredits;  vmCvar_t  g_alienMaxStage;  vmCvar_t  g_alienStage2Threshold;  vmCvar_t  g_alienStage3Threshold;  vmCvar_t  g_teamImbalanceWarnings; +vmCvar_t  g_freeFundPeriod;  vmCvar_t  g_unlagged; @@ -144,105 +107,52 @@ vmCvar_t  g_disabledClasses;  vmCvar_t  g_disabledBuildables;  vmCvar_t  g_markDeconstruct; -vmCvar_t  g_markDeconstructMode; -vmCvar_t  g_deconDead;  vmCvar_t  g_debugMapRotation;  vmCvar_t  g_currentMapRotation; -vmCvar_t  g_currentMap; +vmCvar_t  g_mapRotationNodes; +vmCvar_t  g_mapRotationStack;  vmCvar_t  g_nextMap;  vmCvar_t  g_initialMapRotation; +vmCvar_t  g_debugVoices; +vmCvar_t  g_voiceChats; +  vmCvar_t  g_shove;  vmCvar_t  g_mapConfigs; -vmCvar_t  g_chatTeamPrefix; -vmCvar_t  g_actionPrefix; +vmCvar_t  g_sayAreaRange; +  vmCvar_t  g_floodMaxDemerits;  vmCvar_t  g_floodMinTime; -vmCvar_t  g_spamTime; -vmCvar_t  g_layouts; +vmCvar_t  g_nextLayout; +vmCvar_t  g_layouts[ 9 ];  vmCvar_t  g_layoutAuto; +vmCvar_t  g_emoticonsAllowedInNames; +  vmCvar_t  g_admin; -vmCvar_t  g_adminLog; -vmCvar_t  g_adminParseSay; -vmCvar_t  g_adminSayFilter; -vmCvar_t  g_adminNameProtect; -vmCvar_t  g_adminTempMute;  vmCvar_t  g_adminTempBan;  vmCvar_t  g_adminMaxBan; -vmCvar_t  g_adminTempSpec; -vmCvar_t  g_adminMapLog; -vmCvar_t  g_minLevelToJoinTeam; -vmCvar_t  g_minDeconLevel; -vmCvar_t  g_minDeconAffectsMark; -vmCvar_t  g_forceAutoSelect;  vmCvar_t  g_privateMessages; -vmCvar_t  g_fullIgnore; -vmCvar_t  g_decolourLogfiles; -vmCvar_t  g_minLevelToSpecMM1; -vmCvar_t  g_publicSayadmins; -vmCvar_t  g_myStats; -vmCvar_t  g_AllStats; -vmCvar_t  g_AllStatsTime; -vmCvar_t  g_teamStatus; -vmCvar_t  g_antiSpawnBlock; -vmCvar_t  g_banNotice; - -vmCvar_t  g_devmapKillerHP; -vmCvar_t  g_killerHP; - -vmCvar_t  g_buildLogMaxLength; - -vmCvar_t  g_tag; - -vmCvar_t  g_dretchPunt; - -vmCvar_t  g_allowShare; -vmCvar_t  g_creditOverflow; - -vmCvar_t  g_devmapNoGod; -vmCvar_t  g_devmapNoStructDmg; - -vmCvar_t  g_slapKnockback; -vmCvar_t  g_slapDamage; - -vmCvar_t  g_voteMinTime; -vmCvar_t  g_mapvoteMaxTime; -vmCvar_t  g_votableMaps; +vmCvar_t  g_specChat; +vmCvar_t  g_publicAdminMessages; +vmCvar_t  g_allowTeamOverlay; -vmCvar_t  g_msg; -vmCvar_t  g_msgTime; -vmCvar_t  g_welcomeMsg; -vmCvar_t  g_welcomeMsgTime; -vmCvar_t  g_deconBanTime; +vmCvar_t  g_censorship; +vmCvar_t  g_tag; -vmCvar_t  mod_jetpackFuel; -vmCvar_t  mod_jetpackConsume; -vmCvar_t  mod_jetpackRegen; - -vmCvar_t  g_adminExpireTime; - -vmCvar_t  g_autoGhost; - -vmCvar_t  g_teamKillThreshold; - -vmCvar_t  g_aimbotAdvertBan; -vmCvar_t  g_aimbotAdvertBanTime; -vmCvar_t  g_aimbotAdvertBanReason; -vmCvar_t  g_Bubbles; -vmCvar_t  g_scrimMode; -vmCvar_t  g_gradualFreeFunds; -vmCvar_t  g_bleedingSpree; -vmCvar_t  g_schachtmeisterClearThreshold; -vmCvar_t  g_schachtmeisterAutobahnThreshold; -vmCvar_t  g_schachtmeisterAutobahnMessage; -vmCvar_t  g_adminAutobahnNotify; +// copy cvars that can be set in worldspawn so they can be restored later +static char cv_gravity[ MAX_CVAR_VALUE_STRING ]; +static char cv_humanMaxStage[ MAX_CVAR_VALUE_STRING ]; +static char cv_alienMaxStage[ MAX_CVAR_VALUE_STRING ]; +static char cv_humanRepeaterBuildPoints[ MAX_CVAR_VALUE_STRING ]; +static char cv_humanBuildPoints[ MAX_CVAR_VALUE_STRING ]; +static char cv_alienBuildPoints[ MAX_CVAR_VALUE_STRING ];  static cvarTable_t   gameCvarTable[ ] =  { @@ -251,223 +161,133 @@ static cvarTable_t   gameCvarTable[ ] =    // noset vars    { NULL, "gamename", GAME_VERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  }, -  { NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse  },    { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse  },    { &g_lockTeamsAtStart, "g_lockTeamsAtStart", "0", CVAR_ROM, 0, qfalse  },    { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  },    { NULL, "P", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  }, -  { NULL, "ff", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  }, -  { NULL, "qvm_version", QVM_NAME " " QVM_VERSIONNUM " (" __DATE__ ", " __TIME__ ")", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse  },    // latched vars    { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse  }, -  { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue  },    // change anytime vars -  { &g_timelimit, "timelimit", "45", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, -  { &g_suddenDeathTime, "g_suddenDeathTime", "30", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, -  { &g_suddenDeathMode, "g_suddenDeathMode", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, -  { &g_suddenDeath, "g_suddenDeath", "0", CVAR_SERVERINFO | CVAR_NORESTART, 0, qtrue }, +  { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse  }, -  { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse  }, +  { &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, +  { &g_suddenDeathTime, "g_suddenDeathTime", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, -  { &g_friendlyFire, "g_friendlyFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue  }, -  { &g_friendlyFireAliens, "g_friendlyFireAliens", "0", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_friendlyFireHumans, "g_friendlyFireHumans", "0", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_retribution, "g_retribution", "0", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue  }, -  { &g_friendlyFireMovementAttacks, "g_friendlyFireMovementAttacks", "1", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_devmapNoGod, "g_devmapNoGod", "0", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_devmapNoStructDmg, "g_devmapNoStructDmg", "0", CVAR_ARCHIVE, 0, qtrue  }, +  { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse  }, -  { &g_slapKnockback, "g_slapKnockback", "200", CVAR_ARCHIVE, 0, qfalse}, -  { &g_slapDamage, "g_slapDamage", "0", CVAR_ARCHIVE, 0, qfalse}, +  { &g_friendlyFire, "g_friendlyFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue  }, +  { &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue  }, +  { &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_teamAutoJoin, "g_teamAutoJoin", "0", CVAR_ARCHIVE  }, -  { &g_teamForceBalance, "g_teamForceBalance", "1", CVAR_ARCHIVE  }, +  { &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE, 0, qtrue },    { &g_warmup, "g_warmup", "10", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_warmupMode, "g_warmupMode", "1", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_doWarmup, "g_doWarmup", "1", CVAR_ARCHIVE, 0, qtrue  }, +  { &g_doWarmup, "g_doWarmup", "0", CVAR_ARCHIVE, 0, qtrue  },    { &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse  },    { &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse  },    { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse  }, -  { &g_banIPs, "g_banIPs", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_filterBan, "g_filterBan", "1", CVAR_ARCHIVE, 0, qfalse  }, -    { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, -   -  { &g_autoGhost, "g_autoGhost", "1", CVAR_SERVERINFO, 0, qfalse },    { &g_dedicated, "dedicated", "0", 0, 0, qfalse  }, -  { &g_speed, "g_speed", "320", CVAR_SERVERINFO, 0, qtrue  }, -  { &g_gravity, "g_gravity", "800", CVAR_SERVERINFO, 0, qtrue  }, -  { &g_knockback, "g_knockback", "1000", CVAR_SERVERINFO, 0, qtrue  }, -  { &g_quadfactor, "g_quadfactor", "3", 0, 0, qtrue  }, -  { &g_weaponRespawn, "g_weaponrespawn", "5", 0, 0, qtrue  }, -  { &g_weaponTeamRespawn, "g_weaponTeamRespawn", "30", 0, 0, qtrue }, +  { &g_speed, "g_speed", "320", 0, 0, qtrue  }, +  { &g_gravity, "g_gravity", "800", 0, 0, qtrue, cv_gravity }, +  { &g_knockback, "g_knockback", "1000", 0, 0, qtrue  },    { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue },    { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse },    { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse }, -  { &g_debugAlloc, "g_debugAlloc", "0", 0, 0, qfalse },    { &g_motd, "g_motd", "", 0, 0, qfalse }, -  { &g_blood, "com_blood", "1", 0, 0, qfalse }, - -  { &g_podiumDist, "g_podiumDist", "80", 0, 0, qfalse }, -  { &g_podiumDrop, "g_podiumDrop", "70", 0, 0, qfalse },    { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse }, -  { &g_requireVoteReasons, "g_requireVoteReasons", "0", CVAR_ARCHIVE, 0, qfalse },    { &g_voteLimit, "g_voteLimit", "5", CVAR_ARCHIVE, 0, qfalse }, -  { &g_voteMinTime, "g_voteMinTime", "120", CVAR_ARCHIVE, 0, qfalse }, -  { &g_mapvoteMaxTime, "g_mapvoteMaxTime", "240", CVAR_ARCHIVE, 0, qfalse }, -  { &g_votableMaps, "g_votableMaps", "", CVAR_ARCHIVE, 0, qtrue },    { &g_suddenDeathVotePercent, "g_suddenDeathVotePercent", "74", CVAR_ARCHIVE, 0, qfalse },    { &g_suddenDeathVoteDelay, "g_suddenDeathVoteDelay", "180", CVAR_ARCHIVE, 0, qfalse }, -  { &g_customVote1, "g_customVote1", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_customVote2, "g_customVote2", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_customVote3, "g_customVote3", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_customVote4, "g_customVote4", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_customVote5, "g_customVote5", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_customVote6, "g_customVote6", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_customVote7, "g_customVote7", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_customVote8, "g_customVote8", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_customVotePercent, "g_customVotePercent", "50", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_mapVotesPercent, "g_mapVotesPercent", "50", CVAR_ARCHIVE, 0, qfalse }, -  { &g_extendVotesPercent, "g_extendVotesPercent", "74", CVAR_ARCHIVE, 0, qfalse }, -  { &g_extendVotesTime, "g_extendVotesTime", "10", CVAR_ARCHIVE, 0, qfalse }, -  { &g_extendVotesCount, "g_extendVotesCount", "2", CVAR_ARCHIVE, 0, qfalse }, -  { &g_mapRotationVote, "g_mapRotationVote", "15", CVAR_ARCHIVE, 0, qfalse }, -  { &g_readyPercent, "g_readyPercent", "0", CVAR_ARCHIVE, 0, qfalse }, -  { &g_designateVotes, "g_designateVotes", "0", CVAR_ARCHIVE, 0, qfalse }, -   -  { &g_listEntity, "g_listEntity", "0", 0, 0, qfalse }, -  { &g_minCommandPeriod, "g_minCommandPeriod", "500", 0, 0, qfalse},    { &g_minNameChangePeriod, "g_minNameChangePeriod", "5", 0, 0, qfalse},    { &g_maxNameChanges, "g_maxNameChanges", "5", 0, 0, qfalse}, -  { &g_newbieNumbering, "g_newbieNumbering", "0", CVAR_ARCHIVE, 0, qfalse}, -  { &g_newbieNamePrefix, "g_newbieNamePrefix", "Newbie#", CVAR_ARCHIVE, 0, qfalse},    { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse}, -  { &g_outdatedClientMessage, "g_outdatedClientMessage", "", CVAR_ARCHIVE, 0, qfalse},    { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse},    { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse}, -  { &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, CVAR_SERVERINFO, 0, qfalse  }, -  { &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, CVAR_SERVERINFO, 0, qfalse  }, +  { &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, 0, 0, qfalse, cv_alienBuildPoints }, +  { &g_alienBuildQueueTime, "g_alienBuildQueueTime", DEFAULT_ALIEN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse  }, +  { &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, 0, 0, qfalse, cv_humanBuildPoints }, +  { &g_humanBuildQueueTime, "g_humanBuildQueueTime", DEFAULT_HUMAN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse  }, +  { &g_humanRepeaterBuildPoints, "g_humanRepeaterBuildPoints", DEFAULT_HUMAN_REPEATER_BUILDPOINTS, CVAR_ARCHIVE, 0, qfalse, cv_humanRepeaterBuildPoints }, +  { &g_humanRepeaterMaxZones, "g_humanRepeaterMaxZones", DEFAULT_HUMAN_REPEATER_MAX_ZONES, CVAR_ARCHIVE, 0, qfalse  }, +  { &g_humanRepeaterBuildQueueTime, "g_humanRepeaterBuildQueueTime", DEFAULT_HUMAN_REPEATER_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse  },    { &g_humanStage, "g_humanStage", "0", 0, 0, qfalse  }, -  { &g_humanKills, "g_humanKills", "0", 0, 0, qfalse  }, -  { &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse  }, +  { &g_humanCredits, "g_humanCredits", "0", 0, 0, qfalse  }, +  { &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse, cv_humanMaxStage },    { &g_humanStage2Threshold, "g_humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, 0, 0, qfalse  },    { &g_humanStage3Threshold, "g_humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, 0, 0, qfalse  },    { &g_alienStage, "g_alienStage", "0", 0, 0, qfalse  }, -  { &g_alienKills, "g_alienKills", "0", 0, 0, qfalse  }, -  { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse  }, +  { &g_alienCredits, "g_alienCredits", "0", 0, 0, qfalse  }, +  { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse, cv_alienMaxStage },    { &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0, qfalse  },    { &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0, qfalse  }, -      { &g_teamImbalanceWarnings, "g_teamImbalanceWarnings", "30", CVAR_ARCHIVE, 0, qfalse  }, -   +  { &g_freeFundPeriod, "g_freeFundPeriod", DEFAULT_FREEKILL_PERIOD, CVAR_ARCHIVE, 0, qtrue }, +    { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue  }, -  { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM, 0, qfalse  }, -  { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM, 0, qfalse  }, -  { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM, 0, qfalse  }, +  { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse  }, +  { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse  }, +  { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse  }, + +  { &g_sayAreaRange, "g_sayAreaRange", "1000", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_chatTeamPrefix, "g_chatTeamPrefix", "1", CVAR_ARCHIVE  }, -  { &g_actionPrefix, "g_actionPrefix", "* ", CVAR_ARCHIVE, 0, qfalse },    { &g_floodMaxDemerits, "g_floodMaxDemerits", "5000", CVAR_ARCHIVE, 0, qfalse  },    { &g_floodMinTime, "g_floodMinTime", "2000", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_spamTime, "g_spamTime", "2", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_markDeconstruct, "g_markDeconstruct", "0", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_markDeconstructMode, "g_markDeconstructMode", "0", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_deconDead, "g_deconDead", "0", CVAR_ARCHIVE, 0, qtrue  }, +  { &g_markDeconstruct, "g_markDeconstruct", "3", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue  },    { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse  },    { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse  }, // -1 = NOT_ROTATING -  { &g_currentMap, "g_currentMap", "0", 0, 0, qfalse  }, +  { &g_mapRotationNodes, "g_mapRotationNodes", "", CVAR_ROM, 0, qfalse  }, +  { &g_mapRotationStack, "g_mapRotationStack", "", CVAR_ROM, 0, qfalse  },    { &g_nextMap, "g_nextMap", "", 0 , 0, qtrue  },    { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_shove, "g_shove", "15", CVAR_ARCHIVE, 0, qfalse  }, +  { &g_debugVoices, "g_debugVoices", "0", 0, 0, qfalse  }, +  { &g_voiceChats, "g_voiceChats", "1", CVAR_ARCHIVE, 0, qfalse }, +  { &g_shove, "g_shove", "0.0", CVAR_ARCHIVE, 0, qfalse  },    { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse  },    { NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse  }, -  { &g_layouts, "g_layouts", "", CVAR_LATCH, 0, qfalse  }, +  { &g_nextLayout, "g_nextLayout", "", 0, 0, qfalse  }, +  { &g_layouts[ 0 ], "g_layouts", "", 0, 0, qfalse  }, +  { &g_layouts[ 1 ], "g_layouts2", "", 0, 0, qfalse  }, +  { &g_layouts[ 2 ], "g_layouts3", "", 0, 0, qfalse  }, +  { &g_layouts[ 3 ], "g_layouts4", "", 0, 0, qfalse  }, +  { &g_layouts[ 4 ], "g_layouts5", "", 0, 0, qfalse  }, +  { &g_layouts[ 5 ], "g_layouts6", "", 0, 0, qfalse  }, +  { &g_layouts[ 6 ], "g_layouts7", "", 0, 0, qfalse  }, +  { &g_layouts[ 7 ], "g_layouts8", "", 0, 0, qfalse  }, +  { &g_layouts[ 8 ], "g_layouts9", "", 0, 0, qfalse  },    { &g_layoutAuto, "g_layoutAuto", "1", CVAR_ARCHIVE, 0, qfalse  }, +  { &g_emoticonsAllowedInNames, "g_emoticonsAllowedInNames", "1", CVAR_LATCH|CVAR_ARCHIVE, 0, qfalse  }, +    { &g_admin, "g_admin", "admin.dat", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_adminLog, "g_adminLog", "admin.log", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_adminParseSay, "g_adminParseSay", "1", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_adminSayFilter, "g_adminSayFilter", "0", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_adminNameProtect, "g_adminNameProtect", "1", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_adminTempMute, "g_adminTempMute", "5m", CVAR_ARCHIVE, 0, qfalse },    { &g_adminTempBan, "g_adminTempBan", "2m", CVAR_ARCHIVE, 0, qfalse  },    { &g_adminMaxBan, "g_adminMaxBan", "2w", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_adminTempSpec, "g_adminTempSpec", "2m", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_adminMapLog, "g_adminMapLog", "", CVAR_ROM, 0, qfalse  }, -  { &g_minLevelToJoinTeam, "g_minLevelToJoinTeam", "0", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_minDeconLevel, "g_minDeconLevel", "0", CVAR_ARCHIVE, 0, qfalse}, -  { &g_minDeconAffectsMark, "g_minDeconAffectsMark", "0", CVAR_ARCHIVE, 0, qfalse}, -  { &g_forceAutoSelect, "g_forceAutoSelect", "0", CVAR_ARCHIVE, 0, qtrue },  -  { &g_adminExpireTime, "g_adminExpireTime", "0", CVAR_ARCHIVE, 0, qfalse  }, -   +    { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_fullIgnore, "g_fullIgnore", "1", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_decolourLogfiles, "g_decolourLogfiles", "0", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_buildLogMaxLength, "g_buildLogMaxLength", "50", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_myStats, "g_myStats", "1", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_AllStats, "g_AllStats", "0", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_AllStatsTime, "g_AllStatsTime", "60", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_teamStatus, "g_teamStatus", "0", CVAR_ARCHIVE, 0, qtrue }, -  { &g_publicSayadmins, "g_publicSayadmins", "1", CVAR_ARCHIVE, 0, qfalse  },     -  { &g_minLevelToSpecMM1, "g_minLevelToSpecMM1", "0", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_antiSpawnBlock, "g_antiSpawnBlock", "0", CVAR_ARCHIVE, 0, qfalse  }, -   -  { &g_devmapKillerHP, "g_devmapKillerHP", "0", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_killerHP, "g_killerHP", "0", CVAR_ARCHIVE, 0, qtrue  }, -   -  { &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse }, -   -  { &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qfalse  }, -   -  { &g_msg, "g_msg", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_msgTime, "g_msgTime", "0", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_welcomeMsg, "g_welcomeMsg", "", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_welcomeMsgTime, "g_welcomeMsgTime", "0", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_deconBanTime, "g_deconBanTime", "2h", CVAR_ARCHIVE, 0, qfalse  }, -   -  { &g_rankings, "g_rankings", "0", 0, 0, qfalse }, -  { &g_allowShare, "g_allowShare", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse}, -  { &g_creditOverflow, "g_creditOverflow", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse}, -  { &g_banNotice, "g_banNotice", "", CVAR_ARCHIVE, 0, qfalse  }, -   -  { &mod_jetpackFuel, "mod_jetpackFuel", "0", CVAR_ARCHIVE, 0, qtrue  }, -  { &mod_jetpackConsume, "mod_jetpackConsume", "2", CVAR_ARCHIVE, 0, qfalse  }, -  { &mod_jetpackRegen, "mod_jetpackRegen", "3", CVAR_ARCHIVE, 0, qfalse  }, - -  { &g_teamKillThreshold, "g_teamKillThreshold", "0", CVAR_ARCHIVE, 0, qfalse  }, - -  { &g_aimbotAdvertBan, "g_aimbotAdvertBan", "0", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_aimbotAdvertBanTime, "g_aimbotAdvertBanTime", "0", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_aimbotAdvertBanReason, "g_aimbotAdvertBanReason", "AUTOBAN: AIMBOT", CVAR_ARCHIVE, 0, qfalse  }, - -  { &g_Bubbles, "g_Bubbles", "1", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_scrimMode, "g_scrimMode", "0", CVAR_ARCHIVE, 0, qfalse }, -  { &g_gradualFreeFunds, "g_gradualFreeFunds", "2", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_bleedingSpree, "g_bleedingSpree", "0", CVAR_ARCHIVE, 0, qfalse  }, -  { &g_gradualFreeFunds, "g_gradualFreeFunds", "2", CVAR_ARCHIVE, 0, qtrue  }, -  { &g_schachtmeisterClearThreshold, "g_schachtmeisterClearThreshold", "-10", CVAR_ARCHIVE, 0, qfalse }, -  { &g_schachtmeisterAutobahnThreshold, "g_schachtmeisterAutobahnThreshold", "-30", CVAR_ARCHIVE, 0, qfalse }, -  { &g_schachtmeisterAutobahnMessage, "g_schachtmeisterAutobahnMessage", "Your host is blacklisted.", CVAR_ARCHIVE, 0, qfalse }, -  { &g_adminAutobahnNotify, "g_adminAutobahnNotify", "1", CVAR_ARCHIVE, 0, qfalse } +  { &g_specChat, "g_specChat", "1", CVAR_ARCHIVE, 0, qfalse  }, +  { &g_publicAdminMessages, "g_publicAdminMessages", "1", CVAR_ARCHIVE, 0, qfalse  }, +  { &g_allowTeamOverlay, "g_allowTeamOverlay", "1", CVAR_ARCHIVE, 0, qtrue  }, + +  { &g_censorship, "g_censorship", "", CVAR_ARCHIVE, 0, qfalse  }, + +  { &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse }  }; -static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] ); +static size_t gameCvarTableSize = ARRAY_LEN( gameCvarTable );  void G_InitGame( int levelTime, int randomSeed, int restart ); @@ -486,9 +306,7 @@ This is the only way control passes into the module.  This must be the very first function compiled into the .q3vm file  ================  */ -Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, -                              int arg5, int arg6, int arg7, int arg8, int arg9, -                              int arg10, int arg11 ) +Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2 )  {    switch( command )    { @@ -541,10 +359,10 @@ void QDECL G_Printf( const char *fmt, ... )    char    text[ 1024 ];    va_start( argptr, fmt ); -  vsprintf( text, fmt, argptr ); +  Q_vsnprintf( text, sizeof( text ), fmt, argptr );    va_end( argptr ); -  trap_Printf( text ); +  trap_Print( text );  }  void QDECL G_Error( const char *fmt, ... ) @@ -553,7 +371,7 @@ void QDECL G_Error( const char *fmt, ... )    char    text[ 1024 ];    va_start( argptr, fmt ); -  vsprintf( text, fmt, argptr ); +  Q_vsnprintf( text, sizeof( text ), fmt, argptr );    va_end( argptr );    trap_Error( text ); @@ -579,11 +397,8 @@ void G_FindTeams( void )    c = 0;    c2 = 0; -  for( i = 1, e = g_entities+i; i < level.num_entities; i++, e++ ) +  for( i = MAX_CLIENTS, e = g_entities + i; i < level.num_entities; i++, e++ )    { -    if( !e->inuse ) -      continue; -      if( !e->team )        continue; @@ -596,9 +411,6 @@ void G_FindTeams( void )      for( j = i + 1, e2 = e + 1; j < level.num_entities; j++, e2++ )      { -      if( !e2->inuse ) -        continue; -        if( !e2->team )          continue; @@ -626,10 +438,6 @@ void G_FindTeams( void )    G_Printf( "%i teams with %i entities\n", c, c2 );  } -void G_RemapTeamShaders( void ) -{ -} -  /*  ================= @@ -640,7 +448,6 @@ void G_RegisterCvars( void )  {    int         i;    cvarTable_t *cv; -  qboolean    remapped = qfalse;    for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ )    { @@ -650,12 +457,9 @@ void G_RegisterCvars( void )      if( cv->vmCvar )        cv->modificationCount = cv->vmCvar->modificationCount; -    if( cv->teamShader ) -      remapped = qtrue; +    if( cv->explicit ) +      strcpy( cv->explicit, cv->vmCvar->string );    } - -  if( remapped ) -    G_RemapTeamShaders( );  }  /* @@ -667,7 +471,6 @@ void G_UpdateCvars( void )  {    int         i;    cvarTable_t *cv; -  qboolean    remapped = qfalse;    for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ )    { @@ -680,21 +483,31 @@ void G_UpdateCvars( void )          cv->modificationCount = cv->vmCvar->modificationCount;          if( cv->trackChange ) -        {            trap_SendServerCommand( -1, va( "print \"Server: %s changed to %s\n\"",              cv->cvarName, cv->vmCvar->string ) ); -          // update serverinfo in case this cvar is passed to clients indirectly -          CalculateRanks( ); -        } -        if( cv->teamShader ) -          remapped = qtrue; +        if( !level.spawning && cv->explicit ) +          strcpy( cv->explicit, cv->vmCvar->string );        }      }    } +} + +/* +================= +G_RestoreCvars +================= +*/ +void G_RestoreCvars( void ) +{ +  int         i; +  cvarTable_t *cv; -  if( remapped ) -    G_RemapTeamShaders( ); +  for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) +  { +    if( cv->vmCvar && cv->explicit ) +      trap_Cvar_Set( cv->cvarName, cv->explicit ); +  }  }  /* @@ -713,7 +526,7 @@ void G_MapConfigs( const char *mapname )    trap_SendConsoleCommand( EXEC_APPEND,      va( "exec \"%s/default.cfg\"\n", g_mapConfigs.string ) ); -   +    trap_SendConsoleCommand( EXEC_APPEND,      va( "exec \"%s/%s.cfg\"\n", g_mapConfigs.string, mapname ) ); @@ -736,11 +549,8 @@ void G_InitGame( int levelTime, int randomSeed, int restart )    G_Printf( "------- Game Initialization -------\n" );    G_Printf( "gamename: %s\n", GAME_VERSION ); -  G_Printf( "gamedate: %s\n", __DATE__ ); -  G_ProcessIPBans( ); - -  G_InitMemory( ); +  BG_InitMemory( );    // set some level globals    memset( &level, 0, sizeof( level ) ); @@ -751,9 +561,6 @@ void G_InitGame( int levelTime, int randomSeed, int restart )    level.snd_fry = G_SoundIndex( "sound/misc/fry.wav" ); // FIXME standing in lava / slime -  trap_Cvar_Set( "qvm_version", -                 QVM_NAME " " QVM_VERSIONNUM " (" __DATE__ ", " __TIME__ ")" ); -    if( g_logFile.string[ 0 ] )    {      if( g_logFileSync.integer ) @@ -767,17 +574,15 @@ void G_InitGame( int levelTime, int randomSeed, int restart )      {        char serverinfo[ MAX_INFO_STRING ];        qtime_t qt; -      int t; -        trap_GetServerinfo( serverinfo, sizeof( serverinfo ) );        G_LogPrintf( "------------------------------------------------------------\n" );        G_LogPrintf( "InitGame: %s\n", serverinfo ); -      t = trap_RealTime( &qt ); -      G_LogPrintf("RealTime: %04i/%02i/%02i %02i:%02i:%02i\n", -            qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday,  +      trap_RealTime( &qt ); +      G_LogPrintf("RealTime: %04i-%02i-%02i %02i:%02i:%02i\n", +            qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday,              qt.tm_hour, qt.tm_min, qt.tm_sec );      } @@ -785,19 +590,28 @@ void G_InitGame( int levelTime, int randomSeed, int restart )    else      G_Printf( "Not logging to disk\n" ); +  if( g_mapConfigs.string[ 0 ] && !trap_Cvar_VariableIntegerValue( "g_mapConfigsLoaded" ) )    {      char map[ MAX_CVAR_VALUE_STRING ] = {""}; +    G_Printf( "InitGame: executing map configuration scripts and restarting\n" );      trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) );      G_MapConfigs( map ); +    trap_SendConsoleCommand( EXEC_APPEND, "wait\nmap_restart 0\n" ); +  } +  else +  { +    // we're done with g_mapConfigs, so reset this for the next map +    trap_Cvar_Set( "g_mapConfigsLoaded", "0" );    } -  // we're done with g_mapConfigs, so reset this for the next map -  trap_Cvar_Set( "g_mapConfigsLoaded", "0" ); +  // set this cvar to 0 if it exists, but otherwise avoid its creation +  if( trap_Cvar_VariableIntegerValue( "g_rangeMarkerWarningGiven" ) ) +    trap_Cvar_Set( "g_rangeMarkerWarningGiven", "0" ); -  if ( g_admin.string[ 0 ] ) { -    G_admin_readconfig( NULL, 0 ); -  } +  G_RegisterCommands( ); +  G_admin_readconfig( NULL ); +  G_LoadCensors( );    // initialize all entities for this game    memset( g_entities, 0, MAX_GENTITIES * sizeof( g_entities[ 0 ] ) ); @@ -817,26 +631,29 @@ void G_InitGame( int levelTime, int randomSeed, int restart )    // range are NEVER anything but clients    level.num_entities = MAX_CLIENTS; +  for( i = 0; i < MAX_CLIENTS; i++ ) +    g_entities[ i ].classname = "clientslot"; +    // let the server system know where the entites are    trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ),      &level.clients[ 0 ].ps, sizeof( level.clients[ 0 ] ) ); +  level.emoticonCount = BG_LoadEmoticons( level.emoticons, MAX_EMOTICONS ); +    trap_SetConfigstring( CS_INTERMISSION, "0" ); -  // update maplog -  G_admin_maplog_update( ); +  G_InitPlayerModel();    // test to see if a custom buildable layout will be loaded    G_LayoutSelect( ); +  // this has to be flipped after the first UpdateCvars +  level.spawning = qtrue;    // parse the key/value pairs and spawn gentities    G_SpawnEntitiesFromString( );    // load up a custom building layout if there is one -  G_LayoutLoad( ); -   -  // load any nobuild markers that have been saved -  G_NobuildLoad( ); +  G_LayoutLoad( level.layout );    // the map might disable some things    BG_InitAllowedGameElements( ); @@ -844,9 +661,8 @@ void G_InitGame( int levelTime, int randomSeed, int restart )    // general initialization    G_FindTeams( ); -  //TA: -  BG_InitClassOverrides( ); -  BG_InitBuildableOverrides( ); +  BG_InitClassConfigs( ); +  BG_InitBuildableConfigs( );    G_InitDamageLocations( );    G_InitMapRotations( );    G_InitSpawnQueue( &level.alienSpawnQueue ); @@ -855,27 +671,27 @@ void G_InitGame( int levelTime, int randomSeed, int restart )    if( g_debugMapRotation.integer )      G_PrintRotations( ); +  level.voices = BG_VoiceInit( ); +  BG_PrintVoices( level.voices, g_debugVoices.integer ); +    //reset stages    trap_Cvar_Set( "g_alienStage", va( "%d", S1 ) );    trap_Cvar_Set( "g_humanStage", va( "%d", S1 ) ); -  trap_Cvar_Set( "g_alienKills", 0 ); -  trap_Cvar_Set( "g_humanKills", 0 ); -  trap_Cvar_Set( "g_suddenDeath", 0 ); +  trap_Cvar_Set( "g_alienCredits", 0 ); +  trap_Cvar_Set( "g_humanCredits", 0 );    level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000;    G_Printf( "-----------------------------------\n" ); -  G_RemapTeamShaders( ); - -  //TA: so the server counts the spawns without a client attached +  // So the server counts the spawns without a client attached    G_CountSpawns( ); -  G_ResetPTRConnections( ); -   -  if(g_lockTeamsAtStart.integer) +  G_UpdateTeamConfigStrings( ); + +  if( g_lockTeamsAtStart.integer )    { -    level.alienTeamLocked=qtrue; -    level.humanTeamLocked=qtrue; +    level.alienTeamLocked = qtrue; +    level.humanTeamLocked = qtrue;      trap_Cvar_Set( "g_lockTeamsAtStart", "0" );    }  } @@ -889,15 +705,13 @@ remove all currently active votes  */  static void G_ClearVotes( void )  { -  level.voteTime = 0; -  trap_SetConfigstring( CS_VOTE_TIME, "" ); -  trap_SetConfigstring( CS_VOTE_STRING, "" ); -  level.teamVoteTime[ 0 ] = 0; -  trap_SetConfigstring( CS_TEAMVOTE_TIME, "" ); -  trap_SetConfigstring( CS_TEAMVOTE_STRING, "" ); -  level.teamVoteTime[ 1 ] = 0; -  trap_SetConfigstring( CS_TEAMVOTE_TIME + 1, "" ); -  trap_SetConfigstring( CS_TEAMVOTE_STRING + 1, "" ); +  int i; +  memset( level.voteTime, 0, sizeof( level.voteTime ) ); +  for( i = 0; i < NUM_TEAMS; i++ ) +  { +    trap_SetConfigstring( CS_VOTE_TIME + i, "" ); +    trap_SetConfigstring( CS_VOTE_STRING + i, "" ); +  }  }  /* @@ -910,6 +724,8 @@ void G_ShutdownGame( int restart )    // in case of a map_restart    G_ClearVotes( ); +  G_RestoreCvars( ); +    G_Printf( "==== ShutdownGame ====\n" );    if( level.logFile ) @@ -917,20 +733,21 @@ void G_ShutdownGame( int restart )      G_LogPrintf( "ShutdownGame:\n" );      G_LogPrintf( "------------------------------------------------------------\n" );      trap_FS_FCloseFile( level.logFile ); +    level.logFile = 0;    } -  // write admin.dat for !seen data -  admin_writeconfig(); -    // write all the client session data so we can get it back    G_WriteSessionData( );    G_admin_cleanup( ); -  G_admin_namelog_cleanup( ); -  G_admin_adminlog_cleanup( ); +  G_namelog_cleanup( ); +  G_UnregisterCommands( ); + +  G_FreePlayerModel( ); +  G_ShutdownMapRotations( );    level.restarted = qfalse; -  level.surrenderTeam = PTE_NONE; +  level.surrenderTeam = TEAM_NONE;    trap_SetConfigstring( CS_WINNER, "" );  } @@ -944,7 +761,7 @@ void QDECL Com_Error( int level, const char *error, ... )    char    text[ 1024 ];    va_start( argptr, error ); -  vsprintf( text, error, argptr ); +  Q_vsnprintf( text, sizeof( text ), error, argptr );    va_end( argptr );    G_Error( "%s", text ); @@ -956,7 +773,7 @@ void QDECL Com_Printf( const char *msg, ... )    char    text[ 1024 ];    va_start( argptr, msg ); -  vsprintf( text, msg, argptr ); +  Q_vsnprintf( text, sizeof( text ), msg, argptr );    va_end( argptr );    G_Printf( "%s", text ); @@ -985,9 +802,9 @@ int QDECL SortRanks( const void *a, const void *b )    cb = &level.clients[ *(int *)b ];    // then sort by score -  if( ca->pers.score > cb->pers.score ) +  if( ca->ps.persistant[ PERS_SCORE ] > cb->ps.persistant[ PERS_SCORE ] )      return -1; -  else if( ca->pers.score < cb->pers.score ) +  if( ca->ps.persistant[ PERS_SCORE ] < cb->ps.persistant[ PERS_SCORE ] )      return 1;    else      return 0; @@ -1016,7 +833,7 @@ void G_InitSpawnQueue( spawnQueue_t *sq )  ============  G_GetSpawnQueueLength -Return tha length of a spawn queue +Return the length of a spawn queue  ============  */  int G_GetSpawnQueueLength( spawnQueue_t *sq ) @@ -1047,6 +864,7 @@ int G_PopSpawnQueue( spawnQueue_t *sq )    {      sq->clients[ sq->front ] = -1;      sq->front = QUEUE_PLUS1( sq->front ); +    G_StopFollowing( g_entities + clientNum );      g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED;      return clientNum; @@ -1079,8 +897,11 @@ qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum )    int i;    for( i = 0; i < MAX_CLIENTS; i++ ) +  {      if( sq->clients[ i ] == clientNum )        return qtrue; +  } +    return qfalse;  } @@ -1211,24 +1032,20 @@ G_SpawnClients  Spawn queued clients  ============  */ -void G_SpawnClients( pTeam_t team ) +void G_SpawnClients( team_t team )  {    int           clientNum;    gentity_t     *ent, *spawn;    vec3_t        spawn_origin, spawn_angles;    spawnQueue_t  *sq = NULL;    int           numSpawns = 0; -  if( g_doWarmup.integer && ( g_warmupMode.integer==1 || g_warmupMode.integer == 2 ) && -      level.time - level.startTime < g_warmup.integer * 1000 ) -  { -    return; -  } -  if( team == PTE_ALIENS ) + +  if( team == TEAM_ALIENS )    {      sq = &level.alienSpawnQueue;      numSpawns = level.numAlienSpawns;    } -  else if( team == PTE_HUMANS ) +  else if( team == TEAM_HUMANS )    {      sq = &level.humanSpawnQueue;      numSpawns = level.numHumanSpawns; @@ -1250,7 +1067,7 @@ void G_SpawnClients( pTeam_t team )        ent = &g_entities[ clientNum ]; -      ent->client->sess.sessionTeam = TEAM_FREE; +      ent->client->sess.spectatorState = SPECTATOR_NOT;        ClientUserinfoChanged( clientNum, qfalse );        ClientSpawn( ent, spawn, spawn_origin, spawn_angles );      } @@ -1271,33 +1088,31 @@ void G_CountSpawns( void )    level.numAlienSpawns = 0;    level.numHumanSpawns = 0; -    for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ )    { -    if( !ent->inuse ) +    if( !ent->inuse || ent->s.eType != ET_BUILDABLE || ent->health <= 0 )        continue; -    if( ent->s.modelindex == BA_A_SPAWN && ent->health > 0 ) +    if( ent->s.modelindex == BA_A_SPAWN )        level.numAlienSpawns++; -    if( ent->s.modelindex == BA_H_SPAWN && ent->health > 0 ) +    if( ent->s.modelindex == BA_H_SPAWN )        level.numHumanSpawns++;    } - -  //let the client know how many spawns there are -  trap_SetConfigstring( CS_SPAWNS, va( "%d %d", -        level.numAlienSpawns, level.numHumanSpawns ) );  } +  /*  ============  G_TimeTilSuddenDeath  ============  */ +#define SUDDENDEATHWARNING 60000  int G_TimeTilSuddenDeath( void )  { -  if( (!g_suddenDeathTime.integer && level.suddenDeathBeginTime==0 ) || level.suddenDeathBeginTime<0 ) -    return 999999999; // Always some time away +  if( ( !g_suddenDeathTime.integer && level.suddenDeathBeginTime == 0 ) || +      ( level.suddenDeathBeginTime < 0 ) ) +    return SUDDENDEATHWARNING + 1; // Always some time away    return ( ( level.suddenDeathBeginTime ) - ( level.time - level.startTime ) );  } @@ -1314,180 +1129,152 @@ Recalculate the quantity of building points available to the teams  */  void G_CalculateBuildPoints( void )  { -  int         i; -  buildable_t buildable; -  gentity_t   *ent; -  int         localHTP = g_humanBuildPoints.integer, -              localATP = g_alienBuildPoints.integer; - -  // g_suddenDeath sets what state we want it to be.   -  // level.suddenDeath says whether we've calculated BPs at the 'start' of SD or not +  int               i; +  buildable_t       buildable; +  buildPointZone_t  *zone; -  // reset if SD was on, but now it's off -  if(!g_suddenDeath.integer && level.suddenDeath)  +  // BP queue updates +  while( level.alienBuildPointQueue > 0 && +         level.alienNextQueueTime < level.time )    { -    level.suddenDeath = qfalse; -    level.suddenDeathWarning = 0; -    level.suddenDeathBeginTime = -1; -    if((level.time - level.startTime) < (g_suddenDeathTime.integer * 60000 ) ) -      level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; -    else -      level.suddenDeathBeginTime = -1; +    level.alienBuildPointQueue--; +    level.alienNextQueueTime += G_NextQueueTime( level.alienBuildPointQueue, +                                               g_alienBuildPoints.integer, +                                               g_alienBuildQueueTime.integer );    } -  if(!level.suddenDeath) +  while( level.humanBuildPointQueue > 0 && +         level.humanNextQueueTime < level.time )    { -    if(g_suddenDeath.integer || G_TimeTilSuddenDeath( ) <= 0 ) //Conditions to enter SD -    { -      //begin sudden death -      if( level.suddenDeathWarning < TW_PASSED ) -      { -        trap_SendServerCommand( -1, "cp \"Sudden Death!\"" ); -        G_LogPrintf("Beginning Sudden Death (Mode %d)\n",g_suddenDeathMode.integer); -        localHTP = 0; -        localATP = 0; +    level.humanBuildPointQueue--; +    level.humanNextQueueTime += G_NextQueueTime( level.humanBuildPointQueue, +                                               g_humanBuildPoints.integer, +                                               g_humanBuildQueueTime.integer ); +  } -        if( g_suddenDeathMode.integer == SDMODE_SELECTIVE ) -        { -          for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) -          { -            if( ent->s.eType != ET_BUILDABLE ) -              continue; -         -            if( BG_FindReplaceableTestForBuildable( ent->s.modelindex ) ) -            { -              int t = BG_FindTeamForBuildable( ent->s.modelindex ); -         -              if( t == BIT_HUMANS ) -                localHTP += BG_FindBuildPointsForBuildable( ent->s.modelindex ); -              else if( t == BIT_ALIENS ) -                localATP += BG_FindBuildPointsForBuildable( ent->s.modelindex ); -            } -          } -        } -        level.suddenDeathHBuildPoints = localHTP; -        level.suddenDeathABuildPoints = localATP; -        level.suddenDeathBeginTime = level.time; -        level.suddenDeath=qtrue; -        trap_Cvar_Set( "g_suddenDeath", "1" ); +  // Sudden Death checks +  if( G_TimeTilSuddenDeath( ) <= 0 && level.suddenDeathWarning < TW_PASSED ) +  { +    G_LogPrintf( "Beginning Sudden Death\n" ); +    trap_SendServerCommand( -1, "cp \"Sudden Death!\"" ); +    trap_SendServerCommand( -1, "print \"Beginning Sudden Death.\n\"" ); +    level.suddenDeathWarning = TW_PASSED; +    G_ClearDeconMarks( ); -        level.suddenDeathWarning = TW_PASSED; -      } -    }   -    else  +    // Clear blueprints, or else structs that cost 0 BP can still be built after SD +    for( i = 0; i < level.maxclients; i++ )      { -       //warn about sudden death -       if( ( G_TimeTilSuddenDeath( ) <= 60000 ) && -           (  level.suddenDeathWarning < TW_IMMINENT ) ) -       { -         trap_SendServerCommand( -1, va("cp \"Sudden Death in %d seconds!\"",  -               (int)(G_TimeTilSuddenDeath() / 1000 ) ) ); -         level.suddenDeathWarning = TW_IMMINENT; -       } +      if( g_entities[ i ].client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) +        g_entities[ i ].client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;      }    } -   -  //set BP at each cycle -  if( g_suddenDeath.integer ) +  else if( G_TimeTilSuddenDeath( ) <= SUDDENDEATHWARNING && +    level.suddenDeathWarning < TW_IMMINENT )    { -    localHTP = level.suddenDeathHBuildPoints; -    localATP = level.suddenDeathABuildPoints; +    trap_SendServerCommand( -1, va( "cp \"Sudden Death in %d seconds!\"", +          (int)( G_TimeTilSuddenDeath( ) / 1000 ) ) ); +    trap_SendServerCommand( -1, va( "print \"Sudden Death will begin in %d seconds.\n\"", +          (int)( G_TimeTilSuddenDeath( ) / 1000 ) ) ); +    level.suddenDeathWarning = TW_IMMINENT;    } -  else + +  level.humanBuildPoints = g_humanBuildPoints.integer - level.humanBuildPointQueue; +  level.alienBuildPoints = g_alienBuildPoints.integer - level.alienBuildPointQueue; + +  // Reset buildPointZones +  for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ )    { -    localHTP = g_humanBuildPoints.integer; -    localATP = g_alienBuildPoints.integer; +    buildPointZone_t *zone = &level.buildPointZones[ i ]; + +    zone->active = qfalse; +    zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer;    } -  level.humanBuildPoints = level.humanBuildPointsPowered = localHTP; -  level.alienBuildPoints = localATP; +  // Iterate through entities +  for( i = MAX_CLIENTS; i < level.num_entities; i++ ) +  { +    gentity_t         *ent = &g_entities[ i ]; +    buildPointZone_t  *zone; +    buildable_t       buildable; +    int               cost; -  level.reactorPresent = qfalse; -  level.overmindPresent = qfalse; +    if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD ) +      continue; -  for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) +    // mark a zone as active +    if( ent->usesBuildPointZone ) +    { +      assert( ent->buildPointZone >= 0 && ent->buildPointZone < g_humanRepeaterMaxZones.integer ); + +      zone = &level.buildPointZones[ ent->buildPointZone ]; +      zone->active = qtrue; +    } + +    // Subtract the BP from the appropriate pool +    buildable = ent->s.modelindex; +    cost = BG_Buildable( buildable )->buildPoints; + +    if( ent->buildableTeam == TEAM_ALIENS ) +      level.alienBuildPoints -= cost; +    if( buildable == BA_H_REPEATER ) +      level.humanBuildPoints -= cost; +    else if( buildable != BA_H_REACTOR ) +    { +      gentity_t *power = G_PowerEntityForEntity( ent ); + +      if( power ) +      { +        if( power->s.modelindex == BA_H_REACTOR ) +          level.humanBuildPoints -= cost; +        else if( power->s.modelindex == BA_H_REPEATER && power->usesBuildPointZone ) +          level.buildPointZones[ power->buildPointZone ].totalBuildPoints -= cost; +      } +    } +  } + +  // Finally, update repeater zones and their queues +  // note that this has to be done after the used BP is calculated +  for( i = MAX_CLIENTS; i < level.num_entities; i++ )    { -    if( !ent->inuse ) -      continue; +    gentity_t *ent = &g_entities[ i ]; -    if( ent->s.eType != ET_BUILDABLE ) +    if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD || +        ent->buildableTeam != TEAM_HUMANS )        continue;      buildable = ent->s.modelindex; -    if( buildable != BA_NONE ) -    { -      if( buildable == BA_H_REACTOR && ent->spawned && ent->health > 0 ) -        level.reactorPresent = qtrue; +    if( buildable != BA_H_REPEATER ) +      continue; -      if( buildable == BA_A_OVERMIND && ent->spawned && ent->health > 0 ) -        level.overmindPresent = qtrue; +    if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active ) +    { +      zone = &level.buildPointZones[ ent->buildPointZone ]; -      if( !g_suddenDeath.integer || BG_FindReplaceableTestForBuildable( buildable ) ) +      if( G_TimeTilSuddenDeath( ) > 0 )        { -        if( BG_FindTeamForBuildable( buildable ) == BIT_HUMANS ) -        { -          level.humanBuildPoints -= BG_FindBuildPointsForBuildable( buildable ); -          if( ent->powered ) -            level.humanBuildPointsPowered -= BG_FindBuildPointsForBuildable( buildable ); -        } -        else +        // BP queue updates +        while( zone->queuedBuildPoints > 0 && +               zone->nextQueueTime < level.time )          { -          level.alienBuildPoints -= BG_FindBuildPointsForBuildable( buildable ); +          zone->queuedBuildPoints--; +          zone->nextQueueTime += G_NextQueueTime( zone->queuedBuildPoints, +                                     zone->totalBuildPoints, +                                     g_humanRepeaterBuildQueueTime.integer );          }        } +      else +      { +        zone->totalBuildPoints = zone->queuedBuildPoints = 0; +      }      }    }    if( level.humanBuildPoints < 0 ) -  { -    localHTP -= level.humanBuildPoints; -    level.humanBuildPointsPowered -= level.humanBuildPoints;      level.humanBuildPoints = 0; -  }    if( level.alienBuildPoints < 0 ) -  { -    localATP -= level.alienBuildPoints;      level.alienBuildPoints = 0; -  } - -  trap_SetConfigstring( CS_BUILDPOINTS, va( "%d %d %d %d %d", -        level.alienBuildPoints, localATP, -        level.humanBuildPoints, localHTP, -        level.humanBuildPointsPowered ) ); - -  //may as well pump the stages here too -  { -    float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD; -    float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD; -    int   alienNextStageThreshold, humanNextStageThreshold; - -    if( alienPlayerCountMod < 0.1f ) -      alienPlayerCountMod = 0.1f; - -    if( humanPlayerCountMod < 0.1f ) -      humanPlayerCountMod = 0.1f; - -    if( g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) -      alienNextStageThreshold = (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ); -    else if( g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) -      alienNextStageThreshold = (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ); -    else -      alienNextStageThreshold = -1; - -    if( g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) -      humanNextStageThreshold = (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ); -    else if( g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) -      humanNextStageThreshold = (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ); -    else -      humanNextStageThreshold = -1; - -    trap_SetConfigstring( CS_STAGES, va( "%d %d %d %d %d %d", -          g_alienStage.integer, g_humanStage.integer, -          g_alienKills.integer, g_humanKills.integer, -          alienNextStageThreshold, humanNextStageThreshold ) ); -  }  }  /* @@ -1499,8 +1286,11 @@ void G_CalculateStages( void )  {    float         alienPlayerCountMod     = level.averageNumAlienClients / PLAYER_COUNT_MOD;    float         humanPlayerCountMod     = level.averageNumHumanClients / PLAYER_COUNT_MOD; +  int           alienNextStageThreshold, humanNextStageThreshold;    static int    lastAlienStageModCount  = 1;    static int    lastHumanStageModCount  = 1; +  static int    alienTriggerStage       = 0; +  static int    humanTriggerStage       = 0;    if( alienPlayerCountMod < 0.1f )      alienPlayerCountMod = 0.1f; @@ -1508,7 +1298,7 @@ void G_CalculateStages( void )    if( humanPlayerCountMod < 0.1f )      humanPlayerCountMod = 0.1f; -  if( g_alienKills.integer >= +  if( g_alienCredits.integer >=        (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ) &&        g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 )    { @@ -1518,7 +1308,7 @@ void G_CalculateStages( void )      G_LogPrintf("Stage: A 2: Aliens reached Stage 2\n");    } -  if( g_alienKills.integer >= +  if( g_alienCredits.integer >=        (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ) &&        g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 )    { @@ -1528,7 +1318,7 @@ void G_CalculateStages( void )      G_LogPrintf("Stage: A 3: Aliens reached Stage 3\n");    } -  if( g_humanKills.integer >= +  if( g_humanCredits.integer >=        (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ) &&        g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 )    { @@ -1538,30 +1328,33 @@ void G_CalculateStages( void )      G_LogPrintf("Stage: H 2: Humans reached Stage 2\n");    } -  if( g_humanKills.integer >= +  if( g_humanCredits.integer >=        (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ) &&        g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 )    {      trap_Cvar_Set( "g_humanStage", va( "%d", S3 ) );      level.humanStage3Time = level.time; -    G_LogPrintf("Stage: H 3: Humans reached Stage 3\n");      lastHumanStageModCount = g_humanStage.modificationCount; +    G_LogPrintf("Stage: H 3: Humans reached Stage 3\n");    } -  +    if( g_alienStage.modificationCount > lastAlienStageModCount )    { -    G_Checktrigger_stages( PTE_ALIENS, g_alienStage.integer ); -      if( g_alienStage.integer == S2 ) +    while( alienTriggerStage < MIN( g_alienStage.integer, S3 ) ) +      G_Checktrigger_stages( TEAM_ALIENS, ++alienTriggerStage ); + +    if( g_alienStage.integer == S2 )        level.alienStage2Time = level.time;      else if( g_alienStage.integer == S3 )        level.alienStage3Time = level.time; -  +      lastAlienStageModCount = g_alienStage.modificationCount;    } -  +    if( g_humanStage.modificationCount > lastHumanStageModCount )    { -    G_Checktrigger_stages( PTE_HUMANS, g_humanStage.integer ); +    while( humanTriggerStage < MIN( g_humanStage.integer, S3 ) ) +      G_Checktrigger_stages( TEAM_HUMANS, ++humanTriggerStage );      if( g_humanStage.integer == S2 )        level.humanStage2Time = level.time; @@ -1570,6 +1363,35 @@ void G_CalculateStages( void )      lastHumanStageModCount = g_humanStage.modificationCount;    } + +  if( g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) +    alienNextStageThreshold = (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ); +  else if( g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) +    alienNextStageThreshold = (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ); +  else +    alienNextStageThreshold = -1; + +  if( g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) +    humanNextStageThreshold = (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ); +  else if( g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) +    humanNextStageThreshold = (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ); +  else +    humanNextStageThreshold = -1; + +  // save a lot of bandwidth by rounding thresholds up to the nearest 100 +  if( alienNextStageThreshold > 0 ) +    alienNextStageThreshold = ceil( (float)alienNextStageThreshold / 100 ) * 100; + +  if( humanNextStageThreshold > 0 ) +    humanNextStageThreshold = ceil( (float)humanNextStageThreshold / 100 ) * 100; + +  trap_SetConfigstring( CS_ALIEN_STAGES, va( "%d %d %d", +        g_alienStage.integer, g_alienCredits.integer, +        alienNextStageThreshold ) ); + +  trap_SetConfigstring( CS_HUMAN_STAGES, va( "%d %d %d", +        g_humanStage.integer, g_humanCredits.integer, +        humanNextStageThreshold ) );  }  /* @@ -1587,13 +1409,13 @@ void G_CalculateAvgPlayers( void )    if( !level.numAlienClients )    {      level.numAlienSamples = 0; -    trap_Cvar_Set( "g_alienKills", "0" ); +    trap_Cvar_Set( "g_alienCredits", "0" );    }    if( !level.numHumanClients )    {      level.numHumanSamples = 0; -    trap_Cvar_Set( "g_humanKills", "0" ); +    trap_Cvar_Set( "g_humanCredits", "0" );    }    //calculate average number of clients for stats @@ -1623,16 +1445,14 @@ void CalculateRanks( void )  {    int       i;    char      P[ MAX_CLIENTS + 1 ] = {""}; -  int       ff = 0;    level.numConnectedClients = 0; -  level.numNonSpectatorClients = 0;    level.numPlayingClients = 0; -  level.numVotingClients = 0;   // don't count bots +  memset( level.numVotingClients, 0, sizeof( level.numVotingClients ) );    level.numAlienClients = 0;    level.numHumanClients = 0; -  level.numLiveAlienClients = 0; -  level.numLiveHumanClients = 0; +  level.numAlienClientsAlive = 0; +  level.numHumanClientsAlive = 0;    for( i = 0; i < level.maxclients; i++ )    { @@ -1643,46 +1463,36 @@ void CalculateRanks( void )        level.numConnectedClients++;        P[ i ] = (char)'0' + level.clients[ i ].pers.teamSelection; +      level.numVotingClients[ TEAM_NONE ]++; +        if( level.clients[ i ].pers.connected != CON_CONNECTED )          continue; -      level.numVotingClients++; -      if( level.clients[ i ].pers.teamSelection != PTE_NONE ) +      if( level.clients[ i ].pers.teamSelection != TEAM_NONE )        {          level.numPlayingClients++; -        if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR ) -          level.numNonSpectatorClients++; - -        if( level.clients[ i ].pers.teamSelection == PTE_ALIENS ) +        if( level.clients[ i ].pers.teamSelection == TEAM_ALIENS )          {            level.numAlienClients++; -          if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR ) -            level.numLiveAlienClients++; +          if( level.clients[ i ].sess.spectatorState == SPECTATOR_NOT ) +            level.numAlienClientsAlive++;          } -        else if( level.clients[ i ].pers.teamSelection == PTE_HUMANS ) +        else if( level.clients[ i ].pers.teamSelection == TEAM_HUMANS )          {            level.numHumanClients++; -          if( level.clients[ i ].sess.sessionTeam != TEAM_SPECTATOR ) -            level.numLiveHumanClients++; +          if( level.clients[ i ].sess.spectatorState == SPECTATOR_NOT ) +            level.numHumanClientsAlive++;          }        }      }    } -  level.numteamVotingClients[ 0 ] = level.numHumanClients; -  level.numteamVotingClients[ 1 ] = level.numAlienClients; +  level.numNonSpectatorClients = level.numAlienClientsAlive + +    level.numHumanClientsAlive; +  level.numVotingClients[ TEAM_ALIENS ] = level.numAlienClients; +  level.numVotingClients[ TEAM_HUMANS ] = level.numHumanClients;    P[ i ] = '\0';    trap_Cvar_Set( "P", P ); -  if( g_friendlyFire.value>0 ) -    ff |= ( FFF_HUMANS | FFF_ALIENS ); -  if( g_friendlyFireHumans.value>0  ) -    ff |=  FFF_HUMANS; -  if( g_friendlyFireAliens.value>0  ) -    ff |=  FFF_ALIENS; -  if( g_friendlyBuildableFire.value>0  ) -    ff |=  FFF_BUILDABLES; -  trap_Cvar_Set( "ff", va( "%i", ff ) ); -    qsort( level.sortedClients, level.numConnectedClients,      sizeof( level.sortedClients[ 0 ] ), SortRanks ); @@ -1690,7 +1500,7 @@ void CalculateRanks( void )    CheckExitRules( );    // if we are at the intermission, send the new info to everyone -  if( level.intermissiontime && !level.mapRotationVoteTime ) +  if( level.intermissiontime )      SendScoreboardMessageToAllClients( );  } @@ -1737,8 +1547,10 @@ void MoveClientToIntermission( gentity_t *ent )      G_StopFollowing( ent );    // move to the spot -  VectorCopy( level.intermission_origin, ent->s.origin ); +  VectorCopy( level.intermission_origin, ent->s.pos.trBase ); +  VectorCopy( level.intermission_origin, ent->r.currentOrigin );    VectorCopy( level.intermission_origin, ent->client->ps.origin ); +  VectorCopy( level.intermission_angle, ent->s.apos.trBase );    VectorCopy( level.intermission_angle, ent->client->ps.viewangles );    ent->client->ps.pm_type = PM_INTERMISSION; @@ -1748,7 +1560,6 @@ void MoveClientToIntermission( gentity_t *ent )    ent->client->ps.eFlags = 0;    ent->s.eFlags = 0;    ent->s.eType = ET_GENERAL; -  ent->s.modelindex = 0;    ent->s.loopSound = 0;    ent->s.event = 0;    ent->r.contents = 0; @@ -1775,8 +1586,8 @@ void FindIntermissionPoint( void )    }    else    { -    VectorCopy( ent->s.origin, level.intermission_origin ); -    VectorCopy( ent->s.angles, level.intermission_angle ); +    VectorCopy( ent->r.currentOrigin, level.intermission_origin ); +    VectorCopy( ent->r.currentAngles, level.intermission_angle );      // if it has a target, look towards it      if( ent->target )      { @@ -1784,7 +1595,7 @@ void FindIntermissionPoint( void )        if( target )        { -        VectorSubtract( target->s.origin, level.intermission_origin, dir ); +        VectorSubtract( target->r.currentOrigin, level.intermission_origin, dir );          vectoangles( dir, level.intermission_angle );        }      } @@ -1805,12 +1616,12 @@ void BeginIntermission( void )    if( level.intermissiontime )      return;   // already active -  level.numTeamWarnings = 99; -      level.intermissiontime = level.time;    G_ClearVotes( ); +  G_UpdateTeamConfigStrings( ); +    FindIntermissionPoint( );    // move all clients to the intermission point @@ -1830,42 +1641,8 @@ void BeginIntermission( void )    // send the current scoring to all clients    SendScoreboardMessageToAllClients( ); - -  if( g_nextMap.string[ 0 ] ) -  { -    trap_SendServerCommand( -1, -      va( "print \"next map has been set to %s^7%s\n\"", -      g_nextMap.string, -      ( G_CheckMapRotationVote() ) ? ", voting will be skipped" : "" ) ); -  }  } -void BeginMapRotationVote( void ) -{ -  gentity_t *ent; -  int length; -  int i; - -  if( level.mapRotationVoteTime ) -    return; - -  length = g_mapRotationVote.integer; -  if( length > 60 ) -    length = 60; -  level.mapRotationVoteTime = level.time + ( length * 1000 ); - -  for( i = 0; i < level.maxclients; i++ ) -  { -    ent = g_entities + i; - -    if( !ent->inuse ) -      continue; - -    ent->client->ps.pm_type = PM_SPECTATOR; -    ent->client->sess.sessionTeam = TEAM_SPECTATOR; -    ent->client->sess.spectatorState = SPECTATOR_LOCKED; -  } -}  /*  ============= @@ -1879,44 +1656,22 @@ void ExitLevel( void )  {    int       i;    gclient_t *cl; -  buildHistory_t *tmp, *mark; - -  char currentmap[ MAX_CVAR_VALUE_STRING ]; - -  trap_Cvar_VariableStringBuffer( "mapname", currentmap, sizeof( currentmap )); - -  if( level.mapRotationVoteTime ) -  { -    if( level.time < level.mapRotationVoteTime && -        !G_IntermissionMapVoteWinner( ) ) -      return; -  } -  else if( g_mapRotationVote.integer > 0 && -           G_CheckMapRotationVote() && -           !g_nextMap.string[ 0 ] ) -  { -    BeginMapRotationVote( ); -    return; -  } -  while( ( tmp = level.buildHistory ) ) +  if ( G_MapExists( g_nextMap.string ) )    { -    level.buildHistory = level.buildHistory->next; -    while( ( mark = tmp ) ) -    { -      tmp = tmp->marked; -      G_Free( mark ); -    } +    G_MapConfigs( g_nextMap.string ); +    trap_SendConsoleCommand( EXEC_APPEND, va( "%smap \"%s\"\n", +      ( g_cheats.integer ? "dev" : "" ), g_nextMap.string ) );    } - -  if( !Q_stricmp( currentmap, g_nextMap.string ) ) -    trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); -  else if ( G_MapExists( g_nextMap.string ) ) -    trap_SendConsoleCommand( EXEC_APPEND, va("!map %s\n", g_nextMap.string ) );    else if( G_MapRotationActive( ) ) -    G_AdvanceMapRotation( ); +    G_AdvanceMapRotation( 0 );    else +  { +    char map[ MAX_CVAR_VALUE_STRING ]; +    trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); +    G_MapConfigs( map );      trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); +  }    trap_Cvar_Set( "g_nextMap", "" ); @@ -1934,7 +1689,7 @@ void ExitLevel( void )      cl->ps.persistant[ PERS_SCORE ] = 0;    } -  // we need to do this here before chaning to CON_CONNECTING +  // we need to do this here before changing to CON_CONNECTING    G_WriteSessionData( );    // change all client states to connecting, so the early players into the @@ -1946,79 +1701,48 @@ void ExitLevel( void )    }  } +  /*  ================= -G_AdminsPrintf +G_AdminMessage -Print to all active admins, and the logfile with a time stamp if it is open, and to the console +Print to all active server admins, and to the logfile, and to the server console  =================  */ -void QDECL G_AdminsPrintf( const char *fmt, ... ) +void G_AdminMessage( gentity_t *ent, const char *msg )  { -  va_list argptr;    char    string[ 1024 ]; -  gentity_t   *tempent; -  int j; - -  va_start( argptr, fmt ); -  vsprintf( string, fmt,argptr ); -  va_end( argptr ); +  int     i; -  for( j = 0; j < level.maxclients; j++ ) -  { -    tempent = &g_entities[ j ]; -    if( G_admin_permission( tempent, ADMF_ADMINCHAT ) && -        !tempent->client->pers.ignoreAdminWarnings )  -    { -       trap_SendServerCommand(tempent-g_entities,va( "print \"^6[Admins]^7 %s\"", string) );  -    } -  } -   -  G_LogPrintf("%s",string); +  Com_sprintf( string, sizeof( string ), "chat %d %d \"%s\"", +    (int)( ent ? ent - g_entities : -1 ), +    G_admin_permission( ent, ADMF_ADMINCHAT ) ? SAY_ADMINS : SAY_ADMINS_PUBLIC, +    msg ); +  // Send to all appropriate clients +  for( i = 0; i < level.maxclients; i++ ) +    if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) +       trap_SendServerCommand( i, string ); + +  // Send to the logfile and server console +  G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_MAGENTA "%s\n", +    G_admin_permission( ent, ADMF_ADMINCHAT ) ? "AdminMsg" : "AdminMsgPublic", +    (int)( ent ? ent - g_entities : -1 ), ent ? ent->client->pers.netname : "console", +    msg );  } -/* -================= -G_WarningsPrintf -  -Print to everyone with a certain flag, and the logfile with a time stamp if it is open, and to the console  -(just a copy of the G_AdminsPrintf with flag suport) -================= -*/ -void QDECL G_WarningsPrintf( char *flag, const char *fmt, ... ) -{ -  va_list argptr; -  char    string[ 1024 ]; -  gentity_t   *tempent; -  int j; -  va_start( argptr, fmt ); -  vsprintf( string, fmt,argptr ); -  va_end( argptr ); - -  for( j = 0; j < level.maxclients; j++ ) -  { -    tempent = &g_entities[ j ]; -    if( G_admin_permission( tempent, flag ) )  -    { -       trap_SendServerCommand(tempent-g_entities,va( "print \"^6[Warnings]^7 %s\"", string) );  -    } -  } -   -  G_LogPrintf("%s",string); -}  /*  =================  G_LogPrintf -Print to the logfile with a time stamp if it is open +Print to the logfile with a time stamp if it is open, and to the server console  =================  */  void QDECL G_LogPrintf( const char *fmt, ... )  {    va_list argptr; -  char    string[ 1024 ], decoloured[ 1024 ]; +  char    string[ 1024 ], decolored[ 1024 ];    int     min, tens, sec;    sec = ( level.time - level.startTime ) / 1000; @@ -2031,186 +1755,20 @@ void QDECL G_LogPrintf( const char *fmt, ... )    Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec );    va_start( argptr, fmt ); -  vsprintf( string +7 , fmt,argptr ); +  Q_vsnprintf( string + 7, sizeof( string ) - 7, fmt, argptr );    va_end( argptr );    if( g_dedicated.integer ) -    G_Printf( "%s", string + 7 ); - -  if( !level.logFile ) -    return; - -  if( g_decolourLogfiles.integer ) -  { -    G_DecolorString( string, decoloured ); -    trap_FS_Write( decoloured, strlen( decoloured ), level.logFile ); -  } -  else    { -    trap_FS_Write( string, strlen( string ), level.logFile ); +    G_UnEscapeString( string, decolored, sizeof( decolored ) ); +    G_Printf( "%s", decolored + 7 );    } -} - -/* -================= -G_LogPrintfColoured - -Bypasses g_decolourLogfiles for events that need colors in the logs -================= -*/ -void QDECL G_LogPrintfColoured( const char *fmt, ... ) -{ -  va_list argptr; -  char    string[ 1024 ]; -  int     min, tens, sec; - -  sec = (level.time - level.startTime) / 1000; - -  min = sec / 60; -  sec -= min * 60; -  tens = sec / 10; -  sec -= tens * 10; - -  Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec ); - -  va_start( argptr, fmt ); -  vsprintf( string +7 , fmt,argptr ); -  va_end( argptr ); - -  if( g_dedicated.integer ) -    G_Printf( "%s", string + 7 );    if( !level.logFile )      return; -  trap_FS_Write( string, strlen( string ), level.logFile ); -} - -/* -================= -G_LogOnlyPrintf - -Print to the logfile only (not console) with a time stamp if it is open -================= -*/ -void QDECL G_LogOnlyPrintf( const char *fmt, ... ) -{ -  va_list argptr; -  char    string[ 1024 ], decoloured[ 1024 ]; -  int     min, tens, sec; - -  sec = (level.time - level.startTime) / 1000; - -  min = sec / 60; -  sec -= min * 60; -  tens = sec / 10; -  sec -= tens * 10; - -  Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec ); - -  va_start( argptr, fmt ); -  vsprintf( string +7 , fmt,argptr ); -  va_end( argptr ); - -  if( !level.logFile ) -    return; - -  if( g_decolourLogfiles.integer ) -  { -    G_DecolorString( string, decoloured ); -    trap_FS_Write( decoloured, strlen( decoloured ), level.logFile ); -  } -  else -  { -    trap_FS_Write( string, strlen( string ), level.logFile ); -  } -} - -/* -================= -G_SendGameStat -================= -*/ -void G_SendGameStat( pTeam_t team ) -{ -  char      map[ MAX_STRING_CHARS ]; -  char      teamChar; -  char      data[ BIG_INFO_STRING ]; -  char      entry[ MAX_STRING_CHARS ]; -  int       i, dataLength, entryLength; -  gclient_t *cl; - -  trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - -  switch( team ) -  { -    case PTE_ALIENS:  teamChar = 'A'; break; -    case PTE_HUMANS:  teamChar = 'H'; break; -    case PTE_NONE:    teamChar = 'L'; break; -    default: return; -  } - -  Com_sprintf( data, BIG_INFO_STRING, -      "%s %s T:%c A:%f H:%f M:%s D:%d SD:%d AS:%d AS2T:%d AS3T:%d HS:%d HS2T:%d HS3T:%d CL:%d", -      Q3_VERSION, -      g_tag.string, -      teamChar, -      level.averageNumAlienClients, -      level.averageNumHumanClients, -      map, -      level.time - level.startTime, -      G_TimeTilSuddenDeath( ), -      g_alienStage.integer, -      level.alienStage2Time - level.startTime, -      level.alienStage3Time - level.startTime, -      g_humanStage.integer, -      level.humanStage2Time - level.startTime, -      level.humanStage3Time - level.startTime, -      level.numConnectedClients ); - -  dataLength = strlen( data ); - -  for( i = 0; i < level.numConnectedClients; i++ ) -  { -    int ping; - -    cl = &level.clients[ level.sortedClients[ i ] ]; -     -    // Ignore invisible players -    if ( cl->sess.invisible == qtrue ) -      continue; - -    if( cl->pers.connected == CON_CONNECTING ) -      ping = -1; -    else -      ping = cl->ps.ping < 999 ? cl->ps.ping : 999; - -    switch( cl->ps.stats[ STAT_PTEAM ] ) -    { -      case PTE_ALIENS:  teamChar = 'A'; break; -      case PTE_HUMANS:  teamChar = 'H'; break; -      case PTE_NONE:    teamChar = 'S'; break; -      default: return; -    } - -    Com_sprintf( entry, MAX_STRING_CHARS, -      " \"%s\" %c %d %d %d", -      cl->pers.netname, -      teamChar, -      cl->ps.persistant[ PERS_SCORE ], -      ping, -      ( level.time - cl->pers.enterTime ) / 60000 ); - -    entryLength = strlen( entry ); - -    if( dataLength + entryLength >= BIG_INFO_STRING ) -      break; - -    strcpy( data + dataLength, entry ); -    dataLength += entryLength; -  } - -  trap_SendGameStat( data ); +  G_DecolorString( string, decolored, sizeof( decolored ) ); +  trap_FS_Write( decolored, strlen( decolored ), level.logFile );  }  /* @@ -2226,6 +1784,8 @@ void LogExit( const char *string )    gclient_t   *cl;    gentity_t   *ent; +  level.exited = qtrue; +    G_LogPrintf( "Exit: %s\n", string );    level.intermissionQueued = level.time; @@ -2245,7 +1805,7 @@ void LogExit( const char *string )      cl = &level.clients[ level.sortedClients[ i ] ]; -    if( cl->ps.stats[ STAT_PTEAM ] == PTE_NONE ) +    if( cl->ps.stats[ STAT_TEAM ] == TEAM_NONE )        continue;      if( cl->pers.connected == CON_CONNECTING ) @@ -2270,8 +1830,6 @@ void LogExit( const char *string )          ent->use( ent, ent, ent );      }    } - -  G_SendGameStat( level.lastWin );  } @@ -2287,20 +1845,13 @@ wait 10 seconds before going on.  */  void CheckIntermissionExit( void )  { -  int       ready, notReady, numPlayers; +  int       ready, notReady;    int       i;    gclient_t *cl; -  int       readyMask; +  clientList_t readyMasks;    //if no clients are connected, just exit -  if( !level.numConnectedClients ) -  { -    ExitLevel( ); -    return; -  } - -  // map vote started -  if( level.mapRotationVoteTime ) +  if( level.numConnectedClients == 0 )    {      ExitLevel( );      return; @@ -2309,30 +1860,28 @@ void CheckIntermissionExit( void )    // see which players are ready    ready = 0;    notReady = 0; -  readyMask = 0; -  numPlayers = 0; +  Com_Memset( &readyMasks, 0, sizeof( readyMasks ) );    for( i = 0; i < g_maxclients.integer; i++ )    {      cl = level.clients + i; +      if( cl->pers.connected != CON_CONNECTED )        continue; -    if( cl->ps.stats[ STAT_PTEAM ] == PTE_NONE ) +    if( cl->ps.stats[ STAT_TEAM ] == TEAM_NONE )        continue;      if( cl->readyToExit )      {        ready++; -      if( i < 16 ) -        readyMask |= 1 << i; + +      Com_ClientListAdd( &readyMasks, i );      }      else        notReady++; - -    numPlayers++;    } -  trap_SetConfigstring( CS_CLIENTS_READY, va( "%d", readyMask ) ); +  trap_SetConfigstring( CS_CLIENTS_READY, Com_ClientListString( &readyMasks ) );    // never exit in less than five seconds    if( level.time < level.intermissiontime + 5000 ) @@ -2346,22 +1895,14 @@ void CheckIntermissionExit( void )    }    // if nobody wants to go, clear timer -  if( !ready && numPlayers ) +  if( ready == 0 && notReady > 0 )    {      level.readyToExit = qfalse;      return;    }    // if everyone wants to go, go now -  if( !notReady ) -  { -    ExitLevel( ); -    return; -  } - -  // if only a percent is needed to ready, check for it -  if( g_readyPercent.integer && numPlayers && -      ready * 100 / numPlayers >= g_readyPercent.integer ) +  if( notReady == 0 )    {      ExitLevel( );      return; @@ -2434,11 +1975,10 @@ void CheckExitRules( void )    {      if( level.time - level.startTime >= g_timelimit.integer * 60000 )      { -      level.lastWin = PTE_NONE; +      level.lastWin = TEAM_NONE;        trap_SendServerCommand( -1, "print \"Timelimit hit\n\"" );        trap_SetConfigstring( CS_WINNER, "Stalemate" );        LogExit( "Timelimit hit." ); -      G_admin_maplog_result( "t" );        return;      }      else if( level.time - level.startTime >= ( g_timelimit.integer - 5 ) * 60000 && @@ -2456,295 +1996,177 @@ void CheckExitRules( void )    }    if( level.uncondHumanWin || -      ( ( level.time > level.startTime + 1000 ) && +      ( !level.uncondAlienWin && +        ( level.time > level.startTime + 1000 ) &&          ( level.numAlienSpawns == 0 ) && -        ( level.numLiveAlienClients == 0 ) ) ) +        ( level.numAlienClientsAlive == 0 ) ) )    {      //humans win -    level.lastWin = PTE_HUMANS; +    level.lastWin = TEAM_HUMANS;      trap_SendServerCommand( -1, "print \"Humans win\n\"");      trap_SetConfigstring( CS_WINNER, "Humans Win" );      LogExit( "Humans win." ); -    G_admin_maplog_result( "h" );    }    else if( level.uncondAlienWin ||             ( ( level.time > level.startTime + 1000 ) &&               ( level.numHumanSpawns == 0 ) && -             ( level.numLiveHumanClients == 0 ) ) ) +             ( level.numHumanClientsAlive == 0 ) ) )    {      //aliens win -    level.lastWin = PTE_ALIENS; +    level.lastWin = TEAM_ALIENS;      trap_SendServerCommand( -1, "print \"Aliens win\n\"");      trap_SetConfigstring( CS_WINNER, "Aliens Win" );      LogExit( "Aliens win." ); -    G_admin_maplog_result( "a" );    }  } - - -/* -======================================================================== - -FUNCTIONS CALLED EVERY FRAME - -======================================================================== -*/ - -  /*  ================== -CheckVote +G_Vote  ==================  */ -void CheckVote( void ) +void G_Vote( gentity_t *ent, team_t team, qboolean voting )  { -  int votePassThreshold=level.votePassThreshold; -  int voteYesPercent; - -  if( level.voteExecuteTime && level.voteExecuteTime < level.time ) -  { -    level.voteExecuteTime = 0; - -    if( !Q_stricmp( level.voteString, "map_restart" ) ) -    { -      G_admin_maplog_result( "r" ); -    } -    else if( !Q_stricmpn( level.voteString, "map", 3 ) ) -    { -      G_admin_maplog_result( "m" ); -    } - - -    if( !Q_stricmp( level.voteString, "suddendeath" ) ) -    { -      level.suddenDeathBeginTime = level.time + ( 1000 * g_suddenDeathVoteDelay.integer ) - level.startTime; - -      level.voteString[0] = '\0'; - -      if( g_suddenDeathVoteDelay.integer ) -        trap_SendServerCommand( -1, va("cp \"Sudden Death will begin in %d seconds\n\"", g_suddenDeathVoteDelay.integer  ) ); -    } - -    if( level.voteString[0] ) -      trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) ); +  if( !level.voteTime[ team ] ) +    return; -    if( !Q_stricmp( level.voteString, "map_restart" ) || -        !Q_stricmpn( level.voteString, "map", 3 ) ) -    { -      level.restarted = qtrue; -    } -  } +  if( voting && ent->client->pers.voted & ( 1 << team ) ) +    return; -  if( !level.voteTime ) +  if( !voting && !( ent->client->pers.voted & ( 1 << team ) ) )      return; -  if( level.voteYes + level.voteNo > 0 ) -    voteYesPercent = (int)( 100 * ( level.voteYes ) / ( level.voteYes + level.voteNo ) ); -  else -    voteYesPercent = 0;  -   -  if( ( level.time - level.voteTime >= VOTE_TIME ) ||  -      ( level.voteYes + level.voteNo == level.numConnectedClients ) ) +  ent->client->pers.voted |= 1 << team; + +  if( ent->client->pers.vote & ( 1 << team ) )    { -    if( voteYesPercent> votePassThreshold || level.voteNo == 0 ) -    { -      // execute the command, then remove the vote -      trap_SendServerCommand( -1, va("print \"Vote passed (%d - %d)\n\"",  -            level.voteYes, level.voteNo ) ); -      G_LogPrintf( "Vote: Vote passed (%d-%d)\n", level.voteYes, level.voteNo ); -      level.voteExecuteTime = level.time + 3000; -    } +    if( voting ) +      level.voteYes[ team ]++;      else -    { -      // same behavior as a timeout -      trap_SendServerCommand( -1, va("print \"Vote failed (%d - %d)\n\"", -            level.voteYes, level.voteNo ) ); -      G_LogPrintf( "Vote: Vote failed (%d - %d)\n", level.voteYes, level.voteNo ); -    } +      level.voteYes[ team ]--; + +    trap_SetConfigstring( CS_VOTE_YES + team, +      va( "%d", level.voteYes[ team ] ) );    }    else    { -    if( level.voteYes > (int)( (double) level.numConnectedClients *  -                                 ( (double) votePassThreshold/100.0 ) ) ) -    { -      // execute the command, then remove the vote -      trap_SendServerCommand( -1, va("print \"Vote passed (%d - %d)\n\"", -            level.voteYes, level.voteNo ) ); -      G_LogPrintf( "Vote: Vote passed (%d - %d)\n", level.voteYes, level.voteNo ); -      level.voteExecuteTime = level.time + 3000; -    } -    else if( level.voteNo > (int)( (double) level.numConnectedClients *  -                                     ( (double) ( 100.0-votePassThreshold )/ 100.0 ) ) ) -    { -      // same behavior as a timeout -      trap_SendServerCommand( -1, va("print \"Vote failed (%d - %d)\n\"", -            level.voteYes, level.voteNo ) ); -      G_LogPrintf("Vote failed\n"); -    } +    if( voting ) +      level.voteNo[ team ]++;      else -    { -      // still waiting for a majority -      return; -    } -  } +      level.voteNo[ team ]--; -  level.voteTime = 0; -  trap_SetConfigstring( CS_VOTE_TIME, "" ); -  trap_SetConfigstring( CS_VOTE_STRING, "" ); +    trap_SetConfigstring( CS_VOTE_NO + team, +      va( "%d", level.voteNo[ team ] ) ); +  }  }  /* -================== -CheckTeamVote -================== +======================================================================== + +FUNCTIONS CALLED EVERY FRAME + +========================================================================  */ -void CheckTeamVote( int team ) -{ -  int cs_offset; -  if ( team == PTE_HUMANS ) -    cs_offset = 0; -  else if ( team == PTE_ALIENS ) -    cs_offset = 1; -  else -    return; -  if( !level.teamVoteTime[ cs_offset ] ) -    return; +void G_ExecuteVote( team_t team ) +{ +  level.voteExecuteTime[ team ] = 0; -  if( level.time - level.teamVoteTime[ cs_offset ] >= VOTE_TIME ) +  if( !Q_stricmpn( level.voteString[ team ], "map_restart", 11 ) )    { -    if( level.teamVoteYes[ cs_offset ] > level.teamVoteNo[ cs_offset ] && level.teamVoteYes[ cs_offset ] >= 2 ) -    { -      // execute the command, then remove the vote -      trap_SendServerCommand( -1, va("print \"Team vote passed  (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); -      trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) ); -    } -    else -    { -      trap_SendServerCommand( -1, va("print \"Team vote failed  (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); -      G_LogPrintf( "Teamvote: Team vote failed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ); -    } +    char map[ MAX_QPATH ]; +    trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); +    G_MapConfigs( map );    } -  else +  else if( !Q_stricmpn( level.voteString[ team ], "map", 3 ) )    { -    if( level.teamVoteYes[ cs_offset ] > level.numteamVotingClients[ cs_offset ] / 2 ) -    { -      // execute the command, then remove the vote -      trap_SendServerCommand( -1, va("print \"Team vote passed  (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); -      G_LogPrintf( "Teamvote: Team vote passed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ); -      // -      trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.teamVoteString[ cs_offset ] ) ); -    } -    else if( level.teamVoteNo[ cs_offset ] >= level.numteamVotingClients[ cs_offset ] / 2 ) -    { -      // same behavior as a timeout -      trap_SendServerCommand( -1, va("print \"Team vote failed  (%d - %d)\n\"", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ) ); -      G_LogPrintf( "Teamvote: Team vote failed (%d - %d)\n", level.teamVoteYes[ cs_offset ], level.teamVoteNo[ cs_offset ] ); -    } -    else -    { -      // still waiting for a majority -      return; -    } +    char map[ MAX_QPATH ]; +    char *p; +    Q_strncpyz( map, strchr( level.voteString[ team ], '"' ) + 1, sizeof( map ) ); +    if( ( p = strchr( map, '"' ) ) ) +      *p = '\0'; +    G_MapConfigs( map );    } -  level.teamVoteTime[ cs_offset ] = 0; -  trap_SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, "" ); -  trap_SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, "" ); +  trap_SendConsoleCommand( EXEC_APPEND, va( "%s%s\n", +    ( !Q_stricmp( level.voteString[ team ], "map" ) && g_cheats.integer ? "dev" : "" ), +    level.voteString[ team ] ) ); + +  if( !Q_stricmpn( level.voteString[ team ], "map", 3 ) ) +    level.restarted = qtrue;  }  /*  ================== -CheckMsgTimer +G_CheckVote  ==================  */ -void CheckMsgTimer( void ) +void G_CheckVote( team_t team )  { -  static int LastTime = 0; +  float    votePassThreshold = (float)level.voteThreshold[ team ] / 100.0f; +  qboolean pass = qfalse; +  const char *msg; +  int      i; -  if( level.time - LastTime < 1000 ) -    return; +  if( level.voteExecuteTime[ team ] && +      level.voteExecuteTime[ team ] < level.time ) +  { +    G_ExecuteVote( team ); +  } -  LastTime = level.time; +  if( !level.voteTime[ team ] ) +    return; -  if( level.mapRotationVoteTime ) +  if( ( level.time - level.voteTime[ team ] >= VOTE_TIME ) || +      ( level.voteYes[ team ] + level.voteNo[ team ] == level.numVotingClients[ team ] ) )    { -    G_IntermissionMapVoteMessageAll( ); -    return; +    pass = ( level.voteYes[ team ] && +             (float)level.voteYes[ team ] / ( (float)level.voteYes[ team ] + (float)level.voteNo[ team ] ) > votePassThreshold );    } - -  if( g_welcomeMsgTime.integer && g_welcomeMsg.string[ 0 ] ) +  else    { -    char buffer[ MAX_STRING_CHARS ]; -    int wt; -    int i; - -    buffer[ 0 ] = '\0'; -    wt = g_welcomeMsgTime.integer * 1000; -    for( i = 0; i < level.maxclients; i++ ) +    if( (float)level.voteYes[ team ] > +        (float)level.numVotingClients[ team ] * votePassThreshold )      { -      if( level.clients[ i ].pers.connected != CON_CONNECTED ) -        continue; - -      if( level.time - level.clients[ i ].pers.enterTime < wt ) -      { -        if( buffer[ 0 ] == '\0' ) -        { -          Q_strncpyz( buffer, g_welcomeMsg.string, sizeof( buffer ) ); -          G_ParseEscapedString( buffer ); -        } -      trap_SendServerCommand( i, va( "cp \"%s\"", buffer ) ); -      } +      pass = qtrue; +    } +    else if( (float)level.voteNo[ team ] <= +             (float)level.numVotingClients[ team ] * ( 1.0f - votePassThreshold ) ) +    { +      return;      }    } -  if( !g_msgTime.integer ) -    return; +  if( pass ) +    level.voteExecuteTime[ team ] = level.time + level.voteDelay[ team ]; -  if( level.time - level.lastMsgTime < abs( g_msgTime.integer ) * 60000 ) -    return; +  G_LogPrintf( "EndVote: %s %s %d %d %d\n", +    team == TEAM_NONE ? "global" : BG_TeamName( team ), +    pass ? "pass" : "fail", +    level.voteYes[ team ], level.voteNo[ team ], level.numVotingClients[ team ] ); -  // negative settings only print once per map -  if( ( level.lastMsgTime ) && g_msgTime.integer < 0 ) -    return; +  msg = va( "print \"%sote %sed (%d - %d)\n\"", +    team == TEAM_NONE ? "V" : "Team v", pass ? "pass" : "fail", +    level.voteYes[ team ], level.voteNo[ team ] ); -  level.lastMsgTime = level.time; - -  if( g_msg.string[0] ) -  { -    char buffer[ MAX_STRING_CHARS ]; - -    Q_strncpyz( buffer, g_msg.string, sizeof( buffer ) ); -    G_ParseEscapedString( buffer ); -    trap_SendServerCommand( -1, va( "cp \"%s\"", buffer ) ); -    trap_SendServerCommand( -1, va( "print \"%s\n\"", buffer ) ); -  } -} - -/* -================== -CheckCountdown -================== -*/ -void CheckCountdown( void ) -{ -  static int lastmsg = 0; -  int timeleft = g_warmup.integer - ( level.time - level.startTime ) / 1000; +  if( team == TEAM_NONE ) +    trap_SendServerCommand( -1, msg ); +  else +    G_TeamCommand( team, msg ); -  if( !g_doWarmup.integer || timeleft < 0 ) -    return; +  level.voteTime[ team ] = 0; +  level.voteYes[ team ] = 0; +  level.voteNo[ team ] = 0; -  if( level.time - lastmsg < 1000 ) -    return; +  for( i = 0; i < level.maxclients; i++ ) +    level.clients[ i ].pers.voted &= ~( 1 << team ); -  lastmsg = level.time; -  if( timeleft > 0 ) -    trap_SendServerCommand( -1, va( "cp \"^1Warmup Time:^7\n^%i----- ^7%i ^%i-----\"", timeleft % 7, timeleft, timeleft % 7 ) ); -  else if( timeleft == 0 )  -    trap_SendServerCommand( -1, "cp \"^2----- GO! -----^7\"" ); +  trap_SetConfigstring( CS_VOTE_TIME + team, "" ); +  trap_SetConfigstring( CS_VOTE_STRING + team, "" ); +  trap_SetConfigstring( CS_VOTE_YES + team, "0" ); +  trap_SetConfigstring( CS_VOTE_NO + team, "0" );  } @@ -2758,6 +2180,7 @@ void CheckCvars( void )    static int lastPasswordModCount   = -1;    static int lastMarkDeconModCount  = -1;    static int lastSDTimeModCount = -1; +  static int lastNumZones = 0;    if( g_password.modificationCount != lastPasswordModCount )    { @@ -2773,29 +2196,36 @@ void CheckCvars( void )    // the server setting is changed    if( g_markDeconstruct.modificationCount != lastMarkDeconModCount )    { -    int       i; -    gentity_t *ent; -      lastMarkDeconModCount = g_markDeconstruct.modificationCount; - -    for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) -    { -      if( !ent->inuse ) -        continue; - -      if( ent->s.eType != ET_BUILDABLE ) -        continue; - -      ent->deconstruct = qfalse; -    } +    G_ClearDeconMarks( );    } +  // If we change g_suddenDeathTime during a map, we need to update +  // when sd will begin    if( g_suddenDeathTime.modificationCount != lastSDTimeModCount )    {      lastSDTimeModCount = g_suddenDeathTime.modificationCount;      level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000;    } +  // If the number of zones changes, we need a new array +  if( g_humanRepeaterMaxZones.integer != lastNumZones ) +  { +    buildPointZone_t  *newZones; +    size_t            newsize = g_humanRepeaterMaxZones.integer * sizeof( buildPointZone_t ); +    size_t            oldsize = lastNumZones * sizeof( buildPointZone_t ); + +    newZones = BG_Alloc( newsize ); +    if( level.buildPointZones ) +    { +      Com_Memcpy( newZones, level.buildPointZones, MIN( oldsize, newsize ) ); +      BG_Free( level.buildPointZones ); +    } + +    level.buildPointZones = newZones; +    lastNumZones = g_humanRepeaterMaxZones.integer; +  } +    level.frameMsec = trap_Milliseconds( );  } @@ -2855,47 +2285,62 @@ Advances the non-player objects in the world  */  void G_RunFrame( int levelTime )  { -  int       i; -  gentity_t *ent; -  int       msec; -  int       start, end; +  int        i; +  gentity_t  *ent; +  int        msec; +  static int ptime3000 = 0;    // if we are waiting for the level to restart, do nothing    if( level.restarted )      return; -   -  if( level.paused )  + +  if( level.pausedTime )    { -    if( ( levelTime % 6000 ) == 0) -      trap_SendServerCommand( -1, "cp \"^3Game is paused.\"" ); +    msec = levelTime - level.time - level.pausedTime; +    level.pausedTime = levelTime - level.time; + +    ptime3000 += msec; +    while( ptime3000 > 3000 ) +    { +      ptime3000 -= 3000; +      trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" ); -    level.startTime += levelTime - level.time; -    trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime ) ); +      if( level.pausedTime >= 110000  && level.pausedTime <= 119000 ) +        trap_SendServerCommand( -1, va( "print \"Server: Game will auto-unpause in %d seconds\n\"",  +          (int) ( (float) ( 120000 - level.pausedTime ) / 1000.0f ) ) ); +    } -    if( levelTime - level.pauseTime > 3 * 60000 ) +    // Prevents clients from getting lagged-out messages +    for( i = 0; i < level.maxclients; i++ )      { -      trap_SendConsoleCommand( EXEC_APPEND, "!unpause" ); +      if( level.clients[ i ].pers.connected == CON_CONNECTED ) +        level.clients[ i ].ps.commandTime = levelTime;      } + +    if( level.pausedTime > 120000 ) +    { +      trap_SendServerCommand( -1, "print \"Server: The game has been unpaused automatically (2 minute max)\n\"" ); +      trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" ); +      level.pausedTime = 0; +    } + +    return;    } -  CheckMsgTimer( ); -  CheckCountdown( ); -      level.framenum++;    level.previousTime = level.time;    level.time = levelTime;    msec = level.time - level.previousTime; -  //TA: seed the rng -  srand( level.framenum ); -    // get any cvar changes    G_UpdateCvars( ); +  CheckCvars( ); +  // now we are done spawning +  level.spawning = qfalse;    //    // go through all allocated objects    // -  start = trap_Milliseconds( );    ent = &g_entities[ 0 ];    for( i = 0; i < level.num_entities; i++, ent++ ) @@ -2935,7 +2380,7 @@ void G_RunFrame( int levelTime )      if( ent->freeAfterEvent )        continue; -    //TA: calculate the acceleration of this entity +    // calculate the acceleration of this entity      if( ent->evaluateAcceleration )        G_EvaluateAcceleration( ent, msec ); @@ -2948,6 +2393,12 @@ void G_RunFrame( int levelTime )        continue;      } +    if ( ent->s.eType == ET_WEAPON_DROP ) +    { +      G_RunWeaponDrop( ent ); +      continue; +    } +      if( ent->s.eType == ET_BUILDABLE )      {        G_BuildableThink( ent, msec ); @@ -2974,9 +2425,6 @@ void G_RunFrame( int levelTime )      G_RunThink( ent );    } -  end = trap_Milliseconds(); - -  start = trap_Milliseconds();    // perform final fixups on the players    ent = &g_entities[ 0 ]; @@ -2987,19 +2435,20 @@ void G_RunFrame( int levelTime )        ClientEndFrame( ent );    } -  // save position information for all active clients  +  // save position information for all active clients    G_UnlaggedStore( ); -  end = trap_Milliseconds(); - -  //TA:    G_CountSpawns( ); -  G_CalculateBuildPoints( ); -  G_CalculateStages( ); -  G_SpawnClients( PTE_ALIENS ); -  G_SpawnClients( PTE_HUMANS ); -  G_CalculateAvgPlayers( ); -  G_UpdateZaps( msec ); +  if( !g_doWarmup.integer || level.warmupTime <= level.time ) +  { +    G_CalculateBuildPoints( ); +    G_CalculateStages( ); +    G_SpawnClients( TEAM_ALIENS ); +    G_SpawnClients( TEAM_HUMANS ); +    G_CalculateAvgPlayers( ); +    G_UpdateZaps( msec ); +  } +  G_UpdateBuildableRangeMarkers( );    // see if it is time to end the level    CheckExitRules( ); @@ -3008,23 +2457,8 @@ void G_RunFrame( int levelTime )    CheckTeamStatus( );    // cancel vote if timed out -  CheckVote( ); - -  // check team votes -  CheckTeamVote( PTE_HUMANS ); -  CheckTeamVote( PTE_ALIENS ); +  for( i = 0; i < NUM_TEAMS; i++ ) +    G_CheckVote( i ); -  G_admin_schachtmeisterFrame(); - -  // for tracking changes -  CheckCvars( ); - -  if( g_listEntity.integer ) -  { -    for( i = 0; i < MAX_GENTITIES; i++ ) -      G_Printf( "%4i: %s\n", i, g_entities[ i ].classname ); - -    trap_Cvar_Set( "g_listEntity", "0" ); -  } +  level.frameMsec = trap_Milliseconds();  } - diff --git a/src/game/g_maprotation.c b/src/game/g_maprotation.c index a846c79..90a51cf 100644 --- a/src/game/g_maprotation.c +++ b/src/game/g_maprotation.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -25,10 +26,91 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #include "g_local.h" -mapRotations_t mapRotations; +#define MAX_MAP_ROTATIONS       64 +#define MAX_MAP_ROTATION_MAPS   256 + +#define NOT_ROTATING            -1 + +typedef enum +{ +  CV_ERR, +  CV_RANDOM, +  CV_NUMCLIENTS, +  CV_LASTWIN +} conditionVariable_t; + +typedef enum +{ +  CO_LT, +  CO_EQ, +  CO_GT +} conditionOp_t; + +typedef struct condition_s +{ +  struct node_s       *target; + +  conditionVariable_t lhs; +  conditionOp_t       op; + +  int                 numClients; +  team_t              lastWin; +} condition_t; + +typedef struct map_s +{ +  char  name[ MAX_QPATH ]; + +  char  postCommand[ MAX_STRING_CHARS ]; +  char  layouts[ MAX_CVAR_VALUE_STRING ]; +} map_t; + +typedef struct label_s +{ +  char name[ MAX_QPATH ]; +} label_t; + +typedef enum +{ +  NT_MAP, +  NT_CONDITION, +  NT_GOTO, +  NT_RESUME, +  NT_LABEL, +  NT_RETURN +} nodeType_t; + +typedef struct node_s +{ +  nodeType_t    type; + +  union +  { +    map_t       map; +    condition_t condition; +    label_t     label; +  } u; +} node_t; -static qboolean G_GetVotedMap( char *name, int size, int rotation, int map ); +typedef struct mapRotation_s +{ +  char    name[ MAX_QPATH ]; + +  node_t  *nodes[ MAX_MAP_ROTATION_MAPS ]; +  int     numNodes; +  int     currentNode; +} mapRotation_t; + +typedef struct mapRotations_s +{ +  mapRotation_t   rotations[ MAX_MAP_ROTATIONS ]; +  int             numRotations; +} mapRotations_t; + +static mapRotations_t mapRotations; + +static int G_NodeIndexAfter( int currentNode, int rotation );  /*  =============== @@ -37,9 +119,21 @@ G_MapExists  Check if a map exists  ===============  */ -qboolean G_MapExists( char *name ) +qboolean G_MapExists( const char *name ) +{ +  return trap_FS_FOpenFile( va( "maps/%s.bsp", name ), NULL, FS_READ ) > 0; +} + +/* +=============== +G_LayoutExists + +Check if a layout exists for a map +=============== +*/ +qboolean G_LayoutExists( const char *map, const char *layout )  { -  return trap_FS_FOpenFile( va( "maps/%s.bsp", name ), NULL, FS_READ ); +  return !Q_stricmp( layout, "*BUILTIN*" ) || trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, layout ), NULL, FS_READ ) > 0;  }  /* @@ -64,62 +158,111 @@ static qboolean G_RotationExists( char *name )  /*  =============== -G_ParseCommandSection +G_LabelExists + +Check if a label exists in a rotation +=============== +*/ +static qboolean G_LabelExists( int rotation, char *name ) +{ +  mapRotation_t *mr = &mapRotations.rotations[ rotation ]; +  int           i; + +  for( i = 0; i < mr->numNodes; i++ ) +  { +    node_t *node = mr->nodes[ i ]; + +    if( node->type == NT_LABEL && +        !Q_stricmp( name, node->u.label.name ) ) +      return qtrue; + +    if( node->type == NT_MAP && +        !Q_stricmp( name, node->u.map.name ) ) +      return qtrue; +  } + +  return qfalse; +} + +/* +=============== +G_AllocateNode + +Allocate memory for a node_t +=============== +*/ +static node_t *G_AllocateNode( void ) +{ +  node_t *node = BG_Alloc( sizeof( node_t ) ); + +  return node; +} + +/* +=============== +G_ParseMapCommandSection  Parse a map rotation command section  ===============  */ -static qboolean G_ParseMapCommandSection( mapRotationEntry_t *mre, char **text_p ) +static qboolean G_ParseMapCommandSection( node_t *node, char **text_p )  { -  char *token; +  char  *token; +  map_t *map = &node->u.map; +  int   commandLength = 0;    // read optional parameters    while( 1 )    {      token = COM_Parse( text_p ); -    if( !token ) +    if( !*token )        break;      if( !Q_stricmp( token, "" ) )        return qfalse;      if( !Q_stricmp( token, "}" ) ) +    { +      if( commandLength > 0 ) +      { +        // Replace last ; with \n +        map->postCommand[ commandLength - 1 ] = '\n'; +      } +        return qtrue; //reached the end of this command section +    }      if( !Q_stricmp( token, "layouts" ) )      {        token = COM_ParseExt( text_p, qfalse ); -      mre->layouts[ 0 ] = '\0'; -      while( token && token[ 0 ] != 0 ) +      map->layouts[ 0 ] = '\0'; + +      while( token[ 0 ] != 0 )        { -        Q_strcat( mre->layouts, sizeof( mre->layouts ), token ); -        Q_strcat( mre->layouts, sizeof( mre->layouts ), " " ); +        Q_strcat( map->layouts, sizeof( map->layouts ), token ); +        Q_strcat( map->layouts, sizeof( map->layouts ), " " );          token = COM_ParseExt( text_p, qfalse );        } +        continue;      } -    Q_strncpyz( mre->postCmds[ mre->numCmds ], token, sizeof( mre->postCmds[ 0 ] ) ); -    Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " ); +    // Parse the rest of the line into map->postCommand +    Q_strcat( map->postCommand, sizeof( map->postCommand ), token ); +    Q_strcat( map->postCommand, sizeof( map->postCommand ), " " );      token = COM_ParseExt( text_p, qfalse ); -    while( token && token[ 0 ] != 0 ) +    while( token[ 0 ] != 0 )      { -      Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), token ); -      Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " ); +      Q_strcat( map->postCommand, sizeof( map->postCommand ), token ); +      Q_strcat( map->postCommand, sizeof( map->postCommand ), " " );        token = COM_ParseExt( text_p, qfalse );      } -    if( mre->numCmds == MAX_MAP_COMMANDS ) -    { -      G_Printf( S_COLOR_RED "ERROR: maximum number of map commands (%d) reached\n", -                MAX_MAP_COMMANDS ); -      return qfalse; -    } -    else -      mre->numCmds++; +    commandLength = strlen( map->postCommand ); +    map->postCommand[ commandLength - 1 ] = ';';    }    return qfalse; @@ -127,291 +270,195 @@ static qboolean G_ParseMapCommandSection( mapRotationEntry_t *mre, char **text_p  /*  =============== -G_ParseMapRotation +G_ParseNode -Parse a map rotation section +Parse a node  ===============  */ -static qboolean G_ParseMapRotation( mapRotation_t *mr, char **text_p ) +static qboolean G_ParseNode( node_t **node, char *token, char **text_p, qboolean conditional )  { -  char                    *token; -  qboolean                mnSet = qfalse; -  mapRotationEntry_t      *mre = NULL; -  mapRotationCondition_t  *mrc; - -  // read optional parameters -  while( 1 ) +  if( !Q_stricmp( token, "if" ) )    { -    token = COM_Parse( text_p ); +    condition_t *condition; -    if( !token ) -      break; +    (*node)->type = NT_CONDITION; +    condition = &(*node)->u.condition; -    if( !Q_stricmp( token, "" ) ) +    token = COM_Parse( text_p ); + +    if( !*token )        return qfalse; -    if( !Q_stricmp( token, "{" ) ) +    if( !Q_stricmp( token, "numClients" ) )      { -      if( !mnSet ) -      { -        G_Printf( S_COLOR_RED "ERROR: map settings section with no name\n" ); +      condition->lhs = CV_NUMCLIENTS; + +      token = COM_Parse( text_p ); + +      if( !*token )          return qfalse; -      } -      if( !G_ParseMapCommandSection( mre, text_p ) ) +      if( !Q_stricmp( token, "<" ) ) +        condition->op = CO_LT; +      else if( !Q_stricmp( token, ">" ) ) +        condition->op = CO_GT; +      else if( !Q_stricmp( token, "=" ) ) +        condition->op = CO_EQ; +      else        { -        G_Printf( S_COLOR_RED "ERROR: failed to parse map command section\n" ); +        G_Printf( S_COLOR_RED "ERROR: invalid operator in expression: %s\n", token );          return qfalse;        } -      mnSet = qfalse; -      continue; -    } -    else if( !Q_stricmp( token, "goto" ) ) -    {        token = COM_Parse( text_p ); -      if( !token ) -        break; - -      mrc = &mre->conditions[ mre->numConditions ]; -      mrc->unconditional = qtrue; -      Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) ); - -      if( mre->numConditions == MAX_MAP_ROTATION_CONDS ) -      { -        G_Printf( S_COLOR_RED "ERROR: maximum number of conditions for one map (%d) reached\n", -                  MAX_MAP_ROTATION_CONDS ); +      if( !*token )          return qfalse; -      } -      else -        mre->numConditions++; -      continue; +      condition->numClients = atoi( token );      } -    else if( !Q_stricmp( token, "if" ) ) +    else if( !Q_stricmp( token, "lastWin" ) )      { -      token = COM_Parse( text_p ); +      condition->lhs = CV_LASTWIN; -      if( !token ) -        break; +      token = COM_Parse( text_p ); -      mrc = &mre->conditions[ mre->numConditions ]; +      if( !*token ) +        return qfalse; -      if( !Q_stricmp( token, "numClients" ) ) +      if( !Q_stricmp( token, "aliens" ) ) +        condition->lastWin = TEAM_ALIENS; +      else if( !Q_stricmp( token, "humans" ) ) +        condition->lastWin = TEAM_HUMANS; +      else        { -        mrc->lhs = MCV_NUMCLIENTS; +        G_Printf( S_COLOR_RED "ERROR: invalid right hand side in expression: %s\n", token ); +        return qfalse; +      } +    } +    else if( !Q_stricmp( token, "random" ) ) +      condition->lhs = CV_RANDOM; +    else +    { +      G_Printf( S_COLOR_RED "ERROR: invalid left hand side in expression: %s\n", token ); +      return qfalse; +    } -        token = COM_Parse( text_p ); +    token = COM_Parse( text_p ); -        if( !token ) -          break; +    if( !*token ) +      return qfalse; -        if( !Q_stricmp( token, "<" ) ) -          mrc->op = MCO_LT; -        else if( !Q_stricmp( token, ">" ) ) -          mrc->op = MCO_GT; -        else if( !Q_stricmp( token, "=" ) ) -          mrc->op = MCO_EQ; -        else -        { -          G_Printf( S_COLOR_RED "ERROR: invalid operator in expression: %s\n", token ); -          return qfalse; -        } +    condition->target = G_AllocateNode( ); +    *node = condition->target; -        token = COM_Parse( text_p ); +    return G_ParseNode( node, token, text_p, qtrue ); +  } +  else if( !Q_stricmp( token, "return" ) ) +  { +    (*node)->type = NT_RETURN; +  } +  else if( !Q_stricmp( token, "goto" ) || +           !Q_stricmp( token, "resume" ) ) +  { +    label_t *label; -        if( !token ) -          break; +    if( !Q_stricmp( token, "goto" ) ) +      (*node)->type = NT_GOTO; +    else +      (*node)->type = NT_RESUME; +    label = &(*node)->u.label; -        mrc->numClients = atoi( token ); -      } -      else if( !Q_stricmp( token, "lastWin" ) ) -      { -        mrc->lhs = MCV_LASTWIN; +    token = COM_Parse( text_p ); -        token = COM_Parse( text_p ); +    if( !*token ) +    { +      G_Printf( S_COLOR_RED "ERROR: goto or resume without label\n" ); +      return qfalse; +    } -        if( !token ) -          break; +    Q_strncpyz( label->name, token, sizeof( label->name ) ); +  } +  else if( *token == '#' || conditional ) +  { +    label_t *label; -        if( !Q_stricmp( token, "aliens" ) ) -          mrc->lastWin = PTE_ALIENS; -        else if( !Q_stricmp( token, "humans" ) ) -          mrc->lastWin = PTE_HUMANS; -        else -        { -          G_Printf( S_COLOR_RED "ERROR: invalid right hand side in expression: %s\n", token ); -          return qfalse; -        } -      } -      else if( !Q_stricmp( token, "random" ) ) -        mrc->lhs = MCV_RANDOM; -      else -      { -        G_Printf( S_COLOR_RED "ERROR: invalid left hand side in expression: %s\n", token ); -        return qfalse; -      } +    (*node)->type = ( conditional ) ? NT_GOTO : NT_LABEL; +    label = &(*node)->u.label; -      token = COM_Parse( text_p ); +    Q_strncpyz( label->name, token, sizeof( label->name ) ); +  } +  else +  { +    map_t *map; -      if( !token ) -        break; +    (*node)->type = NT_MAP; +    map = &(*node)->u.map; -      mrc->unconditional = qfalse; -      Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) ); +    Q_strncpyz( map->name, token, sizeof( map->name ) ); +    map->postCommand[ 0 ] = '\0'; +  } -      if( mre->numConditions == MAX_MAP_ROTATION_CONDS ) -      { -        G_Printf( S_COLOR_RED "ERROR: maximum number of conditions for one map (%d) reached\n", -                  MAX_MAP_ROTATION_CONDS ); -        return qfalse; -      } -      else -        mre->numConditions++; +  return qtrue; +} -      continue; -    } -    else if( !Q_stricmp( token, "*VOTE*" ) ) -    { -      if( mr->numMaps == MAX_MAP_ROTATION_MAPS ) -      { -        G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n", -                  MAX_MAP_ROTATION_MAPS ); -        return qfalse; -      } -      mre = &mr->maps[ mr->numMaps ]; -      Q_strncpyz( mre->name, token, sizeof( mre->name ) ); +/* +=============== +G_ParseMapRotation -      token = COM_Parse( text_p ); +Parse a map rotation section +=============== +*/ +static qboolean G_ParseMapRotation( mapRotation_t *mr, char **text_p ) +{ +  char      *token; +  node_t    *node = NULL; -      if( !Q_stricmp( token, "{" ) ) -      { -        while( 1 ) -        { -          token = COM_Parse( text_p ); +  // read optional parameters +  while( 1 ) +  { +    token = COM_Parse( text_p ); -          if( !token ) -            break; +    if( !*token ) +      break; -          if( !Q_stricmp( token, "}" ) ) -          { -            break; -          } -          else -          { -            if( mre->numConditions < MAX_MAP_ROTATION_CONDS ) -            { -              mrc = &mre->conditions[ mre->numConditions ]; -              mrc->lhs = MCV_VOTE; -              mrc->unconditional = qfalse; -              Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) ); - -              mre->numConditions++; -            } -            else -            { -              G_Printf( S_COLOR_YELLOW "WARNING: maximum number of maps for one vote (%d) reached\n", -                        MAX_MAP_ROTATION_CONDS ); -            } -          } -        } -        if( !mre->numConditions ) -        { -          G_Printf( S_COLOR_YELLOW "WARNING: no maps in *VOTE* section\n" ); -        } -        else -        { -          mr->numMaps++; -          mnSet = qtrue; -        } -      } -      else -      { -        G_Printf( S_COLOR_RED "ERROR: *VOTE* with no section\n" ); -        return qfalse; -      } +    if( !Q_stricmp( token, "" ) ) +      return qfalse; -      continue; -    } -    else if( !Q_stricmp( token, "*RANDOM*" ) ) +    if( !Q_stricmp( token, "{" ) )      { -      if( mr->numMaps == MAX_MAP_ROTATION_MAPS ) +      if( node == NULL )        { -        G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n", -                  MAX_MAP_ROTATION_MAPS ); +        G_Printf( S_COLOR_RED "ERROR: map command section with no associated map\n" );          return qfalse;        } -      mre = &mr->maps[ mr->numMaps ]; -      Q_strncpyz( mre->name, token, sizeof( mre->name ) ); - -      token = COM_Parse( text_p ); - -      if( !Q_stricmp( token, "{" ) ) -      { -        while( 1 ) -        { -          token = COM_Parse( text_p ); - -          if( !token ) -            break; -          if( !Q_stricmp( token, "}" ) ) -          { -            break; -          } -          else -          { -            if( mre->numConditions < MAX_MAP_ROTATION_CONDS ) -            { -              mrc = &mre->conditions[ mre->numConditions ]; -              mrc->lhs = MCV_SELECTEDRANDOM; -              mrc->unconditional = qfalse; -              Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) ); - -              mre->numConditions++; -            } -            else -            { -              G_Printf( S_COLOR_YELLOW "WARNING: maximum number of maps for one Random Slot (%d) reached\n", -                        MAX_MAP_ROTATION_CONDS ); -            } -          } -        } -        if( !mre->numConditions ) -        { -          G_Printf( S_COLOR_YELLOW "WARNING: no maps in *RANDOM* section\n" ); -        } -        else -        { -          mr->numMaps++; -          mnSet = qtrue; -        } -      } -      else +      if( !G_ParseMapCommandSection( node, text_p ) )        { -        G_Printf( S_COLOR_RED "ERROR: *RANDOM* with no section\n" ); +        G_Printf( S_COLOR_RED "ERROR: failed to parse map command section\n" );          return qfalse;        }        continue;      }      else if( !Q_stricmp( token, "}" ) ) -      return qtrue; //reached the end of this map rotation - -    mre = &mr->maps[ mr->numMaps ]; +    { +      // Reached the end of this map rotation +      return qtrue; +    } -    if( mr->numMaps == MAX_MAP_ROTATION_MAPS ) +    if( mr->numNodes == MAX_MAP_ROTATION_MAPS )      {        G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n",                  MAX_MAP_ROTATION_MAPS );        return qfalse;      } -    else -      mr->numMaps++; -    Q_strncpyz( mre->name, token, sizeof( mre->name ) ); -    mnSet = qtrue; +    node = G_AllocateNode( ); +    mr->nodes[ mr->numNodes++ ] = node; + +    if( !G_ParseNode( &node, token, text_p, qfalse ) ) +      return qfalse;    }    return qfalse; @@ -427,7 +474,7 @@ Load the map rotations from a map rotation file  static qboolean G_ParseMapRotationFile( const char *fileName )  {    char          *text_p; -  int           i, j, k; +  int           i, j;    int           len;    char          *token;    char          text[ 20000 ]; @@ -460,7 +507,7 @@ static qboolean G_ParseMapRotationFile( const char *fileName )    {      token = COM_Parse( &text_p ); -    if( !token ) +    if( !*token )        break;      if( !Q_stricmp( token, "" ) ) @@ -471,13 +518,17 @@ static qboolean G_ParseMapRotationFile( const char *fileName )        if( mrNameSet )        {          //check for name space clashes -        for( i = 0; i < mapRotations.numRotations; i++ ) +        if( G_RotationExists( mrName ) )          { -          if( !Q_stricmp( mapRotations.rotations[ i ].name, mrName ) ) -          { -            G_Printf( S_COLOR_RED "ERROR: a map rotation is already named %s\n", mrName ); -            return qfalse; -          } +          G_Printf( S_COLOR_RED "ERROR: a map rotation is already named %s\n", mrName ); +          return qfalse; +        } + +        if( mapRotations.numRotations == MAX_MAP_ROTATIONS ) +        { +          G_Printf( S_COLOR_RED "ERROR: maximum number of map rotations (%d) reached\n", +                    MAX_MAP_ROTATIONS ); +          return qfalse;          }          Q_strncpyz( mapRotations.rotations[ mapRotations.numRotations ].name, mrName, MAX_QPATH ); @@ -488,23 +539,16 @@ static qboolean G_ParseMapRotationFile( const char *fileName )            return qfalse;          } +        mapRotations.numRotations++; +          //start parsing map rotations again          mrNameSet = qfalse; -        if( mapRotations.numRotations == MAX_MAP_ROTATIONS ) -        { -          G_Printf( S_COLOR_RED "ERROR: maximum number of map rotations (%d) reached\n", -                    MAX_MAP_ROTATIONS ); -          return qfalse; -        } -        else -          mapRotations.numRotations++; -          continue;        }        else        { -        G_Printf( S_COLOR_RED "ERROR: unamed map rotation\n" ); +        G_Printf( S_COLOR_RED "ERROR: unnamed map rotation\n" );          return qfalse;        }      } @@ -523,29 +567,46 @@ static qboolean G_ParseMapRotationFile( const char *fileName )    for( i = 0; i < mapRotations.numRotations; i++ )    { -    for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ ) +    mapRotation_t *mr = &mapRotations.rotations[ i ]; +    int           mapCount = 0; + +    for( j = 0; j < mr->numNodes; j++ )      { -      if( Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*VOTE*") != 0 && -          Q_stricmp( mapRotations.rotations[ i ].maps[ j ].name, "*RANDOM*") != 0 && -          !G_MapExists( mapRotations.rotations[ i ].maps[ j ].name ) ) -      { -        G_Printf( S_COLOR_RED "ERROR: map \"%s\" doesn't exist\n", -          mapRotations.rotations[ i ].maps[ j ].name ); -        return qfalse; -      } +      node_t        *node = mr->nodes[ j ]; -      for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ ) +      if( node->type == NT_MAP )        { -        if( !G_MapExists( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest ) && -            !G_RotationExists( mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest ) ) +        mapCount++; +        if( !G_MapExists( node->u.map.name ) )          { -          G_Printf( S_COLOR_RED "ERROR: conditional destination \"%s\" doesn't exist\n", -            mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest ); +          G_Printf( S_COLOR_RED "ERROR: rotation map \"%s\" doesn't exist\n", +                    node->u.map.name );            return qfalse;          } +        continue; +      } +      else if( node->type == NT_RETURN ) +        continue; +      else if( node->type == NT_LABEL ) +        continue; +      else while( node->type == NT_CONDITION ) +        node = node->u.condition.target; +      if( ( node->type == NT_GOTO || node->type == NT_RESUME ) && +          !G_LabelExists( i, node->u.label.name ) && +          !G_RotationExists( node->u.label.name ) ) +      { +        G_Printf( S_COLOR_RED "ERROR: goto destination named \"%s\" doesn't exist\n", +                  node->u.label.name ); +        return qfalse;        } +    } +    if( mapCount == 0 ) +    { +      G_Printf( S_COLOR_RED "ERROR: rotation \"%s\" needs at least one map entry\n", +        mr->name ); +      return qfalse;      }    } @@ -554,6 +615,19 @@ static qboolean G_ParseMapRotationFile( const char *fileName )  /*  =============== +G_PrintSpaces +=============== +*/ +static void G_PrintSpaces( int spaces ) +{ +  int i; + +  for( i = 0; i < spaces; i++ ) +    G_Printf( " " ); +} + +/* +===============  G_PrintRotations  Print the parsed map rotations @@ -561,55 +635,167 @@ Print the parsed map rotations  */  void G_PrintRotations( void )  { -  int i, j, k; +  int i, j; +  int size = sizeof( mapRotations );    G_Printf( "Map rotations as parsed:\n\n" );    for( i = 0; i < mapRotations.numRotations; i++ )    { -    G_Printf( "rotation: %s\n{\n", mapRotations.rotations[ i ].name ); +    mapRotation_t *mr = &mapRotations.rotations[ i ]; -    for( j = 0; j < mapRotations.rotations[ i ].numMaps; j++ ) +    G_Printf( "rotation: %s\n{\n", mr->name ); + +    size += mr->numNodes * sizeof( node_t ); + +    for( j = 0; j < mr->numNodes; j++ )      { -      G_Printf( "  map: %s\n  {\n", mapRotations.rotations[ i ].maps[ j ].name ); +      node_t *node = mr->nodes[ j ]; +      int indentation = 0; -      for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numCmds; k++ ) +      while( node->type == NT_CONDITION )        { -        G_Printf( "    command: %s\n", -                  mapRotations.rotations[ i ].maps[ j ].postCmds[ k ] ); +        G_PrintSpaces( indentation ); +        G_Printf( "  condition\n" ); +        node = node->u.condition.target; + +        size += sizeof( node_t ); + +        indentation += 2;        } -      G_Printf( "  }\n" ); +      G_PrintSpaces( indentation ); -      for( k = 0; k < mapRotations.rotations[ i ].maps[ j ].numConditions; k++ ) +      switch( node->type )        { -        G_Printf( "  conditional: %s\n", -                  mapRotations.rotations[ i ].maps[ j ].conditions[ k ].dest ); -      } +        case NT_MAP: +          G_Printf( "  %s\n", node->u.map.name ); +          if( strlen( node->u.map.postCommand ) > 0 ) +            G_Printf( "    command: %s", node->u.map.postCommand ); + +          break; + +        case NT_RETURN: +          G_Printf( "  return\n" ); +          break; + +        case NT_LABEL: +          G_Printf( "  label: %s\n", node->u.label.name ); +          break; + +        case NT_GOTO: +          G_Printf( "  goto: %s\n", node->u.label.name ); +          break; + +        case NT_RESUME: +          G_Printf( "  resume: %s\n", node->u.label.name ); +          break; + +        default: +          break; +      }      }      G_Printf( "}\n" );    } -  G_Printf( "Total memory used: %d bytes\n", sizeof( mapRotations ) ); +  G_Printf( "Total memory used: %d bytes\n", size ); +} + +/* +=============== +G_ClearRotationStack + +Clear the rotation stack +=============== +*/ +void G_ClearRotationStack( void ) +{ +  trap_Cvar_Set( "g_mapRotationStack", "" ); +  trap_Cvar_Update( &g_mapRotationStack ); +} + +/* +=============== +G_PushRotationStack + +Push the rotation stack +=============== +*/ +static void G_PushRotationStack( int rotation ) +{ +  char text[ MAX_CVAR_VALUE_STRING ]; + +  Com_sprintf( text, sizeof( text ), "%d %s", +               rotation, g_mapRotationStack.string ); +  trap_Cvar_Set( "g_mapRotationStack", text ); +  trap_Cvar_Update( &g_mapRotationStack ); +} + +/* +=============== +G_PopRotationStack + +Pop the rotation stack +=============== +*/ +static int G_PopRotationStack( void ) +{ +  int   value = -1; +  char  text[ MAX_CVAR_VALUE_STRING ]; +  char  *text_p, *token; + +  Q_strncpyz( text, g_mapRotationStack.string, sizeof( text ) ); + +  text_p = text; +  token = COM_Parse( &text_p ); + +  if( *token ) +    value = atoi( token ); + +  if( text_p ) +  { +    while ( *text_p == ' ' ) +      text_p++; +    trap_Cvar_Set( "g_mapRotationStack", text_p ); +    trap_Cvar_Update( &g_mapRotationStack ); +  } +  else +    G_ClearRotationStack( ); + +  return value;  }  /*  =============== -G_GetCurrentMapArray +G_RotationNameByIndex -Fill a static array with the current map of each rotation +Returns the name of a rotation by its index  ===============  */ -static int *G_GetCurrentMapArray( void ) +static char *G_RotationNameByIndex( int index )  { -  static int  currentMap[ MAX_MAP_ROTATIONS ]; +  if( index >= 0 && index < mapRotations.numRotations ) +    return mapRotations.rotations[ index ].name; +  return NULL; +} + +/* +=============== +G_CurrentNodeIndexArray + +Fill a static array with the current node of each rotation +=============== +*/ +static int *G_CurrentNodeIndexArray( void ) +{ +  static int  currentNode[ MAX_MAP_ROTATIONS ];    int         i = 0;    char        text[ MAX_MAP_ROTATIONS * 2 ];    char        *text_p, *token; -  Q_strncpyz( text, g_currentMap.string, sizeof( text ) ); +  Q_strncpyz( text, g_mapRotationNodes.string, sizeof( text ) );    text_p = text; @@ -617,166 +803,138 @@ static int *G_GetCurrentMapArray( void )    {      token = COM_Parse( &text_p ); -    if( !token ) -      break; - -    if( !Q_stricmp( token, "" ) ) +    if( !*token )        break; -    currentMap[ i++ ] = atoi( token ); +    currentNode[ i++ ] = atoi( token );    } -  return currentMap; +  return currentNode;  }  /*  =============== -G_SetCurrentMap +G_SetCurrentNodeByIndex  Set the current map in some rotation  ===============  */ -static void G_SetCurrentMap( int currentMap, int rotation ) +static void G_SetCurrentNodeByIndex( int currentNode, int rotation )  { -  char  text[ MAX_MAP_ROTATIONS * 2 ] = { 0 }; -  int   *p = G_GetCurrentMapArray( ); +  char  text[ MAX_MAP_ROTATIONS * 4 ] = { 0 }; +  int   *p = G_CurrentNodeIndexArray( );    int   i; -  p[ rotation ] = currentMap; +  p[ rotation ] = currentNode;    for( i = 0; i < mapRotations.numRotations; i++ )      Q_strcat( text, sizeof( text ), va( "%d ", p[ i ] ) ); -  trap_Cvar_Set( "g_currentMap", text ); -  trap_Cvar_Update( &g_currentMap ); +  trap_Cvar_Set( "g_mapRotationNodes", text ); +  trap_Cvar_Update( &g_mapRotationNodes );  }  /*  =============== -G_GetCurrentMap +G_CurrentNodeIndex -Return the current map in some rotation +Return the current node index in some rotation  ===============  */ -int G_GetCurrentMap( int rotation ) +static int G_CurrentNodeIndex( int rotation )  { -  int   *p = G_GetCurrentMapArray( ); +  int   *p = G_CurrentNodeIndexArray( );    return p[ rotation ];  } -static qboolean G_GetRandomMap( char *name, int size, int rotation, int map ); -  /*  =============== -G_IssueMapChange +G_NodeByIndex -Send commands to the server to actually change the map +Return a node in a rotation by its index  ===============  */ -static void G_IssueMapChange( int rotation ) +static node_t *G_NodeByIndex( int index, int rotation )  { -  int   i; -  int   map = G_GetCurrentMap( rotation ); -  char  cmd[ MAX_TOKEN_CHARS ]; -  char  newmap[ MAX_CVAR_VALUE_STRING ]; -  char  currentmap[ MAX_CVAR_VALUE_STRING ]; - -  Q_strncpyz( newmap, mapRotations.rotations[rotation].maps[map].name, sizeof( newmap )); -  trap_Cvar_VariableStringBuffer( "mapname", currentmap, sizeof( currentmap )); +  if( rotation >= 0 && rotation < mapRotations.numRotations && +      index >= 0 && index < mapRotations.rotations[ rotation ].numNodes ) +    return mapRotations.rotations[ rotation ].nodes[ index ]; -  if (!Q_stricmp( newmap, "*VOTE*") ) -  { -    fileHandle_t f; +  return NULL; +} -    G_GetVotedMap( newmap, sizeof( newmap ), rotation, map ); -    if( trap_FS_FOpenFile( va("maps/%s.bsp", newmap), &f, FS_READ ) > 0 ) -    { -      trap_FS_FCloseFile( f ); -    } -    else -    { -      G_AdvanceMapRotation(); -      return; -    } -  } -  else if(!Q_stricmp( newmap, "*RANDOM*") ) -  { -    fileHandle_t f; +/* +=============== +G_IssueMapChange -    G_GetRandomMap( newmap, sizeof( newmap ), rotation, map ); -    if( trap_FS_FOpenFile( va("maps/%s.bsp", newmap), &f, FS_READ ) > 0 ) -    { -      trap_FS_FCloseFile( f ); -    } -    else -    { -      G_AdvanceMapRotation(); -      return; -    } -  } +Send commands to the server to actually change the map +=============== +*/ +static void G_IssueMapChange( int index, int rotation ) +{ +  node_t *node = mapRotations.rotations[ rotation ].nodes[ index ]; +  map_t  *map = &node->u.map; -  // allow a manually defined g_layouts setting to override the maprotation -  if( !g_layouts.string[ 0 ] && -    mapRotations.rotations[ rotation ].maps[ map ].layouts[ 0 ] ) +  // allow a manually defined g_nextLayout setting to override the maprotation +  if( !g_nextLayout.string[ 0 ] && map->layouts[ 0 ] )    { -    trap_Cvar_Set( "g_layouts", -      mapRotations.rotations[ rotation ].maps[ map ].layouts ); +    trap_Cvar_Set( "g_nextLayout", map->layouts );    } -  if( !Q_stricmp( currentmap, newmap ) ) -  { -    trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); -  } -  else -  { -    trap_SendConsoleCommand( EXEC_APPEND, va( "map %s\n", newmap ) ); -  } +  G_MapConfigs( map->name ); -  // load up map defaults if g_mapConfigs is set -  G_MapConfigs( newmap ); +  trap_SendConsoleCommand( EXEC_APPEND, va( "map \"%s\"\n", map->name ) ); -  for( i = 0; i < mapRotations.rotations[ rotation ].maps[ map ].numCmds; i++ ) -  { -    Q_strncpyz( cmd, mapRotations.rotations[ rotation ].maps[ map ].postCmds[ i ], -                sizeof( cmd ) ); -    Q_strcat( cmd, sizeof( cmd ), "\n" ); -    trap_SendConsoleCommand( EXEC_APPEND, cmd ); -  } +  if( strlen( map->postCommand ) > 0 ) +    trap_SendConsoleCommand( EXEC_APPEND, map->postCommand );  }  /*  =============== -G_ResolveConditionDestination +G_GotoLabel -Resolve the destination of some condition +Resolve the label of some condition  ===============  */ -static mapConditionType_t G_ResolveConditionDestination( int *n, char *name ) +static qboolean G_GotoLabel( int rotation, int nodeIndex, char *name, +                             qboolean reset_index, int depth )  { -  int i; +  node_t *node; +  int    i; + +  // Search the rotation names... +  if( G_StartMapRotation( name, qtrue, qtrue, reset_index, depth ) ) +    return qtrue; -  //search the current rotation first... -  for( i = 0; i < mapRotations.rotations[ g_currentMapRotation.integer ].numMaps; i++ ) +  // ...then try labels in the rotation +  for( i = 0; i < mapRotations.rotations[ rotation ].numNodes; i++ )    { -    if( !Q_stricmp( mapRotations.rotations[ g_currentMapRotation.integer ].maps[ i ].name, name ) ) +    node = mapRotations.rotations[ rotation ].nodes[ i ]; + +    if( node->type == NT_LABEL && !Q_stricmp( node->u.label.name, name ) )      { -      *n = i; -      return MCT_MAP; +      G_SetCurrentNodeByIndex( G_NodeIndexAfter( i, rotation ), rotation ); +      G_AdvanceMapRotation( depth ); +      return qtrue;      }    } -  //...then search the rotation names -  for( i = 0; i < mapRotations.numRotations; i++ ) +  // finally check for a map by name +  for( i = 0; i < mapRotations.rotations[ rotation ].numNodes; i++ )    { -    if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) ) +    nodeIndex = G_NodeIndexAfter( nodeIndex, rotation ); +    node = mapRotations.rotations[ rotation ].nodes[ nodeIndex ]; + +    if( node->type == NT_MAP && !Q_stricmp( node->u.map.name, name ) )      { -      *n = i; -      return MCT_ROTATION; +      G_SetCurrentNodeByIndex( nodeIndex, rotation ); +      G_AdvanceMapRotation( depth ); +      return qtrue;      }    } -  return MCT_ERR; +  return qfalse;  }  /* @@ -786,133 +944,231 @@ G_EvaluateMapCondition  Evaluate a map condition  ===============  */ -static qboolean G_EvaluateMapCondition( mapRotationCondition_t *mrc ) +static qboolean G_EvaluateMapCondition( condition_t **condition )  { -  switch( mrc->lhs ) +  qboolean    result = qfalse; +  condition_t *localCondition = *condition; + +  switch( localCondition->lhs )    { -    case MCV_RANDOM: -      return rand( ) & 1; +    case CV_RANDOM: +      result = rand( ) / ( RAND_MAX / 2 + 1 );        break; -    case MCV_NUMCLIENTS: -      switch( mrc->op ) +    case CV_NUMCLIENTS: +      switch( localCondition->op )        { -        case MCO_LT: -          return level.numConnectedClients < mrc->numClients; +        case CO_LT: +          result = level.numConnectedClients < localCondition->numClients;            break; -        case MCO_GT: -          return level.numConnectedClients > mrc->numClients; +        case CO_GT: +          result = level.numConnectedClients > localCondition->numClients;            break; -        case MCO_EQ: -          return level.numConnectedClients == mrc->numClients; +        case CO_EQ: +          result = level.numConnectedClients == localCondition->numClients;            break;        }        break; -    case MCV_LASTWIN: -      return level.lastWin == mrc->lastWin; -      break; - -    case MCV_VOTE: -      // ignore vote for conditions; -      break; -    case MCV_SELECTEDRANDOM: -      // ignore vote for conditions; +    case CV_LASTWIN: +      result = level.lastWin == localCondition->lastWin;        break;      default: -    case MCV_ERR: -      G_Printf( S_COLOR_RED "ERROR: malformed map switch condition\n" ); +    case CV_ERR: +      G_Printf( S_COLOR_RED "ERROR: malformed map switch localCondition\n" );        break;    } -  return qfalse; +  if( localCondition->target->type == NT_CONDITION ) +  { +    *condition = &localCondition->target->u.condition; + +    return result && G_EvaluateMapCondition( condition ); +  } + +  return result;  }  /*  =============== -G_AdvanceMapRotation - -Increment the current map rotation +G_NodeIndexAfter  ===============  */ -qboolean G_AdvanceMapRotation( void ) +static int G_NodeIndexAfter( int currentNode, int rotation )  { -  mapRotation_t           *mr; -  mapRotationEntry_t      *mre; -  mapRotationCondition_t  *mrc; -  int                     currentRotation, currentMap, nextMap; -  int                     i, n; -  mapConditionType_t      mct; - -  if( ( currentRotation = g_currentMapRotation.integer ) == NOT_ROTATING ) -    return qfalse; +  mapRotation_t *mr = &mapRotations.rotations[ rotation ]; + +  return ( currentNode + 1 ) % mr->numNodes; +} + +/* +=============== +G_StepMapRotation -  currentMap = G_GetCurrentMap( currentRotation ); +Run one node of a map rotation +=============== +*/ +qboolean G_StepMapRotation( int rotation, int nodeIndex, int depth ) +{ +  node_t        *node; +  condition_t   *condition; +  int           returnRotation; +  qboolean      step = qtrue; -  mr = &mapRotations.rotations[ currentRotation ]; -  mre = &mr->maps[ currentMap ]; -  nextMap = ( currentMap + 1 ) % mr->numMaps; +  node = G_NodeByIndex( nodeIndex, rotation ); +  depth++; -  for( i = 0; i < mre->numConditions; i++ ) +  // guard against inifinite loop in conditional code +  if( depth > 32 && node->type != NT_MAP )    { -    mrc = &mre->conditions[ i ]; +    if( depth > 64 ) +    { +      G_Printf( S_COLOR_RED "ERROR: infinite loop protection stopped at map rotation %s\n", +              G_RotationNameByIndex( rotation ) ); +      return qfalse; +    } -    if( mrc->unconditional || G_EvaluateMapCondition( mrc ) ) +    G_Printf( S_COLOR_YELLOW "WARNING: possible infinite loop in map rotation %s\n", +              G_RotationNameByIndex( rotation ) ); +    return qtrue; +  } + +  while( step ) +  { +    step = qfalse; +    switch( node->type )      { -      mct = G_ResolveConditionDestination( &n, mrc->dest ); +      case NT_CONDITION: +        condition = &node->u.condition; -      switch( mct ) -      { -        case MCT_MAP: -          nextMap = n; -          break; +        if( G_EvaluateMapCondition( &condition ) ) +        { +          node = condition->target; +          step = qtrue; +          continue; +        } +        break; -        case MCT_ROTATION: -          //need to increment the current map before changing the rotation -          //or you get infinite loops with some conditionals -          G_SetCurrentMap( nextMap, currentRotation ); -          G_StartMapRotation( mrc->dest, qtrue ); -          return qtrue; -          break; +      case NT_RETURN: +        returnRotation = G_PopRotationStack( ); +        if( returnRotation >= 0 ) +        { +          G_SetCurrentNodeByIndex( +            G_NodeIndexAfter( nodeIndex, rotation ), rotation ); +          if( G_StartMapRotation( G_RotationNameByIndex( returnRotation ), +                                  qtrue, qfalse, qfalse, depth ) ) +          { +            return qfalse; +          } +        } +        break; -        default: -        case MCT_ERR: -          G_Printf( S_COLOR_YELLOW "WARNING: map switch destination could not be resolved: %s\n", -                    mrc->dest ); -          break; -      } +      case NT_MAP: +        if( G_MapExists( node->u.map.name ) ) +        { +          G_SetCurrentNodeByIndex( +            G_NodeIndexAfter( nodeIndex, rotation ), rotation ); +          G_IssueMapChange( nodeIndex, rotation ); +          return qfalse; +        } + +        G_Printf( S_COLOR_YELLOW "WARNING: skipped missing map %s in rotation %s\n", +                  node->u.map.name, G_RotationNameByIndex( rotation ) ); +        break; + +      case NT_LABEL: +        break; + +      case NT_GOTO: +      case NT_RESUME: +        G_SetCurrentNodeByIndex( +          G_NodeIndexAfter( nodeIndex, rotation ), rotation ); +        if ( G_GotoLabel( rotation, nodeIndex, node->u.label.name, +                          ( node->type == NT_GOTO ), depth ) ) +          return qfalse; + +        G_Printf( S_COLOR_YELLOW "WARNING: label, map, or rotation %s not found in %s\n", +                  node->u.label.name, G_RotationNameByIndex( rotation ) ); +        break;      }    } -  G_SetCurrentMap( nextMap, currentRotation ); -  G_IssueMapChange( currentRotation ); -    return qtrue;  }  /*  =============== +G_AdvanceMapRotation + +Increment the current map rotation +=============== +*/ +void G_AdvanceMapRotation( int depth ) +{ +  node_t *node; +  int    rotation; +  int    nodeIndex; + +  rotation = g_currentMapRotation.integer; +  if( rotation < 0 || rotation >= MAX_MAP_ROTATIONS ) +    return; + +  nodeIndex = G_CurrentNodeIndex( rotation ); +  node = G_NodeByIndex( nodeIndex, rotation ); +  if( !node ) +  { +    G_Printf( S_COLOR_YELLOW "WARNING: index incorrect for map rotation %s, trying 0\n", +              G_RotationNameByIndex( rotation) ); +    nodeIndex = 0; +    node = G_NodeByIndex( nodeIndex, rotation ); +  } + +  while( node && G_StepMapRotation( rotation, nodeIndex, depth ) ) +  { +    nodeIndex = G_NodeIndexAfter( nodeIndex, rotation ); +    node = G_NodeByIndex( nodeIndex, rotation ); +    depth++; +  } + +  if( !node ) +    G_Printf( S_COLOR_RED "ERROR: unexpected end of maprotation '%s'\n", +              G_RotationNameByIndex( rotation) ); +} + +/* +===============  G_StartMapRotation  Switch to a new map rotation  ===============  */ -qboolean G_StartMapRotation( char *name, qboolean changeMap ) +qboolean G_StartMapRotation( char *name, qboolean advance, +                             qboolean putOnStack, qboolean reset_index, int depth )  {    int i; +  int currentRotation = g_currentMapRotation.integer;    for( i = 0; i < mapRotations.numRotations; i++ )    {      if( !Q_stricmp( mapRotations.rotations[ i ].name, name ) )      { +      if( putOnStack && currentRotation >= 0 ) +        G_PushRotationStack( currentRotation ); +        trap_Cvar_Set( "g_currentMapRotation", va( "%d", i ) );        trap_Cvar_Update( &g_currentMapRotation ); -      if( changeMap ) -        G_IssueMapChange( i ); +      if( advance ) +      { +        if( reset_index ) +          G_SetCurrentNodeByIndex( 0, i ); + +        G_AdvanceMapRotation( depth ); +      } +        break;      }    } @@ -952,14 +1208,14 @@ qboolean G_MapRotationActive( void )  ===============  G_InitMapRotations -Load and intialise the map rotations +Load and initialise the map rotations  ===============  */  void G_InitMapRotations( void )  { -  const char    *fileName = "maprotation.cfg"; +  const char  *fileName = "maprotation.cfg"; -  //load the file if it exists +  // Load the file if it exists    if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) )    {      if( !G_ParseMapRotationFile( fileName ) ) @@ -972,7 +1228,7 @@ void G_InitMapRotations( void )    {      if( g_initialMapRotation.string[ 0 ] != 0 )      { -      G_StartMapRotation( g_initialMapRotation.string, qfalse ); +      G_StartMapRotation( g_initialMapRotation.string, qfalse, qtrue, qfalse, 0 );        trap_Cvar_Set( "g_initialMapRotation", "" );        trap_Cvar_Update( &g_initialMapRotation ); @@ -980,347 +1236,41 @@ void G_InitMapRotations( void )    }  } -static char rotationVoteList[ MAX_MAP_ROTATION_CONDS ][ MAX_QPATH ]; -static int rotationVoteLen = 0; - -static int rotationVoteClientPosition[ MAX_CLIENTS ]; -static int rotationVoteClientSelection[ MAX_CLIENTS ]; -  /*  =============== -G_CheckMapRotationVote +G_FreeNode + +Free up memory used by a node  ===============  */ -qboolean G_CheckMapRotationVote( void ) -{ -  mapRotation_t           *mr; -  mapRotationEntry_t      *mre; -  mapRotationCondition_t  *mrc; -  int                     currentRotation, currentMap, nextMap; -  int                     i; - -  rotationVoteLen = 0; - -  if( g_mapRotationVote.integer < 1 ) -    return qfalse; - -  if( ( currentRotation = g_currentMapRotation.integer ) == NOT_ROTATING ) -    return qfalse; - -  currentMap = G_GetCurrentMap( currentRotation ); - -  mr = &mapRotations.rotations[ currentRotation ]; -  nextMap = ( currentMap + 1 ) % mr->numMaps; -  mre = &mr->maps[ nextMap ]; - -  for( i = 0; i < mre->numConditions; i++ ) -  { -    mrc = &mre->conditions[ i ]; - -    if( mrc->lhs == MCV_VOTE ) -    { -      Q_strncpyz( rotationVoteList[ rotationVoteLen ], mrc->dest, -        sizeof(  rotationVoteList[ rotationVoteLen ] ) ); -      rotationVoteLen++; -      if( rotationVoteLen >= MAX_MAP_ROTATION_CONDS ) -        break; -    } -  } - -  if( !rotationVoteLen ) -    return qfalse; - -  for( i = 0; i < MAX_CLIENTS; i++ ) -  { -    rotationVoteClientPosition[ i ] = 0; -    rotationVoteClientSelection[ i ] = -1; -  } - -  return qtrue; -} - -typedef struct { -  int votes; -  int map; -} MapVoteResultsSort_t; - -static int SortMapVoteResults( const void *av, const void *bv ) -{ -  const MapVoteResultsSort_t *a = av; -  const MapVoteResultsSort_t *b = bv; - -  if( a->votes > b->votes ) -    return -1; -  if( a->votes < b->votes ) -    return 1; - -  if( a->map > b->map ) -    return 1; -  if( a->map < b->map ) -    return -1; - -  return 0; -} - -static int G_GetMapVoteWinner( int *winvotes, int *totalvotes, int *resultorder ) -{ -  MapVoteResultsSort_t results[ MAX_MAP_ROTATION_CONDS ]; -  int tv = 0; -  int i, n; - -  for( i = 0; i < MAX_MAP_ROTATION_CONDS; i++ ) -  { -    results[ i ].votes = 0; -    results[ i ].map = i; -  } -  for( i = 0; i < MAX_CLIENTS; i++ ) -  { -    n = rotationVoteClientSelection[ i ]; -    if( n >=0 && n < MAX_MAP_ROTATION_CONDS ) -    { -      results[ n ].votes += 1; -      tv++; -    } -  } - -  qsort ( results, MAX_MAP_ROTATION_CONDS, sizeof( results[ 0 ] ), SortMapVoteResults ); - -  if( winvotes != NULL ) -    *winvotes = results[ 0 ].votes; -  if( totalvotes != NULL ) -    *totalvotes = tv; - -  if( resultorder != NULL ) -  { -    for( i = 0; i < MAX_MAP_ROTATION_CONDS; i++ ) -      resultorder[ results[ i ].map ] = i; -  } - -  return results[ 0 ].map; -} - -qboolean G_IntermissionMapVoteWinner( void ) +void G_FreeNode( node_t *node )  { -  int winner, winvotes, totalvotes; -  int nonvotes; +  if( node->type == NT_CONDITION ) +    G_FreeNode( node->u.condition.target ); -  winner = G_GetMapVoteWinner( &winvotes, &totalvotes, NULL ); -  if( winvotes * 2 > level.numConnectedClients ) -    return qtrue; -  nonvotes = level.numConnectedClients - totalvotes; -  if( nonvotes < 0 ) -    nonvotes = 0; -  if( winvotes > nonvotes + ( totalvotes - winvotes ) ) -    return qtrue; - -  return qfalse; +  BG_Free( node );  } -static qboolean G_GetVotedMap( char *name, int size, int rotation, int map ) -{ -  mapRotation_t           *mr; -  mapRotationEntry_t      *mre; -  mapRotationCondition_t  *mrc; -  int                     i, n; -  int                     winner; -  qboolean                found = qfalse; - -  if( !rotationVoteLen ) -    return qfalse; - -  winner = G_GetMapVoteWinner( NULL, NULL, NULL ); - -  mr = &mapRotations.rotations[ rotation ]; -  mre = &mr->maps[ map ]; - -  n = 0; -  for( i = 0; i < mre->numConditions && n < rotationVoteLen; i++ ) -  { -    mrc = &mre->conditions[ i ]; - -    if( mrc->lhs == MCV_VOTE ) -    { -      if( n == winner ) -      { -        Q_strncpyz( name, mrc->dest, size ); -        found = qtrue; -        break; -      } -      n++; -    } -  } - -  rotationVoteLen = 0; - -  return found; -} +/* +=============== +G_ShutdownMapRotations -static void G_IntermissionMapVoteMessageReal( gentity_t *ent, int winner, int winvotes, int totalvotes, int *ranklist ) +Free up memory used by map rotations +=============== +*/ +void G_ShutdownMapRotations( void )  { -  int  clientNum; -  char string[ MAX_STRING_CHARS ]; -  char entry[ MAX_STRING_CHARS ]; -  int  ourlist[ MAX_MAP_ROTATION_CONDS ]; -  int  len = 0; -  int  index, selection; -  int  i; -  char *color; -  char *rank; - -  clientNum = ent-g_entities; - -  index = rotationVoteClientSelection[ clientNum ]; -  selection = rotationVoteClientPosition[ clientNum ]; - -  if( winner < 0 || winner >= MAX_MAP_ROTATION_CONDS || ranklist == NULL ) -  { -    ranklist = &ourlist[0]; -    winner = G_GetMapVoteWinner( &winvotes, &totalvotes, ranklist ); -  } +  int i, j; -  Q_strncpyz( string, "^7Attack = down ^0/^7 Repair = up ^0/^7 F1 = vote\n\n" -    "^2Map Vote Menu\n" -    "^7+------------------+\n", sizeof( string ) ); -  for( i = 0; i < rotationVoteLen; i++ ) +  for( i = 0; i < mapRotations.numRotations; i++ )    { -    if( !G_MapExists( rotationVoteList[ i ] ) ) -      continue; -     -    if( i == selection ) -      color = "^5"; -    else if( i == index ) -      color = "^1"; -    else -      color = "^7"; +    mapRotation_t *mr = &mapRotations.rotations[ i ]; -    switch( ranklist[ i ] ) +    for( j = 0; j < mr->numNodes; j++ )      { -      case 0: -        rank = "^7---"; -        break; -      case 1: -        rank = "^7--"; -        break; -      case 2: -        rank = "^7-"; -        break; -      default: -        rank = ""; -        break; -    } - -    Com_sprintf( entry, sizeof( entry ), "^7%s%s%s%s %s %s%s^7%s\n", -     ( i == index ) ? "^1>>>" : "", -     ( i == selection ) ? "^7(" : " ", -     rank, -     color, -     rotationVoteList[ i ], -     rank, -     ( i == selection ) ? "^7)" : " ", -     ( i == index ) ? "^1<<<" : "" ); - -    Q_strcat( string, sizeof( string ), entry ); -    len += strlen( entry ); -  } - -  Com_sprintf( entry, sizeof( entry ), -    "\n^7+----------------+\nleader: ^3%s^7 with %d vote%s\nvoters: %d\ntime left: %d", -    rotationVoteList[ winner ], -    winvotes, -    ( winvotes == 1 ) ? "" : "s", -    totalvotes, -    ( level.mapRotationVoteTime - level.time ) / 1000 ); -  Q_strcat( string, sizeof( string ), entry ); +      node_t *node = mr->nodes[ j ]; -  trap_SendServerCommand( ent-g_entities, va( "cp \"%s\"\n", string ) ); -} - -void G_IntermissionMapVoteMessageAll( void ) -{ -  int ranklist[ MAX_MAP_ROTATION_CONDS ]; -  int winner; -  int winvotes, totalvotes; -  int i; - -  winner = G_GetMapVoteWinner( &winvotes, &totalvotes, &ranklist[ 0 ] ); -  for( i = 0; i < level.maxclients; i++ ) -  { -    if( level.clients[ i ].pers.connected == CON_CONNECTED ) -      G_IntermissionMapVoteMessageReal( g_entities + i, winner, winvotes, totalvotes, ranklist ); -  } -} - -void G_IntermissionMapVoteMessage( gentity_t *ent ) -{ -   G_IntermissionMapVoteMessageReal( ent, -1, 0, 0, NULL ); -} - -void G_IntermissionMapVoteCommand( gentity_t *ent, qboolean next, qboolean choose ) -{ -  int clientNum; -  int n; - -  clientNum = ent-g_entities; - -  if( choose ) -  { -    rotationVoteClientSelection[ clientNum ] = rotationVoteClientPosition[ clientNum ]; -  } -  else -  { -    n = rotationVoteClientPosition[ clientNum ]; -    if( next ) -      n++; -    else -      n--; - -    if( n >= rotationVoteLen ) -      n = rotationVoteLen - 1; -    if( n < 0 ) -      n = 0; - -    rotationVoteClientPosition[ clientNum ] = n; -  } - -  G_IntermissionMapVoteMessage( ent ); -} - -static qboolean G_GetRandomMap( char *name, int size, int rotation, int map ) -{ -  mapRotation_t           *mr; -  mapRotationEntry_t      *mre; -  mapRotationCondition_t  *mrc; -  int                     i, nummaps; -  int                     randompick = 0; -  int                     maplist[ 32 ]; - -  mr = &mapRotations.rotations[ rotation ]; -  mre = &mr->maps[ map ]; - -  nummaps = 0; -  //count the number of map votes -  for( i = 0; i < mre->numConditions; i++ ) -  { -    mrc = &mre->conditions[ i ]; - -    if( mrc->lhs == MCV_SELECTEDRANDOM ) -    { -      //map doesnt exist -      if( !G_MapExists( mrc->dest ) ) { -        continue; -      } -      maplist[ nummaps ] = i; -      nummaps++; +      G_FreeNode( node );      }    } - -  if( nummaps == 0 ) { -    return qfalse; -  } - -  randompick = (int)( random() * nummaps ); - -  Q_strncpyz( name, mre->conditions[ maplist[ randompick ] ].dest, size ); - -  return qtrue;  } diff --git a/src/game/g_misc.c b/src/game/g_misc.c index a68eeb8..66066ba 100644 --- a/src/game/g_misc.c +++ b/src/game/g_misc.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -44,7 +45,7 @@ target_position does the same thing  */  void SP_info_notnull( gentity_t *self )  { -  G_SetOrigin( self, self->s.origin ); +  G_SetOrigin( self, self->r.currentOrigin );  } @@ -70,39 +71,47 @@ TELEPORTERS  =================================================================================  */ -void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles ) +void TeleportPlayer( gentity_t *player, vec3_t origin, vec3_t angles, float speed )  {    // unlink to make sure it can't possibly interfere with G_KillBox    trap_UnlinkEntity( player );    VectorCopy( origin, player->client->ps.origin ); -  player->client->ps.origin[ 2 ] += 1; +  player->client->ps.groundEntityNum = ENTITYNUM_NONE; +  player->client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED; -  // spit the player out    AngleVectors( angles, player->client->ps.velocity, NULL, NULL ); -  VectorScale( player->client->ps.velocity, 400, player->client->ps.velocity ); -  player->client->ps.pm_time = 160;   // hold time -  player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; +  VectorScale( player->client->ps.velocity, speed, player->client->ps.velocity ); +  player->client->ps.pm_time = 0.4f * fabs( speed ); // duration of loss of control +  if( player->client->ps.pm_time > 160 ) +    player->client->ps.pm_time = 160; +  if( player->client->ps.pm_time != 0 ) +    player->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;    // toggle the teleport bit so the client knows to not lerp    player->client->ps.eFlags ^= EF_TELEPORT_BIT;    G_UnlaggedClear( player ); +  // cut all relevant zap beams +  G_ClearPlayerZapEffects( player ); +    // set angles    G_SetClientViewAngle( player, angles ); -  // kill anything at the destination -  if( player->client->sess.sessionTeam != TEAM_SPECTATOR ) -    G_KillBox( player ); -    // save results of pmove    BG_PlayerStateToEntityState( &player->client->ps, &player->s, qtrue );    // use the precise origin for linking    VectorCopy( player->client->ps.origin, player->r.currentOrigin ); +  VectorCopy( player->client->ps.viewangles, player->r.currentAngles ); + +  if( player->client->sess.spectatorState == SPECTATOR_NOT ) +  { +    // kill anything at the destination +    G_KillBox( player ); -  if( player->client->sess.sessionTeam != TEAM_SPECTATOR )      trap_LinkEntity (player); +  }  } @@ -129,8 +138,7 @@ void SP_misc_model( gentity_t *ent )    VectorSet (ent->maxs, 16, 16, 16);    trap_LinkEntity (ent); -  G_SetOrigin( ent, ent->s.origin ); -  VectorCopy( ent->s.angles, ent->s.apos.trBase ); +  G_SetOrigin( ent, ent->r.currentOrigin );  #else    G_FreeEntity( ent );  #endif @@ -171,19 +179,23 @@ void locateCamera( gentity_t *ent )    // clientNum holds the rotate offset    ent->s.clientNum = owner->s.clientNum; -  VectorCopy( owner->s.origin, ent->s.origin2 ); +  VectorCopy( owner->r.currentOrigin, ent->s.origin2 );    // see if the portal_camera has a target    target = G_PickTarget( owner->target );    if( target )    { -    VectorSubtract( target->s.origin, owner->s.origin, dir ); +    VectorSubtract( target->r.currentOrigin, owner->r.currentOrigin, dir );      VectorNormalize( dir );    }    else -    G_SetMovedir( owner->s.angles, dir ); +    G_SetMovedir( owner->r.currentAngles, dir );    ent->s.eventParm = DirToByte( dir ); + +  ByteToDir( ent->s.eventParm, dir ); +  vectoangles( dir, ent->r.currentAngles ); +  ent->r.currentAngles[ 2 ] = ent->s.clientNum * 360.0f / 256;  }  /*QUAKED misc_portal_surface (0 0 1) (-8 -8 -8) (8 8 8) @@ -201,7 +213,7 @@ void SP_misc_portal_surface( gentity_t *ent )    if( !ent->target )    { -    VectorCopy( ent->s.origin, ent->s.origin2 ); +    VectorCopy( ent->r.currentOrigin, ent->s.origin2 );    }    else    { @@ -273,7 +285,7 @@ void SP_misc_particle_system( gentity_t *self )  {    char  *s; -  G_SetOrigin( self, self->s.origin ); +  G_SetOrigin( self, self->r.currentOrigin );    G_SpawnString( "psName", "", &s );    G_SpawnFloat( "wait", "0", &self->wait ); @@ -420,7 +432,7 @@ void SP_misc_light_flare( gentity_t *self )    //try to find a spot near to the flare which is empty. This    //is used to facilitate visibility testing -  findEmptySpot( self->s.origin, 8.0f, self->s.angles2 ); +  findEmptySpot( self->r.currentOrigin, 8.0f, self->s.angles2 );    self->use = SP_use_light_flare; diff --git a/src/game/g_missile.c b/src/game/g_missile.c index 26cb97e..14494cd 100644 --- a/src/game/g_missile.c +++ b/src/game/g_missile.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -82,7 +83,6 @@ void G_ExplodeMissile( gentity_t *ent )    ent->s.eType = ET_GENERAL; -  //TA: tired... can't be fucked... hack    if( ent->s.weapon != WP_LOCKBLOB_LAUNCHER &&        ent->s.weapon != WP_FLAMER )      G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) ); @@ -92,7 +92,7 @@ void G_ExplodeMissile( gentity_t *ent )    // splash damage    if( ent->splashDamage )      G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, -                    ent->splashRadius, ent, ent->dflags, ent->splashMethodOfDeath ); +                    ent->splashRadius, ent, ent->splashMethodOfDeath );    trap_LinkEntity( ent );  } @@ -140,7 +140,7 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace )    }    else if( !strcmp( ent->classname, "lockblob" ) )    { -    if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +    if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )      {        other->client->ps.stats[ STAT_STATE ] |= SS_BLOBLOCKED;        other->client->lastLockTime = level.time; @@ -150,7 +150,7 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace )    }    else if( !strcmp( ent->classname, "slowblob" ) )    { -    if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +    if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )      {        other->client->ps.stats[ STAT_STATE ] |= SS_SLOWLOCKED;        other->client->lastSlowTime = level.time; @@ -175,11 +175,11 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace )        //prevent collision with the client when returning        ent->r.ownerNum = other->s.number; -      ent->think = AHive_ReturnToHive; +      ent->think = G_ExplodeMissile;        ent->nextthink = level.time + FRAMETIME;        //only damage humans -      if( other->client && other->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +      if( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS )          returnAfterDamage = qtrue;        else          return; @@ -198,8 +198,8 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace )        if( VectorLength( velocity ) == 0 )          velocity[ 2 ] = 1;  // stepped on a grenade -      G_Damage( other, ent, attacker, velocity, ent->s.origin, ent->damage, -        ent->dflags, ent->methodOfDeath ); +      G_Damage( other, ent, attacker, velocity, ent->r.currentOrigin, ent->damage, +        DAMAGE_NO_LOCDAMAGE, ent->methodOfDeath );      }    } @@ -209,7 +209,8 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace )    // is it cheaper in bandwidth to just remove this ent and create a new    // one, rather than changing the missile into the explosion? -  if( other->takedamage && other->client ) +  if( other->takedamage &&  +      ( other->s.eType == ET_PLAYER || other->s.eType == ET_BUILDABLE ) )    {      G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) );      ent->s.otherEntityNum = other->s.number; @@ -231,7 +232,7 @@ void G_MissileImpact( gentity_t *ent, trace_t *trace )    // splash damage (doesn't apply to person directly hit)    if( ent->splashDamage )      G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius, -                    other, ent->dflags, ent->splashMethodOfDeath ); +                    other, ent->splashMethodOfDeath );    trap_LinkEntity( ent );  } @@ -247,7 +248,8 @@ void G_RunMissile( gentity_t *ent )  {    vec3_t    origin;    trace_t   tr; -  int     passent; +  int       passent; +  qboolean  impact = qfalse;    // get current position    BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); @@ -255,40 +257,73 @@ void G_RunMissile( gentity_t *ent )    // ignore interactions with the missile owner    passent = ent->r.ownerNum; -  // trace a line from the previous position to the current position -  trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, ent->clipmask ); +  // general trace to see if we hit anything at all +  trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, +              origin, passent, ent->clipmask );    if( tr.startsolid || tr.allsolid )    { -    // make sure the tr.entityNum is set to the entity we're stuck in -    trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, passent, ent->clipmask ); -    tr.fraction = 0; +    tr.fraction = 0.0f; +    VectorCopy( ent->r.currentOrigin, tr.endpos );    } -  else -    VectorCopy( tr.endpos, ent->r.currentOrigin ); -  ent->r.contents = CONTENTS_SOLID; //trick trap_LinkEntity into... -  trap_LinkEntity( ent ); -  ent->r.contents = 0; //...encoding bbox information +  if( tr.fraction < 1.0f ) +  { +    if( !ent->pointAgainstWorld || tr.contents & CONTENTS_BODY ) +    { +      // We hit an entity or we don't care +      impact = qtrue; +    } +    else +    { +      trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, origin,  +                  passent, ent->clipmask ); + +      if( tr.fraction < 1.0f ) +      { +        // Hit the world with point trace +        impact = qtrue; +      } +      else +      { +        if( tr.contents & CONTENTS_BODY ) +        { +          // Hit an entity +          impact = qtrue; +        } +        else +        { +          trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs,  +                      origin, passent, CONTENTS_BODY ); + +          if( tr.fraction < 1.0f ) +            impact = qtrue; +        } +      } +    } +  } -  if( tr.fraction != 1 ) +  VectorCopy( tr.endpos, ent->r.currentOrigin ); + +  if( impact )    { -    // never explode or bounce on sky      if( tr.surfaceFlags & SURF_NOIMPACT )      { -      // If grapple, reset owner -      if( ent->parent && ent->parent->client && ent->parent->client->hook == ent ) -        ent->parent->client->hook = NULL; - +      // Never explode or bounce on sky        G_FreeEntity( ent );        return;      }      G_MissileImpact( ent, &tr ); +      if( ent->s.eType != ET_MISSILE )        return;   // exploded    } +  ent->r.contents = CONTENTS_SOLID; //trick trap_LinkEntity into... +  trap_LinkEntity( ent ); +  ent->r.contents = 0; //...encoding bbox information +    // check think function after bouncing    G_RunThink( ent );  } @@ -311,23 +346,23 @@ gentity_t *fire_flamer( gentity_t *self, vec3_t start, vec3_t dir )    bolt = G_Spawn();    bolt->classname = "flame"; +  bolt->pointAgainstWorld = qfalse;    bolt->nextthink = level.time + FLAMER_LIFETIME;    bolt->think = G_ExplodeMissile;    bolt->s.eType = ET_MISSILE; -  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;    bolt->s.weapon = WP_FLAMER;    bolt->s.generic1 = self->s.generic1; //weaponMode    bolt->r.ownerNum = self->s.number;    bolt->parent = self;    bolt->damage = FLAMER_DMG; -  bolt->splashDamage = FLAMER_DMG; +  bolt->splashDamage = FLAMER_SPLASHDAMAGE;    bolt->splashRadius = FLAMER_RADIUS;    bolt->methodOfDeath = MOD_FLAMER;    bolt->splashMethodOfDeath = MOD_FLAMER_SPLASH;    bolt->clipmask = MASK_SHOT;    bolt->target_ent = NULL; -  bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -15.0f; -  bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = 15.0f; +  bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -FLAMER_SIZE; +  bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = FLAMER_SIZE;    bolt->s.pos.trType = TR_LINEAR;    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;   // move a bit on the very first frame @@ -357,10 +392,10 @@ gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir )    bolt = G_Spawn();    bolt->classname = "blaster"; +  bolt->pointAgainstWorld = qtrue;    bolt->nextthink = level.time + 10000;    bolt->think = G_ExplodeMissile;    bolt->s.eType = ET_MISSILE; -  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;    bolt->s.weapon = WP_BLASTER;    bolt->s.generic1 = self->s.generic1; //weaponMode    bolt->r.ownerNum = self->s.number; @@ -372,6 +407,8 @@ gentity_t *fire_blaster( gentity_t *self, vec3_t start, vec3_t dir )    bolt->splashMethodOfDeath = MOD_BLASTER;    bolt->clipmask = MASK_SHOT;    bolt->target_ent = NULL; +  bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -BLASTER_SIZE; +  bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = BLASTER_SIZE;    bolt->s.pos.trType = TR_LINEAR;    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;   // move a bit on the very first frame @@ -400,10 +437,10 @@ gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir )    bolt = G_Spawn();    bolt->classname = "pulse"; +  bolt->pointAgainstWorld = qtrue;    bolt->nextthink = level.time + 10000;    bolt->think = G_ExplodeMissile;    bolt->s.eType = ET_MISSILE; -  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;    bolt->s.weapon = WP_PULSE_RIFLE;    bolt->s.generic1 = self->s.generic1; //weaponMode    bolt->r.ownerNum = self->s.number; @@ -415,6 +452,8 @@ gentity_t *fire_pulseRifle( gentity_t *self, vec3_t start, vec3_t dir )    bolt->splashMethodOfDeath = MOD_PRIFLE;    bolt->clipmask = MASK_SHOT;    bolt->target_ent = NULL; +  bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = -PRIFLE_SIZE; +  bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = PRIFLE_SIZE;    bolt->s.pos.trType = TR_LINEAR;    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;   // move a bit on the very first frame @@ -435,42 +474,53 @@ fire_luciferCannon  =================  */ -gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, int damage, int radius ) +gentity_t *fire_luciferCannon( gentity_t *self, vec3_t start, vec3_t dir, +  int damage, int radius, int speed )  {    gentity_t *bolt; -  int localDamage = (int)( ceil( ( (float)damage / -                                   (float)LCANNON_TOTAL_CHARGE ) * (float)LCANNON_DAMAGE ) ); +  float charge;    VectorNormalize( dir );    bolt = G_Spawn( );    bolt->classname = "lcannon"; +  bolt->pointAgainstWorld = qtrue; -  if( damage == LCANNON_TOTAL_CHARGE ) +  if( damage == LCANNON_DAMAGE )      bolt->nextthink = level.time;    else      bolt->nextthink = level.time + 10000;    bolt->think = G_ExplodeMissile;    bolt->s.eType = ET_MISSILE; -  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;    bolt->s.weapon = WP_LUCIFER_CANNON;    bolt->s.generic1 = self->s.generic1; //weaponMode    bolt->r.ownerNum = self->s.number;    bolt->parent = self; -  bolt->damage = localDamage; -  bolt->dflags = DAMAGE_KNOCKBACK; -  bolt->splashDamage = localDamage / 2; +  bolt->damage = damage; +  bolt->splashDamage = damage / 2;    bolt->splashRadius = radius;    bolt->methodOfDeath = MOD_LCANNON;    bolt->splashMethodOfDeath = MOD_LCANNON_SPLASH;    bolt->clipmask = MASK_SHOT;    bolt->target_ent = NULL; +   +  // Give the missile a small bounding box +  bolt->r.mins[ 0 ] = bolt->r.mins[ 1 ] = bolt->r.mins[ 2 ] = +    -LCANNON_SIZE; +  bolt->r.maxs[ 0 ] = bolt->r.maxs[ 1 ] = bolt->r.maxs[ 2 ] = +    -bolt->r.mins[ 0 ]; +   +  // Pass the missile charge through +  charge = (float)( damage - LCANNON_SECONDARY_DAMAGE ) / LCANNON_DAMAGE; +  bolt->s.torsoAnim = charge * 255; +  if( bolt->s.torsoAnim < 0 ) +    bolt->s.torsoAnim = 0;    bolt->s.pos.trType = TR_LINEAR;    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;   // move a bit on the very first frame    VectorCopy( start, bolt->s.pos.trBase ); -  VectorScale( dir, LCANNON_SPEED, bolt->s.pos.trDelta ); +  VectorScale( dir, speed, bolt->s.pos.trDelta );    SnapVector( bolt->s.pos.trDelta );      // save net bandwidth    VectorCopy( start, bolt->r.currentOrigin ); @@ -492,17 +542,16 @@ gentity_t *launch_grenade( gentity_t *self, vec3_t start, vec3_t dir )    bolt = G_Spawn( );    bolt->classname = "grenade"; +  bolt->pointAgainstWorld = qfalse;    bolt->nextthink = level.time + 5000;    bolt->think = G_ExplodeMissile;    bolt->s.eType = ET_MISSILE; -  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;    bolt->s.weapon = WP_GRENADE;    bolt->s.eFlags = EF_BOUNCE_HALF;    bolt->s.generic1 = WPM_PRIMARY; //weaponMode    bolt->r.ownerNum = self->s.number;    bolt->parent = self;    bolt->damage = GRENADE_DAMAGE; -  bolt->dflags = DAMAGE_KNOCKBACK;    bolt->splashDamage = GRENADE_DAMAGE;    bolt->splashRadius = GRENADE_RANGE;    bolt->methodOfDeath = MOD_GRENADE; @@ -525,85 +574,79 @@ gentity_t *launch_grenade( gentity_t *self, vec3_t start, vec3_t dir )  }  //============================================================================= + +  /*  ================ -AHive_ReturnToHive +AHive_SearchAndDestroy -Adjust the trajectory to point towards the hive +Adjust the trajectory to point towards the target  ================  */ -void AHive_ReturnToHive( gentity_t *self ) +void AHive_SearchAndDestroy( gentity_t *self )  { -  vec3_t  dir; -  trace_t tr; - -  if( !self->parent ) -  { -    G_Printf( S_COLOR_YELLOW "WARNING: AHive_ReturnToHive called with no self->parent\n" ); -    return; -  } +  vec3_t    dir; +  trace_t   tr; +  gentity_t *ent; +  int       i; +  float     d, nearest; -  trap_UnlinkEntity( self->parent ); -  trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, -              self->parent->r.currentOrigin, self->r.ownerNum, self->clipmask ); -  trap_LinkEntity( self->parent ); +  if( self->parent && !self->parent->inuse ) +    self->parent = NULL; -  if( tr.fraction < 1.0f ) +  if( level.time > self->timestamp )    { -    //if can't see hive then disperse      VectorCopy( self->r.currentOrigin, self->s.pos.trBase );      self->s.pos.trType = TR_STATIONARY;      self->s.pos.trTime = level.time;      self->think = G_ExplodeMissile; -    self->nextthink = level.time + 2000; -    self->parent->active = qfalse; //allow the parent to start again +    self->nextthink = level.time + 50; +    if( self->parent ) +      self->parent->active = qfalse; //allow the parent to start again +    return;    } + +  ent = self->target_ent; +  if( ent && ent->health > 0 && ent->client && ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) +    nearest = DistanceSquared( self->r.currentOrigin, ent->r.currentOrigin );    else    { -    VectorSubtract( self->parent->r.currentOrigin, self->r.currentOrigin, dir ); -    VectorNormalize( dir ); - -    //change direction towards the hive -    VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta ); -    SnapVector( self->s.pos.trDelta );      // save net bandwidth -    VectorCopy( self->r.currentOrigin, self->s.pos.trBase ); -    self->s.pos.trTime = level.time; - -    self->think = G_ExplodeMissile; -    self->nextthink = level.time + 15000; +    self->target_ent = NULL; +    nearest = 0; // silence warning    } -} - -/* -================ -AHive_SearchAndDestroy -Adjust the trajectory to point towards the target -================ -*/ -void AHive_SearchAndDestroy( gentity_t *self ) -{ -  vec3_t dir; -  trace_t tr; - -  trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, -              self->target_ent->r.currentOrigin, self->r.ownerNum, self->clipmask ); - -  //if there is no LOS or the parent hive is too far away or the target is dead or notargeting, return -  if( tr.entityNum == ENTITYNUM_WORLD || -      Distance( self->r.currentOrigin, self->parent->r.currentOrigin ) > ( HIVE_RANGE * 5 ) || -      self->target_ent->health <= 0 || self->target_ent->flags & FL_NOTARGET ) +  //find the closest human +  for( i = 0; i < MAX_CLIENTS; i++ )    { -    self->r.ownerNum = ENTITYNUM_WORLD; +    ent = &g_entities[ i ]; + +    if( ent->flags & FL_NOTARGET ) +      continue; -    self->think = AHive_ReturnToHive; -    self->nextthink = level.time + FRAMETIME; +    if( ent->client && +        ent->health > 0 &&    +        ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && +        ( d = DistanceSquared( ent->r.currentOrigin, self->r.currentOrigin ), +          ( self->target_ent == NULL || d < nearest ) ) ) +    { +      trap_Trace( &tr, self->r.currentOrigin, self->r.mins, self->r.maxs, +                  ent->r.currentOrigin, self->r.ownerNum, self->clipmask ); +      if( tr.entityNum != ENTITYNUM_WORLD ) +      { +        nearest = d; +        self->target_ent = ent; +      } +    }    } + +  if( self->target_ent == NULL ) +    VectorClear( dir );    else    {      VectorSubtract( self->target_ent->r.currentOrigin, self->r.currentOrigin, dir );      VectorNormalize( dir ); +  }      //change direction towards the player      VectorScale( dir, HIVE_SPEED, self->s.pos.trDelta ); @@ -612,7 +655,6 @@ void AHive_SearchAndDestroy( gentity_t *self )      self->s.pos.trTime = level.time;      self->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD; -  }  }  /* @@ -628,11 +670,11 @@ gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir )    bolt = G_Spawn( );    bolt->classname = "hive"; +  bolt->pointAgainstWorld = qfalse;    bolt->nextthink = level.time + HIVE_DIR_CHANGE_PERIOD;    bolt->think = AHive_SearchAndDestroy;    bolt->s.eType = ET_MISSILE; -  bolt->s.eFlags |= EF_BOUNCE|EF_NO_BOUNCE_SOUND; -  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN; +  bolt->s.eFlags |= EF_BOUNCE | EF_NO_BOUNCE_SOUND;    bolt->s.weapon = WP_HIVE;    bolt->s.generic1 = WPM_PRIMARY; //weaponMode    bolt->r.ownerNum = self->s.number; @@ -643,6 +685,7 @@ gentity_t *fire_hive( gentity_t *self, vec3_t start, vec3_t dir )    bolt->methodOfDeath = MOD_SWARM;    bolt->clipmask = MASK_SHOT;    bolt->target_ent = self->target_ent; +  bolt->timestamp = level.time + HIVE_LIFETIME;    bolt->s.pos.trType = TR_LINEAR;    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;   // move a bit on the very first frame @@ -669,10 +712,10 @@ gentity_t *fire_lockblob( gentity_t *self, vec3_t start, vec3_t dir )    bolt = G_Spawn( );    bolt->classname = "lockblob"; +  bolt->pointAgainstWorld = qtrue;    bolt->nextthink = level.time + 15000;    bolt->think = G_ExplodeMissile;    bolt->s.eType = ET_MISSILE; -  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;    bolt->s.weapon = WP_LOCKBLOB_LAUNCHER;    bolt->s.generic1 = WPM_PRIMARY; //weaponMode    bolt->r.ownerNum = self->s.number; @@ -707,10 +750,10 @@ gentity_t *fire_slowBlob( gentity_t *self, vec3_t start, vec3_t dir )    bolt = G_Spawn( );    bolt->classname = "slowblob"; +  bolt->pointAgainstWorld = qtrue;    bolt->nextthink = level.time + 15000;    bolt->think = G_ExplodeMissile;    bolt->s.eType = ET_MISSILE; -  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;    bolt->s.weapon = WP_ABUILD2;    bolt->s.generic1 = self->s.generic1; //weaponMode    bolt->r.ownerNum = self->s.number; @@ -746,10 +789,10 @@ gentity_t *fire_paraLockBlob( gentity_t *self, vec3_t start, vec3_t dir )    bolt = G_Spawn( );    bolt->classname = "lockblob"; +  bolt->pointAgainstWorld = qtrue;    bolt->nextthink = level.time + 15000;    bolt->think = G_ExplodeMissile;    bolt->s.eType = ET_MISSILE; -  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;    bolt->s.weapon = WP_LOCKBLOB_LAUNCHER;    bolt->s.generic1 = self->s.generic1; //weaponMode    bolt->r.ownerNum = self->s.number; @@ -783,18 +826,17 @@ gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir )    bolt = G_Spawn( );    bolt->classname = "bounceball"; +  bolt->pointAgainstWorld = qtrue;    bolt->nextthink = level.time + 3000;    bolt->think = G_ExplodeMissile;    bolt->s.eType = ET_MISSILE; -  bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;    bolt->s.weapon = WP_ALEVEL3_UPG;    bolt->s.generic1 = self->s.generic1; //weaponMode    bolt->r.ownerNum = self->s.number;    bolt->parent = self;    bolt->damage = LEVEL3_BOUNCEBALL_DMG; -  bolt->dflags = DAMAGE_KNOCKBACK; -  bolt->splashDamage = 0; -  bolt->splashRadius = 0; +  bolt->splashDamage = LEVEL3_BOUNCEBALL_DMG; +  bolt->splashRadius = LEVEL3_BOUNCEBALL_RADIUS;    bolt->methodOfDeath = MOD_LEVEL3_BOUNCEBALL;    bolt->splashMethodOfDeath = MOD_LEVEL3_BOUNCEBALL;    bolt->clipmask = MASK_SHOT; @@ -806,8 +848,6 @@ gentity_t *fire_bounceBall( gentity_t *self, vec3_t start, vec3_t dir )    VectorScale( dir, LEVEL3_BOUNCEBALL_SPEED, bolt->s.pos.trDelta );    SnapVector( bolt->s.pos.trDelta );      // save net bandwidth    VectorCopy( start, bolt->r.currentOrigin ); -  /*bolt->s.eFlags |= EF_BOUNCE;*/    return bolt;  } - diff --git a/src/game/g_mover.c b/src/game/g_mover.c index 20b5c08..77c1e0c 100644 --- a/src/game/g_mover.c +++ b/src/game/g_mover.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -33,8 +34,6 @@ PUSHMOVE  ===============================================================================  */ -void MatchTeam( gentity_t *teamLeader, int moverState, int time ); -  typedef struct  {    gentity_t *ent; @@ -55,17 +54,11 @@ G_TestEntityPosition  gentity_t *G_TestEntityPosition( gentity_t *ent )  {    trace_t tr; -  int   mask; - -  if( ent->clipmask ) -    mask = ent->clipmask; -  else -    mask = MASK_SOLID;    if( ent->client ) -    trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, mask ); +    trap_Trace( &tr, ent->client->ps.origin, ent->r.mins, ent->r.maxs, ent->client->ps.origin, ent->s.number, ent->clipmask );    else -    trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, mask ); +    trap_Trace( &tr, ent->s.pos.trBase, ent->r.mins, ent->r.maxs, ent->s.pos.trBase, ent->s.number, ent->clipmask );    if( tr.startsolid )      return &g_entities[ tr.entityNum ]; @@ -183,7 +176,7 @@ qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, v    // may have pushed them off an edge    if( check->s.groundEntityNum != pusher->s.number ) -    check->s.groundEntityNum = -1; +    check->s.groundEntityNum = ENTITYNUM_NONE;    block = G_TestEntityPosition( check ); @@ -212,7 +205,7 @@ qboolean G_TryPushingEntity( gentity_t *check, gentity_t *pusher, vec3_t move, v    if( !block )    { -    check->s.groundEntityNum = -1; +    check->s.groundEntityNum = ENTITYNUM_NONE;      pushed_p--;      return qtrue;    } @@ -285,7 +278,7 @@ qboolean G_MoverPush( gentity_t *pusher, vec3_t move, vec3_t amove, gentity_t **    listedEntities = trap_EntitiesInBox( totalMins, totalMaxs, entityList, MAX_GENTITIES ); -  // move the pusher to it's final position +  // move the pusher to its final position    VectorAdd( pusher->r.currentOrigin, move, pusher->r.currentOrigin );    VectorAdd( pusher->r.currentAngles, amove, pusher->r.currentAngles );    trap_LinkEntity( pusher ); @@ -379,6 +372,12 @@ void G_MoverTeam( gentity_t *ent )    pushed_p = pushed;    for( part = ent; part; part = part->teamchain )    { +    if( part->s.pos.trType == TR_STATIONARY && +        part->s.apos.trType == TR_STATIONARY ) +    { +      continue; +    } +      // get current position      BG_EvaluateTrajectory( &part->s.pos, level.time, origin );      BG_EvaluateTrajectory( &part->s.apos, level.time, angles ); @@ -393,6 +392,12 @@ void G_MoverTeam( gentity_t *ent )      // go back to the previous position      for( part = ent; part; part = part->teamchain )      { +      if( part->s.pos.trType == TR_STATIONARY && +          part->s.apos.trType == TR_STATIONARY ) +      { +        continue; +      } +        part->s.pos.trTime += level.time - level.previousTime;        part->s.apos.trTime += level.time - level.previousTime;        BG_EvaluateTrajectory( &part->s.pos, level.time, part->r.currentOrigin ); @@ -442,10 +447,7 @@ void G_RunMover( gentity_t *ent )    if( ent->flags & FL_TEAMSLAVE )      return; -  // if stationary at one of the positions, don't move anything -  if( ( ent->s.pos.trType != TR_STATIONARY || ent->s.apos.trType != TR_STATIONARY ) && -      ent->moverState < MODEL_POS1 ) //yuck yuck hack -    G_MoverTeam( ent ); +  G_MoverTeam( ent );    // check think function    G_RunThink( ent ); @@ -554,7 +556,6 @@ void SetMoverState( gentity_t *ent, moverState_t moverState, int time )  MatchTeam  All entities in a mover team will move from pos1 to pos2 -in the same amount of time  ================  */  void MatchTeam( gentity_t *teamLeader, int moverState, int time ) @@ -566,40 +567,65 @@ void MatchTeam( gentity_t *teamLeader, int moverState, int time )  } +/* +================ +MasterOf +================ +*/ +gentity_t *MasterOf( gentity_t *ent ) +{ +  if( ent->teammaster ) +    return ent->teammaster; +  else +    return ent; +} +  /*  ================ -ReturnToPos1 +GetMoverTeamState + +Returns a MOVER_* value representing the phase (either one + of pos1, 1to2, pos2, or 2to1) of a mover team as a whole.  ================  */ -void ReturnToPos1( gentity_t *ent ) +moverState_t GetMoverTeamState( gentity_t *ent )  { -  MatchTeam( ent, MOVER_2TO1, level.time ); +  qboolean pos1 = qfalse; -  // looping sound -  ent->s.loopSound = ent->soundLoop; +  for( ent = MasterOf( ent ); ent; ent = ent->teamchain ) +  { +    if( ent->moverState == MOVER_POS1 || ent->moverState == ROTATOR_POS1 ) +      pos1 = qtrue; +    else if( ent->moverState == MOVER_1TO2 || ent->moverState == ROTATOR_1TO2 ) +      return MOVER_1TO2; +    else if( ent->moverState == MOVER_2TO1 || ent->moverState == ROTATOR_2TO1 ) +      return MOVER_2TO1; +  } -  // starting sound -  if( ent->sound2to1 ) -    G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); +  if( pos1 ) +    return MOVER_POS1; +  else +    return MOVER_POS2;  }  /*  ================ -ReturnToApos1 +ReturnToPos1orApos1 + +Used only by a master movers.  ================  */ -void ReturnToApos1( gentity_t *ent ) -{ -  MatchTeam( ent, ROTATOR_2TO1, level.time ); -  // looping sound -  ent->s.loopSound = ent->soundLoop; +void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator ); -  // starting sound -  if( ent->sound2to1 ) -    G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); +void ReturnToPos1orApos1( gentity_t *ent ) +{ +  if( GetMoverTeamState( ent ) != MOVER_POS2 ) +    return; // not every mover in the team has reached its endpoint yet + +  Use_BinaryMover( ent, ent, ent->activator );  } @@ -690,10 +716,10 @@ void Think_OpenModelDoor( gentity_t *ent )    //set brush non-solid    trap_UnlinkEntity( ent->clipBrush ); -  // looping sound -  ent->s.loopSound = ent->soundLoop; +  // stop the looping sound +  ent->s.loopSound = 0; -  // starting sound +  // play sound    if( ent->soundPos2 )      G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 ); @@ -718,8 +744,10 @@ Reached_BinaryMover  */  void Reached_BinaryMover( gentity_t *ent )  { +  gentity_t *master = MasterOf( ent ); +    // stop the looping sound -  ent->s.loopSound = ent->soundLoop; +  ent->s.loopSound = 0;    if( ent->moverState == MOVER_1TO2 )    { @@ -731,8 +759,8 @@ void Reached_BinaryMover( gentity_t *ent )        G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 );      // return to pos1 after a delay -    ent->think = ReturnToPos1; -    ent->nextthink = level.time + ent->wait; +    master->think = ReturnToPos1orApos1; +    master->nextthink = MAX( master->nextthink, level.time + ent->wait );      // fire targets      if( !ent->activator ) @@ -763,8 +791,8 @@ void Reached_BinaryMover( gentity_t *ent )        G_AddEvent( ent, EV_GENERAL_SOUND, ent->soundPos2 );      // return to apos1 after a delay -    ent->think = ReturnToApos1; -    ent->nextthink = level.time + ent->wait; +    master->think = ReturnToPos1orApos1; +    master->nextthink = MAX( master->nextthink, level.time + ent->wait );      // fire targets      if( !ent->activator ) @@ -799,6 +827,8 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )  {    int   total;    int   partial; +  gentity_t *master; +  moverState_t teamState;    // if this is a non-client-usable door return    if( ent->targetname && other && other->client ) @@ -813,11 +843,17 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )    ent->activator = activator; +  master = MasterOf( ent ); +  teamState = GetMoverTeamState( ent ); + +  for( ent = master; ent; ent = ent->teamchain ) +  { +  //ind    if( ent->moverState == MOVER_POS1 )    {      // start moving 50 msec later, becase if this was player      // triggered, level.time hasn't been advanced yet -    MatchTeam( ent, MOVER_1TO2, level.time + 50 ); +    SetMoverState( ent, MOVER_1TO2, level.time + 50 );      // starting sound      if( ent->sound1to2 ) @@ -830,10 +866,30 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )      if( ent->teammaster == ent || !ent->teammaster )        trap_AdjustAreaPortalState( ent, qtrue );    } -  else if( ent->moverState == MOVER_POS2 ) +  else if( ent->moverState == MOVER_POS2 && +           !( teamState == MOVER_1TO2 || other == master ) )    {      // if all the way up, just delay before coming down -    ent->nextthink = level.time + ent->wait; +    master->think = ReturnToPos1orApos1; +    master->nextthink = MAX( master->nextthink, level.time + ent->wait ); +  } +  else if( ent->moverState == MOVER_POS2 && +           ( teamState == MOVER_1TO2 || other == master ) ) +  { +    // start moving 50 msec later, becase if this was player +    // triggered, level.time hasn't been advanced yet +    SetMoverState( ent, MOVER_2TO1, level.time + 50 ); + +    // starting sound +    if( ent->sound2to1 ) +      G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + +    // looping sound +    ent->s.loopSound = ent->soundLoop; + +    // open areaportal +    if( ent->teammaster == ent || !ent->teammaster ) +      trap_AdjustAreaPortalState( ent, qtrue );    }    else if( ent->moverState == MOVER_2TO1 )    { @@ -844,7 +900,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )      if( partial > total )        partial = total; -    MatchTeam( ent, MOVER_1TO2, level.time - ( total - partial ) ); +    SetMoverState( ent, MOVER_1TO2, level.time - ( total - partial ) );      if( ent->sound1to2 )        G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); @@ -858,7 +914,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )      if( partial > total )        partial = total; -    MatchTeam( ent, MOVER_2TO1, level.time - ( total - partial ) ); +    SetMoverState( ent, MOVER_2TO1, level.time - ( total - partial ) );      if( ent->sound2to1 )        G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); @@ -867,7 +923,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )    {      // start moving 50 msec later, becase if this was player      // triggered, level.time hasn't been advanced yet -    MatchTeam( ent, ROTATOR_1TO2, level.time + 50 ); +    SetMoverState( ent, ROTATOR_1TO2, level.time + 50 );      // starting sound      if( ent->sound1to2 ) @@ -880,10 +936,30 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )      if( ent->teammaster == ent || !ent->teammaster )        trap_AdjustAreaPortalState( ent, qtrue );    } -  else if( ent->moverState == ROTATOR_POS2 ) +  else if( ent->moverState == ROTATOR_POS2 && +           !( teamState == MOVER_1TO2 || other == master ) )    {      // if all the way up, just delay before coming down -    ent->nextthink = level.time + ent->wait; +    master->think = ReturnToPos1orApos1; +    master->nextthink = MAX( master->nextthink, level.time + ent->wait ); +  } +  else if( ent->moverState == ROTATOR_POS2 && +           ( teamState == MOVER_1TO2 || other == master ) ) +  { +    // start moving 50 msec later, becase if this was player +    // triggered, level.time hasn't been advanced yet +    SetMoverState( ent, ROTATOR_2TO1, level.time + 50 ); + +    // starting sound +    if( ent->sound2to1 ) +      G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); + +    // looping sound +    ent->s.loopSound = ent->soundLoop; + +    // open areaportal +    if( ent->teammaster == ent || !ent->teammaster ) +      trap_AdjustAreaPortalState( ent, qtrue );    }    else if( ent->moverState == ROTATOR_2TO1 )    { @@ -894,7 +970,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )      if( partial > total )        partial = total; -    MatchTeam( ent, ROTATOR_1TO2, level.time - ( total - partial ) ); +    SetMoverState( ent, ROTATOR_1TO2, level.time - ( total - partial ) );      if( ent->sound1to2 )        G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound1to2 ); @@ -908,7 +984,7 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )      if( partial > total )        partial = total; -    MatchTeam( ent, ROTATOR_2TO1, level.time - ( total - partial ) ); +    SetMoverState( ent, ROTATOR_2TO1, level.time - ( total - partial ) );      if( ent->sound2to1 )        G_AddEvent( ent, EV_GENERAL_SOUND, ent->sound2to1 ); @@ -939,6 +1015,8 @@ void Use_BinaryMover( gentity_t *ent, gentity_t *other, gentity_t *activator )      // if all the way up, just delay before coming down      ent->nextthink = level.time + ent->wait;    } +  //outd +  }  } @@ -959,15 +1037,16 @@ void InitMover( gentity_t *ent )    vec3_t    color;    qboolean  lightSet, colorSet;    char      *sound; +  char      *team;    // if the "model2" key is set, use a seperate model    // for drawing, but clip against the brushes    if( ent->model2 )      ent->s.modelindex2 = G_ModelIndex( ent->model2 ); -  // if the "loopsound" key is set, use a constant looping sound when moving -  if( G_SpawnString( "noise", "100", &sound ) ) -    ent->s.loopSound = G_SoundIndex( sound ); +  // if the "noise" key is set, use a constant looping sound when moving +  if( G_SpawnString( "noise", "", &sound ) ) +    ent->soundLoop = G_SoundIndex( sound );    // if the "color" or "light" keys are set, setup constantLight    lightSet = G_SpawnFloat( "light", "100", &light ); @@ -1000,8 +1079,10 @@ void InitMover( gentity_t *ent )    ent->use = Use_BinaryMover;    ent->reached = Reached_BinaryMover; +  if( G_SpawnString( "team", "", &team ) ) +    ent->team = G_CopyString( team ); +    ent->moverState = MOVER_POS1; -  ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;    ent->s.eType = ET_MOVER;    VectorCopy( ent->pos1, ent->r.currentOrigin );    trap_LinkEntity( ent ); @@ -1039,15 +1120,16 @@ void InitRotator( gentity_t *ent )    vec3_t    color;    qboolean  lightSet, colorSet;    char      *sound; +  char      *team;    // if the "model2" key is set, use a seperate model    // for drawing, but clip against the brushes    if( ent->model2 )      ent->s.modelindex2 = G_ModelIndex( ent->model2 ); -  // if the "loopsound" key is set, use a constant looping sound when moving -  if( G_SpawnString( "noise", "100", &sound ) ) -    ent->s.loopSound = G_SoundIndex( sound ); +  // if the "noise" key is set, use a constant looping sound when moving +  if( G_SpawnString( "noise", "", &sound ) ) +    ent->soundLoop = G_SoundIndex( sound );    // if the "color" or "light" keys are set, setup constantLight    lightSet = G_SpawnFloat( "light", "100", &light ); @@ -1084,8 +1166,10 @@ void InitRotator( gentity_t *ent )    ent->use = Use_BinaryMover;    ent->reached = Reached_BinaryMover; +  if( G_SpawnString( "team", "", &team ) ) +    ent->team = G_CopyString( team ); +    ent->moverState = ROTATOR_POS1; -  ent->r.svFlags = SVF_USE_CURRENT_ORIGIN;    ent->s.eType = ET_MOVER;    VectorCopy( ent->pos1, ent->r.currentAngles );    trap_LinkEntity( ent ); @@ -1156,8 +1240,8 @@ static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_    axis = ent->count;    VectorClear( dir ); -  if( fabs( other->s.origin[ axis ] - ent->r.absmax[ axis ] ) < -      fabs( other->s.origin[ axis ] - ent->r.absmin[ axis ] ) ) +  if( fabs( other->r.currentOrigin[ axis ] - ent->r.absmax[ axis ] ) < +      fabs( other->r.currentOrigin[ axis ] - ent->r.absmin[ axis ] ) )    {      origin[ axis ] = ent->r.absmin[ axis ] - 20;      dir[ axis ] = -1; @@ -1177,7 +1261,7 @@ static void Touch_DoorTriggerSpectator( gentity_t *ent, gentity_t *other, trace_    }    vectoangles( dir, angles ); -  TeleportPlayer( other, origin, angles ); +  TeleportPlayer( other, origin, angles, 400.0f );  } @@ -1237,7 +1321,7 @@ static void manualDoorTriggerSpectator( gentity_t *door, gentity_t *player )  ================  manualTriggerSpectator -Trip to skip the closest door targetted by trigger +Trip to skip the closest door targeted by trigger  ================  */  void manualTriggerSpectator( gentity_t *trigger, gentity_t *player ) @@ -1259,14 +1343,6 @@ void manualTriggerSpectator( gentity_t *trigger, gentity_t *player )    {      if( !strcmp( t->classname, "func_door" ) )        targets[ i++ ] = t; -    else if( t == trigger ) -      G_Printf( "WARNING: Entity used itself.\n" ); - -    if( !trigger->inuse ) -    { -      G_Printf( "triggerity was removed while using targets\n" ); -      return; -    }    }    //if more than 0 targets @@ -1299,25 +1375,30 @@ Touch_DoorTrigger  */  void Touch_DoorTrigger( gentity_t *ent, gentity_t *other, trace_t *trace )  { +  moverState_t teamState; +    //buildables don't trigger movers    if( other->s.eType == ET_BUILDABLE )      return; -  if( other->client && other->client->sess.sessionTeam == TEAM_SPECTATOR ) +  teamState = GetMoverTeamState( ent->parent ); + +  if( other->client && other->client->sess.spectatorState != SPECTATOR_NOT )    {      // if the door is not open and not opening -    if( ent->parent->moverState != MOVER_1TO2 && -        ent->parent->moverState != MOVER_POS2 && -        ent->parent->moverState != ROTATOR_1TO2 && -        ent->parent->moverState != ROTATOR_POS2 ) +    if( teamState != MOVER_POS2 && teamState != MOVER_1TO2 )        Touch_DoorTriggerSpectator( ent, other, trace );    } -  else if( ent->parent->moverState != MOVER_1TO2 && -           ent->parent->moverState != ROTATOR_1TO2 && -           ent->parent->moverState != ROTATOR_2TO1 ) -  { +  else if( teamState != MOVER_1TO2 )      Use_BinaryMover( ent->parent, ent, other ); -  } +} + + +void Think_MatchTeam( gentity_t *ent ) +{ +  if( ent->flags & FL_TEAMSLAVE ) +    return; +  MatchTeam( ent, ent->moverState, level.time );  } @@ -1335,11 +1416,6 @@ void Think_SpawnNewDoorTrigger( gentity_t *ent )    vec3_t    mins, maxs;    int       i, best; -  //TA: disable shootable doors -  // set all of the slaves as shootable -  //for( other = ent; other; other = other->teamchain ) -  //  other->takedamage = qtrue; -    // find the bounds of everything on the team    VectorCopy( ent->r.absmin, mins );    VectorCopy( ent->r.absmax, maxs ); @@ -1374,12 +1450,7 @@ void Think_SpawnNewDoorTrigger( gentity_t *ent )    trap_LinkEntity( other );    if( ent->moverState < MODEL_POS1 ) -    MatchTeam( ent, ent->moverState, level.time ); -} - -void Think_MatchTeam( gentity_t *ent ) -{ -  MatchTeam( ent, ent->moverState, level.time ); +    Think_MatchTeam( ent );  } @@ -1406,6 +1477,7 @@ void SP_func_door( gentity_t *ent )    vec3_t  size;    float   lip;    char    *s; +  int     health;    G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s );    ent->sound2to1 = G_SoundIndex( s ); @@ -1436,11 +1508,13 @@ void SP_func_door( gentity_t *ent )    G_SpawnInt( "dmg", "2", &ent->damage );    // first position at start -  VectorCopy( ent->s.origin, ent->pos1 ); +  VectorCopy( ent->r.currentOrigin, ent->pos1 ); +  G_SetMovedir( ent->r.currentAngles, ent->movedir ); +  VectorClear( ent->s.apos.trBase ); +  VectorClear( ent->r.currentOrigin );    // calculate second position    trap_SetBrushModel( ent, ent->model ); -  G_SetMovedir( ent->s.angles, ent->movedir );    abs_movedir[ 0 ] = fabs( ent->movedir[ 0 ] );    abs_movedir[ 1 ] = fabs( ent->movedir[ 1 ] );    abs_movedir[ 2 ] = fabs( ent->movedir[ 2 ] ); @@ -1454,7 +1528,7 @@ void SP_func_door( gentity_t *ent )      vec3_t  temp;      VectorCopy( ent->pos2, temp ); -    VectorCopy( ent->s.origin, ent->pos2 ); +    VectorCopy( ent->r.currentOrigin, ent->pos2 );      VectorCopy( temp, ent->pos1 );    } @@ -1462,22 +1536,17 @@ void SP_func_door( gentity_t *ent )    ent->nextthink = level.time + FRAMETIME; -  if( !( ent->flags & FL_TEAMSLAVE ) ) -  { -    int health; - -    G_SpawnInt( "health", "0", &health ); -    if( health ) -      ent->takedamage = qtrue; +  G_SpawnInt( "health", "0", &health ); +  if( health ) +    ent->takedamage = qtrue; -    if( ent->targetname || health ) -    { -      // non touch/shoot doors -      ent->think = Think_MatchTeam; -    } -    else -      ent->think = Think_SpawnNewDoorTrigger; +  if( ent->targetname || health ) +  { +    // non touch/shoot doors +    ent->think = Think_MatchTeam;    } +  else +    ent->think = Think_SpawnNewDoorTrigger;  }  /*QUAKED func_door_rotating (0 .5 .8) START_OPEN CRUSHER REVERSE TOGGLE X_AXIS Y_AXIS @@ -1502,6 +1571,7 @@ void SP_func_door( gentity_t *ent )  void SP_func_door_rotating( gentity_t *ent )  {    char    *s; +  int     health;    G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s );    ent->sound2to1 = G_SoundIndex( s ); @@ -1534,7 +1604,8 @@ void SP_func_door_rotating( gentity_t *ent )    // set the axis of rotation    VectorClear( ent->movedir ); -  VectorClear( ent->s.angles ); +  VectorClear( ent->s.apos.trBase ); +  VectorClear( ent->r.currentAngles );    if( ent->spawnflags & 32 )      ent->movedir[ 2 ] = 1.0; @@ -1552,12 +1623,12 @@ void SP_func_door_rotating( gentity_t *ent )    if( !ent->rotatorAngle )    {      G_Printf( "%s at %s with no rotatorAngle set.\n", -              ent->classname, vtos( ent->s.origin ) ); +              ent->classname, vtos( ent->r.currentOrigin ) );      ent->rotatorAngle = 90.0;    } -  VectorCopy( ent->s.angles, ent->pos1 ); +  VectorCopy( ent->r.currentAngles, ent->pos1 );    trap_SetBrushModel( ent, ent->model );    VectorMA( ent->pos1, ent->rotatorAngle, ent->movedir, ent->pos2 ); @@ -1567,36 +1638,26 @@ void SP_func_door_rotating( gentity_t *ent )      vec3_t  temp;      VectorCopy( ent->pos2, temp ); -    VectorCopy( ent->s.angles, ent->pos2 ); +    VectorCopy( ent->pos1, ent->pos2 );      VectorCopy( temp, ent->pos1 );      VectorNegate( ent->movedir, ent->movedir );    } -  // set origin -  VectorCopy( ent->s.origin, ent->s.pos.trBase ); -  VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); -    InitRotator( ent );    ent->nextthink = level.time + FRAMETIME; -  if( !( ent->flags & FL_TEAMSLAVE ) ) -  { -    int health; - -    G_SpawnInt( "health", "0", &health ); - -    if( health ) -      ent->takedamage = qtrue; +  G_SpawnInt( "health", "0", &health ); +  if( health ) +    ent->takedamage = qtrue; -    if( ent->targetname || health ) -    { -      // non touch/shoot doors -      ent->think = Think_MatchTeam; -    } -    else -      ent->think = Think_SpawnNewDoorTrigger; +  if( ent->targetname || health ) +  { +    // non touch/shoot doors +    ent->think = Think_MatchTeam;    } +  else +    ent->think = Think_SpawnNewDoorTrigger;  }  /*QUAKED func_door_model (0 .5 .8) ? START_OPEN @@ -1620,6 +1681,7 @@ void SP_func_door_model( gentity_t *ent )    qboolean  lightSet, colorSet;    char      *sound;    gentity_t *clipBrush; +  int       health;    G_SpawnString( "sound2to1", "sound/movers/doors/dr1_strt.wav", &s );    ent->sound2to1 = G_SoundIndex( s ); @@ -1643,6 +1705,8 @@ void SP_func_door_model( gentity_t *ent )    //brush model    clipBrush = ent->clipBrush = G_Spawn( ); +  clipBrush->classname = "func_door_model_clip_brush"; +  clipBrush->clipBrush = ent; // link back    clipBrush->model = ent->model;    trap_SetBrushModel( clipBrush, clipBrush->model );    clipBrush->s.eType = ET_INVISIBLE; @@ -1655,7 +1719,7 @@ void SP_func_door_model( gentity_t *ent )    VectorCopy( clipBrush->r.mins, ent->r.mins );    VectorCopy( clipBrush->r.maxs, ent->r.maxs ); -  G_SpawnVector( "modelOrigin", "0 0 0", ent->s.origin ); +  G_SpawnVector( "modelOrigin", "0 0 0", ent->r.currentOrigin );    G_SpawnVector( "scale", "1 1 1", ent->s.origin2 ); @@ -1666,9 +1730,9 @@ void SP_func_door_model( gentity_t *ent )    else      ent->s.modelindex = G_ModelIndex( ent->model2 ); -  // if the "loopsound" key is set, use a constant looping sound when moving -  if( G_SpawnString( "noise", "100", &sound ) ) -    ent->s.loopSound = G_SoundIndex( sound ); +  // if the "noise" key is set, use a constant looping sound when moving +  if( G_SpawnString( "noise", "", &sound ) ) +    ent->soundLoop = G_SoundIndex( sound );    // if the "color" or "light" keys are set, setup constantLight    lightSet = G_SpawnFloat( "light", "100", &light ); @@ -1701,19 +1765,17 @@ void SP_func_door_model( gentity_t *ent )    ent->moverState = MODEL_POS1;    ent->s.eType = ET_MODELDOOR; -  VectorCopy( ent->s.origin, ent->s.pos.trBase );    ent->s.pos.trType = TR_STATIONARY;    ent->s.pos.trTime = 0;    ent->s.pos.trDuration = 0;    VectorClear( ent->s.pos.trDelta ); -  VectorCopy( ent->s.angles, ent->s.apos.trBase );    ent->s.apos.trType = TR_STATIONARY;    ent->s.apos.trTime = 0;    ent->s.apos.trDuration = 0;    VectorClear( ent->s.apos.trDelta ); -  ent->s.misc      = (int)ent->animation[ 0 ];                  //first frame -  ent->s.weapon    = abs( (int)ent->animation[ 1 ] );           //number of frames +  ent->s.misc   = (int)ent->animation[ 0 ];                  //first frame +  ent->s.weapon = abs( (int)ent->animation[ 1 ] );           //number of frames    //must be at least one frame -- mapper has forgotten animation key    if( ent->s.weapon == 0 ) @@ -1723,19 +1785,14 @@ void SP_func_door_model( gentity_t *ent )    trap_LinkEntity( ent ); -  if( !( ent->flags & FL_TEAMSLAVE ) ) -  { -    int health; - -    G_SpawnInt( "health", "0", &health ); -    if( health ) -      ent->takedamage = qtrue; +  G_SpawnInt( "health", "0", &health ); +  if( health ) +    ent->takedamage = qtrue; -    if( !( ent->targetname || health ) ) -    { -      ent->nextthink = level.time + FRAMETIME; -      ent->think = Think_SpawnNewDoorTrigger; -    } +  if( !( ent->targetname || health ) ) +  { +    ent->nextthink = level.time + FRAMETIME; +    ent->think = Think_SpawnNewDoorTrigger;    }  } @@ -1751,7 +1808,7 @@ PLAT  ==============  Touch_Plat -Don't allow decent if a living player is on it +Don't allow to descend if a player is on it and is alive  ===============  */  void Touch_Plat( gentity_t *ent, gentity_t *other, trace_t *trace ) @@ -1860,7 +1917,8 @@ void SP_func_plat( gentity_t *ent )    G_SpawnString( "soundPos1", "sound/movers/plats/pt1_end.wav", &s );    ent->soundPos1 = G_SoundIndex( s ); -  VectorClear( ent->s.angles ); +  VectorClear( ent->s.apos.trBase ); +  VectorClear( ent->r.currentAngles );    G_SpawnFloat( "speed", "200", &ent->speed );    G_SpawnInt( "dmg", "2", &ent->damage ); @@ -1876,7 +1934,7 @@ void SP_func_plat( gentity_t *ent )      height = ( ent->r.maxs[ 2 ] - ent->r.mins[ 2 ] ) - lip;    // pos1 is the rest (bottom) position, pos2 is the top -  VectorCopy( ent->s.origin, ent->pos2 ); +  VectorCopy( ent->r.currentOrigin, ent->pos2 );    VectorCopy( ent->pos2, ent->pos1 );    ent->pos1[ 2 ] -= height; @@ -1921,7 +1979,7 @@ void Touch_Button( gentity_t *ent, gentity_t *other, trace_t *trace )  /*QUAKED func_button (0 .5 .8) ? -When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. +When a button is touched, it moves some distance in the direction of its angle, triggers all of its targets, waits some time, then returns to its original position where it can be triggered again.  "model2"  .md3 model to also draw  "angle"   determines the opening direction @@ -1953,14 +2011,16 @@ void SP_func_button( gentity_t *ent )    ent->wait *= 1000;    // first position -  VectorCopy( ent->s.origin, ent->pos1 ); +  VectorCopy( ent->r.currentOrigin, ent->pos1 ); +  G_SetMovedir( ent->r.currentAngles, ent->movedir ); +  VectorClear( ent->s.apos.trBase ); +  VectorClear( ent->r.currentAngles );    // calculate second position    trap_SetBrushModel( ent, ent->model );    G_SpawnFloat( "lip", "4", &lip ); -  G_SetMovedir( ent->s.angles, ent->movedir );    abs_movedir[ 0 ] = fabs( ent->movedir[ 0 ] );    abs_movedir[ 1 ] = fabs( ent->movedir[ 1 ] );    abs_movedir[ 2 ] = fabs( ent->movedir[ 2 ] ); @@ -2031,8 +2091,8 @@ void Reached_Train( gentity_t *ent )    // set the new trajectory    ent->nextTrain = next->nextTrain; -  VectorCopy( next->s.origin, ent->pos1 ); -  VectorCopy( next->nextTrain->s.origin, ent->pos2 ); +  VectorCopy( next->r.currentOrigin, ent->pos1 ); +  VectorCopy( next->nextTrain->r.currentOrigin, ent->pos2 );    // if the path_corner has a speed, use that    if( next->speed ) @@ -2056,6 +2116,19 @@ void Reached_Train( gentity_t *ent )    ent->s.pos.trDuration = length * 1000 / speed; +  // Be sure to send to clients after any fast move case +  ent->r.svFlags &= ~SVF_NOCLIENT; + +  // Fast move case +  if( ent->s.pos.trDuration < 1 ) +  { +    // As trDuration is used later in a division, we need to avoid that case now +    ent->s.pos.trDuration = 1; + +    // Don't send entity to clients so it becomes really invisible +    ent->r.svFlags |= SVF_NOCLIENT; +  } +    // looping sound    ent->s.loopSound = next->soundLoop; @@ -2160,7 +2233,7 @@ void Think_SetupTrainTargets( gentity_t *ent )      if( !path->target )      {        G_Printf( "Train corner at %s without a target\n", -        vtos( path->s.origin ) ); +        vtos( path->r.currentOrigin ) );        return;      } @@ -2175,7 +2248,7 @@ void Think_SetupTrainTargets( gentity_t *ent )        if( !next )        {          G_Printf( "Train corner at %s without a target path_corner\n", -          vtos( path->s.origin ) ); +          vtos( path->r.currentOrigin ) );          return;        }      } while( strcmp( next->classname, "path_corner" ) ); @@ -2199,7 +2272,7 @@ void SP_path_corner( gentity_t *self )  {    if( !self->targetname )    { -    G_Printf( "path_corner with no targetname at %s\n", vtos( self->s.origin ) ); +    G_Printf( "path_corner with no targetname at %s\n", vtos( self->r.currentOrigin ) );      G_FreeEntity( self );      return;    } @@ -2231,16 +2304,16 @@ void Blocked_Train( gentity_t *self, gentity_t *other )          vec3_t    dir;          gentity_t *tent; -        if( other->biteam == BIT_ALIENS ) +        if( other->buildableTeam == TEAM_ALIENS )          {            VectorCopy( other->s.origin2, dir ); -          tent = G_TempEntity( other->s.origin, EV_ALIEN_BUILDABLE_EXPLOSION ); +          tent = G_TempEntity( other->r.currentOrigin, EV_ALIEN_BUILDABLE_EXPLOSION );            tent->s.eventParm = DirToByte( dir );          } -        else if( other->biteam == BIT_HUMANS ) +        else if( other->buildableTeam == TEAM_HUMANS )          {            VectorSet( dir, 0.0f, 0.0f, 1.0f ); -          tent = G_TempEntity( other->s.origin, EV_HUMAN_BUILDABLE_EXPLOSION ); +          tent = G_TempEntity( other->r.currentOrigin, EV_HUMAN_BUILDABLE_EXPLOSION );            tent->s.eventParm = DirToByte( dir );          }        } @@ -2271,7 +2344,8 @@ The train spawns at the first target it is pointing at.  */  void SP_func_train( gentity_t *self )  { -  VectorClear( self->s.angles ); +  VectorClear( self->s.apos.trBase ); +  VectorClear( self->r.currentAngles );    if( self->spawnflags & TRAIN_BLOCK_STOPS )      self->damage = 0; @@ -2318,10 +2392,12 @@ A bmodel that just sits there, doing nothing.  Can be used for conditional walls  */  void SP_func_static( gentity_t *ent )  { +  vec3_t savedOrigin;    trap_SetBrushModel( ent, ent->model ); +  VectorCopy( ent->r.currentOrigin, savedOrigin );    InitMover( ent ); -  VectorCopy( ent->s.origin, ent->s.pos.trBase ); -  VectorCopy( ent->s.origin, ent->r.currentOrigin ); +  VectorCopy( savedOrigin, ent->r.currentOrigin ); +  VectorCopy( savedOrigin, ent->s.pos.trBase );  } @@ -2347,6 +2423,8 @@ check either the X_AXIS or Y_AXIS box to change that.  */  void SP_func_rotating( gentity_t *ent )  { +  vec3_t savedOrigin; +    if( !ent->speed )      ent->speed = 100; @@ -2364,11 +2442,10 @@ void SP_func_rotating( gentity_t *ent )      ent->damage = 2;    trap_SetBrushModel( ent, ent->model ); +  VectorCopy( ent->r.currentOrigin, savedOrigin );    InitMover( ent ); - -  VectorCopy( ent->s.origin, ent->s.pos.trBase ); -  VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); -  VectorCopy( ent->s.apos.trBase, ent->r.currentAngles ); +  VectorCopy( savedOrigin, ent->r.currentOrigin ); +  VectorCopy( savedOrigin, ent->s.pos.trBase );    trap_LinkEntity( ent );  } @@ -2397,6 +2474,7 @@ void SP_func_bobbing( gentity_t *ent )  {    float   height;    float   phase; +  vec3_t  savedOrigin;    G_SpawnFloat( "speed", "4", &ent->speed );    G_SpawnFloat( "height", "32", &height ); @@ -2404,10 +2482,10 @@ void SP_func_bobbing( gentity_t *ent )    G_SpawnFloat( "phase", "0", &phase );    trap_SetBrushModel( ent, ent->model ); +  VectorCopy( ent->r.currentOrigin, savedOrigin );    InitMover( ent ); - -  VectorCopy( ent->s.origin, ent->s.pos.trBase ); -  VectorCopy( ent->s.origin, ent->r.currentOrigin ); +  VectorCopy( savedOrigin, ent->r.currentOrigin ); +  VectorCopy( savedOrigin, ent->s.pos.trBase );    ent->s.pos.trDuration = ent->speed * 1000;    ent->s.pos.trTime = ent->s.pos.trDuration * phase; @@ -2448,6 +2526,7 @@ void SP_func_pendulum( gentity_t *ent )    float length;    float phase;    float speed; +  vec3_t savedOrigin;    G_SpawnFloat( "speed", "30", &speed );    G_SpawnInt( "dmg", "2", &ent->damage ); @@ -2465,12 +2544,10 @@ void SP_func_pendulum( gentity_t *ent )    ent->s.pos.trDuration = ( 1000 / freq ); +  VectorCopy( ent->r.currentOrigin, savedOrigin );    InitMover( ent ); - -  VectorCopy( ent->s.origin, ent->s.pos.trBase ); -  VectorCopy( ent->s.origin, ent->r.currentOrigin ); - -  VectorCopy( ent->s.angles, ent->s.apos.trBase ); +  VectorCopy( savedOrigin, ent->r.currentOrigin ); +  VectorCopy( savedOrigin, ent->s.pos.trBase );    ent->s.apos.trDuration = 1000 / freq;    ent->s.apos.trTime = ent->s.apos.trDuration * phase; diff --git a/src/game/g_namelog.c b/src/game/g_namelog.c new file mode 100644 index 0000000..b789743 --- /dev/null +++ b/src/game/g_namelog.c @@ -0,0 +1,128 @@ +/* +=========================================================================== +Copyright (C) 2010 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 3 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, see <https://www.gnu.org/licenses/> + +=========================================================================== +*/ + +#include "g_local.h" + +void G_namelog_cleanup( void ) +{ +  namelog_t *namelog, *n; + +  for( namelog = level.namelogs; namelog; namelog = n ) +  { +    n = namelog->next; +    BG_Free( namelog ); +  } +} + +void G_namelog_connect( gclient_t *client ) +{ +  namelog_t *n, *p = NULL; +  int       i; +  char      *newname; + +  for( n = level.namelogs; n; p = n, n = n->next ) +  { +    if( n->slot != -1 ) +      continue; +    if( !Q_stricmp( client->pers.guid, n->guid ) ) +      break; +  } +  if( !n ) +  { +    n = BG_Alloc( sizeof( namelog_t ) ); +    strcpy( n->guid, client->pers.guid ); +    n->guidless = client->pers.guidless; +    if( p ) +    { +      p->next = n; +      n->id = p->id + 1; +    } +    else +    { +      level.namelogs = n; +      n->id = MAX_CLIENTS; +    } +  } +  client->pers.namelog = n; +  n->slot = client - level.clients; +  n->banned = qfalse; + +  newname = n->name[ n->nameOffset ]; +  // If they're muted, copy in their last known name - this will stop people +  // reconnecting to get around the name change protection. +  if( n->muted && G_admin_name_check( &g_entities[ n->slot ], +      newname, NULL, 0 ) ) +    Q_strncpyz( client->pers.netname, newname, MAX_NAME_LENGTH ); + +  for( i = 0; i < MAX_NAMELOG_ADDRS && n->ip[ i ].str[ 0 ]; i++ ) +    if( !strcmp( n->ip[ i ].str, client->pers.ip.str ) ) +      return; +  if( i == MAX_NAMELOG_ADDRS ) +    i--; +  memcpy( &n->ip[ i ], &client->pers.ip, sizeof( n->ip[ i ] ) ); +} + +void G_namelog_disconnect( gclient_t *client ) +{ +  if( client->pers.namelog == NULL ) +    return; +  client->pers.namelog->slot = -1; +  client->pers.namelog = NULL; +} + +void G_namelog_update_score( gclient_t *client ) +{ +  namelog_t *n = client->pers.namelog; +  if( n == NULL ) +    return; + +  n->team = client->pers.teamSelection; +  n->score = client->ps.persistant[ PERS_SCORE ]; +  n->credits = client->pers.credit; +} + +void G_namelog_update_name( gclient_t *client ) +{ +  char      n1[ MAX_NAME_LENGTH ], n2[ MAX_NAME_LENGTH ]; +  namelog_t *n = client->pers.namelog; + +  if( n->name[ n->nameOffset ][ 0 ] ) +  { +    G_SanitiseString( client->pers.netname, n1, sizeof( n1 ) ); +    G_SanitiseString( n->name[ n->nameOffset ], +      n2, sizeof( n2 ) ); +    if( strcmp( n1, n2 ) != 0 ) +      n->nameOffset = ( n->nameOffset + 1 ) % MAX_NAMELOG_NAMES; +  } +  strcpy( n->name[ n->nameOffset ], client->pers.netname ); +} + +void G_namelog_restore( gclient_t *client ) +{ +  namelog_t *n = client->pers.namelog; + +  G_ChangeTeam( g_entities + n->slot, n->team ); +  client->ps.persistant[ PERS_SCORE ] = n->score; +  client->ps.persistant[ PERS_CREDIT ] = 0; +  G_AddCreditToClient( client, n->credits, qfalse ); +} diff --git a/src/game/g_physics.c b/src/game/g_physics.c index 58c6487..9384c17 100644 --- a/src/game/g_physics.c +++ b/src/game/g_physics.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -45,8 +46,8 @@ static void G_Bounce( gentity_t *ent, trace_t *trace )    if( ent->s.eType == ET_BUILDABLE )    { -    minNormal = BG_FindMinNormalForBuildable( ent->s.modelindex ); -    invert = BG_FindInvertNormalForBuildable( ent->s.modelindex ); +    minNormal = BG_Buildable( ent->s.modelindex )->minNormal; +    invert = BG_Buildable( ent->s.modelindex )->invertNormal;    }    else      minNormal = 0.707f; @@ -61,7 +62,6 @@ static void G_Bounce( gentity_t *ent, trace_t *trace )    if( VectorLength( ent->s.pos.trDelta ) < 10 )    { -    VectorMA( trace->endpos, 0.5f, trace->plane.normal, trace->endpos ); // make sure it is off ground      G_SetOrigin( ent, trace->endpos );      ent->s.groundEntityNum = trace->entityNum;      VectorCopy( trace->plane.normal, ent->s.origin2 ); @@ -69,8 +69,8 @@ static void G_Bounce( gentity_t *ent, trace_t *trace )      return;    } +  VectorMA( ent->r.currentOrigin, 0.15, trace->plane.normal, ent->r.currentOrigin );    VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); -  VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);    ent->s.pos.trTime = level.time;  } @@ -87,16 +87,15 @@ void G_Physics( gentity_t *ent, int msec )    vec3_t    origin;    trace_t   tr;    int     contents; -  int     mask; -  // if groundentity has been set to -1, it may have been pushed off an edge -  if( ent->s.groundEntityNum == -1 ) +  // if groundentity has been set to ENTITYNUM_NONE, ent may have been pushed off an edge +  if( ent->s.groundEntityNum == ENTITYNUM_NONE )    {      if( ent->s.eType == ET_BUILDABLE )      { -      if( ent->s.pos.trType != BG_FindTrajectoryForBuildable( ent->s.modelindex ) ) +      if( ent->s.pos.trType != BG_Buildable( ent->s.modelindex )->traj )        { -        ent->s.pos.trType = BG_FindTrajectoryForBuildable( ent->s.modelindex ); +        ent->s.pos.trType = BG_Buildable( ent->s.modelindex )->traj;          ent->s.pos.trTime = level.time;        }      } @@ -107,12 +106,6 @@ void G_Physics( gentity_t *ent, int msec )      }    } -  // trace a line from the previous position to the current position -  if( ent->clipmask ) -    mask = ent->clipmask; -  else -    mask = MASK_PLAYERSOLID & ~CONTENTS_BODY;//MASK_SOLID; -    if( ent->s.pos.trType == TR_STATIONARY )    {      // check think function @@ -125,10 +118,10 @@ void G_Physics( gentity_t *ent, int msec )        VectorMA( origin, -2.0f, ent->s.origin2, origin ); -      trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, mask ); +      trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, ent->clipmask );        if( tr.fraction == 1.0f ) -        ent->s.groundEntityNum = -1; +        ent->s.groundEntityNum = ENTITYNUM_NONE;        ent->nextPhysicsTime = level.time + PHYSICS_TIME;      } @@ -136,10 +129,12 @@ void G_Physics( gentity_t *ent, int msec )      return;    } +  // trace a line from the previous position to the current position +    // get current position    BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); -  trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, mask ); +  trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, ent->s.number, ent->clipmask );    VectorCopy( tr.endpos, ent->r.currentOrigin ); @@ -158,10 +153,11 @@ void G_Physics( gentity_t *ent, int msec )    contents = trap_PointContents( ent->r.currentOrigin, -1 );    if( contents & CONTENTS_NODROP )    { +    if( ent->s.eType == ET_BUILDABLE ) +      G_RemoveRangeMarkerFrom( ent );      G_FreeEntity( ent );      return;    }    G_Bounce( ent, &tr );  } - diff --git a/src/game/g_playermodel.c b/src/game/g_playermodel.c new file mode 100644 index 0000000..7f79b38 --- /dev/null +++ b/src/game/g_playermodel.c @@ -0,0 +1,193 @@ +// +// Author: blowFish <blowfish@badsec.org> +// + +#include "g_local.h" + +//------------------------------------------------------------------------- +// Player models +//------------------------------------------------------------------------- +// +static qboolean +_is_playermodel_uniq(const char *model) +{ +    int i; +    for ( i = 0; i < level.playerModelCount; i++ ) +    { +        if ( !strcmp( model, level.playerModel[i] ) ) +            return qfalse; +    } + +    return qtrue; +} + +static void +G_AddPlayerModel(const char *model) +{ +    if (!_is_playermodel_uniq(model)) +        return; + +    // HACK! +    if (!strcmp(model, "human_bsuit")) +        return; + +    level.playerModel[ level.playerModelCount ] = G_CopyString(model); +    level.playerModelCount++; +} + +void G_InitPlayerModel(void) +{ +    char fileList[ 16*1024 ] = {""}; +    char *filePtr; +    int  numFiles; +    int  fileLen = 0; +    int  i; + +    // TODO: Add an FS trap which is does correct file globbing +    numFiles = trap_FS_GetFilteredFiles( "/models/players", "", +            "models*players*head_*.skin", +            fileList, sizeof(fileList) ); +    filePtr = fileList; + +    for( i = 0; i < numFiles && level.playerModelCount < MAX_PLAYER_MODEL; +            i++, filePtr += fileLen + 1 ) +    { +        char *start, *c; + +        fileLen = strlen( filePtr ); + +        // skip leading '/' +        start = filePtr + 15; + +        // Only want directory names at the current depth. +        for ( c = start; c != '\0'; c++ ) +        { +            if ( *c == '/' || *c == '\\' ) +            { +                *c = '\0'; +                break; +            } +        } + +        G_AddPlayerModel(start); +    } +} + +qboolean G_IsValidPlayerModel(const char *model) +{ +    return !_is_playermodel_uniq(model); +} + +void G_FreePlayerModel(void) +{ +    int i; +    for ( i = 0; i < level.playerModelCount; i++ ) +        BG_Free( level.playerModel[i] ); +} + +//------------------------------------------------------------------------- +// Skins +//------------------------------------------------------------------------- + +void G_GetPlayerModelSkins( const char *modelname, char skins[][ 64 ], + int maxskins, int *numskins ) +{ +    char fileList[ 16*1024 ] = {""}; +    int nFiles; +    char *filePtr; +    int fileLen = 0; +    int i; + +    *numskins = 0; +    nFiles = trap_FS_GetFilteredFiles("models/players", ".skin", +            va("models*players*%s*skin", modelname), +            fileList, sizeof(fileList)); +    filePtr = fileList; +    for (i = 0; i < nFiles && i < maxskins; i++ ) +    { +        char *start, *end; + +        fileLen = strlen( filePtr ); + +        start = filePtr; +        start += strlen(va("models/players/%s/", modelname)); + +        end = filePtr + fileLen; +        end -= 5; +        *end = '\0'; +        filePtr += fileLen + 1; + +        // dumb way to filter out the unique skins of segmented and +        // nonsegmented models. +        // TODO: Stop writing code at 4am. +        if ( start[0] == 'h' +          && start[1] == 'e' +          && start[2] == 'a' +          && start[3] == 'd' +          && start[4] == '_' ) +            start += 5; + +        else if ( start[0] == 'n' +               && start[1] == 'o' +               && start[2] == 'n' +               && start[3] == 's' +               && start[4] == 'e' +               && start[5] == 'g' +               && start[6] == '_' ) +            start += 7; + +        else +            continue; + +        strncpy(skins[*numskins], start, 64 ); +        (*numskins)++; +    } +} + +/* +====================== +GetSkin + +Probably should be called GetSkin[or]Default. Tries to recreate what +appears to be an undocumented set of conventions that must be allowed +in other q3 derives. + +This algorithm is not really good enough for Tremulous considering +armour + upgrade/advanced in gameplay + +XXX Move this into bg_ +====================== +*/ +char *GetSkin( char *modelname, char *wish ) +{ +    char skins[ MAX_PLAYER_MODEL ][ 64 ]; +    int numskins; +    int i; +    qboolean foundDefault = qfalse; +    qboolean foundSelfNamed = qfalse; +    static char lastpick[ 64 ] = {""}; +    lastpick[0] = '\0'; // reset static buf + +    G_GetPlayerModelSkins(modelname, skins, MAX_PLAYER_MODEL, &numskins); + +    for (i = 0; i < numskins; i++) +    { +        if ( i == 0 ) +            strncpy(lastpick, skins[0], 64 ); + +        if ( !strcmp(wish, skins[i]) ) +            return wish; +        else if ( !strcmp("default", skins[i])) +            foundDefault = qtrue; +        else if ( !strcmp(modelname, skins[i])) +            foundSelfNamed = qtrue; +    } + +    if (foundDefault) +        return "default"; +    else if (foundSelfNamed) +        return modelname; + +    return lastpick; +} + diff --git a/src/game/g_ptr.c b/src/game/g_ptr.c deleted file mode 100644 index e102183..0000000 --- a/src/game/g_ptr.c +++ /dev/null @@ -1,143 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus - -This file is part of Tremulous. - -Tremulous is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Tremulous is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA -=========================================================================== -*/ - -// g_ptr.c -- post timeout restoration handling - -#include "g_local.h" - -static connectionRecord_t connections[ MAX_CLIENTS ]; - -/* -=============== -G_CheckForUniquePTRC - -Callback to detect ptrc clashes -=============== -*/ -static qboolean G_CheckForUniquePTRC( int code ) -{ -  int i; - -  if( code == 0 ) -    return qfalse; - -  for( i = 0; i < MAX_CLIENTS; i++ ) -  { -    if( connections[ i ].ptrCode == code ) -      return qfalse; -  } - -  return qtrue; -} - -/* -=============== -G_UpdatePTRConnection - -Update the data in a connection record -=============== -*/ -void G_UpdatePTRConnection( gclient_t *client ) -{ -  if( client && client->pers.connection ) -  { -    client->pers.connection->clientTeam = client->pers.teamSelection; -    client->pers.connection->clientCredit = client->pers.credit; -    client->pers.connection->clientScore = client->pers.score; -  } -} - -/* -=============== -G_GenerateNewConnection - -Generates a new connection -=============== -*/ -connectionRecord_t *G_GenerateNewConnection( gclient_t *client ) -{ -  int     code = 0; -  int     i; - -  // this should be really random -  srand( trap_Milliseconds( ) ); - -  // there is a very very small possibility that this -  // will loop infinitely -  do -  { -    code = rand( ); -  } while( !G_CheckForUniquePTRC( code ) ); - -  for( i = 0; i < MAX_CLIENTS; i++ ) -  { -    //found an unused slot -    if( !connections[ i ].ptrCode ) -    { -      connections[ i ].ptrCode = code; -      connections[ i ].clientNum = client->ps.clientNum; -      client->pers.connection = &connections[ i ]; -      G_UpdatePTRConnection( client ); -      client->pers.connection->clientEnterTime = client->pers.enterTime; - -      return &connections[ i ]; -    } -  } - -  return NULL; -} - -/* -=============== -G_FindConnectionForCode - -Finds a connection for a given code -=============== -*/ -connectionRecord_t *G_FindConnectionForCode( int code ) -{ -  int i; - -  if( code == 0 ) -    return NULL; - -  for( i = 0; i < MAX_CLIENTS; i++ ) -  { -    if( connections[ i ].ptrCode == code ) -      return &connections[ i ]; -  } - -  return NULL; -} - -/* -=============== -G_ResetPTRConnections - -Invalidate any existing codes -=============== -*/ -void G_ResetPTRConnections( void ) -{ -  memset( connections, 0, sizeof( connectionRecord_t ) * MAX_CLIENTS ); -} diff --git a/src/game/g_public.h b/src/game/g_public.h index e1e01d7..415ab96 100644 --- a/src/game/g_public.h +++ b/src/game/g_public.h @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,252 +17,253 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */  // g_public.h -- game module information visible to server +#ifndef GAME_PUBLIC_H +#define GAME_PUBLIC_H + +#include "qcommon/q_shared.h" -#define GAME_API_VERSION  8 +#define GAME_API_VERSION 9  // entity->svFlags  // the server does not know how to interpret most of the values  // in entityStates (level eType), so the game must explicitly flag  // special server behaviors -#define SVF_NOCLIENT            0x00000001  // don't send entity to clients, even if it has effects - -// TTimo -// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=551 -#define SVF_CLIENTMASK 0x00000002 - -#define SVF_BROADCAST           0x00000020  // send to all connected clients -#define SVF_PORTAL              0x00000040  // merge a second pvs at origin2 into snapshots -#define SVF_USE_CURRENT_ORIGIN  0x00000080  // entity->r.currentOrigin instead of entity->s.origin -                      // for link position (missiles and movers) -#define SVF_SINGLECLIENT    0x00000100  // only send to a single client (entityShared_t->singleClient) -#define SVF_NOSERVERINFO    0x00000200  // don't send CS_SERVERINFO updates to this client -                      // so that it can be updated for ping tools without -                      // lagging clients -#define SVF_CAPSULE        0x00000400  // use capsule for collision detection instead of bbox -#define SVF_NOTSINGLECLIENT    0x00000800  // send entity to everyone but one client -                      // (entityShared_t->singleClient) +#define SVF_NOCLIENT 0x00000001  // don't send entity to clients, even if it has effects -//=============================================================== +#define SVF_CLIENTMASK 0x00000002  // send to clients specified by these bitmasks: +// entityShared_t->singleClient: low-order bits (0..31) +// entityShared_t->hack.generic1: high-order bits (32..63) +#define SVF_BROADCAST 0x00000020  // send to all connected clients +#define SVF_PORTAL 0x00000040  // merge a second pvs at origin2 into snapshots -typedef struct { -  entityState_t s;        // communicated by server to clients - -  qboolean  linked;       // qfalse if not in any good cluster -  int     linkcount; - -  int     svFlags;      // SVF_NOCLIENT, SVF_BROADCAST, etc -  int     singleClient;   // only send to this client when SVF_SINGLECLIENT is set - -  qboolean  bmodel;       // if false, assume an explicit mins / maxs bounding box -                  // only set by trap_SetBrushModel -  vec3_t    mins, maxs; -  int     contents;     // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc -                  // a non-solid entity should set to 0 - -  vec3_t    absmin, absmax;   // derived from mins/maxs and origin + rotation - -  // currentOrigin will be used for all collision detection and world linking. -  // it will not necessarily be the same as the trajectory evaluation for the current -  // time, because each entity must be moved one at a time after time is advanced -  // to avoid simultanious collision issues -  vec3_t    currentOrigin; -  vec3_t    currentAngles; - -  // when a trace call is made and passEntityNum != ENTITYNUM_NONE, -  // an ent will be excluded from testing if: -  // ent->s.number == passEntityNum (don't interact with self) -  // ent->s.ownerNum = passEntityNum  (don't interact with your own missiles) -  // entity[ent->s.ownerNum].ownerNum = passEntityNum (don't interact with other missiles from owner) -  int     ownerNum; -} entityShared_t; +#define SVF_SINGLECLIENT 0x00000100  // only send to a single client (entityShared_t->singleClient) +#define SVF_NOSERVERINFO 0x00000200  // don't send CS_SERVERINFO updates to this client +// so that it can be updated for ping tools without +// lagging clients +#define SVF_CAPSULE 0x00000400  // use capsule for collision detection instead of bbox +#define SVF_NOTSINGLECLIENT 0x00000800  // send entity to everyone but one client +// (entityShared_t->singleClient) +//=============================================================== +typedef struct { +    entityState_t hack;  // exists (as padding) to retain ABI compatibility +                         //  with GPP, but can be used for extension hacks + +    qboolean linked;  // qfalse if not in any good cluster +    int linkcount; + +    int svFlags;  // SVF_NOCLIENT, SVF_BROADCAST, etc. +    int singleClient;  // only send to this client when SVF_SINGLECLIENT is set + +    qboolean bmodel;  // if false, assume an explicit mins / maxs bounding box +                      // only set by trap_SetBrushModel +    vec3_t mins, maxs; +    int contents;  // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc +                   // a non-solid entity should set to 0 + +    vec3_t absmin, absmax;  // derived from mins/maxs and origin + rotation + +    // currentOrigin will be used for all collision detection and world linking. +    // it will not necessarily be the same as the trajectory evaluation for the current +    // time, because each entity must be moved one at a time after time is advanced +    // to avoid simultanious collision issues +    vec3_t currentOrigin; +    vec3_t currentAngles; + +    // when a trace call is made and passEntityNum != ENTITYNUM_NONE, +    // an ent will be excluded from testing if: +    // ent->s.number == passEntityNum (don't interact with self) +    // ent->r.ownerNum == passEntityNum (don't interact with your own missiles) +    // entity[ent->r.ownerNum].r.ownerNum == passEntityNum (don't interact with other missiles from owner) +    int ownerNum; +} entityShared_t;  // the server looks at a sharedEntity, which is the start of the game's gentity_t structure  typedef struct { -  entityState_t s;        // communicated by server to clients -  entityShared_t  r;        // shared by both the server system and game +    entityState_t s;  // communicated by server to clients +    entityShared_t r;  // shared by both the server system and game  } sharedEntity_t; - -  //===============================================================  //  // system traps provided by the main engine  //  typedef enum { -  //============== general Quake services ================== - -  G_PRINT,    // ( const char *string ); -  // print message on the local console +    //============== general Quake services ================== -  G_ERROR,    // ( const char *string ); -  // abort the game +    G_PRINT,  // ( const char *string ); +    // print message on the local console -  G_MILLISECONDS, // ( void ); -  // get current time for profiling reasons -  // this should NOT be used for any game related tasks, -  // because it is not journaled +    G_ERROR,  // ( const char *string ); +    // abort the game -  // console variable interaction -  G_CVAR_REGISTER,  // ( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); -  G_CVAR_UPDATE,  // ( vmCvar_t *vmCvar ); -  G_CVAR_SET,   // ( const char *var_name, const char *value ); -  G_CVAR_VARIABLE_INTEGER_VALUE,  // ( const char *var_name ); +    G_MILLISECONDS,  // ( void ); +    // get current time for profiling reasons +    // this should NOT be used for any game related tasks, +    // because it is not journaled -  G_CVAR_VARIABLE_STRING_BUFFER,  // ( const char *var_name, char *buffer, int bufsize ); +    // console variable interaction +    G_CVAR_REGISTER,  // ( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +    G_CVAR_UPDATE,  // ( vmCvar_t *vmCvar ); +    G_CVAR_SET,  // ( const char *var_name, const char *value ); +    G_CVAR_VARIABLE_INTEGER_VALUE,  // ( const char *var_name ); -  G_ARGC,     // ( void ); -  // ClientCommand and ServerCommand parameter access +    G_CVAR_VARIABLE_STRING_BUFFER,  // ( const char *var_name, char *buffer, int bufsize ); -  G_ARGV,     // ( int n, char *buffer, int bufferLength ); +    G_ARGC,  // ( void ); +    // ClientCommand and ServerCommand parameter access -  G_FS_FOPEN_FILE,  // ( const char *qpath, fileHandle_t *file, fsMode_t mode ); -  G_FS_READ,    // ( void *buffer, int len, fileHandle_t f ); -  G_FS_WRITE,   // ( const void *buffer, int len, fileHandle_t f ); -  G_FS_FCLOSE_FILE,   // ( fileHandle_t f ); +    G_ARGV,  // ( int n, char *buffer, int bufferLength ); -  G_SEND_CONSOLE_COMMAND, // ( const char *text ); -  // add commands to the console as if they were typed in -  // for map changing, etc +    G_FS_FOPEN_FILE,  // ( const char *qpath, fileHandle_t *file, enum FS_Mode mode ); +    G_FS_READ,  // ( void *buffer, int len, fileHandle_t f ); +    G_FS_WRITE,  // ( const void *buffer, int len, fileHandle_t f ); +    G_FS_FCLOSE_FILE,  // ( fileHandle_t f ); +    G_SEND_CONSOLE_COMMAND,  // ( const char *text ); +    // add commands to the console as if they were typed in +    // for map changing, etc -  //=========== server specific functionality ============= +    //=========== server specific functionality ============= -  G_LOCATE_GAME_DATA,   // ( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, -  //              playerState_t *clients, int sizeofGameClient ); -  // the game needs to let the server system know where and how big the gentities -  // are, so it can look at them directly without going through an interface +    G_LOCATE_GAME_DATA,  // ( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, +    //              playerState_t *clients, int sizeofGameClient ); +    // the game needs to let the server system know where and how big the gentities +    // are, so it can look at them directly without going through an interface -  G_DROP_CLIENT,    // ( int clientNum, const char *reason ); -  // kick a client off the server with a message +    G_DROP_CLIENT,  // ( int clientNum, const char *reason ); +    // kick a client off the server with a message -  G_SEND_SERVER_COMMAND,  // ( int clientNum, const char *fmt, ... ); -  // reliably sends a command string to be interpreted by the given -  // client.  If clientNum is -1, it will be sent to all clients +    G_SEND_SERVER_COMMAND,  // ( int clientNum, const char *fmt, ... ); +    // reliably sends a command string to be interpreted by the given +    // client.  If clientNum is -1, it will be sent to all clients -  G_SET_CONFIGSTRING, // ( int num, const char *string ); -  // config strings hold all the index strings, and various other information -  // that is reliably communicated to all clients -  // All of the current configstrings are sent to clients when -  // they connect, and changes are sent to all connected clients. -  // All confgstrings are cleared at each level start. +    G_SET_CONFIGSTRING,  // ( int num, const char *string ); +    // config strings hold all the index strings, and various other information +    // that is reliably communicated to all clients +    // All of the current configstrings are sent to clients when +    // they connect, and changes are sent to all connected clients. +    // All confgstrings are cleared at each level start. -  G_GET_CONFIGSTRING, // ( int num, char *buffer, int bufferSize ); +    G_GET_CONFIGSTRING,  // ( int num, char *buffer, int bufferSize ); -  G_SET_CONFIGSTRING_RESTRICTIONS, // ( int num, const clientList* clientList ); +    G_SET_CONFIGSTRING_RESTRICTIONS,  // ( int num, const clientList* clientList ); -  G_GET_USERINFO,   // ( int num, char *buffer, int bufferSize ); -  // userinfo strings are maintained by the server system, so they -  // are persistant across level loads, while all other game visible -  // data is completely reset +    G_GET_USERINFO,  // ( int num, char *buffer, int bufferSize ); +    // userinfo strings are maintained by the server system, so they +    // are persistant across level loads, while all other game visible +    // data is completely reset -  G_SET_USERINFO,   // ( int num, const char *buffer ); +    G_SET_USERINFO,  // ( int num, const char *buffer ); -  G_GET_SERVERINFO, // ( char *buffer, int bufferSize ); -  // the serverinfo info string has all the cvars visible to server browsers +    G_GET_SERVERINFO,  // ( char *buffer, int bufferSize ); +    // the serverinfo info string has all the cvars visible to server browsers -  G_SET_BRUSH_MODEL,  // ( gentity_t *ent, const char *name ); -  // sets mins and maxs based on the brushmodel name +    G_SET_BRUSH_MODEL,  // ( gentity_t *ent, const char *name ); +    // sets mins and maxs based on the brushmodel name -  G_TRACE,  // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); -  // collision detection against all linked entities +    G_TRACE,  // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int +              // passEntityNum, int contentmask ); +    // collision detection against all linked entities -  G_POINT_CONTENTS, // ( const vec3_t point, int passEntityNum ); -  // point contents against all linked entities +    G_POINT_CONTENTS,  // ( const vec3_t point, int passEntityNum ); +    // point contents against all linked entities -  G_IN_PVS,     // ( const vec3_t p1, const vec3_t p2 ); +    G_IN_PVS,  // ( const vec3_t p1, const vec3_t p2 ); -  G_IN_PVS_IGNORE_PORTALS,  // ( const vec3_t p1, const vec3_t p2 ); +    G_IN_PVS_IGNORE_PORTALS,  // ( const vec3_t p1, const vec3_t p2 ); -  G_ADJUST_AREA_PORTAL_STATE, // ( gentity_t *ent, qboolean open ); +    G_ADJUST_AREA_PORTAL_STATE,  // ( gentity_t *ent, qboolean open ); -  G_AREAS_CONNECTED,  // ( int area1, int area2 ); +    G_AREAS_CONNECTED,  // ( int area1, int area2 ); -  G_LINKENTITY,   // ( gentity_t *ent ); -  // an entity will never be sent to a client or used for collision -  // if it is not passed to linkentity.  If the size, position, or -  // solidity changes, it must be relinked. +    G_LINKENTITY,  // ( gentity_t *ent ); +    // an entity will never be sent to a client or used for collision +    // if it is not passed to linkentity.  If the size, position, or +    // solidity changes, it must be relinked. -  G_UNLINKENTITY,   // ( gentity_t *ent ); -  // call before removing an interactive entity +    G_UNLINKENTITY,  // ( gentity_t *ent ); +    // call before removing an interactive entity -  G_ENTITIES_IN_BOX,  // ( const vec3_t mins, const vec3_t maxs, gentity_t **list, int maxcount ); -  // EntitiesInBox will return brush models based on their bounding box, -  // so exact determination must still be done with EntityContact +    G_ENTITIES_IN_BOX,  // ( const vec3_t mins, const vec3_t maxs, gentity_t **list, int maxcount ); +    // EntitiesInBox will return brush models based on their bounding box, +    // so exact determination must still be done with EntityContact -  G_ENTITY_CONTACT, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); -  // perform an exact check against inline brush models of non-square shape +    G_ENTITY_CONTACT,  // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); +    // perform an exact check against inline brush models of non-square shape -  G_GET_USERCMD,  // ( int clientNum, usercmd_t *cmd ) +    G_GET_USERCMD,  // ( int clientNum, usercmd_t *cmd ) -  G_GET_ENTITY_TOKEN, // qboolean ( char *buffer, int bufferSize ) -  // Retrieves the next string token from the entity spawn text, returning -  // false when all tokens have been parsed. -  // This should only be done at GAME_INIT time. +    G_GET_ENTITY_TOKEN,  // qboolean ( char *buffer, int bufferSize ) +    // Retrieves the next string token from the entity spawn text, returning +    // false when all tokens have been parsed. +    // This should only be done at GAME_INIT time. -  G_FS_GETFILELIST, -  G_REAL_TIME, -  G_SNAPVECTOR, +    G_FS_GETFILELIST, +    G_REAL_TIME, +    G_SNAPVECTOR, -  G_TRACECAPSULE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); -  G_ENTITY_CONTACTCAPSULE,  // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); +    G_TRACECAPSULE,  // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, +                     // int passEntityNum, int contentmask ); +    G_ENTITY_CONTACTCAPSULE,  // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); -  // 1.32 -  G_FS_SEEK, +    // 1.32 +    G_FS_SEEK, -  G_PARSE_ADD_GLOBAL_DEFINE, -  G_PARSE_LOAD_SOURCE, -  G_PARSE_FREE_SOURCE, -  G_PARSE_READ_TOKEN, -  G_PARSE_SOURCE_FILE_AND_LINE, +    G_PARSE_ADD_GLOBAL_DEFINE, +    G_PARSE_LOAD_SOURCE, +    G_PARSE_FREE_SOURCE, +    G_PARSE_READ_TOKEN, +    G_PARSE_SOURCE_FILE_AND_LINE, -  G_SEND_GAMESTAT, +    G_SEND_GAMESTAT, -  G_ADDCOMMAND, -  G_REMOVECOMMAND +    G_ADDCOMMAND, +    G_REMOVECOMMAND, +    G_FS_GETFILTEREDFILES  } gameImport_t; -  //  // functions exported by the game subsystem  //  typedef enum { -  GAME_INIT,  // ( int levelTime, int randomSeed, int restart ); -  // init and shutdown will be called every single level -  // The game should call G_GET_ENTITY_TOKEN to parse through all the -  // entity configuration text and spawn gentities. +    GAME_INIT,  // ( int levelTime, int randomSeed, int restart ); +    // init and shutdown will be called every single level +    // The game should call G_GET_ENTITY_TOKEN to parse through all the +    // entity configuration text and spawn gentities. -  GAME_SHUTDOWN,  // (void); +    GAME_SHUTDOWN,  // (void); -  GAME_CLIENT_CONNECT,  // ( int clientNum, qboolean firstTime ); -  // return NULL if the client is allowed to connect, otherwise return -  // a text string with the reason for denial +    GAME_CLIENT_CONNECT,  // ( int clientNum, qboolean firstTime ); +    // return NULL if the client is allowed to connect, otherwise return +    // a text string with the reason for denial -  GAME_CLIENT_BEGIN,        // ( int clientNum ); +    GAME_CLIENT_BEGIN,  // ( int clientNum ); -  GAME_CLIENT_USERINFO_CHANGED, // ( int clientNum ); +    GAME_CLIENT_USERINFO_CHANGED,  // ( int clientNum ); -  GAME_CLIENT_DISCONNECT,     // ( int clientNum ); +    GAME_CLIENT_DISCONNECT,  // ( int clientNum ); -  GAME_CLIENT_COMMAND,      // ( int clientNum ); +    GAME_CLIENT_COMMAND,  // ( int clientNum ); -  GAME_CLIENT_THINK,        // ( int clientNum ); +    GAME_CLIENT_THINK,  // ( int clientNum ); -  GAME_RUN_FRAME,         // ( int levelTime ); +    GAME_RUN_FRAME,  // ( int levelTime ); -  GAME_CONSOLE_COMMAND      // ( void ); -  // ConsoleCommand will be called when a command has been issued -  // that is not recognized as a builtin function. -  // The game can issue trap_argc() / trap_argv() commands to get the command -  // and parameters.  Return qfalse if the game doesn't recognize it as a command. +    GAME_CONSOLE_COMMAND  // ( void ); +    // ConsoleCommand will be called when a command has been issued +    // that is not recognized as a builtin function. +    // The game can issue trap_argc() / trap_argv() commands to get the command +    // and parameters.  Return qfalse if the game doesn't recognize it as a command.  } gameExport_t; +#endif diff --git a/src/game/g_session.c b/src/game/g_session.c index ef78e8a..9063ce1 100644 --- a/src/game/g_session.c +++ b/src/game/g_session.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -46,20 +47,15 @@ void G_WriteClientSessionData( gclient_t *client )    const char  *s;    const char  *var; -  s = va( "%i %i %i %i %i %i %i %i %i %s", -    client->sess.sessionTeam, -    client->sess.restartTeam, +  s = va( "%i %i %i %i %s",      client->sess.spectatorTime,      client->sess.spectatorState,      client->sess.spectatorClient, -    client->sess.wins, -    client->sess.losses, -    client->sess.teamLeader, -    client->sess.invisible, -    BG_ClientListString( &client->sess.ignoreList ) +    client->sess.restartTeam, +    Com_ClientListString( &client->sess.ignoreList )      ); -  var = va( "session%i", client - level.clients ); +  var = va( "session%i", (int)( client - level.clients ) );    trap_Cvar_Set( var, s );  } @@ -73,40 +69,26 @@ Called on a reconnect  */  void G_ReadSessionData( gclient_t *client )  { -  char  s[ MAX_STRING_CHARS ]; +  char        s[ MAX_STRING_CHARS ];    const char  *var; +  int         spectatorState; +  int         restartTeam; +  char        ignorelist[ 17 ]; -  // bk001205 - format -  int teamLeader; -  int spectatorState; -  int sessionTeam; -  int restartTeam; -  int invisible; - -  var = va( "session%i", client - level.clients ); +  var = va( "session%i", (int)( client - level.clients ) );    trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); -  // FIXME: should be using BG_ClientListParse() for ignoreList, but -  //        bg_lib.c's sscanf() currently lacks %s -  sscanf( s, "%i %i %i %i %i %i %i %i %i %x%x", -    &sessionTeam, -    &restartTeam, +  sscanf( s, "%i %i %i %i %16s",      &client->sess.spectatorTime,      &spectatorState,      &client->sess.spectatorClient, -    &client->sess.wins, -    &client->sess.losses, -    &teamLeader, -    &invisible, -    &client->sess.ignoreList.hi, -    &client->sess.ignoreList.lo +    &restartTeam, +    ignorelist      ); -  // bk001205 - format issues -  client->sess.sessionTeam = (team_t)sessionTeam; -  client->sess.restartTeam = (pTeam_t)restartTeam; +    client->sess.spectatorState = (spectatorState_t)spectatorState; -  client->sess.teamLeader = (qboolean)teamLeader; -  client->sess.invisible = (qboolean)invisible; +  client->sess.restartTeam = (team_t)restartTeam; +  Com_ClientListParse( &client->sess.ignoreList, ignorelist );  } @@ -129,18 +111,18 @@ void G_InitSessionData( gclient_t *client, char *userinfo )    if( value[ 0 ] == 's' )    {      // a willing spectator, not a waiting-in-line -    sess->sessionTeam = TEAM_SPECTATOR; +    sess->spectatorState = SPECTATOR_FREE;    }    else    {      if( g_maxGameClients.integer > 0 &&        level.numNonSpectatorClients >= g_maxGameClients.integer ) -      sess->sessionTeam = TEAM_SPECTATOR; +      sess->spectatorState = SPECTATOR_FREE;      else -      sess->sessionTeam = TEAM_FREE; +      sess->spectatorState = SPECTATOR_NOT;    } -  sess->restartTeam = PTE_NONE; +  sess->restartTeam = TEAM_NONE;    sess->spectatorState = SPECTATOR_FREE;    sess->spectatorTime = level.time;    sess->spectatorClient = -1; @@ -160,7 +142,7 @@ void G_WriteSessionData( void )  {    int    i; -  //TA: ? +  //FIXME: What's this for?    trap_Cvar_Set( "session", va( "%i", 0 ) );    for( i = 0 ; i < level.maxclients ; i++ ) diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c index 028c39f..f7eea93 100644 --- a/src/game/g_spawn.c +++ b/src/game/g_spawn.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -31,6 +32,7 @@ qboolean G_SpawnString( const char *key, const char *defaultString, char **out )    {      *out = (char *)defaultString;  //    G_Error( "G_SpawnString() called while not spawning" ); +    return qfalse;    }    for( i = 0; i < level.numSpawnVars; i++ ) @@ -95,55 +97,45 @@ typedef enum  {    F_INT,    F_FLOAT, -  F_LSTRING,      // string on disk, pointer in memory, TAG_LEVEL -  F_GSTRING,      // string on disk, pointer in memory, TAG_GAME +  F_STRING,    F_VECTOR, -  F_VECTOR4,    //TA -  F_ANGLEHACK, -  F_ENTITY,     // index on disk, pointer in memory -  F_ITEM,       // index on disk, pointer in memory -  F_CLIENT,     // index on disk, pointer in memory -  F_IGNORE +  F_VECTOR4, +  F_ANGLEHACK  } fieldtype_t;  typedef struct  {    char  *name; -  int   ofs; +  size_t ofs;    fieldtype_t type; -  int   flags;  } field_t;  field_t fields[ ] =  { -  {"classname", FOFS(classname), F_LSTRING}, -  {"origin", FOFS(s.origin), F_VECTOR}, -  {"model", FOFS(model), F_LSTRING}, -  {"model2", FOFS(model2), F_LSTRING}, -  {"spawnflags", FOFS(spawnflags), F_INT}, -  {"speed", FOFS(speed), F_FLOAT}, -  {"target", FOFS(target), F_LSTRING}, -  {"targetname", FOFS(targetname), F_LSTRING}, -  {"message", FOFS(message), F_LSTRING}, -  {"team", FOFS(team), F_LSTRING}, -  {"wait", FOFS(wait), F_FLOAT}, -  {"random", FOFS(random), F_FLOAT}, +  {"acceleration", FOFS(acceleration), F_VECTOR}, +  {"alpha", FOFS(pos1), F_VECTOR}, +  {"angle", FOFS(s.apos.trBase), F_ANGLEHACK}, +  {"angles", FOFS(s.apos.trBase), F_VECTOR}, +  {"animation", FOFS(animation), F_VECTOR4}, +  {"bounce", FOFS(physicsBounce), F_FLOAT}, +  {"classname", FOFS(classname), F_STRING},    {"count", FOFS(count), F_INT}, -  {"health", FOFS(health), F_INT}, -  {"light", 0, F_IGNORE},    {"dmg", FOFS(damage), F_INT}, -  {"angles", FOFS(s.angles), F_VECTOR}, -  {"angle", FOFS(s.angles), F_ANGLEHACK}, -  {"bounce", FOFS(physicsBounce), F_FLOAT}, -  {"alpha", FOFS(pos1), F_VECTOR}, +  {"health", FOFS(health), F_INT}, +  {"message", FOFS(message), F_STRING}, +  {"model", FOFS(model), F_STRING}, +  {"model2", FOFS(model2), F_STRING}, +  {"origin", FOFS(s.pos.trBase), F_VECTOR},    {"radius", FOFS(pos2), F_VECTOR}, -  {"acceleration", FOFS(acceleration), F_VECTOR}, -  {"animation", FOFS(animation), F_VECTOR4}, +  {"random", FOFS(random), F_FLOAT},    {"rotatorAngle", FOFS(rotatorAngle), F_FLOAT}, -  {"targetShaderName", FOFS(targetShaderName), F_LSTRING}, -  {"targetShaderNewName", FOFS(targetShaderNewName), F_LSTRING}, - -  {NULL} +  {"spawnflags", FOFS(spawnflags), F_INT}, +  {"speed", FOFS(speed), F_FLOAT}, +  {"target", FOFS(target), F_STRING}, +  {"targetname", FOFS(targetname), F_STRING}, +  {"targetShaderName", FOFS(targetShaderName), F_STRING}, +  {"targetShaderNewName", FOFS(targetShaderNewName), F_STRING}, +  {"wait", FOFS(wait), F_FLOAT}  }; @@ -160,11 +152,6 @@ void SP_info_player_intermission( gentity_t *ent );  void SP_info_alien_intermission( gentity_t *ent );  void SP_info_human_intermission( gentity_t *ent ); -void SP_info_firstplace( gentity_t *ent ); -void SP_info_secondplace( gentity_t *ent ); -void SP_info_thirdplace( gentity_t *ent ); -void SP_info_podium( gentity_t *ent ); -  void SP_func_plat( gentity_t *ent );  void SP_func_static( gentity_t *ent );  void SP_func_rotating( gentity_t *ent ); @@ -194,7 +181,6 @@ void SP_trigger_ammo( gentity_t *ent );  void SP_target_delay( gentity_t *ent );  void SP_target_speaker( gentity_t *ent );  void SP_target_print( gentity_t *ent ); -void SP_target_character( gentity_t *ent );  void SP_target_score( gentity_t *ent );  void SP_target_teleporter( gentity_t *ent );  void SP_target_relay( gentity_t *ent ); @@ -210,7 +196,6 @@ void SP_target_hurt( gentity_t *ent );  void SP_light( gentity_t *self );  void SP_info_null( gentity_t *self );  void SP_info_notnull( gentity_t *self ); -void SP_info_camp( gentity_t *self );  void SP_path_corner( gentity_t *self );  void SP_misc_teleporter_dest( gentity_t *self ); @@ -218,41 +203,60 @@ void SP_misc_model( gentity_t *ent );  void SP_misc_portal_camera( gentity_t *ent );  void SP_misc_portal_surface( gentity_t *ent ); -void SP_shooter_rocket( gentity_t *ent ); -void SP_shooter_plasma( gentity_t *ent ); -void SP_shooter_grenade( gentity_t *ent ); -  void SP_misc_particle_system( gentity_t *ent );  void SP_misc_anim_model( gentity_t *ent );  void SP_misc_light_flare( gentity_t *ent );  spawn_t spawns[ ] =  { +  { "func_bobbing",             SP_func_bobbing }, +  { "func_button",              SP_func_button }, +  { "func_door",                SP_func_door }, +  { "func_door_model",          SP_func_door_model }, +  { "func_door_rotating",       SP_func_door_rotating }, +  { "func_group",               SP_info_null }, +  { "func_pendulum",            SP_func_pendulum }, +  { "func_plat",                SP_func_plat }, +  { "func_rotating",            SP_func_rotating }, +  { "func_static",              SP_func_static }, +  { "func_timer",               SP_func_timer },      // rename trigger_timer? +  { "func_train",               SP_func_train }, +    // info entities don't do anything at all, but provide positional    // information for things controlled by other processes -  { "info_player_start",        SP_info_player_start }, -  { "info_player_deathmatch",   SP_info_player_deathmatch }, -  { "info_player_intermission", SP_info_player_intermission }, - -  //TA: extra bits    { "info_alien_intermission",  SP_info_alien_intermission },    { "info_human_intermission",  SP_info_human_intermission }, - -  { "info_null",                SP_info_null },    { "info_notnull",             SP_info_notnull },    // use target_position instead +  { "info_null",                SP_info_null }, +  { "info_player_deathmatch",   SP_info_player_deathmatch }, +  { "info_player_intermission", SP_info_player_intermission }, +  { "info_player_start",        SP_info_player_start }, +  { "light",                    SP_light }, +  { "misc_anim_model",          SP_misc_anim_model }, +  { "misc_light_flare",         SP_misc_light_flare }, +  { "misc_model",               SP_misc_model }, +  { "misc_particle_system",     SP_misc_particle_system }, +  { "misc_portal_camera",       SP_misc_portal_camera }, +  { "misc_portal_surface",      SP_misc_portal_surface }, +  { "misc_teleporter_dest",     SP_misc_teleporter_dest }, +  { "path_corner",              SP_path_corner }, -  { "func_plat",                SP_func_plat }, -  { "func_button",              SP_func_button }, -  { "func_door",                SP_func_door }, -  { "func_door_rotating",       SP_func_door_rotating }, //TA -  { "func_door_model",          SP_func_door_model }, //TA -  { "func_static",              SP_func_static }, -  { "func_rotating",            SP_func_rotating }, -  { "func_bobbing",             SP_func_bobbing }, -  { "func_pendulum",            SP_func_pendulum }, -  { "func_train",               SP_func_train }, -  { "func_group",               SP_info_null }, -  { "func_timer",               SP_func_timer },      // rename trigger_timer? +  // targets perform no action by themselves, but must be triggered +  // by another entity +  { "target_alien_win",         SP_target_alien_win }, +  { "target_delay",             SP_target_delay }, +  { "target_human_win",         SP_target_human_win }, +  { "target_hurt",              SP_target_hurt }, +  { "target_kill",              SP_target_kill }, +  { "target_location",          SP_target_location }, +  { "target_position",          SP_target_position }, +  { "target_print",             SP_target_print }, +  { "target_push",              SP_target_push }, +  { "target_relay",             SP_target_relay }, +  { "target_rumble",            SP_target_rumble }, +  { "target_score",             SP_target_score }, +  { "target_speaker",           SP_target_speaker }, +  { "target_teleporter",        SP_target_teleporter },    // Triggers are brush objects that cause an effect when contacted    // by a living player, usually involving firing targets. @@ -260,49 +264,18 @@ spawn_t spawns[ ] =    // a single trigger class and different targets, triggered effects    // could not be client side predicted (push and teleport).    { "trigger_always",           SP_trigger_always }, -  { "trigger_multiple",         SP_trigger_multiple }, -  { "trigger_push",             SP_trigger_push }, -  { "trigger_teleport",         SP_trigger_teleport }, -  { "trigger_hurt",             SP_trigger_hurt }, -  { "trigger_stage",            SP_trigger_stage }, -  { "trigger_win",              SP_trigger_win }, +  { "trigger_ammo",             SP_trigger_ammo },    { "trigger_buildable",        SP_trigger_buildable },    { "trigger_class",            SP_trigger_class },    { "trigger_equipment",        SP_trigger_equipment },    { "trigger_gravity",          SP_trigger_gravity },    { "trigger_heal",             SP_trigger_heal }, -  { "trigger_ammo",             SP_trigger_ammo }, - -  // targets perform no action by themselves, but must be triggered -  // by another entity -  { "target_delay",             SP_target_delay }, -  { "target_speaker",           SP_target_speaker }, -  { "target_print",             SP_target_print }, -  { "target_score",             SP_target_score }, -  { "target_teleporter",        SP_target_teleporter }, -  { "target_relay",             SP_target_relay }, -  { "target_kill",              SP_target_kill }, -  { "target_position",          SP_target_position }, -  { "target_location",          SP_target_location }, -  { "target_push",              SP_target_push }, -  { "target_rumble",            SP_target_rumble }, -  { "target_alien_win",         SP_target_alien_win }, -  { "target_human_win",         SP_target_human_win }, -  { "target_hurt",              SP_target_hurt }, - -  { "light",                    SP_light }, -  { "path_corner",              SP_path_corner }, - -  { "misc_teleporter_dest",     SP_misc_teleporter_dest }, -  { "misc_model",               SP_misc_model }, -  { "misc_portal_surface",      SP_misc_portal_surface }, -  { "misc_portal_camera",       SP_misc_portal_camera }, - -  { "misc_particle_system",     SP_misc_particle_system }, -  { "misc_anim_model",          SP_misc_anim_model }, -  { "misc_light_flare",         SP_misc_light_flare }, - -  { NULL, 0 } +  { "trigger_hurt",             SP_trigger_hurt }, +  { "trigger_multiple",         SP_trigger_multiple }, +  { "trigger_push",             SP_trigger_push }, +  { "trigger_stage",            SP_trigger_stage }, +  { "trigger_teleport",         SP_trigger_teleport }, +  { "trigger_win",              SP_trigger_win }  };  /* @@ -325,16 +298,18 @@ qboolean G_CallSpawn( gentity_t *ent )    }    //check buildable spawn functions -  if( ( buildable = BG_FindBuildNumForEntityName( ent->classname ) ) != BA_NONE ) +  buildable = BG_BuildableByEntityName( ent->classname )->number; +  if( buildable != BA_NONE )    {      // don't spawn built-in buildings if we are using a custom layout      if( level.layout[ 0 ] && Q_stricmp( level.layout, "*BUILTIN*" ) ) -      return qtrue; +      return qfalse;      if( buildable == BA_A_SPAWN || buildable == BA_H_SPAWN )      { -      ent->s.angles[ YAW ] += 180.0f; -      AngleNormalize360( ent->s.angles[ YAW ] ); +      ent->r.currentAngles[ YAW ] += 180.0f; +      AngleNormalize360( ent->r.currentAngles[ YAW ] ); +      ent->s.apos.trBase[ YAW ] = ent->r.currentAngles[ YAW ];      }      G_SpawnBuildable( ent, buildable ); @@ -342,14 +317,13 @@ qboolean G_CallSpawn( gentity_t *ent )    }    // check normal spawn functions -  for( s = spawns; s->name; s++ ) +  s = bsearch( ent->classname, spawns, ARRAY_LEN( spawns ), +    sizeof( spawn_t ), cmdcmp ); +  if( s )    { -    if( !strcmp( s->name, ent->classname ) ) -    { -      // found it -      s->spawn( ent ); -      return qtrue; -    } +    // found it +    s->spawn( ent ); +    return qtrue;    }    G_Printf( "%s doesn't have a spawn function\n", ent->classname ); @@ -371,7 +345,7 @@ char *G_NewString( const char *string )    l = strlen( string ) + 1; -  newb = G_Alloc( l ); +  newb = BG_Alloc( l );    new_p = newb; @@ -412,58 +386,49 @@ void G_ParseField( const char *key, const char *value, gentity_t *ent )    vec3_t  vec;    vec4_t  vec4; -  for( f = fields; f->name; f++ ) +  f = bsearch( key, fields, ARRAY_LEN( fields ), +    sizeof( field_t ), cmdcmp ); +  if( !f ) +    return; +  b = (byte *)ent; + +  switch( f->type )    { -    if( !Q_stricmp( f->name, key ) ) -    { -      // found it -      b = (byte *)ent; - -      switch( f->type ) -      { -        case F_LSTRING: -          *(char **)( b + f->ofs ) = G_NewString( value ); -          break; - -        case F_VECTOR: -          sscanf( value, "%f %f %f", &vec[ 0 ], &vec[ 1 ], &vec[ 2 ] ); - -          ( (float *)( b + f->ofs ) )[ 0 ] = vec[ 0 ]; -          ( (float *)( b + f->ofs ) )[ 1 ] = vec[ 1 ]; -          ( (float *)( b + f->ofs ) )[ 2 ] = vec[ 2 ]; -          break; - -        case F_VECTOR4: -          sscanf( value, "%f %f %f %f", &vec4[ 0 ], &vec4[ 1 ], &vec4[ 2 ], &vec4[ 3 ] ); - -          ( (float *)( b + f->ofs ) )[ 0 ] = vec4[ 0 ]; -          ( (float *)( b + f->ofs ) )[ 1 ] = vec4[ 1 ]; -          ( (float *)( b + f->ofs ) )[ 2 ] = vec4[ 2 ]; -          ( (float *)( b + f->ofs ) )[ 3 ] = vec4[ 3 ]; -          break; - -        case F_INT: -          *(int *)( b + f->ofs ) = atoi( value ); -          break; - -        case F_FLOAT: -          *(float *)( b + f->ofs ) = atof( value ); -          break; - -        case F_ANGLEHACK: -          v = atof( value ); -          ( (float *)( b + f->ofs ) )[ 0 ] = 0; -          ( (float *)( b + f->ofs ) )[ 1 ] = v; -          ( (float *)( b + f->ofs ) )[ 2 ] = 0; -          break; - -        default: -        case F_IGNORE: -          break; -      } - -      return; -    } +    case F_STRING: +      *(char **)( b + f->ofs ) = G_NewString( value ); +      break; + +    case F_VECTOR: +      sscanf( value, "%f %f %f", &vec[ 0 ], &vec[ 1 ], &vec[ 2 ] ); + +      ( (float *)( b + f->ofs ) )[ 0 ] = vec[ 0 ]; +      ( (float *)( b + f->ofs ) )[ 1 ] = vec[ 1 ]; +      ( (float *)( b + f->ofs ) )[ 2 ] = vec[ 2 ]; +      break; + +    case F_VECTOR4: +      sscanf( value, "%f %f %f %f", &vec4[ 0 ], &vec4[ 1 ], &vec4[ 2 ], &vec4[ 3 ] ); + +      ( (float *)( b + f->ofs ) )[ 0 ] = vec4[ 0 ]; +      ( (float *)( b + f->ofs ) )[ 1 ] = vec4[ 1 ]; +      ( (float *)( b + f->ofs ) )[ 2 ] = vec4[ 2 ]; +      ( (float *)( b + f->ofs ) )[ 3 ] = vec4[ 3 ]; +      break; + +    case F_INT: +      *(int *)( b + f->ofs ) = atoi( value ); +      break; + +    case F_FLOAT: +      *(float *)( b + f->ofs ) = atof( value ); +      break; + +    case F_ANGLEHACK: +      v = atof( value ); +      ( (float *)( b + f->ofs ) )[ 0 ] = 0; +      ( (float *)( b + f->ofs ) )[ 1 ] = v; +      ( (float *)( b + f->ofs ) )[ 2 ] = 0; +      break;    }  } @@ -497,9 +462,8 @@ void G_SpawnGEntityFromSpawnVars( void )      return;    } -  // move editor origin to pos -  VectorCopy( ent->s.origin, ent->s.pos.trBase ); -  VectorCopy( ent->s.origin, ent->r.currentOrigin ); +  VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); +  VectorCopy( ent->s.apos.trBase, ent->r.currentAngles );    // if we didn't get a classname, don't bother spawning anything    if( !G_CallSpawn( ent ) ) @@ -617,38 +581,23 @@ void SP_worldspawn( void )    trap_SetConfigstring( CS_MOTD, g_motd.string );   // message of the day -  G_SpawnString( "gravity", "800", &s ); -  trap_Cvar_Set( "g_gravity", s ); - -  G_SpawnString( "humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, &s ); -  trap_Cvar_Set( "g_humanBuildPoints", s ); - -  G_SpawnString( "humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, &s ); -  trap_Cvar_Set( "g_humanMaxStage", s ); +  if( G_SpawnString( "gravity", "", &s ) ) +    trap_Cvar_Set( "g_gravity", s ); -  G_SpawnString( "humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, &s ); -  trap_Cvar_Set( "g_humanStage2Threshold", s ); +  if( G_SpawnString( "humanMaxStage", "", &s ) ) +    trap_Cvar_Set( "g_humanMaxStage", s ); -  G_SpawnString( "humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, &s ); -  trap_Cvar_Set( "g_humanStage3Threshold", s ); +  if( G_SpawnString( "alienMaxStage", "", &s ) ) +    trap_Cvar_Set( "g_alienMaxStage", s ); -  G_SpawnString( "alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, &s ); -  trap_Cvar_Set( "g_alienBuildPoints", s ); +  if( G_SpawnString( "humanRepeaterBuildPoints", "", &s ) ) +    trap_Cvar_Set( "g_humanRepeaterBuildPoints", s ); -  G_SpawnString( "alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, &s ); -  trap_Cvar_Set( "g_alienMaxStage", s ); +  if( G_SpawnString( "humanBuildPoints", "", &s ) ) +    trap_Cvar_Set( "g_humanBuildPoints", s ); -  G_SpawnString( "alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, &s ); -  trap_Cvar_Set( "g_alienStage2Threshold", s ); - -  G_SpawnString( "alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, &s ); -  trap_Cvar_Set( "g_alienStage3Threshold", s ); - -  G_SpawnString( "enableDust", "0", &s ); -  trap_Cvar_Set( "g_enableDust", s ); - -  G_SpawnString( "enableBreath", "0", &s ); -  trap_Cvar_Set( "g_enableBreath", s ); +  if( G_SpawnString( "alienBuildPoints", "", &s ) ) +    trap_Cvar_Set( "g_alienBuildPoints", s );    G_SpawnString( "disabledEquipment", "", &s );    trap_Cvar_Set( "g_disabledEquipment", s ); @@ -660,11 +609,25 @@ void SP_worldspawn( void )    trap_Cvar_Set( "g_disabledBuildables", s );    g_entities[ ENTITYNUM_WORLD ].s.number = ENTITYNUM_WORLD; +  g_entities[ ENTITYNUM_WORLD ].r.ownerNum = ENTITYNUM_NONE;    g_entities[ ENTITYNUM_WORLD ].classname = "worldspawn"; +  g_entities[ ENTITYNUM_NONE ].s.number = ENTITYNUM_NONE; +  g_entities[ ENTITYNUM_NONE ].r.ownerNum = ENTITYNUM_NONE; +  g_entities[ ENTITYNUM_NONE ].classname = "nothing"; +    if( g_restarted.integer )      trap_Cvar_Set( "g_restarted", "0" ); +  // see if we want a warmup time +  trap_SetConfigstring( CS_WARMUP, "-1" ); +  if( g_doWarmup.integer ) +  { +    level.warmupTime = level.time - level.startTime + ( g_warmup.integer * 1000 ); +    trap_SetConfigstring( CS_WARMUP, va( "%i", level.warmupTime ) ); +    G_LogPrintf( "Warmup: %i\n", g_warmup.integer ); +  } +  } @@ -677,8 +640,6 @@ Parses textual entity definitions out of an entstring and spawns gentities.  */  void G_SpawnEntitiesFromString( void )  { -  // allow calls to G_Spawn*() -  level.spawning = qtrue;    level.numSpawnVars = 0;    // the worldspawn is not an actual entity, but it still @@ -692,7 +653,4 @@ void G_SpawnEntitiesFromString( void )    // parse ents    while( G_ParseSpawnVars( ) )      G_SpawnGEntityFromSpawnVars( ); - -  level.spawning = qfalse;      // any future calls to G_Spawn*() will be errors  } - diff --git a/src/game/g_svcmds.c b/src/game/g_svcmds.c index f0d45b1..3760fdd 100644 --- a/src/game/g_svcmds.c +++ b/src/game/g_svcmds.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -25,315 +26,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #include "g_local.h" - -/* -============================================================================== - -PACKET FILTERING - - -You can add or remove addresses from the filter list with: - -addip <ip> -removeip <ip> - -The ip address is specified in dot format, and you can use '*' to match any value -so you can specify an entire class C network with "addip 192.246.40.*" - -Removeip will only remove an address specified exactly the same way.  You cannot addip a subnet, then removeip a single host. - -listip -Prints the current list of filters. - -g_filterban <0 or 1> - -If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game.  This is the default setting. - -If 0, then only addresses matching the list will be allowed.  This lets you easily set up a private game, or a game that only allows players from your local network. - -TTimo NOTE: for persistence, bans are stored in g_banIPs cvar MAX_CVAR_VALUE_STRING -The size of the cvar string buffer is limiting the banning to around 20 masks -this could be improved by putting some g_banIPs2 g_banIps3 etc. maybe -still, you should rely on PB for banning instead - -============================================================================== -*/ - -// extern vmCvar_t  g_banIPs; -// extern vmCvar_t  g_filterBan; - - -typedef struct ipFilter_s -{ -  unsigned  mask; -  unsigned  compare; -} ipFilter_t; - -#define MAX_IPFILTERS 1024 - -static ipFilter_t ipFilters[ MAX_IPFILTERS ]; -static int        numIPFilters; - -/* -================= -StringToFilter -================= -*/ -static qboolean StringToFilter( char *s, ipFilter_t *f ) -{ -  char  num[ 128 ]; -  int   i, j; -  byte  b[ 4 ]; -  byte  m[ 4 ]; - -  for( i = 0; i < 4; i++ ) -  { -    b[ i ] = 0; -    m[ i ] = 0; -  } - -  for( i = 0; i < 4; i++ ) -  { -    if( *s < '0' || *s > '9' ) -    { -      if( *s == '*' ) // 'match any' -      { -        //b[ i ] and m[ i ] to 0 -        s++; -        if ( !*s ) -          break; - -        s++; -        continue; -      } - -      G_Printf( "Bad filter address: %s\n", s ); -      return qfalse; -    } - -    j = 0; -    while( *s >= '0' && *s <= '9' ) -      num[ j++ ] = *s++; - -    num[ j ] = 0; -    b[ i ] = atoi( num ); - -    m[ i ] = 255; - -    if( !*s ) -      break; - -    s++; -  } - -  f->mask = *(unsigned *)m; -  f->compare = *(unsigned *)b; - -  return qtrue; -} - -/* -================= -UpdateIPBans -================= -*/ -static void UpdateIPBans( void ) -{ -  byte  b[ 4 ]; -  byte  m[ 4 ]; -  int    i, j; -  char  iplist_final[ MAX_CVAR_VALUE_STRING ]; -  char  ip[ 64 ]; - -  *iplist_final = 0; - -  for( i = 0 ; i < numIPFilters ; i++ ) -  { -    if( ipFilters[ i ].compare == 0xffffffff ) -      continue; - -    *(unsigned *)b = ipFilters[ i ].compare; -    *(unsigned *)m = ipFilters[ i ].mask; -    *ip = 0; - -    for( j = 0 ; j < 4 ; j++ ) -    { -      if( m[ j ] != 255 ) -        Q_strcat( ip, sizeof( ip ), "*" ); -      else -        Q_strcat( ip, sizeof( ip ), va( "%i", b[ j ] ) ); - -      Q_strcat( ip, sizeof( ip ), ( j < 3 ) ? "." : " " ); -    } - -    if( strlen( iplist_final ) + strlen( ip ) < MAX_CVAR_VALUE_STRING ) -      Q_strcat( iplist_final, sizeof( iplist_final ), ip ); -    else -    { -      Com_Printf( "g_banIPs overflowed at MAX_CVAR_VALUE_STRING\n" ); -      break; -    } -  } - -  trap_Cvar_Set( "g_banIPs", iplist_final ); -} - -/* -================= -G_FilterPacket -================= -*/ -qboolean G_FilterPacket( char *from ) -{ -  int       i; -  unsigned  in; -  byte      m[ 4 ]; -  char      *p; - -  i = 0; -  p = from; -  while( *p && i < 4 ) -  { -    m[ i ] = 0; -    while( *p >= '0' && *p <= '9' ) -    { -      m[ i ] = m[ i ] * 10 + ( *p - '0' ); -      p++; -    } - -    if( !*p || *p == ':' ) -      break; - -    i++, p++; -  } - -  in = *(unsigned *)m; - -  for( i = 0; i < numIPFilters; i++ ) -    if( ( in & ipFilters[ i ].mask ) == ipFilters[ i ].compare ) -      return g_filterBan.integer != 0; - -  return g_filterBan.integer == 0; -} - -/* -================= -AddIP -================= -*/ -static void AddIP( char *str ) -{ -  int   i; - -  for( i = 0 ; i < numIPFilters ; i++ ) -    if( ipFilters[ i ].compare == 0xffffffff ) -      break;    // free spot - -  if( i == numIPFilters ) -  { -    if( numIPFilters == MAX_IPFILTERS ) -    { -      G_Printf( "IP filter list is full\n" ); -      return; -    } - -    numIPFilters++; -  } - -  if( !StringToFilter( str, &ipFilters[ i ] ) ) -    ipFilters[ i ].compare = 0xffffffffu; - -  UpdateIPBans( ); -} - -/* -================= -G_ProcessIPBans -================= -*/ -void G_ProcessIPBans( void ) -{ -  char *s, *t; -  char str[ MAX_CVAR_VALUE_STRING ]; - -  Q_strncpyz( str, g_banIPs.string, sizeof( str ) ); - -  for( t = s = g_banIPs.string; *t; /* */ ) -  { -    s = strchr( s, ' ' ); - -    if( !s ) -      break; - -    while( *s == ' ' ) -      *s++ = 0; - -    if( *t ) -      AddIP( t ); - -    t = s; -  } -} - - -/* -================= -Svcmd_AddIP_f -================= -*/ -void Svcmd_AddIP_f( void ) -{ -  char str[ MAX_TOKEN_CHARS ]; - -  if( trap_Argc( ) < 2 ) -  { -    G_Printf( "Usage:  addip <ip-mask>\n" ); -    return; -  } - -  trap_Argv( 1, str, sizeof( str ) ); - -  AddIP( str ); -} - -/* -================= -Svcmd_RemoveIP_f -================= -*/ -void Svcmd_RemoveIP_f( void ) -{ -  ipFilter_t  f; -  int         i; -  char        str[ MAX_TOKEN_CHARS ]; - -  if( trap_Argc( ) < 2 ) -  { -    G_Printf( "Usage:  sv removeip <ip-mask>\n" ); -    return; -  } - -  trap_Argv( 1, str, sizeof( str ) ); - -  if( !StringToFilter( str, &f ) ) -    return; - -  for( i = 0; i < numIPFilters; i++ ) -  { -    if( ipFilters[ i ].mask == f.mask && -        ipFilters[ i ].compare == f.compare) -    { -      ipFilters[ i ].compare = 0xffffffffu; -      G_Printf ( "Removed.\n" ); - -      UpdateIPBans( ); -      return; -    } -  } - -  G_Printf ( "Didn't find %s.\n", str ); -} -  /*  ===================  Svcmd_EntityList_f @@ -367,6 +59,12 @@ void  Svcmd_EntityList_f( void )        case ET_BUILDABLE:          G_Printf( "ET_BUILDABLE        " );          break; +      case ET_RANGE_MARKER: +        G_Printf( "ET_RANGE_MARKER     " ); +        break; +      case ET_LOCATION: +        G_Printf( "ET_LOCATION         " ); +        break;        case ET_MISSILE:          G_Printf( "ET_MISSILE          " );          break; @@ -394,8 +92,26 @@ void  Svcmd_EntityList_f( void )        case ET_GRAPPLE:          G_Printf( "ET_GRAPPLE          " );          break; +      case ET_CORPSE: +        G_Printf( "ET_CORPSE           " ); +        break; +      case ET_PARTICLE_SYSTEM: +        G_Printf( "ET_PARTICLE_SYSTEM  " ); +        break; +      case ET_ANIMMAPOBJ: +        G_Printf( "ET_ANIMMAPOBJ       " ); +        break; +      case ET_MODELDOOR: +        G_Printf( "ET_MODELDOOR        " ); +        break; +      case ET_LIGHTFLARE: +        G_Printf( "ET_LIGHTFLARE       " ); +        break; +      case ET_LEV2_ZAP_CHAIN: +        G_Printf( "ET_LEV2_ZAP_CHAIN   " ); +        break;        default: -        G_Printf( "%3i                 ", check->s.eType ); +        G_Printf( "%-3i                 ", check->s.eType );          break;      } @@ -406,48 +122,52 @@ void  Svcmd_EntityList_f( void )    }  } -gclient_t *ClientForString( const char *s ) +static gclient_t *ClientForString( char *s )  { -  gclient_t *cl; -  int       i; -  int       idnum; +  int  idnum; +  char err[ MAX_STRING_CHARS ]; -  // numeric values are just slot numbers -  if( s[ 0 ] >= '0' && s[ 0 ] <= '9' ) +  idnum = G_ClientNumberFromString( s, err, sizeof( err ) ); +  if( idnum == -1 )    { -    idnum = atoi( s ); - -    if( idnum < 0 || idnum >= level.maxclients ) -    { -      Com_Printf( "Bad client slot: %i\n", idnum ); -      return NULL; -    } - -    cl = &level.clients[ idnum ]; +    G_Printf( "%s", err ); +    return NULL; +  } -    if( cl->pers.connected == CON_DISCONNECTED ) -    { -      G_Printf( "Client %i is not connected\n", idnum ); -      return NULL; -    } +  return &level.clients[ idnum ]; +} -    return cl; -  } +static void Svcmd_Status_f( void ) +{ +  int       i; +  gclient_t *cl; +  char      userinfo[ MAX_INFO_STRING ]; -  // check for a name match -  for( i = 0; i < level.maxclients; i++ ) +  G_Printf( "slot score ping address               rate     name\n" ); +  G_Printf( "---- ----- ---- -------               ----     ----\n" ); +  for( i = 0, cl = level.clients; i < level.maxclients; i++, cl++ )    { -    cl = &level.clients[ i ];      if( cl->pers.connected == CON_DISCONNECTED )        continue; -    if( !Q_stricmp( cl->pers.netname, s ) ) -      return cl; -  } +    G_Printf( "%-4d ", i ); +    G_Printf( "%-5d ", cl->ps.persistant[ PERS_SCORE ] ); -  G_Printf( "User %s is not on the server\n", s ); +    if( cl->pers.connected == CON_CONNECTING ) +      G_Printf( "CNCT " ); +    else +      G_Printf( "%-4d ", cl->ps.ping ); -  return NULL; +    trap_GetUserinfo( i, userinfo, sizeof( userinfo ) ); +    G_Printf( "%-21s ", Info_ValueForKey( userinfo, "ip" ) ); +    G_Printf( "%-8d ", atoi( Info_ValueForKey( userinfo, "rate" ) ) ); +    G_Printf( "%s\n", cl->pers.netname ); // Info_ValueForKey( userinfo, "name" ) +  } +} + +static void Svcmd_SMR_f( void ) +{ +  G_Printf( "unrecognized Schachtmeister response: %s\n", ConcatArgs( 1 ) );  }  /* @@ -457,22 +177,32 @@ Svcmd_ForceTeam_f  forceteam <player> <team>  ===================  */ -void  Svcmd_ForceTeam_f( void ) +static void Svcmd_ForceTeam_f( void )  {    gclient_t *cl;    char      str[ MAX_TOKEN_CHARS ]; +  team_t    team; + +  if( trap_Argc( ) != 3 ) +  { +    G_Printf( "usage: forceteam <player> <team>\n" ); +    return; +  } -  // find the player    trap_Argv( 1, str, sizeof( str ) );    cl = ClientForString( str );    if( !cl )      return; -  // set the team    trap_Argv( 2, str, sizeof( str ) ); -  /*SetTeam( &g_entities[cl - level.clients], str );*/ -  //FIXME: tremulise this +  team = G_TeamFromString( str ); +  if( team == NUM_TEAMS ) +  { +    G_Printf( "forceteam: invalid team \"%s\"\n", str ); +    return; +  } +  G_ChangeTeam( &g_entities[ cl - level.clients ], team );  }  /* @@ -482,37 +212,40 @@ Svcmd_LayoutSave_f  layoutsave <name>  ===================  */ -void  Svcmd_LayoutSave_f( void ) +static void Svcmd_LayoutSave_f( void )  {    char str[ MAX_QPATH ];    char str2[ MAX_QPATH - 4 ];    char *s;    int i = 0; +  qboolean pipeEncountered = qfalse;    if( trap_Argc( ) != 2 )    { -    G_Printf( "usage: layoutsave LAYOUTNAME\n" ); +    G_Printf( "usage: layoutsave <name>\n" );      return;    }    trap_Argv( 1, str, sizeof( str ) );    // sanitize name +  str2[ 0 ] = '\0';    s = &str[ 0 ];    while( *s && i < sizeof( str2 ) - 1 )    { -    if( ( *s >= '0' && *s <= '9' ) || -      ( *s >= 'a' && *s <= 'z' ) || -      ( *s >= 'A' && *s <= 'Z' ) || *s == '-' || *s == '_' ) +    if( isalnum( *s ) || *s == '-' || *s == '_' || +        ( ( *s == '|' || *s == ',' ) && !pipeEncountered ) )      {        str2[ i++ ] = *s;        str2[ i ] = '\0'; +      if( *s == '|' ) +        pipeEncountered = qtrue;      }      s++;    }    if( !str2[ 0 ] )    { -    G_Printf("layoutsave: invalid name \"%s\"\n", str ); +    G_Printf( "layoutsave: invalid name \"%s\"\n", str );      return;    } @@ -528,18 +261,27 @@ Svcmd_LayoutLoad_f  layoutload [<name> [<name2> [<name3 [...]]]]  This is just a silly alias for doing: - set g_layouts "name name2 name3" + set g_nextLayout "name name2 name3"   map_restart  ===================  */ -void  Svcmd_LayoutLoad_f( void ) +static void Svcmd_LayoutLoad_f( void )  {    char layouts[ MAX_CVAR_VALUE_STRING ]; +  char map[ MAX_CVAR_VALUE_STRING ];    char *s; +  if( trap_Argc( ) < 2 ) +  { +    G_Printf( "usage: layoutload <name> ...\n" ); +    return; +  } +    s = ConcatArgs( 1 );    Q_strncpyz( layouts, s, sizeof( layouts ) ); -  trap_Cvar_Set( "g_layouts", layouts );  +  trap_Cvar_Set( "g_nextLayout", layouts ); +  trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); +  G_MapConfigs( map );    trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" );    level.restarted = qtrue;  } @@ -555,225 +297,363 @@ static void Svcmd_AdmitDefeat_f( void )      return;    }    trap_Argv( 1, teamNum, sizeof( teamNum ) ); -  team = atoi( teamNum ); -  if( team == PTE_ALIENS || teamNum[ 0 ] == 'a' ) +  team = G_TeamFromString( teamNum ); +  if( team == TEAM_ALIENS )    { -    level.surrenderTeam = PTE_ALIENS; -    G_BaseSelfDestruct( PTE_ALIENS ); -    G_TeamCommand( PTE_ALIENS, "cp \"Hivemind Link Broken\" 1"); +    G_TeamCommand( TEAM_ALIENS, "cp \"Hivemind Link Broken\" 1");      trap_SendServerCommand( -1, "print \"Alien team has admitted defeat\n\"" );    } -  else if( team == PTE_HUMANS || teamNum[ 0 ] == 'h' ) +  else if( team == TEAM_HUMANS )    { -    level.surrenderTeam = PTE_HUMANS; -    G_BaseSelfDestruct( PTE_HUMANS ); -    G_TeamCommand( PTE_HUMANS, "cp \"Life Support Terminated\" 1"); +    G_TeamCommand( TEAM_HUMANS, "cp \"Life Support Terminated\" 1");      trap_SendServerCommand( -1, "print \"Human team has admitted defeat\n\"" );    }    else    {      G_Printf("admitdefeat: invalid team\n"); -  }  +    return; +  } +  level.surrenderTeam = team; +  G_BaseSelfDestruct( team );  } -/* -================= -ConsoleCommand - -================= -*/ -qboolean  ConsoleCommand( void ) +static void Svcmd_TeamWin_f( void )  { -  char cmd[ MAX_TOKEN_CHARS ]; - +  // this is largely made redundant by admitdefeat <team> +  char cmd[ 6 ];    trap_Argv( 0, cmd, sizeof( cmd ) ); -  if( Q_stricmp( cmd, "entitylist" ) == 0 ) +  switch( G_TeamFromString( cmd ) )    { -    Svcmd_EntityList_f( ); -    return qtrue; -  } +    case TEAM_ALIENS: +      G_BaseSelfDestruct( TEAM_HUMANS ); +      break; -  if( Q_stricmp( cmd, "forceteam" ) == 0 ) -  { -    Svcmd_ForceTeam_f( ); -    return qtrue; -  } +    case TEAM_HUMANS: +      G_BaseSelfDestruct( TEAM_ALIENS ); +      break; -  if( Q_stricmp( cmd, "game_memory" ) == 0 ) -  { -    Svcmd_GameMem_f( ); -    return qtrue; +    default: +      return;    } +} -  if( Q_stricmp( cmd, "addip" ) == 0 ) -  { -    Svcmd_AddIP_f( ); -    return qtrue; -  } +static void Svcmd_Evacuation_f( void ) +{ +  if( level.exited ) +    return; +  trap_SendServerCommand( -1, "print \"Evacuation ordered\n\"" ); +  level.lastWin = TEAM_NONE; +  trap_SetConfigstring( CS_WINNER, "Evacuation" ); +  LogExit( "Evacuation." ); +} + +static void Svcmd_MapRotation_f( void ) +{ +  char rotationName[ MAX_QPATH ]; -  if( Q_stricmp( cmd, "removeip" ) == 0 ) +  if( trap_Argc( ) != 2 )    { -    Svcmd_RemoveIP_f( ); -    return qtrue; +    G_Printf( "usage: maprotation <name>\n" ); +    return;    } -  if( Q_stricmp( cmd, "listip" ) == 0 ) +  G_ClearRotationStack( ); + +  trap_Argv( 1, rotationName, sizeof( rotationName ) ); +  if( !G_StartMapRotation( rotationName, qfalse, qtrue, qfalse, 0 ) ) +    G_Printf( "maprotation: invalid map rotation \"%s\"\n", rotationName ); +} + +static void Svcmd_TeamMessage_f( void ) +{ +  char   teamNum[ 2 ]; +  team_t team; + +  if( trap_Argc( ) < 3 )    { -    trap_SendConsoleCommand( EXEC_NOW, "g_banIPs\n" ); -    return qtrue; +    G_Printf( "usage: say_team <team> <message>\n" ); +    return;    } -  if( Q_stricmp( cmd, "mapRotation" ) == 0 ) +  trap_Argv( 1, teamNum, sizeof( teamNum ) ); +  team = G_TeamFromString( teamNum ); + +  if( team == NUM_TEAMS )    { -    char *rotationName = ConcatArgs( 1 ); +    G_Printf( "say_team: invalid team \"%s\"\n", teamNum ); +    return; +  } -    if( !G_StartMapRotation( rotationName, qfalse ) ) -      G_Printf( "Can't find map rotation %s\n", rotationName ); +  G_TeamCommand( team, va( "chat -1 %d \"%s\"", SAY_TEAM, ConcatArgs( 2 ) ) ); +  G_LogPrintf( "SayTeam: -1 \"console\": %s\n", ConcatArgs( 2 ) ); +} -    return qtrue; +static void Svcmd_CenterPrint_f( void ) +{ +  if( trap_Argc( ) < 2 ) +  { +    G_Printf( "usage: cp <message>\n" ); +    return;    } -  if( Q_stricmp( cmd, "stopMapRotation" ) == 0 ) -  { -    G_StopMapRotation( ); +  trap_SendServerCommand( -1, va( "cp \"%s\"", ConcatArgs( 1 ) ) ); +} -    return qtrue; -  } +static void Svcmd_EjectClient_f( void ) +{ +  char *reason, name[ MAX_STRING_CHARS ]; -  if( Q_stricmp( cmd, "advanceMapRotation" ) == 0 ) +  if( trap_Argc( ) < 2 )    { -    G_AdvanceMapRotation( ); - -    return qtrue; +    G_Printf( "usage: eject <player|-1> <reason>\n" ); +    return;    } -  if( Q_stricmp( cmd, "alienWin" ) == 0 ) -  { -    int       i; -    gentity_t *e; +  trap_Argv( 1, name, sizeof( name ) ); +  reason = ConcatArgs( 2 ); -    for( i = 1, e = g_entities + i; i < level.num_entities; i++, e++ ) +  if( atoi( name ) == -1 ) +  { +    int i; +    for( i = 0; i < level.maxclients; i++ )      { -      if( e->s.modelindex == BA_H_SPAWN ) -        G_Damage( e, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); +      if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) +        continue; +      if( level.clients[ i ].pers.localClient ) +        continue; +      trap_DropClient( i, reason );      } - -    return qtrue;    } - -  if( Q_stricmp( cmd, "humanWin" ) == 0 ) +  else    { -    int       i; -    gentity_t *e; - -    for( i = 1, e = g_entities + i; i < level.num_entities; i++, e++ ) +    gclient_t *cl = ClientForString( name ); +    if( !cl ) +      return; +    if( cl->pers.localClient )      { -      if( e->s.modelindex == BA_A_SPAWN ) -        G_Damage( e, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); +      G_Printf( "eject: cannot eject local clients\n" ); +      return;      } +    trap_DropClient( cl-level.clients, reason ); +  } +} -    return qtrue; +static void Svcmd_DumpUser_f( void ) +{ +  char name[ MAX_STRING_CHARS ], userinfo[ MAX_INFO_STRING ]; +  char key[ BIG_INFO_KEY ], value[ BIG_INFO_VALUE ]; +  const char *info; +  gclient_t *cl; + +  if( trap_Argc( ) != 2 ) +  { +    G_Printf( "usage: dumpuser <player>\n" ); +    return;    } -  if( !Q_stricmp( cmd, "layoutsave" ) ) +  trap_Argv( 1, name, sizeof( name ) ); +  cl = ClientForString( name ); +  if( !cl ) +    return; + +  trap_GetUserinfo( cl-level.clients, userinfo, sizeof( userinfo ) ); +  info = &userinfo[ 0 ]; +  G_Printf( "userinfo\n--------\n" ); +  //Info_Print( userinfo ); +  while( 1 )    { -    Svcmd_LayoutSave_f( ); -    return qtrue; +    Info_NextPair( &info, key, value ); +    if( !*info ) +      return; + +    G_Printf( "%-20s%s\n", key, value );    } -   -  if( !Q_stricmp( cmd, "layoutload" ) ) +} + +static void Svcmd_Pr_f( void ) +{ +  char targ[ 4 ]; +  int cl; + +  if( trap_Argc( ) < 3 )    { -    Svcmd_LayoutLoad_f( ); -    return qtrue; +    G_Printf( "usage: <clientnum|-1> <message>\n" ); +    return;    } -   -  if( !Q_stricmp( cmd, "admitdefeat" ) ) + +  trap_Argv( 1, targ, sizeof( targ ) ); +  cl = atoi( targ ); + +  if( cl >= MAX_CLIENTS || cl < -1 )    { -    Svcmd_AdmitDefeat_f( ); -    return qtrue; +    G_Printf( "invalid clientnum %d\n", cl ); +    return;    } -  if( !Q_stricmp( cmd, "evacuation" ) ) +  trap_SendServerCommand( cl, va( "print \"%s\n\"", ConcatArgs( 2 ) ) ); +} + +static void Svcmd_PrintQueue_f( void ) +{ +  char team[ MAX_STRING_CHARS ]; + +  if( trap_Argc() != 2 )    { -    trap_SendServerCommand( -1, "print \"Evacuation ordered\n\"" ); -    level.lastWin = PTE_NONE; -    trap_SetConfigstring( CS_WINNER, "Evacuation" ); -    LogExit( "Evacuation." ); -    G_admin_maplog_result( "d" ); -    return qtrue; +    G_Printf( "usage: printqueue <team>\n" ); +    return;    } -  if( !Q_stricmp( cmd, "smr" ) ) +  trap_Argv( 1, team, sizeof( team ) ); + +  switch( G_TeamFromString( team ) )    { -    if( trap_Argc() >= 2 ) -    { -      char arg[ 32 ]; -      trap_Argv( 1, arg, sizeof( arg ) ); +    case TEAM_ALIENS: +      G_PrintSpawnQueue( &level.alienSpawnQueue ); +      break; -      if( !Q_stricmp( arg, "ipa" ) && trap_Argc() >= 4 ) -      { -        int rating; -        const char *comment = NULL; +    case TEAM_HUMANS: +      G_PrintSpawnQueue( &level.humanSpawnQueue ); +      break; -        trap_Argv( 3, arg, sizeof( arg ) ); -        rating = atoi( arg ); -        if( trap_Argc() >= 5 ) -          comment = ConcatArgs( 4 ); -        trap_Argv( 2, arg, sizeof( arg ) ); +    default: +      G_Printf( "unknown team\n" ); +  } +} -        G_admin_IPA_judgement( arg, rating, comment ); +// dumb wrapper for "a", "m", "chat", and "say" +static void Svcmd_MessageWrapper( void ) +{ +  char cmd[ 5 ]; +  trap_Argv( 0, cmd, sizeof( cmd ) ); -        return qtrue; -      } -    } +  if( !Q_stricmp( cmd, "a" ) ) +    Cmd_AdminMessage_f( NULL ); +  else if( !Q_stricmp( cmd, "m" ) ) +    Cmd_PrivateMessage_f( NULL ); +  else if( !Q_stricmp( cmd, "say" ) ) +    G_Say( NULL, SAY_ALL, ConcatArgs( 1 ) ); +  else if( !Q_stricmp( cmd, "chat" ) ) +    G_Say( NULL, SAY_RAW, ConcatArgs( 1 ) ); +} -    G_Printf( "unrecognized Schachtmeister response: %s\n", ConcatArgs( 1 ) ); -    return qtrue; -  } +static void Svcmd_ListMapsWrapper( void ) +{ +  Cmd_ListMaps_f( NULL ); +} + +static void Svcmd_SuddenDeath_f( void ) +{ +  char secs[ 5 ]; +  int  offset; +  trap_Argv( 1, secs, sizeof( secs ) ); +  offset = atoi( secs ); + +  level.suddenDeathBeginTime = level.time - level.startTime + offset * 1000; +  trap_SendServerCommand( -1, +    va( "cp \"Sudden Death will begin in %d second%s\"", +      offset, offset == 1 ? "" : "s" ) ); +} -  // see if this is a a admin command -  if( G_admin_cmd_check( NULL, qfalse ) ) -    return qtrue; +static void Svcmd_G_AdvanceMapRotation_f( void ) +{ +  G_AdvanceMapRotation( 0 ); +} -  if( g_dedicated.integer ) +struct svcmd +{ +  char     *cmd; +  qboolean dedicated; +  void     ( *function )( void ); +} svcmds[ ] = { +  { "a", qtrue, Svcmd_MessageWrapper }, +  { "admitDefeat", qfalse, Svcmd_AdmitDefeat_f }, +  { "advanceMapRotation", qfalse, Svcmd_G_AdvanceMapRotation_f }, +  { "alienWin", qfalse, Svcmd_TeamWin_f }, +  { "chat", qtrue, Svcmd_MessageWrapper }, +  { "cp", qtrue, Svcmd_CenterPrint_f }, +  { "dumpuser", qfalse, Svcmd_DumpUser_f }, +  { "eject", qfalse, Svcmd_EjectClient_f }, +  { "entityList", qfalse, Svcmd_EntityList_f }, +  { "evacuation", qfalse, Svcmd_Evacuation_f }, +  { "forceTeam", qfalse, Svcmd_ForceTeam_f }, +  { "game_memory", qfalse, BG_MemoryInfo }, +  { "humanWin", qfalse, Svcmd_TeamWin_f }, +  { "layoutLoad", qfalse, Svcmd_LayoutLoad_f }, +  { "layoutSave", qfalse, Svcmd_LayoutSave_f }, +  { "listmaps", qtrue, Svcmd_ListMapsWrapper }, +  { "loadcensors", qfalse, G_LoadCensors }, +  { "m", qtrue, Svcmd_MessageWrapper }, +  { "mapRotation", qfalse, Svcmd_MapRotation_f }, +  { "pr", qfalse, Svcmd_Pr_f }, +  { "printqueue", qfalse, Svcmd_PrintQueue_f }, +  { "say", qtrue, Svcmd_MessageWrapper }, +  { "say_team", qtrue, Svcmd_TeamMessage_f }, +  { "smr", qfalse, Svcmd_SMR_f }, +  { "status", qfalse, Svcmd_Status_f }, +  { "stopMapRotation", qfalse, G_StopMapRotation }, +  { "suddendeath", qfalse, Svcmd_SuddenDeath_f } +}; + +/* +================= +ConsoleCommand + +================= +*/ +qboolean  ConsoleCommand( void ) +{ +  char cmd[ MAX_TOKEN_CHARS ]; +  struct svcmd *command; + +  trap_Argv( 0, cmd, sizeof( cmd ) ); + +  command = bsearch( cmd, svcmds, ARRAY_LEN( svcmds ), +    sizeof( struct svcmd ), cmdcmp ); + +  if( !command )    { -    if( Q_stricmp( cmd, "say" ) == 0 ) -    { -      trap_SendServerCommand( -1, va( "print \"server: %s\n\"", ConcatArgs( 1 ) ) ); -      return qtrue; -    } -    else if( !Q_stricmp( cmd, "chat" ) ) -    { -      trap_SendServerCommand( -1, va( "chat \"%s\" -1 0", ConcatArgs( 1 ) ) ); -      G_Printf( "chat: %s\n", ConcatArgs( 1 ) ); -      return qtrue; -    } -    else if( !Q_stricmp( cmd, "cp" ) ) -    { -      G_CP( NULL ); -      return qtrue; -    } -    else if( !Q_stricmp( cmd, "m" ) ) -    { -      G_PrivateMessage( NULL ); -      return qtrue; -    }     -    else if( !Q_stricmp( cmd, "a" ) || !Q_stricmp( cmd, "say_admins" )) -    { -      G_Say( NULL, NULL, SAY_ADMINS, ConcatArgs( 1 )  ); +    // see if this is an admin command +    if( G_admin_cmd_check( NULL ) )        return qtrue; -    } -    else if( !Q_stricmp( cmd, "ha" ) || !Q_stricmp( cmd, "say_hadmins" )) -    { -      G_Say( NULL, NULL, SAY_HADMINS, ConcatArgs( 1 )  ); -      return qtrue; -    } -    G_Printf( "unknown command: %s\n", cmd ); -    return qtrue; +    if( g_dedicated.integer ) +      G_Printf( "unknown command: %s\n", cmd ); + +    return qfalse; +  } + +  if( command->dedicated && !g_dedicated.integer ) +    return qfalse; + +  command->function( ); +  return qtrue; +} + +void G_RegisterCommands( void ) +{ +  int i; + +  for( i = 0; i < ARRAY_LEN( svcmds ); i++ ) +  { +    if( svcmds[ i ].dedicated && !g_dedicated.integer ) +      continue; +    trap_AddCommand( svcmds[ i ].cmd );    } -  return qfalse; +  G_admin_register_cmds( );  } +void G_UnregisterCommands( void ) +{ +  int i; + +  for( i = 0; i < ARRAY_LEN( svcmds ); i++ ) +  { +    if( svcmds[ i ].dedicated && !g_dedicated.integer ) +      continue; +    trap_RemoveCommand( svcmds[ i ].cmd ); +  } + +  G_admin_unregister_cmds( ); +} diff --git a/src/game/g_syscalls.asm b/src/game/g_syscalls.asm index 242c2ad..4057923 100644 --- a/src/game/g_syscalls.asm +++ b/src/game/g_syscalls.asm @@ -1,6 +1,6 @@  code -equ trap_Printf                       -1 +equ trap_Print                        -1  equ trap_Error                        -2  equ trap_Milliseconds                 -3  equ trap_Cvar_Register                -4 @@ -54,6 +54,7 @@ equ trap_SendGameStat                 -49  equ trap_AddCommand                   -50  equ trap_RemoveCommand                -51 +equ trap_FS_GetFilteredFiles           -52  equ memset                            -101  equ memcpy                            -102 diff --git a/src/game/g_syscalls.c b/src/game/g_syscalls.c index 6e3dc26..7c658be 100644 --- a/src/game/g_syscalls.c +++ b/src/game/g_syscalls.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -36,12 +37,12 @@ Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) )  int PASSFLOAT( float x )  { -  float floatTemp; -  floatTemp = x; -  return *(int *)&floatTemp; +  floatint_t fi; +  fi.f = x; +  return fi.i;  } -void  trap_Printf( const char *fmt ) +void  trap_Print( const char *fmt )  {    syscall( G_PRINT, fmt );  } @@ -49,6 +50,8 @@ void  trap_Printf( const char *fmt )  void  trap_Error( const char *fmt )  {    syscall( G_ERROR, fmt ); +  // shut up GCC warning about returning functions, because we know better +  exit(1);  }  int   trap_Milliseconds( void ) @@ -65,7 +68,7 @@ void  trap_Argv( int n, char *buffer, int bufferLength )    syscall( G_ARGV, n, buffer, bufferLength );  } -int   trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) +int   trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, enum FS_Mode mode )  {    return syscall( G_FS_FOPEN_FILE, qpath, f, mode );  } @@ -147,6 +150,11 @@ void trap_GetConfigstring( int num, char *buffer, int bufferSize )    syscall( G_GET_CONFIGSTRING, num, buffer, bufferSize );  } +void trap_SetConfigstringRestrictions( int num, const clientList_t *clientList ) +{ +  syscall( G_SET_CONFIGSTRING_RESTRICTIONS, num, clientList ); +} +  void trap_GetUserinfo( int num, char *buffer, int bufferSize )  {    syscall( G_GET_USERINFO, num, buffer, bufferSize ); @@ -248,13 +256,6 @@ int trap_RealTime( qtime_t *qtime )  void trap_SnapVector( float *v )  {    syscall( G_SNAPVECTOR, v ); -  return; -} - -void trap_SendGameStat( const char *data ) -{ -  syscall( G_SEND_GAMESTAT, data ); -  return;  }  int trap_Parse_AddGlobalDefine( char *define ) @@ -282,3 +283,17 @@ int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line )    return syscall( G_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line );  } +void trap_AddCommand( const char *cmdName ) +{ +  syscall( G_ADDCOMMAND, cmdName ); +} + +void trap_RemoveCommand( const char *cmdName ) +{ +  syscall( G_REMOVECOMMAND, cmdName ); +} + +int trap_FS_GetFilteredFiles( const char *path, const char *extension, const char *filter, char *listbuf, int bufsize ) +{ +  return syscall( G_FS_GETFILTEREDFILES, path, extension, filter, listbuf, bufsize ); +} diff --git a/src/game/g_target.c b/src/game/g_target.c index 467920a..f2a3f0f 100644 --- a/src/game/g_target.c +++ b/src/game/g_target.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -31,6 +32,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  */  void Think_Target_Delay( gentity_t *ent )  { +  if( ent->activator && !ent->activator->inuse ) +    ent->activator = NULL;    G_UseTargets( ent, ent->activator );  } @@ -86,18 +89,19 @@ If "private", only the activator gets the message.  If no checks, all clients ge  */  void Use_Target_Print( gentity_t *ent, gentity_t *other, gentity_t *activator )  { -  if( activator && activator->client && ( ent->spawnflags & 4 ) ) +  if( ent->spawnflags & 4 )    { -    trap_SendServerCommand( activator-g_entities, va( "cp \"%s\"", ent->message ) ); +    if( activator && activator->client ) +      trap_SendServerCommand( activator-g_entities, va( "cp \"%s\"", ent->message ) );      return;    }    if( ent->spawnflags & 3 )    {      if( ent->spawnflags & 1 ) -      G_TeamCommand( PTE_HUMANS, va( "cp \"%s\"", ent->message ) ); +      G_TeamCommand( TEAM_HUMANS, va( "cp \"%s\"", ent->message ) );      if( ent->spawnflags & 2 ) -      G_TeamCommand( PTE_ALIENS, va( "cp \"%s\"", ent->message ) ); +      G_TeamCommand( TEAM_ALIENS, va( "cp \"%s\"", ent->message ) );      return;    } @@ -156,9 +160,9 @@ void SP_target_speaker( gentity_t *ent )    G_SpawnFloat( "random", "0", &ent->random );    if( !G_SpawnString( "noise", "NOSOUND", &s ) ) -    G_Error( "target_speaker without a noise key at %s", vtos( ent->s.origin ) ); +    G_Error( "target_speaker without a noise key at %s", vtos( ent->r.currentOrigin ) ); -  // force all client reletive sounds to be "activator" speakers that +  // force all client relative sounds to be "activator" speakers that    // play on the entity that activates it    if( s[ 0 ] == '*' )      ent->spawnflags |= 8; @@ -186,8 +190,6 @@ void SP_target_speaker( gentity_t *ent )    if( ent->spawnflags & 4 )      ent->r.svFlags |= SVF_BROADCAST; -  VectorCopy( ent->s.origin, ent->s.pos.trBase ); -    // must link the entity so we get areas and clusters so    // the server can determine who to send updates to    trap_LinkEntity( ent ); @@ -210,7 +212,7 @@ void target_teleporter_use( gentity_t *self, gentity_t *other, gentity_t *activa      return;    } -  TeleportPlayer( activator, dest->s.origin, dest->s.angles ); +  TeleportPlayer( activator, dest->r.currentOrigin, dest->r.currentAngles, self->speed );  }  /*QUAKED target_teleporter (1 0 0) (-8 -8 -8) (8 8 8) @@ -219,7 +221,9 @@ The activator will be teleported away.  void SP_target_teleporter( gentity_t *self )  {    if( !self->targetname ) -    G_Printf( "untargeted %s at %s\n", self->classname, vtos( self->s.origin ) ); +    G_Printf( "untargeted %s at %s\n", self->classname, vtos( self->r.currentOrigin ) ); + +  G_SpawnFloat( "speed", "400", &self->speed );    self->use = target_teleporter_use;  } @@ -235,11 +239,11 @@ if RANDOM is checked, only one of the targets will be fired, not all of them  void target_relay_use( gentity_t *self, gentity_t *other, gentity_t *activator )  {    if( ( self->spawnflags & 1 ) && activator && activator->client && -      activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) +      activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS )      return;    if( ( self->spawnflags & 2 ) && activator && activator->client && -      activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) +      activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS )      return;    if( self->spawnflags & 4 ) @@ -285,36 +289,7 @@ Used as a positional target for in-game calculation, like jumppad targets.  */  void SP_target_position( gentity_t *self )  { -  G_SetOrigin( self, self->s.origin ); -} - -static void target_location_linkup( gentity_t *ent ) -{ -  int i; -  int n; - -  if( level.locationLinked ) -    return; - -  level.locationLinked = qtrue; - -  level.locationHead = NULL; - -  trap_SetConfigstring( CS_LOCATIONS, "unknown" ); - -  for( i = 0, ent = g_entities, n = 1; i < level.num_entities; i++, ent++) -  { -    if( ent->classname && !Q_stricmp( ent->classname, "target_location" ) ) -    { -      // lets overload some variables! -      ent->health = n; // use for location marking -      trap_SetConfigstring( CS_LOCATIONS + n, ent->message ); -      n++; -      ent->nextTrain = level.locationHead; -      level.locationHead = ent; -    } -  } -  // All linked together now +  G_SetOrigin( self, self->r.currentOrigin );  }  /*QUAKED target_location (0 0.5 0) (-8 -8 -8) (8 8 8) @@ -327,10 +302,36 @@ in site, closest in distance  */  void SP_target_location( gentity_t *self )  { -  self->think = target_location_linkup; -  self->nextthink = level.time + 200;  // Let them all spawn first +  static int n = 0; +  const char *message; +  self->s.eType = ET_LOCATION; +  self->r.svFlags = SVF_BROADCAST; +  trap_LinkEntity( self ); // make the server send them to the clients +  if( n == MAX_LOCATIONS ) +  { +    G_Printf( S_COLOR_YELLOW "too many target_locations\n" ); +    return; +  } +  if( self->count ) +  { +    if( self->count < 0 ) +      self->count = 0; + +    if( self->count > 7 ) +      self->count = 7; -  G_SetOrigin( self, self->s.origin ); +    message = va( "%c%c%s" S_COLOR_WHITE, Q_COLOR_ESCAPE, self->count + '0', +      (const char*)self->message); +  } +  else +    message = self->message; +  trap_SetConfigstring( CS_LOCATIONS + n, message ); +  self->nextTrain = level.locationHead; +  self->s.generic1 = n; // use for location marking +  level.locationHead = self; +  n++; + +  G_SetOrigin( self, self->r.currentOrigin );  } @@ -391,7 +392,7 @@ void SP_target_rumble( gentity_t *self )    if( !self->targetname )    {      G_Printf( S_COLOR_YELLOW "WARNING: untargeted %s at %s\n", self->classname, -                                                               vtos( self->s.origin ) ); +                                                               vtos( self->r.currentOrigin ) );    }    if( !self->count ) @@ -411,7 +412,8 @@ target_alien_win_use  */  void target_alien_win_use( gentity_t *self, gentity_t *other, gentity_t *activator )  { -  level.uncondAlienWin = qtrue; +  if( !level.uncondHumanWin ) +    level.uncondAlienWin = qtrue;  }  /* @@ -431,7 +433,8 @@ target_human_win_use  */  void target_human_win_use( gentity_t *self, gentity_t *other, gentity_t *activator )  { -  level.uncondHumanWin = qtrue; +  if( !level.uncondAlienWin ) +    level.uncondHumanWin = qtrue;  }  /* @@ -468,7 +471,7 @@ void SP_target_hurt( gentity_t *self )    if( !self->targetname )    {      G_Printf( S_COLOR_YELLOW "WARNING: untargeted %s at %s\n", self->classname, -                                                               vtos( self->s.origin ) ); +                                                               vtos( self->r.currentOrigin ) );    }    if( !self->damage ) diff --git a/src/game/g_team.c b/src/game/g_team.c index 1d8d102..4af6235 100644 --- a/src/game/g_team.c +++ b/src/game/g_team.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,35 +17,54 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */  #include "g_local.h" -// NULL for everyone -void QDECL PrintMsg( gentity_t *ent, const char *fmt, ... ) -{ -  char    msg[ 1024 ]; -  va_list argptr; -  char    *p; - -  va_start( argptr,fmt ); +/* +================ +G_TeamFromString -  if( vsprintf( msg, fmt, argptr ) > sizeof( msg ) ) -    G_Error ( "PrintMsg overrun" ); +Return the team referenced by a string +================ +*/ +team_t G_TeamFromString( char *str ) +{ +  switch( tolower( *str ) ) +  { +    case '0': case 's': return TEAM_NONE; +    case '1': case 'a': return TEAM_ALIENS; +    case '2': case 'h': return TEAM_HUMANS; +    default: return NUM_TEAMS; +  } +} -  va_end( argptr ); +/* +================ +G_TeamCommand -  // double quotes are bad -  while( ( p = strchr( msg, '"' ) ) != NULL ) -    *p = '\''; +Broadcasts a command to only a specific team +================ +*/ +void G_TeamCommand( team_t team, const char *cmd ) +{ +  int   i; -  trap_SendServerCommand( ( ( ent == NULL ) ? -1 : ent-g_entities ), va( "print \"%s\"", msg ) ); +  for( i = 0 ; i < level.maxclients ; i++ ) +  { +    if( level.clients[ i ].pers.connected == CON_CONNECTED ) +    { +      if( level.clients[ i ].pers.teamSelection == team || +        ( level.clients[ i ].pers.teamSelection == TEAM_NONE && +          G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) +        trap_SendServerCommand( i, cmd ); +    } +  }  } -  /*  ==============  OnSameTeam @@ -62,156 +82,312 @@ qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 )  }  /* -=========== -Team_GetLocation +================== +G_ClientListForTeam +================== +*/ +static clientList_t G_ClientListForTeam( team_t team ) +{ +  int           i; +  clientList_t  clientList; -Report a location for the player. Uses placed nearby target_location entities -============ +  Com_Memset( &clientList, 0, sizeof( clientList_t ) ); + +  for( i = 0; i < g_maxclients.integer; i++ ) +  { +    gentity_t *ent = g_entities + i; +    if( ent->client->pers.connected != CON_CONNECTED ) +      continue; + +    if( ent->inuse && ( ent->client->ps.stats[ STAT_TEAM ] == team ) ) +      Com_ClientListAdd( &clientList, ent->client->ps.clientNum ); +  } + +  return clientList; +} + +/* +================== +G_UpdateTeamConfigStrings +==================  */ -gentity_t *Team_GetLocation( gentity_t *ent ) +void G_UpdateTeamConfigStrings( void )  { -  gentity_t   *eloc, *best; -  float       bestlen, len; -  vec3_t      origin; +  clientList_t alienTeam = G_ClientListForTeam( TEAM_ALIENS ); +  clientList_t humanTeam = G_ClientListForTeam( TEAM_HUMANS ); -  best = NULL; -  bestlen = 3.0f * 8192.0f * 8192.0f; +  if( level.intermissiontime ) +  { +    // No restrictions once the game has ended +    Com_Memset( &alienTeam, 0, sizeof( clientList_t ) ); +    Com_Memset( &humanTeam, 0, sizeof( clientList_t ) ); +  } -  VectorCopy( ent->r.currentOrigin, origin ); +  trap_SetConfigstringRestrictions( CS_VOTE_TIME + TEAM_ALIENS,   &humanTeam ); +  trap_SetConfigstringRestrictions( CS_VOTE_STRING + TEAM_ALIENS, &humanTeam ); +  trap_SetConfigstringRestrictions( CS_VOTE_YES + TEAM_ALIENS,    &humanTeam ); +  trap_SetConfigstringRestrictions( CS_VOTE_NO + TEAM_ALIENS,     &humanTeam ); -  for( eloc = level.locationHead; eloc; eloc = eloc->nextTrain ) +  trap_SetConfigstringRestrictions( CS_VOTE_TIME + TEAM_HUMANS,   &alienTeam ); +  trap_SetConfigstringRestrictions( CS_VOTE_STRING + TEAM_HUMANS, &alienTeam ); +  trap_SetConfigstringRestrictions( CS_VOTE_YES + TEAM_HUMANS,    &alienTeam ); +  trap_SetConfigstringRestrictions( CS_VOTE_NO + TEAM_HUMANS,     &alienTeam ); + +  trap_SetConfigstringRestrictions( CS_ALIEN_STAGES, &humanTeam ); +  trap_SetConfigstringRestrictions( CS_HUMAN_STAGES, &alienTeam ); +} + +/* +================== +G_LeaveTeam +================== +*/ +void G_LeaveTeam( gentity_t *self ) +{ +  team_t    team = self->client->pers.teamSelection; +  gentity_t *ent; +  int       i; + +  if( team == TEAM_ALIENS ) +    G_RemoveFromSpawnQueue( &level.alienSpawnQueue, self->client->ps.clientNum ); +  else if( team == TEAM_HUMANS ) +    G_RemoveFromSpawnQueue( &level.humanSpawnQueue, self->client->ps.clientNum ); +  else    { -    len = ( origin[ 0 ] - eloc->r.currentOrigin[ 0 ] ) * ( origin[ 0 ] - eloc->r.currentOrigin[ 0 ] ) -        + ( origin[ 1 ] - eloc->r.currentOrigin[ 1 ] ) * ( origin[ 1 ] - eloc->r.currentOrigin[ 1 ] ) -        + ( origin[ 2 ] - eloc->r.currentOrigin[ 2 ] ) * ( origin[ 2 ] - eloc->r.currentOrigin[ 2 ] ); +    if( self->client->sess.spectatorState == SPECTATOR_FOLLOW ) +      G_StopFollowing( self ); +    return; +  } -    if( len > bestlen ) -      continue; +  // stop any following clients +  G_StopFromFollowing( self ); -    if( !trap_InPVS( origin, eloc->r.currentOrigin ) ) +  G_Vote( self, team, qfalse ); +  self->suicideTime = 0; + +  for( i = 0; i < level.num_entities; i++ ) +  { +    ent = &g_entities[ i ]; +    if( !ent->inuse )        continue; -    bestlen = len; -    best = eloc; +    if( ent->client && ent->client->pers.connected == CON_CONNECTED ) +    { +      // cure poison +      if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONED && +          ent->client->lastPoisonClient == self ) +        ent->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; +    } +    else if( ent->s.eType == ET_MISSILE && ent->r.ownerNum == self->s.number ) +      G_FreeEntity( ent );    } -  return best; +  // cut all relevant zap beams +  G_ClearPlayerZapEffects( self ); + +  G_namelog_update_score( self->client );  } +/* +================= +G_ChangeTeam +================= +*/ +void G_ChangeTeam( gentity_t *ent, team_t newTeam ) +{ +  team_t  oldTeam = ent->client->pers.teamSelection; + +  if( oldTeam == newTeam ) +    return; + +  G_LeaveTeam( ent ); +  ent->client->pers.teamChangeTime = level.time; +  ent->client->pers.teamSelection = newTeam; +  ent->client->pers.classSelection = PCL_NONE; +  ClientSpawn( ent, NULL, NULL, NULL ); + +  if( oldTeam == TEAM_HUMANS && newTeam == TEAM_ALIENS ) +  { +    // Convert from human to alien credits +    ent->client->pers.credit = +      (int)( ent->client->pers.credit * +             ALIEN_MAX_CREDITS / HUMAN_MAX_CREDITS + 0.5f ); +  } +  else if( oldTeam == TEAM_ALIENS && newTeam == TEAM_HUMANS ) +  { +    // Convert from alien to human credits +    ent->client->pers.credit = +      (int)( ent->client->pers.credit * +             HUMAN_MAX_CREDITS / ALIEN_MAX_CREDITS + 0.5f ); +  } + +  if( !g_cheats.integer ) +  { +    if( ent->client->noclip ) +    { +      ent->client->noclip = qfalse; +      ent->r.contents = ent->client->cliprcontents; +    } +    ent->flags &= ~( FL_GODMODE | FL_NOTARGET ); +  } + +  // Copy credits to ps for the client +  ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.credit; + +  ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); + +  G_UpdateTeamConfigStrings( ); + +  G_LogPrintf( "ChangeTeam: %d %s: %s" S_COLOR_WHITE " switched teams\n", +    (int)( ent - g_entities ), BG_TeamName( newTeam ), ent->client->pers.netname ); + +  G_namelog_update_score( ent->client ); +  TeamplayInfoMessage( ent ); +}  /*  =========== -Team_GetLocationMsg +Team_GetLocation -Report a location message for the player. Uses placed nearby target_location entities +Report a location for the player. Uses placed nearby target_location entities  ============  */ -qboolean Team_GetLocationMsg( gentity_t *ent, char *loc, int loclen ) +gentity_t *Team_GetLocation( gentity_t *ent )  { -  gentity_t *best; - -  best = Team_GetLocation( ent ); +  gentity_t   *eloc, *best; +  float       bestlen, len; -  if( !best ) -    return qfalse; +  best = NULL; +  bestlen = 3.0f * 8192.0f * 8192.0f; -  if( best->count ) +  for( eloc = level.locationHead; eloc; eloc = eloc->nextTrain )    { -    if( best->count < 0 ) -      best->count = 0; +    len = DistanceSquared( ent->r.currentOrigin, eloc->r.currentOrigin ); + +    if( len > bestlen ) +      continue; -    if( best->count > 7 ) -      best->count = 7; +    if( !trap_InPVS( ent->r.currentOrigin, eloc->r.currentOrigin ) ) +      continue; -    Com_sprintf( loc, loclen, "%c%c%s" S_COLOR_WHITE, Q_COLOR_ESCAPE, best->count + '0', best->message ); +    bestlen = len; +    best = eloc;    } -  else -    Com_sprintf( loc, loclen, "%s", best->message ); -  return qtrue; +  return best;  }  /*---------------------------------------------------------------------------*/ -static int QDECL SortClients( const void *a, const void *b ) -{ -  return *(int *)a - *(int *)b; -} - -  /*  ================== -TeamplayLocationsMessage +TeamplayInfoMessage  Format: -  clientNum location health armor weapon powerups +  clientNum location health weapon upgrade  ==================  */  void TeamplayInfoMessage( gentity_t *ent )  { -  char      entry[ 1024 ]; -  char      string[ 8192 ]; -  int       stringlength; -  int       i, j; +  char      entry[ 17 ], +            string[ ( MAX_CLIENTS - 1 ) * ( sizeof( entry ) - 1 ) + 1 ]; +  int       i, j;  +  int       team, stringlength;    gentity_t *player; -  int       cnt; -  int       h, a = 0; -  int       clients[ TEAM_MAXOVERLAY ]; +  gclient_t *cl; +  upgrade_t upgrade = UP_NONE; +  int       curWeaponClass = WP_NONE ; // sends weapon for humans, class for aliens +  char      *format; -  if( ! ent->client->pers.teamInfo ) -    return; +  if( !g_allowTeamOverlay.integer ) +     return; -  // figure out what client should be on the display -  // we are limited to 8, but we want to use the top eight players -  // but in client order (so they don't keep changing position on the overlay) -  for( i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++ ) -  { -    player = g_entities + level.sortedClients[ i ]; +  if( !ent->client->pers.teamInfo ) +     return; -    if( player->inuse && player->client->sess.sessionTeam == -        ent->client->sess.sessionTeam ) -      clients[ cnt++ ] = level.sortedClients[ i ]; +  if( ent->client->pers.teamSelection == TEAM_NONE ) +  { +    if( ent->client->sess.spectatorState == SPECTATOR_FREE || +        ent->client->sess.spectatorClient < 0 ) +      return; +    team = g_entities[ ent->client->sess.spectatorClient ].client-> +      pers.teamSelection;    } +  else +    team = ent->client->pers.teamSelection; -  // We have the top eight players, sort them by clientNum -  qsort( clients, cnt, sizeof( clients[ 0 ] ), SortClients ); +  if( team == TEAM_ALIENS ) +    format = " %i %i %i %i"; // aliens don't have upgrades +  else +    format = " %i %i %i %i %i"; -  // send the latest information on all clients -  string[ 0 ] = 0; +  string[ 0 ] = '\0';    stringlength = 0; -  for( i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++) +  for( i = 0; i < level.maxclients; i++)    { -    player = g_entities + i; +    player = g_entities + i ; +    cl = player->client; -    if( player->inuse && player->client->sess.sessionTeam == -        ent->client->sess.sessionTeam ) -    { -      h = player->client->ps.stats[ STAT_HEALTH ]; +    if( ent == player || !cl || team != cl->pers.teamSelection || +        !player->inuse ) +      continue; -      if( h < 0 ) -        h = 0; +    // only update if changed since last time +    if( cl->pers.infoChangeTime <= ent->client->pers.teamInfo ) +      continue; -      Com_sprintf( entry, sizeof( entry ), -        " %i %i %i %i %i %i", -//        level.sortedClients[i], player->client->pers.teamState.location, h, a, -        i, player->client->pers.teamState.location, h, a, -        player->client->ps.weapon, player->s.misc ); +    if( cl->sess.spectatorState != SPECTATOR_NOT ) +    { +      curWeaponClass = WP_NONE; +      upgrade = UP_NONE; +    } +    else if ( cl->pers.teamSelection == TEAM_HUMANS ) +    { +      curWeaponClass = cl->ps.weapon; + +      if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) ) +        upgrade = UP_BATTLESUIT; +      else if( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) ) +        upgrade = UP_JETPACK; +      else if( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) ) +        upgrade = UP_BATTPACK; +      else if( BG_InventoryContainsUpgrade( UP_HELMET, cl->ps.stats ) ) +        upgrade = UP_HELMET; +      else if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) ) +        upgrade = UP_LIGHTARMOUR; +      else +        upgrade = UP_NONE; +    } +    else if( cl->pers.teamSelection == TEAM_ALIENS ) +    { +      curWeaponClass = cl->ps.stats[ STAT_CLASS ]; +      upgrade = UP_NONE; +    } -      j = strlen( entry ); +    Com_sprintf( entry, sizeof( entry ), format, i, +      cl->pers.location, +      cl->ps.stats[ STAT_HEALTH ] < 1 ? 0 : cl->ps.stats[ STAT_HEALTH ], +      curWeaponClass, +      upgrade ); -      if( stringlength + j > sizeof( string ) ) -        break; +    j = strlen( entry ); -      strcpy( string + stringlength, entry ); -      stringlength += j; -      cnt++; -    } +    // this should not happen if entry and string sizes are correct +    if( stringlength + j >= sizeof( string ) ) +      break; + +    strcpy( string + stringlength, entry ); +    stringlength += j;    } -  trap_SendServerCommand( ent - g_entities, va( "tinfo %i %s", cnt, string ) ); +  if( string[ 0 ] ) +  { +    trap_SendServerCommand( ent - g_entities, va( "tinfo%s", string ) ); +    ent->client->pers.teamInfo = level.time; +  }  }  void CheckTeamStatus( void ) @@ -229,16 +405,25 @@ void CheckTeamStatus( void )        if( ent->client->pers.connected != CON_CONNECTED )          continue; -      if( ent->inuse && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS || -                          ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ) +      if( ent->inuse && ( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS || +                          ent->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) )        {          loc = Team_GetLocation( ent );          if( loc ) -          ent->client->pers.teamState.location = loc->health; -        else -          ent->client->pers.teamState.location = 0; +        { +          if( ent->client->pers.location != loc->s.generic1 ) +          { +            ent->client->pers.infoChangeTime = level.time; +            ent->client->pers.location = loc->s.generic1; +          } +        } +        else if( ent->client->pers.location != 0 ) +        { +          ent->client->pers.infoChangeTime = level.time; +          ent->client->pers.location = 0; +        }        }      } @@ -248,29 +433,35 @@ void CheckTeamStatus( void )        if( ent->client->pers.connected != CON_CONNECTED )          continue; -      if( ent->inuse && ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS || -                          ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) ) +      if( ent->inuse )          TeamplayInfoMessage( ent );      }    } -  //Warn on unbalanced teams -  if ( g_teamImbalanceWarnings.integer && !level.intermissiontime && level.time - level.lastTeamUnbalancedTime > ( g_teamImbalanceWarnings.integer * 1000 ) && level.numTeamWarnings<3 ) +  // Warn on imbalanced teams +  if( g_teamImbalanceWarnings.integer && !level.intermissiontime && +      ( level.time - level.lastTeamImbalancedTime > +        ( g_teamImbalanceWarnings.integer * 1000 ) ) && +      level.numTeamImbalanceWarnings < 3 && !level.restarted )    { -	  level.lastTeamUnbalancedTime = level.time; -	  if (level.numAlienSpawns > 0 && level.numHumanClients - level.numAlienClients > 2) -	  { -		  trap_SendServerCommand (-1, "print \"Teams are unbalanced. Humans have more players.\n Humans will keep their points when switching teams.\n\""); -		  level.numTeamWarnings++; -	  } -	  else if (level.numHumanSpawns > 0 && level.numAlienClients - level.numHumanClients > 2) -	  { -		  trap_SendServerCommand (-1, "print \"Teams are unbalanced. Aliens have more players.\n Aliens will keep their points when switching teams.\n\""); -		  level.numTeamWarnings++; -	  } -	  else -	  { -		  level.numTeamWarnings = 0; -	  } +    level.lastTeamImbalancedTime = level.time; +    if( level.numAlienSpawns > 0 &&  +        level.numHumanClients - level.numAlienClients > 2 ) +    { +      trap_SendServerCommand( -1, "print \"Teams are imbalanced. " +                                  "Humans have more players.\n\""); +      level.numTeamImbalanceWarnings++; +    } +    else if( level.numHumanSpawns > 0 &&  +             level.numAlienClients - level.numHumanClients > 2 ) +    { +      trap_SendServerCommand ( -1, "print \"Teams are imbalanced. " +                                   "Aliens have more players.\n\""); +      level.numTeamImbalanceWarnings++; +    } +    else +    { +      level.numTeamImbalanceWarnings = 0; +    }    }  } diff --git a/src/game/g_trigger.c b/src/game/g_trigger.c index 0ac34bb..8d7c518 100644 --- a/src/game/g_trigger.c +++ b/src/game/g_trigger.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -26,8 +27,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  void InitTrigger( gentity_t *self )  { -  if( !VectorCompare( self->s.angles, vec3_origin ) ) -    G_SetMovedir( self->s.angles, self->movedir ); +  if( !VectorCompare( self->r.currentAngles, vec3_origin ) ) +    G_SetMovedir( self->r.currentAngles, self->movedir );    trap_SetBrushModel( self, self->model );    self->r.contents = CONTENTS_TRIGGER;    // replaces the -1 from trap_SetBrushModel @@ -42,6 +43,24 @@ void multi_wait( gentity_t *ent )  } +void trigger_check_wait( gentity_t *self ) +{ +  if( self->wait > 0 ) +  { +    self->think = multi_wait; +    self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; +  } +  else +  { +    // we can't just remove (self) here, because this is a touch function +    // called while looping through area links... +    self->touch = 0; +    self->nextthink = level.time + FRAMETIME; +    self->think = G_FreeEntity; +  } +} + +  // the trigger was just activated  // ent->activator should be set to the activator so it can be held through a delay  // so wait for the delay time before firing @@ -51,32 +70,19 @@ void multi_trigger( gentity_t *ent, gentity_t *activator )    if( ent->nextthink )      return;   // can't retrigger until the wait is over -  if( activator->client ) +  if( activator && activator->client )    {      if( ( ent->spawnflags & 1 ) && -        activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) +        activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS )        return;      if( ( ent->spawnflags & 2 ) && -        activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) +        activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS )        return;    }    G_UseTargets( ent, ent->activator ); - -  if( ent->wait > 0 ) -  { -    ent->think = multi_wait; -    ent->nextthink = level.time + ( ent->wait + ent->random * crandom( ) ) * 1000; -  } -  else -  { -    // we can't just remove (self) here, because this is a touch function -    // called while looping through area links... -    ent->touch = 0; -    ent->nextthink = level.time + FRAMETIME; -    ent->think = G_FreeEntity; -  } +  trigger_check_wait( ent );  }  void Use_Multi( gentity_t *ent, gentity_t *other, gentity_t *activator ) @@ -184,7 +190,7 @@ void AimAtTarget( gentity_t *self )      return;    } -  height = ent->s.origin[ 2 ] - origin[ 2 ]; +  height = ent->r.currentOrigin[ 2 ] - origin[ 2 ];    gravity = g_gravity.value;    time = sqrt( height / ( 0.5 * gravity ) ); @@ -195,7 +201,7 @@ void AimAtTarget( gentity_t *self )    }    // set s.origin2 to the push velocity -  VectorSubtract( ent->s.origin, origin, self->s.origin2 ); +  VectorSubtract( ent->r.currentOrigin, origin, self->s.origin2 );    self->s.origin2[ 2 ] = 0;    dist = VectorNormalize( self->s.origin2 ); @@ -227,7 +233,7 @@ void SP_trigger_push( gentity_t *self )  void Use_target_push( gentity_t *self, gentity_t *other, gentity_t *activator )  { -  if( !activator->client ) +  if( !activator || !activator->client )      return;    if( activator->client->ps.pm_type != PM_NORMAL ) @@ -246,13 +252,13 @@ void SP_target_push( gentity_t *self )    if( !self->speed )      self->speed = 1000; -  G_SetMovedir( self->s.angles, self->s.origin2 ); +  G_SetMovedir( self->r.currentAngles, self->s.origin2 );    VectorScale( self->s.origin2, self->speed, self->s.origin2 );    if( self->target )    { -    VectorCopy( self->s.origin, self->r.absmin ); -    VectorCopy( self->s.origin, self->r.absmax ); +    VectorCopy( self->r.currentOrigin, self->r.absmin ); +    VectorCopy( self->r.currentOrigin, self->r.absmax );      self->think = AimAtTarget;      self->nextthink = level.time + FRAMETIME;    } @@ -283,7 +289,7 @@ void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace    // Spectators only?    if( ( self->spawnflags & 1 ) && -      other->client->sess.sessionTeam != TEAM_SPECTATOR ) +      other->client->sess.spectatorState == SPECTATOR_NOT )      return; @@ -295,7 +301,7 @@ void trigger_teleporter_touch( gentity_t *self, gentity_t *other, trace_t *trace      return;    } -  TeleportPlayer( other, dest->s.origin, dest->s.angles ); +  TeleportPlayer( other, dest->r.currentOrigin, dest->r.currentAngles, self->speed );  }  /* @@ -321,6 +327,8 @@ void SP_trigger_teleport( gentity_t *self )  {    InitTrigger( self ); +  G_SpawnFloat( "speed", "400", &self->speed ); +    // unlike other triggers, we need to send this one to the client    // unless is a spectator trigger    if( self->spawnflags & 1 ) @@ -407,11 +415,12 @@ void SP_trigger_hurt( gentity_t *self )    self->r.contents = CONTENTS_TRIGGER; -  if( self->spawnflags & 2 ) -    self->use = hurt_use; +  self->use = hurt_use;    // link in to the world if starting active -  if( !( self->spawnflags & 1 ) ) +  if( self->spawnflags & 1 ) +    trap_UnlinkEntity( self ); +  else      trap_LinkEntity( self );  } @@ -469,7 +478,7 @@ void SP_func_timer( gentity_t *self )    if( self->random >= self->wait )    {      self->random = self->wait - FRAMETIME; -    G_Printf( "func_timer at %s has random >= wait\n", vtos( self->s.origin ) ); +    G_Printf( "func_timer at %s has random >= wait\n", vtos( self->r.currentOrigin ) );    }    if( self->spawnflags & 1 ) @@ -489,7 +498,7 @@ G_Checktrigger_stages  Called when stages change  ===============  */ -void G_Checktrigger_stages( pTeam_t team, stage_t stage ) +void G_Checktrigger_stages( team_t team, stage_t stage )  {    int i;    gentity_t *ent; @@ -558,6 +567,9 @@ qboolean trigger_buildable_match( gentity_t *self, gentity_t *activator )  {    int i = 0; +  if( !activator ) +    return qfalse; +    //if there is no buildable list every buildable triggers    if( self->bTriggers[ i ] == BA_NONE )      return qtrue; @@ -592,26 +604,18 @@ void trigger_buildable_trigger( gentity_t *self, gentity_t *activator )    if( self->s.eFlags & EF_DEAD )    {      if( !trigger_buildable_match( self, activator ) ) +    {        G_UseTargets( self, activator ); +      trigger_check_wait( self ); +    }    }    else    {      if( trigger_buildable_match( self, activator ) ) +    {        G_UseTargets( self, activator ); -  } - -  if( self->wait > 0 ) -  { -    self->think = multi_wait; -    self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; -  } -  else -  { -    // we can't just remove (self) here, because this is a touch function -    // called while looping through area links... -    self->touch = 0; -    self->nextthink = level.time + FRAMETIME; -    self->think = G_FreeEntity; +      trigger_check_wait( self ); +    }    }  } @@ -686,6 +690,9 @@ qboolean trigger_class_match( gentity_t *self, gentity_t *activator )  {    int i = 0; +  if( !activator ) +    return qfalse; +    //if there is no class list every class triggers (stupid case)    if( self->cTriggers[ i ] == PCL_NONE )      return qtrue; @@ -694,7 +701,7 @@ qboolean trigger_class_match( gentity_t *self, gentity_t *activator )      //otherwise check against the list      for( i = 0; self->cTriggers[ i ] != PCL_NONE; i++ )      { -      if( activator->client->ps.stats[ STAT_PCLASS ] == self->cTriggers[ i ] ) +      if( activator->client->ps.stats[ STAT_CLASS ] == self->cTriggers[ i ] )          return qtrue;      }    } @@ -710,10 +717,10 @@ trigger_class_trigger  void trigger_class_trigger( gentity_t *self, gentity_t *activator )  {    //sanity check -  if( !activator->client ) +  if( !activator || !activator->client )      return; -  if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) +  if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_ALIENS )      return;    if( self->s.eFlags & EF_NODRAW ) @@ -726,27 +733,20 @@ void trigger_class_trigger( gentity_t *self, gentity_t *activator )    if( self->s.eFlags & EF_DEAD )    {      if( !trigger_class_match( self, activator ) ) +    {        G_UseTargets( self, activator ); +      trigger_check_wait( self ); +    }    }    else    {      if( trigger_class_match( self, activator ) ) +    {        G_UseTargets( self, activator ); +      trigger_check_wait( self ); +    }    } -  if( self->wait > 0 ) -  { -    self->think = multi_wait; -    self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; -  } -  else -  { -    // we can't just remove (self) here, because this is a touch function -    // called while looping through area links... -    self->touch = 0; -    self->nextthink = level.time + FRAMETIME; -    self->think = G_FreeEntity; -  }  }  /* @@ -820,6 +820,9 @@ qboolean trigger_equipment_match( gentity_t *self, gentity_t *activator )  {    int i = 0; +  if( !activator ) +    return qfalse; +    //if there is no equipment list all equipment triggers (stupid case)    if( self->wTriggers[ i ] == WP_NONE && self->uTriggers[ i ] == UP_NONE )      return qtrue; @@ -850,10 +853,10 @@ trigger_equipment_trigger  void trigger_equipment_trigger( gentity_t *self, gentity_t *activator )  {    //sanity check -  if( !activator->client ) +  if( !activator || !activator->client )      return; -  if( activator->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) +  if( activator->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS )      return;    if( self->s.eFlags & EF_NODRAW ) @@ -866,26 +869,18 @@ void trigger_equipment_trigger( gentity_t *self, gentity_t *activator )    if( self->s.eFlags & EF_DEAD )    {      if( !trigger_equipment_match( self, activator ) ) +    {        G_UseTargets( self, activator ); +      trigger_check_wait( self ); +    }    }    else    {      if( trigger_equipment_match( self, activator ) ) +    {        G_UseTargets( self, activator ); -  } - -  if( self->wait > 0 ) -  { -    self->think = multi_wait; -    self->nextthink = level.time + ( self->wait + self->random * crandom( ) ) * 1000; -  } -  else -  { -    // we can't just remove (self) here, because this is a touch function -    // called while looping through area links... -    self->touch = 0; -    self->nextthink = level.time + FRAMETIME; -    self->think = G_FreeEntity; +      trigger_check_wait( self ); +    }    }  } @@ -1061,7 +1056,9 @@ void SP_trigger_heal( gentity_t *self )    InitTrigger( self );    // link in to the world if starting active -  if( !( self->spawnflags & 1 ) ) +  if( self->spawnflags & 1 ) +    trap_UnlinkEntity( self ); +  else      trap_LinkEntity( self );  } @@ -1073,12 +1070,13 @@ trigger_ammo_touch  */  void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace )  { -  int ammo, clips, maxClips, maxAmmo; +  int maxClips, maxAmmo; +  weapon_t weapon;    if( !other->client )      return; -  if( other->client->ps.stats[ STAT_PTEAM ] != PTE_HUMANS ) +  if( other->client->ps.stats[ STAT_TEAM ] != TEAM_HUMANS )      return;    if( self->timestamp > level.time ) @@ -1087,10 +1085,11 @@ void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace )    if( other->client->ps.weaponstate != WEAPON_READY )      return; -  if( BG_FindUsesEnergyForWeapon( other->client->ps.weapon ) && self->spawnflags & 2 ) +  weapon = other->client->ps.stats[ STAT_WEAPON ]; +  if( BG_Weapon( weapon )->usesEnergy && self->spawnflags & 2 )      return; -  if( !BG_FindUsesEnergyForWeapon( other->client->ps.weapon ) && self->spawnflags & 4 ) +  if( !BG_Weapon( weapon )->usesEnergy && self->spawnflags & 4 )      return;    if( self->spawnflags & 1 ) @@ -1098,25 +1097,21 @@ void trigger_ammo_touch( gentity_t *self, gentity_t *other, trace_t *trace )    else      self->timestamp = level.time + FRAMETIME; -  BG_FindAmmoForWeapon( other->client->ps.weapon, &maxAmmo, &maxClips ); -  ammo = other->client->ps.ammo; -  clips = other->client->ps.clips; +  maxAmmo = BG_Weapon( weapon )->maxAmmo; +  maxClips = BG_Weapon( weapon )->maxClips; -  if( ( ammo + self->damage ) > maxAmmo ) +  if( ( other->client->ps.ammo + self->damage ) > maxAmmo )    { -    if( clips < maxClips ) +    if( other->client->ps.clips < maxClips )      { -      clips++; -      ammo = 1; +      other->client->ps.clips++; +      other->client->ps.ammo = 1;      }      else -      ammo = maxAmmo; +      other->client->ps.ammo = maxAmmo;    }    else -    ammo += self->damage; - -  other->client->ps.ammo = ammo; -  other->client->ps.clips = clips; +    other->client->ps.ammo += self->damage;  }  /* diff --git a/src/game/g_utils.c b/src/game/g_utils.c index a74df3f..5c74188 100644 --- a/src/game/g_utils.c +++ b/src/game/g_utils.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -93,7 +94,7 @@ G_FindConfigstringIndex  ================  */ -int G_FindConfigstringIndex( char *name, int start, int max, qboolean create ) +int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create )  {    int   i;    char  s[ MAX_STRING_CHARS ]; @@ -122,55 +123,28 @@ int G_FindConfigstringIndex( char *name, int start, int max, qboolean create )    return i;  } -//TA: added ParticleSystemIndex -int G_ParticleSystemIndex( char *name ) +int G_ParticleSystemIndex( const char *name )  {    return G_FindConfigstringIndex( name, CS_PARTICLE_SYSTEMS, MAX_GAME_PARTICLE_SYSTEMS, qtrue );  } -//TA: added ShaderIndex -int G_ShaderIndex( char *name ) +int G_ShaderIndex( const char *name )  {    return G_FindConfigstringIndex( name, CS_SHADERS, MAX_GAME_SHADERS, qtrue );  } -int G_ModelIndex( char *name ) +int G_ModelIndex( const char *name )  {    return G_FindConfigstringIndex( name, CS_MODELS, MAX_MODELS, qtrue );  } -int G_SoundIndex( char *name ) +int G_SoundIndex( const char *name )  {    return G_FindConfigstringIndex( name, CS_SOUNDS, MAX_SOUNDS, qtrue );  }  //===================================================================== - -/* -================ -G_TeamCommand - -Broadcasts a command to only a specific team -================ -*/ -void G_TeamCommand( pTeam_t team, char *cmd ) -{ -  int   i; - -  for( i = 0 ; i < level.maxclients ; i++ ) -  { -    if( level.clients[ i ].pers.connected == CON_CONNECTED ) -    { -      if( level.clients[ i ].pers.teamSelection == team || -        ( level.clients[ i ].pers.teamSelection == PTE_NONE && -          G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) -        trap_SendServerCommand( i, cmd ); -    } -  } -} - -  /*  =============  G_Find @@ -249,7 +223,7 @@ gentity_t *G_PickTarget( char *targetname )      return NULL;    } -  return choice[ rand( ) % num_choices ]; +  return choice[ rand( ) / ( RAND_MAX / num_choices + 1 ) ];  } @@ -268,9 +242,6 @@ void G_UseTargets( gentity_t *ent, gentity_t *activator )  {    gentity_t   *t; -  if( !ent ) -    return; -    if( ent->targetShaderName && ent->targetShaderNewName )    {      float f = level.time * 0.001; @@ -505,7 +476,6 @@ qboolean G_EntitiesFree( void )    return qfalse;  } -  /*  =================  G_FreeEntity @@ -528,6 +498,181 @@ void G_FreeEntity( gentity_t *ent )  /*  ================= +G_RemoveEntity + +Safely remove an entity, perform reasonable cleanup logic +================= +*/ +void G_RemoveEntity( gentity_t *ent ) +{ +  gentity_t *e; + +  if( ent->client ) +  { +    // removing a player causes the player to "unspawn" +    class_t class = ent->client->pers.classSelection; // back up the spawn queue choice +    weapon_t weapon = ent->client->pers.humanItemSelection; // back up +    ent->client->pers.classSelection = PCL_NONE; +    ent->client->pers.humanItemSelection = WP_NONE; +    ent->suicideTime = 0; // cancel any timed suicides +    ClientSpawn( ent, NULL, NULL, NULL ); +    ent->client->pers.classSelection = class; // restore the spawn queue choice +    ent->client->pers.humanItemSelection = weapon; // restore +    return; +  } +  else if( ent->s.eType == ET_RANGE_MARKER ) +  { +    for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) +    { +      if( e->rangeMarker == ent ) +      { +        // clear the buildable's reference to this range marker +        e->rangeMarker = NULL; +        break; +      } +    } +  } +  else if( ent->s.eType == ET_BUILDABLE ) +  { +    // the range marker (if any) goes away with the buildable +    G_RemoveRangeMarkerFrom( ent ); +  } +  else if( !strcmp( ent->classname, "lev2zapchain" ) ) +  { +    zap_t *z; +    for( z = &zaps[ 0 ]; z < &zaps[ MAX_ZAPS ]; ++z ) +    { +      if( z->used && z->effectChannel == ent ) +      { +        // free the zap slot occupied by this zap effect +        z->used = qfalse; +        break; +      } +    } +  } +  else if( ent->s.eType == ET_MOVER ) +  { +    if( !strcmp( ent->classname, "func_door" ) || +        !strcmp( ent->classname, "func_door_rotating" ) || +        !strcmp( ent->classname, "func_door_model" ) || +        !strcmp( ent->classname, "func_door_model_clip_brush" ) || +        !strcmp( ent->classname, "func_plat" ) ) +    { +      // each func_door_model entity is paired with a clip brush, remove the other +      if( ent->clipBrush != NULL ) +        G_FreeEntity( ent->clipBrush ); +      for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) +      { +        if( e->parent == ent ) +        { +          // this mover has a trigger area brush +          if( ent->teammaster != NULL && ent->teammaster->teamchain != NULL ) +          { +            // the mover is part of a team of at least 2 +            e->parent = ent->teammaster->teamchain; // hand the brush over to the next mover in command +          } +          else +            G_FreeEntity( e ); // remove the teamless or to-be-orphaned brush +          break; +        } +      } +    } +    // removing a mover opens the relevant portal +    trap_AdjustAreaPortalState( ent, qtrue ); +  } +  else if( !strcmp( ent->classname, "path_corner" ) ) +  { +    for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) +    { +      if( e->nextTrain == ent ) +        e->nextTrain = ent->nextTrain; // redirect func_train and path_corner entities +    } +  } +  else if( !strcmp( ent->classname, "info_player_intermission" ) || +           !strcmp( ent->classname, "info_player_deathmatch" ) ) +  { +    for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) +    { +      if( e->inuse && e != ent && +          ( !strcmp( e->classname, "info_player_intermission" ) || +            !strcmp( e->classname, "info_player_deathmatch" ) ) ) +      { +        break; +      } +    } +    // refuse to remove the last info_player_intermission/info_player_deathmatch entity +    //  (because it is required for initial camera placement) +    if( e >= &g_entities[ level.num_entities ] ) +      return; +  } +  else if( !strcmp( ent->classname, "target_location" ) ) +  { +    if( ent == level.locationHead ) +      level.locationHead = ent->nextTrain; +    else +    { +      for( e = level.locationHead; e != NULL; e = e->nextTrain ) +      { +        if( e->nextTrain == ent ) +        { +          e->nextTrain = ent->nextTrain; +          break; +        } +      } +    } +  } +  else if( !Q_stricmp( ent->classname, "misc_portal_camera" ) ) +  { +    for( e = &g_entities[ MAX_CLIENTS ]; e < &g_entities[ level.num_entities ]; ++e ) +    { +      if( e->r.ownerNum == ent - g_entities ) +      { +        // disown the surface +        e->r.ownerNum = ENTITYNUM_NONE; +      } +    } +  } + +  if( ent->teammaster != NULL ) +  { +    // this entity is part of a mover team +    if( ent == ent->teammaster ) +    { +      // the entity is the master +      gentity_t *snd = ent->teamchain; +      for( e = snd; e != NULL; e = e->teamchain ) +        e->teammaster = snd; // put the 2nd entity (if any) in command +      if( snd ) +      { +        if( !strcmp( ent->classname, snd->classname ) ) +        { +          // transfer certain activity properties +          snd->think = ent->think; +          snd->nextthink = ent->nextthink; +        } +        snd->flags &= ~FL_TEAMSLAVE; // put the 2nd entity (if any) in command +      } +    } +    else +    { +      // the entity is a slave +      for( e = ent->teammaster; e != NULL; e = e->teamchain ) +      { +        if( e->teamchain == ent ) +        { +          // unlink it from the chain +          e->teamchain = ent->teamchain; +          break; +        } +      } +    } +  } + +  G_FreeEntity( ent ); +} + +/* +=================  G_TempEntity  Spawns an event entity that will be auto-removed @@ -535,7 +680,7 @@ The origin will be snapped to save net bandwidth, so care  must be taken if the origin is right on a surface (snap towards start vector first)  =================  */ -gentity_t *G_TempEntity( vec3_t origin, int event ) +gentity_t *G_TempEntity( const vec3_t origin, int event )  {    gentity_t *e;    vec3_t    snapped; @@ -582,18 +727,18 @@ void G_KillBox( gentity_t *ent )    gentity_t *hit;    vec3_t    mins, maxs; -  VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); -  VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); +  VectorAdd( ent->r.currentOrigin, ent->r.mins, mins ); +  VectorAdd( ent->r.currentOrigin, ent->r.maxs, maxs );    num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );    for( i = 0; i < num; i++ )    {      hit = &g_entities[ touch[ i ] ]; -    if( !hit->client ) +    if( ent->client && !hit->client ) // players can telefrag only other players        continue; -    //TA: impossible to telefrag self +    // impossible to telefrag self      if( ent == hit )        continue; @@ -644,8 +789,8 @@ void G_AddEvent( gentity_t *ent, int event, int eventParm )    // eventParm is converted to uint8_t (0 - 255) in msg.c     if( eventParm & ~0xFF )    { -    G_Printf( S_COLOR_YELLOW "WARNING: G_AddEvent: event %d " -      " eventParm uint8_t overflow (given %d)\n", event, eventParm ); +    G_Printf( S_COLOR_YELLOW "WARNING: G_AddEvent( %s ) has eventParm %d, " +              "which will overflow\n", BG_EventName( event ), eventParm );    }    // clients need to add the event in playerState_t instead of entityState_t @@ -728,7 +873,7 @@ G_SetOrigin  Sets the pos trajectory for a fixed position  ================  */ -void G_SetOrigin( gentity_t *ent, vec3_t origin ) +void G_SetOrigin( gentity_t *ent, const vec3_t origin )  {    VectorCopy( origin, ent->s.pos.trBase );    ent->s.pos.trType = TR_STATIONARY; @@ -737,10 +882,9 @@ void G_SetOrigin( gentity_t *ent, vec3_t origin )    VectorClear( ent->s.pos.trDelta );    VectorCopy( origin, ent->r.currentOrigin ); -  VectorCopy( origin, ent->s.origin ); //TA: if shit breaks - blame this line  } -//TA: from quakestyle.telefragged.com +// from quakestyle.telefragged.com  // (NOBODY): Code helper function  //  gentity_t *G_FindRadius( gentity_t *from, vec3_t org, float rad ) @@ -777,16 +921,14 @@ G_Visible  Test for a LOS between two entities  ===============  */ -qboolean G_Visible( gentity_t *ent1, gentity_t *ent2 ) +qboolean G_Visible( gentity_t *ent1, gentity_t *ent2, int contents )  {    trace_t trace; -  trap_Trace( &trace, ent1->s.pos.trBase, NULL, NULL, ent2->s.pos.trBase, ent1->s.number, MASK_SHOT ); - -  if( trace.contents & CONTENTS_SOLID ) -    return qfalse; +  trap_Trace( &trace, ent1->s.pos.trBase, NULL, NULL, ent2->s.pos.trBase, +              ent1->s.number, contents ); -  return qtrue; +  return trace.fraction >= 1.0f || trace.entityNum == ent2 - g_entities;  }  /* @@ -799,15 +941,21 @@ Test a list of entities for the closest to a particular point  gentity_t *G_ClosestEnt( vec3_t origin, gentity_t **entities, int numEntities )  {    int       i; -  float     nd, d = 1000000.0f; -  gentity_t *closestEnt = NULL; +  float     nd, d; +  gentity_t *closestEnt; + +  if( numEntities <= 0 ) +    return NULL; + +  closestEnt = entities[ 0 ]; +  d = DistanceSquared( origin, closestEnt->r.currentOrigin ); -  for( i = 0; i < numEntities; i++ ) +  for( i = 1; i < numEntities; i++ )    {      gentity_t *ent = entities[ i ]; -    nd = DistanceSquared( origin, ent->s.origin ); -    if( i == 0 || nd < d ) +    nd = DistanceSquared( origin, ent->r.currentOrigin ); +    if( nd < d )      {        d = nd;        closestEnt = ent; @@ -828,10 +976,24 @@ void G_TriggerMenu( int clientNum, dynMenu_t menu )  {    char buffer[ 32 ]; -  Com_sprintf( buffer, 32, "servermenu %d", menu ); +  Com_sprintf( buffer, sizeof( buffer ), "servermenu %d", menu );    trap_SendServerCommand( clientNum, buffer );  } +/* +=============== +G_TriggerMenuArgs + +Trigger a menu on some client and passes an argument +=============== +*/ +void G_TriggerMenuArgs( int clientNum, dynMenu_t menu, int arg ) +{ +  char buffer[ 64 ]; + +  Com_sprintf( buffer, sizeof( buffer ), "servermenu %d %d", menu, arg ); +  trap_SendServerCommand( clientNum, buffer ); +}  /*  =============== @@ -847,3 +1009,179 @@ void G_CloseMenus( int clientNum )    Com_sprintf( buffer, 32, "serverclosemenus" );    trap_SendServerCommand( clientNum, buffer );  } + + +/* +=============== +G_AddressParse + +Make an IP address more usable +=============== +*/ +static const char *addr4parse( const char *str, addr_t *addr ) +{ +  int i; +  int octet = 0; +  int num = 0; +  memset( addr, 0, sizeof( addr_t ) ); +  addr->type = IPv4; +  for( i = 0; octet < 4; i++ ) +  { +    if( isdigit( str[ i ] ) ) +      num = num * 10 + str[ i ] - '0'; +    else +    { +      if( num < 0 || num > 255 ) +        return NULL; +      addr->addr[ octet ] = (byte)num; +      octet++; +      if( str[ i ] != '.' || str[ i + 1 ] == '.' ) +        break; +      num = 0; +    } +  } +  if( octet < 1 ) +    return NULL; +  return str + i; +} + +static const char *addr6parse( const char *str, addr_t *addr ) +{ +  int i; +  qboolean seen = qfalse; +  /* keep track of the parts before and after the :: +     it's either this or even uglier hacks */ +  byte a[ ADDRLEN ], b[ ADDRLEN ]; +  size_t before = 0, after = 0; +  int num = 0; +  /* 8 hexadectets unless :: is present */ +  for( i = 0; before + after <= 8; i++ ) +  { +    //num = num << 4 | str[ i ] - '0'; +    if( isdigit( str[ i ] ) ) +      num = num * 16 + str[ i ] - '0'; +    else if( str[ i ] >= 'A' && str[ i ] <= 'F' ) +      num = num * 16 + 10 + str[ i ] - 'A'; +    else if( str[ i ] >= 'a' && str[ i ] <= 'f' ) +      num = num * 16 + 10 + str[ i ] - 'a'; +    else +    { +      if( num < 0 || num > 65535 ) +        return NULL; +      if( i == 0 ) +      { +        //  +      } +      else if( seen ) // :: has been seen already +      { +        b[ after * 2 ] = num >> 8; +        b[ after * 2 + 1 ] = num & 0xff; +        after++; +      } +      else +      { +        a[ before * 2 ] = num >> 8; +        a[ before * 2 + 1 ] = num & 0xff; +        before++; +      } +      if( !str[ i ] ) +        break; +      if( str[ i ] != ':' || before + after == 8 ) +        break; +      if( str[ i + 1 ] == ':' ) +      { +        // ::: or multiple :: +        if( seen || str[ i + 2 ] == ':' ) +          break; +        seen = qtrue; +        i++; +      } +      else if( i == 0 ) // starts with : but not :: +        return NULL; +      num = 0; +    } +  } +  if( seen ) +  { +    // there have to be fewer than 8 hexadectets when :: is present +    if( before + after == 8 ) +      return NULL; +  } +  else if( before + after < 8 ) // require exactly 8 hexadectets +    return NULL; +  memset( addr, 0, sizeof( addr_t ) ); +  addr->type = IPv6; +  if( before ) +    memcpy( addr->addr, a, before * 2 ); +  if( after ) +    memcpy( addr->addr + ADDRLEN - 2 * after, b, after * 2 ); +  return str + i; +} + +qboolean G_AddressParse( const char *str, addr_t *addr ) +{ +  const char *p; +  int max; +  if( strchr( str, ':' ) ) +  { +    p = addr6parse( str, addr ); +    max = 128; +  } +  else if( strchr( str, '.' ) ) +  { +    p = addr4parse( str, addr ); +    max = 32; +  } +  else +    return qfalse; +  Q_strncpyz( addr->str, str, sizeof( addr->str ) ); +  if( !p ) +    return qfalse; +  if( *p == '/' ) +  { +    addr->mask = atoi( p + 1 ); +    if( addr->mask < 1 || addr->mask > max ) +      addr->mask = max; +  } +  else +  { +    if( *p ) +      return qfalse; +    addr->mask = max; +  } +  return qtrue; +} + +/* +=============== +G_AddressCompare + +Based largely on NET_CompareBaseAdrMask from ioq3 revision 1557 +=============== +*/ +qboolean G_AddressCompare( const addr_t *a, const addr_t *b ) +{ +  int i, netmask; +  if( a->type != b->type ) +    return qfalse; +  netmask = a->mask; +  if( a->type == IPv4 ) +  { +    if( netmask < 1 || netmask > 32 ) +      netmask = 32; +  } +  else if( a->type == IPv6 ) +  { +    if( netmask < 1 || netmask > 128 ) +      netmask = 128; +  } +  for( i = 0; netmask > 7; i++, netmask -= 8 ) +    if( a->addr[ i ] != b->addr[ i ] ) +      return qfalse; +  if( netmask ) +  { +    netmask = ( ( 1 << netmask ) - 1 ) << ( 8 - netmask ); +    return ( a->addr[ i ] & netmask ) == ( b->addr[ i ] & netmask ); +  } +  return qtrue; +} diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c index ddad4af..302c47e 100644 --- a/src/game/g_weapon.c +++ b/src/game/g_weapon.c @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ @@ -36,51 +37,24 @@ G_ForceWeaponChange  */  void G_ForceWeaponChange( gentity_t *ent, weapon_t weapon )  { -  int i; - -  if( !ent ) -    return; - -  if( ent->client->ps.weaponstate == WEAPON_RELOADING ) -  { -    ent->client->ps.torsoAnim = ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_RAISE; -    ent->client->ps.weaponTime = 250; -    ent->client->ps.weaponstate = WEAPON_READY; -  } - -  ent->client->ps.pm_flags |= PMF_WEAPON_SWITCH; +  playerState_t *ps = &ent->client->ps; -  if( weapon == WP_NONE -    || !BG_InventoryContainsWeapon( weapon, ent->client->ps.stats )) +  // stop a reload in progress +  if( ps->weaponstate == WEAPON_RELOADING )    { -    //switch to the first non blaster weapon -    for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) -    { -      if( i == WP_BLASTER ) -        continue; - -      if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) ) -      { -        ent->client->ps.persistant[ PERS_NEWWEAPON ] = i; -        break; -      } -    } - -    //only got the blaster to switch to -    if( i == WP_NUM_WEAPONS ) -      ent->client->ps.persistant[ PERS_NEWWEAPON ] = WP_BLASTER; +    ps->torsoAnim = ( ( ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_RAISE; +    ps->weaponTime = 250; +    ps->weaponstate = WEAPON_READY;    } -  else -    ent->client->ps.persistant[ PERS_NEWWEAPON ] = weapon; -  // Lak: The following hack has been moved to PM_BeginWeaponChange, but I'm going to -  // redundantly leave it here as well just in case there's a case I'm forgetting -  // because I don't want to face the gameplay consequences such an error would have +  ps->persistant[ PERS_NEWWEAPON ] = ( weapon == WP_BLASTER ) ? +    WP_BLASTER : ps->stats[ STAT_WEAPON ];    // force this here to prevent flamer effect from continuing -  ent->client->ps.generic1 = WPM_NOTFIRING; +  ps->generic1 = WPM_NOTFIRING; -  ent->client->ps.weapon = ent->client->ps.persistant[ PERS_NEWWEAPON ]; +  // The PMove will do an animated drop, raise, and set the new weapon +  ps->pm_flags |= PMF_WEAPON_SWITCH;  }  /* @@ -90,43 +64,35 @@ G_GiveClientMaxAmmo  */  void G_GiveClientMaxAmmo( gentity_t *ent, qboolean buyingEnergyAmmo )  { -  int       i; -  int       maxAmmo, maxClips; -  qboolean  weaponType, restoredAmmo = qfalse; +  int maxAmmo; +  weapon_t weapon = ent->client->ps.stats[ STAT_WEAPON ]; -  // GH FIXME +  if( BG_Weapon( weapon )->infiniteAmmo ) +    return; -  for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) -  { -    if( buyingEnergyAmmo ) -      weaponType = BG_FindUsesEnergyForWeapon( i ); -    else -      weaponType = !BG_FindUsesEnergyForWeapon( i ); +  if( buyingEnergyAmmo && !BG_Weapon( weapon )->usesEnergy ) +    return; -    if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) && -        weaponType && !BG_FindInfinteAmmoForWeapon( i ) && -        !BG_WeaponIsFull( i, ent->client->ps.stats, -          ent->client->ps.ammo, ent->client->ps.clips ) ) -    { -      BG_FindAmmoForWeapon( i, &maxAmmo, &maxClips ); +  if( BG_WeaponIsFull( weapon, ent->client->ps.stats, ent->client->ps.ammo, +                       ent->client->ps.clips ) ) +    return; -      if( buyingEnergyAmmo ) -      { -        G_AddEvent( ent, EV_RPTUSE_SOUND, 0 ); +  maxAmmo = BG_Weapon( weapon )->maxAmmo; -        if( BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) -          maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); -      } +  // Apply battery pack modifier +  if( BG_Weapon( weapon )->usesEnergy && +      BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) +  { +    maxAmmo *= BATTPACK_MODIFIER; +  } -      ent->client->ps.ammo = maxAmmo; -      ent->client->ps.clips = maxClips; +  ent->client->ps.ammo = maxAmmo; +  ent->client->ps.clips = BG_Weapon( weapon )->maxClips; -      restoredAmmo = qtrue; -    } -  } +  G_ForceWeaponChange( ent, ent->client->ps.weapon ); -  if( restoredAmmo ) -    G_ForceWeaponChange( ent, ent->client->ps.weapon ); +  if( BG_Weapon( weapon )->usesEnergy ) +    G_AddEvent( ent, EV_RPTUSE_SOUND, 0 );  }  /* @@ -155,50 +121,46 @@ Trace a bounding box against entities, but not the world  Also check there is a line of sight between the start and end point  ================  */ -static void G_WideTrace( trace_t *tr, gentity_t *ent, float range, float width, gentity_t **target ) +static void G_WideTrace( trace_t *tr, gentity_t *ent, float range, +                         float width, float height, gentity_t **target )  {    vec3_t    mins, maxs;    vec3_t    end; -  VectorSet( mins, -width, -width, -width ); -  VectorSet( maxs, width, width, width ); +  VectorSet( mins, -width, -width, -height ); +  VectorSet( maxs, width, width, height );    *target = NULL;    if( !ent->client )      return; -  // Set aiming directions -  AngleVectors( ent->client->ps.viewangles, forward, right, up ); -  CalcMuzzlePoint( ent, forward, right, up, muzzle ); -  VectorMA( muzzle, range, forward, end ); +  G_UnlaggedOn( ent, muzzle, range + width ); -  G_UnlaggedOn( ent, muzzle, range ); +  VectorMA( muzzle, range, forward, end );    // Trace against entities    trap_Trace( tr, muzzle, mins, maxs, end, ent->s.number, CONTENTS_BODY );    if( tr->entityNum != ENTITYNUM_NONE ) -  {      *target = &g_entities[ tr->entityNum ]; -    // Set range to the trace length plus the width, so that the end of the -    // LOS trace is close to the exterior of the target's bounding box -    range = Distance( muzzle, tr->endpos ) + width; -    VectorMA( muzzle, range, forward, end ); +  // Set range to the trace length plus the width, so that the end of the +  // LOS trace is close to the exterior of the target's bounding box +  range = Distance( muzzle, tr->endpos ) + width; +  VectorMA( muzzle, range, forward, end ); -    // Trace for line of sight against the world -    trap_Trace( tr, muzzle, NULL, NULL, end, 0, CONTENTS_SOLID ); -    if( tr->fraction < 1.0f ) -      *target = NULL; -  } +  // Trace for line of sight against the world +  trap_Trace( tr, muzzle, NULL, NULL, end, ent->s.number, CONTENTS_SOLID ); +  if( tr->entityNum != ENTITYNUM_NONE ) +    *target = &g_entities[ tr->entityNum ];    G_UnlaggedOff( );  } -  /*  ======================  SnapVectorTowards +SnapVectorNormal  Round a vector to integers for more efficient network  transmission, but make sure that it rounds towards a given point @@ -212,57 +174,120 @@ void SnapVectorTowards( vec3_t v, vec3_t to )    for( i = 0 ; i < 3 ; i++ )    { -    if( to[ i ] <= v[ i ] ) -      v[ i ] = (int)v[ i ]; +    if( v[ i ] >= 0 ) +      v[ i ] = (int)( v[ i ] + ( to[ i ] <= v[ i ] ? 0 : 1 ) ); +    else +      v[ i ] = (int)( v[ i ] + ( to[ i ] <= v[ i ] ? -1 : 0 ) ); +  } +} + +void SnapVectorNormal( vec3_t v, vec3_t normal ) +{ +  int i; + +  for( i = 0 ; i < 3 ; i++ ) +  { +    if( v[ i ] >= 0 ) +      v[ i ] = (int)( v[ i ] + ( normal[ i ] <= 0 ? 0 : 1 ) );      else -      v[ i ] = (int)v[ i ] + 1; +      v[ i ] = (int)( v[ i ] + ( normal[ i ] <= 0 ? -1 : 0 ) );    }  }  /*  =============== -meleeAttack +BloodSpurt + +Generates a blood spurt event for traces with accurate end points  ===============  */ -void meleeAttack( gentity_t *ent, float range, float width, int damage, meansOfDeath_t mod ) +static void BloodSpurt( gentity_t *attacker, gentity_t *victim, trace_t *tr )  { -  trace_t   tr; -  vec3_t    end;    gentity_t *tent; -  gentity_t *traceEnt; -  vec3_t    mins, maxs; -  VectorSet( mins, -width, -width, -width ); -  VectorSet( maxs, width, width, width ); +  if( !attacker->client ) +    return; -  // set aiming directions -  AngleVectors( ent->client->ps.viewangles, forward, right, up ); +  if( victim->health <= 0 ) +    return; -  CalcMuzzlePoint( ent, forward, right, up, muzzle ); +  tent = G_TempEntity( tr->endpos, EV_MISSILE_HIT ); +  tent->s.otherEntityNum = victim->s.number; +  tent->s.eventParm = DirToByte( tr->plane.normal ); +  tent->s.weapon = attacker->s.weapon; +  tent->s.generic1 = attacker->s.generic1; // weaponMode +} -  VectorMA( muzzle, range, forward, end ); +/* +=============== +WideBloodSpurt -  G_UnlaggedOn( ent, muzzle, range ); -  trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); -  G_UnlaggedOff( ); +Calculates the position of a blood spurt for wide traces and generates an event +=============== +*/ +static void WideBloodSpurt( gentity_t *attacker, gentity_t *victim, trace_t *tr ) +{ +  gentity_t *tent; +  vec3_t normal, origin; +  float mag, radius; -  if( tr.surfaceFlags & SURF_NOIMPACT ) +  if( !attacker->client )      return; -  traceEnt = &g_entities[ tr.entityNum ]; +  if( victim->health <= 0 ) +    return; -  // send blood impact -  if( traceEnt->takedamage && traceEnt->client ) +  if( tr ) +    VectorSubtract( tr->endpos, victim->r.currentOrigin, normal ); +  else +    VectorSubtract( attacker->client->ps.origin, +                    victim->r.currentOrigin, normal ); + +  // Normalize the horizontal components of the vector difference to the +  // "radius" of the bounding box +  mag = sqrt( normal[ 0 ] * normal[ 0 ] + normal[ 1 ] * normal[ 1 ] ); +  radius = victim->r.maxs[ 0 ] * 1.21f; +  if( mag > radius )    { -    tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); -    tent->s.otherEntityNum = traceEnt->s.number; -    tent->s.eventParm = DirToByte( tr.plane.normal ); -    tent->s.weapon = ent->s.weapon; -    tent->s.generic1 = ent->s.generic1; //weaponMode +    normal[ 0 ] = normal[ 0 ] / mag * radius; +    normal[ 1 ] = normal[ 1 ] / mag * radius;    } -  if( traceEnt->takedamage ) -    G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, mod ); +  // Clamp origin to be within bounding box vertically +  if( normal[ 2 ] > victim->r.maxs[ 2 ] ) +    normal[ 2 ] = victim->r.maxs[ 2 ]; +  if( normal[ 2 ] < victim->r.mins[ 2 ] ) +    normal[ 2 ] = victim->r.mins[ 2 ]; + +  VectorAdd( victim->r.currentOrigin, normal, origin ); +  VectorNegate( normal, normal ); +  VectorNormalize( normal ); + +  // Create the blood spurt effect entity +  tent = G_TempEntity( origin, EV_MISSILE_HIT ); +  tent->s.eventParm = DirToByte( normal ); +  tent->s.otherEntityNum = victim->s.number; +  tent->s.weapon = attacker->s.weapon; +  tent->s.generic1 = attacker->s.generic1; // weaponMode +} + +/* +=============== +meleeAttack +=============== +*/ +void meleeAttack( gentity_t *ent, float range, float width, float height, +                  int damage, meansOfDeath_t mod ) +{ +  trace_t   tr; +  gentity_t *traceEnt; + +  G_WideTrace( &tr, ent, range, width, height, &traceEnt ); +  if( traceEnt == NULL || !traceEnt->takedamage ) +    return; + +  WideBloodSpurt( ent, traceEnt, &tr ); +  G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, mod );  }  /* @@ -308,7 +333,9 @@ void bulletFire( gentity_t *ent, float spread, int damage, int mod )    SnapVectorTowards( tr.endpos, muzzle );    // send bullet impact -  if( traceEnt->takedamage && traceEnt->client ) +  if( traceEnt->takedamage && +      (traceEnt->s.eType == ET_PLAYER || +       traceEnt->s.eType == ET_BUILDABLE ) )    {      tent = G_TempEntity( tr.endpos, EV_BULLET_HIT_FLESH );      tent->s.eventParm = traceEnt->s.number; @@ -356,7 +383,7 @@ void ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, gentity_t *ent )    {      r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;      u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16; -    VectorMA( origin, 8192 * 16, forward, end ); +    VectorMA( origin, SHOTGUN_RANGE, forward, end );      VectorMA( end, r, right, end );      VectorMA( end, u, up, end ); @@ -381,9 +408,9 @@ void shotgunFire( gentity_t *ent )    tent = G_TempEntity( muzzle, EV_SHOTGUN );    VectorScale( forward, 4096, tent->s.origin2 );    SnapVector( tent->s.origin2 ); -  tent->s.eventParm = rand() & 255;    // seed for spread pattern +  tent->s.eventParm = rand() / ( RAND_MAX / 0x100 + 1 );    // seed for spread pattern    tent->s.otherEntityNum = ent->s.number; -  G_UnlaggedOn( ent, muzzle, 8192 * 16 ); +  G_UnlaggedOn( ent, muzzle, SHOTGUN_RANGE );    ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent );    G_UnlaggedOff();  } @@ -403,9 +430,9 @@ void massDriverFire( gentity_t *ent )    gentity_t *tent;    gentity_t *traceEnt; -  VectorMA( muzzle, 8192 * 16, forward, end ); +  VectorMA( muzzle, 8192.0f * 16.0f, forward, end ); -  G_UnlaggedOn( ent, muzzle, 8192 * 16 ); +  G_UnlaggedOn( ent, muzzle, 8192.0f * 16.0f );    trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );    G_UnlaggedOff( ); @@ -418,13 +445,11 @@ void massDriverFire( gentity_t *ent )    SnapVectorTowards( tr.endpos, muzzle );    // send impact -  if( traceEnt->takedamage && traceEnt->client ) +  if( traceEnt->takedamage &&  +      (traceEnt->s.eType == ET_BUILDABLE ||  +       traceEnt->s.eType == ET_PLAYER ) )    { -    tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); -    tent->s.otherEntityNum = traceEnt->s.number; -    tent->s.eventParm = DirToByte( tr.plane.normal ); -    tent->s.weapon = ent->s.weapon; -    tent->s.generic1 = ent->s.generic1; //weaponMode +    BloodSpurt( ent, traceEnt, &tr );    }    else    { @@ -451,11 +476,7 @@ LOCKBLOB  void lockBlobLauncherFire( gentity_t *ent )  { -  gentity_t *m; - -  m = fire_lockblob( ent, muzzle, forward ); - -//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics +  fire_lockblob( ent, muzzle, forward );  }  /* @@ -468,11 +489,12 @@ HIVE  void hiveFire( gentity_t *ent )  { -  gentity_t *m; - -  m = fire_hive( ent, muzzle, forward ); +  vec3_t origin; -//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics +  // Fire from the hive tip, not the center +  VectorMA( muzzle, ent->r.maxs[ 2 ], ent->s.origin2, origin ); +   +  fire_hive( ent, origin, forward );  }  /* @@ -485,11 +507,7 @@ BLASTER PISTOL  void blasterFire( gentity_t *ent )  { -  gentity_t *m; - -  m = fire_blaster( ent, muzzle, forward ); - -//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics +  fire_blaster( ent, muzzle, forward );  }  /* @@ -502,11 +520,7 @@ PULSE RIFLE  void pulseRifleFire( gentity_t *ent )  { -  gentity_t *m; - -  m = fire_pulseRifle( ent, muzzle, forward ); - -//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics +  fire_pulseRifle( ent, muzzle, forward );  }  /* @@ -519,9 +533,15 @@ FLAME THROWER  void flamerFire( gentity_t *ent )  { -  gentity_t *m; +  vec3_t origin; + +  // Correct muzzle so that the missile does not start in the ceiling  +  VectorMA( muzzle, -7.0f, up, origin ); + +  // Correct muzzle so that the missile fires from the player's hand +  VectorMA( origin, 4.5f, right, origin ); -  m = fire_flamer( ent, muzzle, forward ); +  fire_flamer( ent, origin, forward );  }  /* @@ -534,9 +554,7 @@ GRENADE  void throwGrenade( gentity_t *ent )  { -  gentity_t *m; - -  m = launch_grenade( ent, muzzle, forward ); +  launch_grenade( ent, muzzle, forward );  }  /* @@ -574,13 +592,11 @@ void lasGunFire( gentity_t *ent )    SnapVectorTowards( tr.endpos, muzzle );    // send impact -  if( traceEnt->takedamage && traceEnt->client ) +  if( traceEnt->takedamage &&  +      (traceEnt->s.eType == ET_BUILDABLE ||  +       traceEnt->s.eType == ET_PLAYER ) )    { -    tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); -    tent->s.otherEntityNum = traceEnt->s.number; -    tent->s.eventParm = DirToByte( tr.plane.normal ); -    tent->s.weapon = ent->s.weapon; -    tent->s.generic1 = ent->s.generic1; //weaponMode +    BloodSpurt( ent, traceEnt, &tr );    }    else    { @@ -605,50 +621,32 @@ PAIN SAW  void painSawFire( gentity_t *ent )  {    trace_t   tr; -  vec3_t    end; -  gentity_t *tent; -  gentity_t *traceEnt; - -  // set aiming directions -  AngleVectors( ent->client->ps.viewangles, forward, right, up ); - -  CalcMuzzlePoint( ent, forward, right, up, muzzle ); - -  VectorMA( muzzle, PAINSAW_RANGE, forward, end ); - -  G_UnlaggedOn( ent, muzzle, PAINSAW_RANGE ); -  trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); -  G_UnlaggedOff( ); +  vec3_t    temp; +  gentity_t *tent, *traceEnt; -  if( tr.surfaceFlags & SURF_NOIMPACT ) +  G_WideTrace( &tr, ent, PAINSAW_RANGE, PAINSAW_WIDTH, PAINSAW_HEIGHT, +               &traceEnt ); +  if( !traceEnt || !traceEnt->takedamage )      return; -  traceEnt = &g_entities[ tr.entityNum ]; +  // hack to line up particle system with weapon model +  tr.endpos[ 2 ] -= 5.0f;    // send blood impact -  if( traceEnt->takedamage ) +  if( traceEnt->s.eType == ET_PLAYER || traceEnt->s.eType == ET_BUILDABLE ) +  { +      BloodSpurt( ent, traceEnt, &tr ); +  } +  else    { -    vec3_t  temp; - -    //hack to get the particle system to line up with the weapon      VectorCopy( tr.endpos, temp ); -    temp[ 2 ] -= 10.0f; - -    if( traceEnt->client ) -    { -      tent = G_TempEntity( temp, EV_MISSILE_HIT ); -      tent->s.otherEntityNum = traceEnt->s.number; -    } -    else -      tent = G_TempEntity( temp, EV_MISSILE_MISS ); - +    tent = G_TempEntity( temp, EV_MISSILE_MISS );      tent->s.eventParm = DirToByte( tr.plane.normal );      tent->s.weapon = ent->s.weapon;      tent->s.generic1 = ent->s.generic1; //weaponMode    } -  if( traceEnt->takedamage ) -    G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, 0, MOD_PAINSAW ); +  G_Damage( traceEnt, ent, ent, forward, tr.endpos, PAINSAW_DAMAGE, DAMAGE_NO_KNOCKBACK, MOD_PAINSAW );  }  /* @@ -666,19 +664,14 @@ LCChargeFire  */  void LCChargeFire( gentity_t *ent, qboolean secondary )  { -  gentity_t *m; - -  if( secondary ) -  { -    m = fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE, -      LCANNON_SECONDARY_RADIUS ); -    ent->client->ps.weaponTime = LCANNON_REPEAT; -  } +  if( secondary && ent->client->ps.stats[ STAT_MISC ] <= 0 ) +    fire_luciferCannon( ent, muzzle, forward, LCANNON_SECONDARY_DAMAGE, +                            LCANNON_SECONDARY_RADIUS, LCANNON_SECONDARY_SPEED );    else -  { -    m = fire_luciferCannon( ent, muzzle, forward, ent->client->ps.stats[ STAT_MISC ], LCANNON_RADIUS ); -    ent->client->ps.weaponTime = LCANNON_CHARGEREPEAT; -  } +    fire_luciferCannon( ent, muzzle, forward, +                            ent->client->ps.stats[ STAT_MISC ] * +                            LCANNON_DAMAGE / LCANNON_CHARGE_TIME_MAX, +                            LCANNON_RADIUS, LCANNON_SPEED );    ent->client->ps.stats[ STAT_MISC ] = 0;  } @@ -692,49 +685,44 @@ TESLA GENERATOR  */ -void teslaFire( gentity_t *ent ) +void teslaFire( gentity_t *self )  { -  trace_t   tr; -  vec3_t    end; -  gentity_t *traceEnt, *tent; - -  VectorMA( muzzle, TESLAGEN_RANGE, forward, end ); - -  trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); +  trace_t tr; +  vec3_t origin, target; +  gentity_t *tent; -  if( tr.entityNum == ENTITYNUM_NONE ) +  if( !self->enemy )      return; -  traceEnt = &g_entities[ tr.entityNum ]; +  // Move the muzzle from the entity origin up a bit to fire over turrets +  VectorMA( muzzle, self->r.maxs[ 2 ], self->s.origin2, origin ); -  if( !traceEnt->client ) -    return; +  // Don't aim for the center, aim at the top of the bounding box +  VectorCopy( self->enemy->r.currentOrigin, target ); +  target[ 2 ] += self->enemy->r.maxs[ 2 ]; -  if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) +  // Trace to the target entity +  trap_Trace( &tr, origin, NULL, NULL, target, self->s.number, MASK_SHOT ); +  if( tr.entityNum != self->enemy->s.number )      return; -  //so the client side knows -  ent->s.eFlags |= EF_FIRING; +  // Client side firing effect +  self->s.eFlags |= EF_FIRING; -  if( traceEnt->takedamage ) +  // Deal damage +  if( self->enemy->takedamage )    { -    G_Damage( traceEnt, ent, ent, forward, tr.endpos, -      TESLAGEN_DMG, 0, MOD_TESLAGEN ); -  } +    vec3_t dir; -  // snap the endpos to integers to save net bandwidth, but nudged towards the line -  SnapVectorTowards( tr.endpos, muzzle ); +    VectorSubtract( target, origin, dir ); +    G_Damage( self->enemy, self, self, dir, tr.endpos, +              TESLAGEN_DMG, 0, MOD_TESLAGEN ); +  } -  // send railgun beam effect +  // Send tesla zap trail    tent = G_TempEntity( tr.endpos, EV_TESLATRAIL ); - -  VectorCopy( muzzle, tent->s.origin2 ); - -  tent->s.generic1 = ent->s.number; //src -  tent->s.clientNum = traceEnt->s.number; //dest - -  // move origin a bit to come closer to the drawn gun muzzle -  VectorMA( tent->s.origin2, 28, up, tent->s.origin2 ); +  tent->s.misc = self->s.number; // src +  tent->s.clientNum = self->enemy->s.number; // dest  } @@ -745,65 +733,62 @@ BUILD GUN  ======================================================================  */ - -/* -=============== -cancelBuildFire -=============== -*/ -void cancelBuildFire( gentity_t *ent ) +void CheckCkitRepair( gentity_t *ent )  { -  vec3_t      forward, end; +  vec3_t      viewOrigin, forward, end;    trace_t     tr;    gentity_t   *traceEnt;    int         bHealth; -  if( ent->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) -  { -    ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; +  if( ent->client->ps.weaponTime > 0 || +      ent->client->ps.stats[ STAT_MISC ] > 0 )      return; -  } -  //repair buildable -  if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) -  { -    AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); -    VectorMA( ent->client->ps.origin, 100, forward, end ); +  BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); +  AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); +  VectorMA( viewOrigin, 100, forward, end ); -    trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); -    traceEnt = &g_entities[ tr.entityNum ]; +  trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); +  traceEnt = &g_entities[ tr.entityNum ]; -    if( tr.fraction < 1.0 && -        ( traceEnt->s.eType == ET_BUILDABLE ) && -        ( traceEnt->biteam == ent->client->ps.stats[ STAT_PTEAM ] ) && -        ( ( ent->client->ps.weapon >= WP_HBUILD2 ) && -          ( ent->client->ps.weapon <= WP_HBUILD ) ) && -        traceEnt->spawned && traceEnt->health > 0 ) +  if( tr.fraction < 1.0f && traceEnt->spawned && traceEnt->health > 0 && +      traceEnt->s.eType == ET_BUILDABLE && traceEnt->buildableTeam == TEAM_HUMANS ) +  { +    bHealth = BG_Buildable( traceEnt->s.modelindex )->health; +    if( traceEnt->health < bHealth )      { -      if( ent->client->ps.stats[ STAT_MISC ] > 0 ) -      { -        G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); -        return; -      } - -      bHealth = BG_FindHealthForBuildable( traceEnt->s.modelindex ); -        traceEnt->health += HBUILD_HEALRATE; -      ent->client->pers.statscounters.repairspoisons++; -      level.humanStatsCounters.repairspoisons++; - -      if( traceEnt->health > bHealth ) +      if( traceEnt->health >= bHealth ) +      {          traceEnt->health = bHealth; - -      if( traceEnt->health == bHealth )          G_AddEvent( ent, EV_BUILD_REPAIRED, 0 ); +      }        else          G_AddEvent( ent, EV_BUILD_REPAIR, 0 ); + +      ent->client->ps.weaponTime += BG_Weapon( ent->client->ps.weapon )->repeatRate1;      }    } -  else if( ent->client->ps.weapon == WP_ABUILD2 ) +} + +/* +=============== +cancelBuildFire +=============== +*/ +void cancelBuildFire( gentity_t *ent ) +{ +  // Cancel ghost buildable +  if( ent->client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) +  { +    ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; +    return; +  } + +  if( ent->client->ps.weapon == WP_ABUILD || +      ent->client->ps.weapon == WP_ABUILD2 )      meleeAttack( ent, ABUILDER_CLAW_RANGE, ABUILDER_CLAW_WIDTH, -                 ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW ); //melee attack for alien builder +                 ABUILDER_CLAW_WIDTH, ABUILDER_CLAW_DMG, MOD_ABUILDER_CLAW );  }  /* @@ -813,7 +798,10 @@ buildFire  */  void buildFire( gentity_t *ent, dynMenu_t menu )  { -  if( ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) +  buildable_t buildable = ( ent->client->ps.stats[ STAT_BUILDABLE ] +                            & ~SB_VALID_TOGGLEBIT ); + +  if( buildable > BA_NONE )    {      if( ent->client->ps.stats[ STAT_MISC ] > 0 )      { @@ -821,33 +809,17 @@ void buildFire( gentity_t *ent, dynMenu_t menu )        return;      } -    if( G_BuildIfValid( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) ) +    if( G_BuildIfValid( ent, buildable ) )      { -      if( g_cheats.integer ) -      { -        ent->client->ps.stats[ STAT_MISC ] = 0; -      } -      else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_IsOvermindBuilt( ) ) -      { -        ent->client->ps.stats[ STAT_MISC ] += -          BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2; -      } -      else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && !G_IsPowered( muzzle ) && -          ( ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) != BA_H_REPEATER ) //hack +      if( !g_cheats.integer )        {          ent->client->ps.stats[ STAT_MISC ] += -          BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2; +          BG_Buildable( buildable )->buildTime;        } -      else -        ent->client->ps.stats[ STAT_MISC ] += -          BG_FindBuildDelayForWeapon( ent->s.weapon );        ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; - -      // don't want it bigger than 32k -      if( ent->client->ps.stats[ STAT_MISC ] > 30000 ) -        ent->client->ps.stats[ STAT_MISC ] = 30000;      } +      return;    } @@ -856,11 +828,7 @@ void buildFire( gentity_t *ent, dynMenu_t menu )  void slowBlobFire( gentity_t *ent )  { -  gentity_t *m; - -  m = fire_slowBlob( ent, muzzle, forward ); - -//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics +  fire_slowBlob( ent, muzzle, forward );  } @@ -880,65 +848,51 @@ CheckVenomAttack  qboolean CheckVenomAttack( gentity_t *ent )  {    trace_t   tr; -  vec3_t    end; -  gentity_t *tent;    gentity_t *traceEnt; -  vec3_t    mins, maxs;    int       damage = LEVEL0_BITE_DMG; -  VectorSet( mins, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH, -LEVEL0_BITE_WIDTH ); -  VectorSet( maxs, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH, LEVEL0_BITE_WIDTH ); +  if( ent->client->ps.weaponTime ) +	return qfalse; -  // set aiming directions +  // Calculate muzzle point    AngleVectors( ent->client->ps.viewangles, forward, right, up ); -    CalcMuzzlePoint( ent, forward, right, up, muzzle ); -  VectorMA( muzzle, LEVEL0_BITE_RANGE, forward, end ); - -  G_UnlaggedOn( ent, muzzle, LEVEL0_BITE_RANGE ); -  trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); -  G_UnlaggedOff( ); +  G_WideTrace( &tr, ent, LEVEL0_BITE_RANGE, LEVEL0_BITE_WIDTH, +               LEVEL0_BITE_WIDTH, &traceEnt ); -  if( tr.surfaceFlags & SURF_NOIMPACT ) +  if( traceEnt == NULL )      return qfalse; -  traceEnt = &g_entities[ tr.entityNum ]; -    if( !traceEnt->takedamage )      return qfalse; -  //allow bites to work against defensive buildables only +  if( traceEnt->health <= 0 ) +      return qfalse; + +  // only allow bites to work against buildings as they are constructing    if( traceEnt->s.eType == ET_BUILDABLE )    { -    if( traceEnt->s.modelindex != BA_H_MGTURRET && -        traceEnt->s.modelindex != BA_H_TESLAGEN ) +    if( traceEnt->spawned )        return qfalse; -    //hackery -    damage *= 0.5f; +    if( traceEnt->buildableTeam == TEAM_ALIENS ) +      return qfalse;    }    if( traceEnt->client )    { -    if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) +    if( traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )        return qfalse;      if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 )        return qfalse;    }    // send blood impact -  if( traceEnt->takedamage && traceEnt->client ) -  { -    tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); -    tent->s.otherEntityNum = traceEnt->s.number; -    tent->s.eventParm = DirToByte( tr.plane.normal ); -    tent->s.weapon = ent->s.weapon; -    tent->s.generic1 = ent->s.generic1; //weaponMode -  } - -  G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, 0, MOD_LEVEL0_BITE ); +  WideBloodSpurt( ent, traceEnt, &tr ); +  G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_LEVEL0_BITE ); +  ent->client->ps.weaponTime += LEVEL0_BITE_REPEAT;    return qtrue;  } @@ -966,7 +920,10 @@ void CheckGrabAttack( gentity_t *ent )    CalcMuzzlePoint( ent, forward, right, up, muzzle ); -  VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end ); +  if( ent->client->ps.weapon == WP_ALEVEL1 ) +    VectorMA( muzzle, LEVEL1_GRAB_RANGE, forward, end ); +  else if( ent->client->ps.weapon == WP_ALEVEL1_UPG ) +    VectorMA( muzzle, LEVEL1_GRAB_U_RANGE, forward, end );    trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT );    if( tr.surfaceFlags & SURF_NOIMPACT ) @@ -979,7 +936,7 @@ void CheckGrabAttack( gentity_t *ent )    if( traceEnt->client )    { -    if( traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) +    if( traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS )        return;      if( traceEnt->client->ps.stats[ STAT_HEALTH ] <= 0 ) @@ -1001,15 +958,6 @@ void CheckGrabAttack( gentity_t *ent )      else if( ent->client->ps.weapon == WP_ALEVEL1_UPG )        traceEnt->client->grabExpiryTime = level.time + LEVEL1_GRAB_U_TIME;    } -  else if( traceEnt->s.eType == ET_BUILDABLE && -      traceEnt->s.modelindex == BA_H_MGTURRET ) -  { -    if( !traceEnt->lev1Grabbed ) -      G_AddPredictableEvent( ent, EV_LEV1_GRAB, 0 ); - -    traceEnt->lev1Grabbed = qtrue; -    traceEnt->lev1GrabTime = level.time; -  }  }  /* @@ -1023,7 +971,7 @@ void poisonCloud( gentity_t *ent )    vec3_t    range = { LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE, LEVEL1_PCLOUD_RANGE };    vec3_t    mins, maxs;    int       i, num; -  gentity_t *target; +  gentity_t *humanPlayer;    trace_t   tr;    VectorAdd( ent->client->ps.origin, range, maxs ); @@ -1033,39 +981,23 @@ void poisonCloud( gentity_t *ent )    num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );    for( i = 0; i < num; i++ )    { -    target = &g_entities[ entityList[ i ] ]; +    humanPlayer = &g_entities[ entityList[ i ] ]; -    if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) +    if( humanPlayer->client && +        humanPlayer->client->pers.teamSelection == TEAM_HUMANS )      { -      if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, target->client->ps.stats ) ) -        continue; - -      if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, target->client->ps.stats ) ) -        continue; - -      trap_Trace( &tr, muzzle, NULL, NULL, target->s.origin, target->s.number, MASK_SHOT ); +      trap_Trace( &tr, muzzle, NULL, NULL, humanPlayer->r.currentOrigin, +                  humanPlayer->s.number, CONTENTS_SOLID );        //can't see target from here        if( tr.entityNum == ENTITYNUM_WORLD )          continue; -      if( !( target->client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) ) -      { -        target->client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED; -        target->client->lastPoisonCloudedTime = level.time; -        target->client->lastPoisonCloudedClient = ent; -        trap_SendServerCommand( target->client->ps.clientNum, "poisoncloud" ); -      } -    } -    if( target->client && target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) -    { -      trap_Trace( &tr, muzzle, NULL, NULL, target->s.origin, target->s.number, MASK_SOLID ); - -      if( tr.entityNum == ENTITYNUM_WORLD ) -        continue; +      humanPlayer->client->ps.eFlags |= EF_POISONCLOUDED; +      humanPlayer->client->lastPoisonCloudedTime = level.time; -      target->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; -      target->client->lastBoostedTime = MAX( target->client->lastBoostedTime, level.time - BOOST_TIME + LEVEL1_PCLOUD_BOOST_TIME ); +      trap_SendServerCommand( humanPlayer->client->ps.clientNum, +                              "poisoncloud" );      }    }    G_UnlaggedOff( ); @@ -1080,72 +1012,60 @@ LEVEL2  ======================================================================  */ -#define MAX_ZAPS  64 - -static zap_t  zaps[ MAX_CLIENTS ]; +zap_t zaps[ MAX_ZAPS ];  /*  =============== -G_FindNewZapTarget +G_FindZapChainTargets  ===============  */ -static gentity_t *G_FindNewZapTarget( gentity_t *ent ) +static void G_FindZapChainTargets( zap_t *zap )  { +  gentity_t *ent = zap->targets[ 0 ]; // the source    int       entityList[ MAX_GENTITIES ]; -  vec3_t    range = { LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_RANGE }; +  vec3_t    range = { LEVEL2_AREAZAP_CHAIN_RANGE, +                      LEVEL2_AREAZAP_CHAIN_RANGE, +                      LEVEL2_AREAZAP_CHAIN_RANGE };    vec3_t    mins, maxs; -  int       i, j, k, num; +  int       i, num;    gentity_t *enemy;    trace_t   tr; +  float     distance; -  VectorScale( range, 1.0f / M_ROOT3, range ); -  VectorAdd( ent->s.origin, range, maxs ); -  VectorSubtract( ent->s.origin, range, mins ); +  VectorAdd( ent->r.currentOrigin, range, maxs ); +  VectorSubtract( ent->r.currentOrigin, range, mins );    num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );    for( i = 0; i < num; i++ )    {      enemy = &g_entities[ entityList[ i ] ]; - -    if( ( ( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || -        ( enemy->s.eType == ET_BUILDABLE && -          BG_FindTeamForBuildable( enemy->s.modelindex ) == BIT_HUMANS ) ) && enemy->health > 0 ) +    // don't chain to self; noclippers can be listed, don't chain to them either +    if( enemy == ent || ( enemy->client && enemy->client->noclip ) ) +      continue; + +    distance = Distance( ent->r.currentOrigin, enemy->r.currentOrigin ); + +    if( ( ( enemy->client && +            enemy->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) || +          ( enemy->s.eType == ET_BUILDABLE && +            BG_Buildable( enemy->s.modelindex )->team == TEAM_HUMANS ) ) && +        enemy->health > 0 && // only chain to living targets +        distance <= LEVEL2_AREAZAP_CHAIN_RANGE )      { -      qboolean foundOldTarget = qfalse; - -      trap_Trace( &tr, muzzle, NULL, NULL, enemy->s.origin, ent->s.number, MASK_SHOT ); - -      //can't see target from here -      if( tr.entityNum == ENTITYNUM_WORLD ) -        continue; +      // world-LOS check: trace against the world, ignoring other BODY entities +      trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, +         enemy->r.currentOrigin, ent->s.number, CONTENTS_SOLID ); -      for( j = 0; j < MAX_ZAPS; j++ ) +      if( tr.entityNum == ENTITYNUM_NONE )        { -        zap_t *zap = &zaps[ j ]; - -        for( k = 0; k < zap->numTargets; k++ ) -        { -          if( zap->targets[ k ] == enemy ) -          { -            foundOldTarget = qtrue; -            break; -          } -        } - -        if( foundOldTarget ) -          break; +        zap->targets[ zap->numTargets ] = enemy; +        zap->distances[ zap->numTargets ] = distance; +        if( ++zap->numTargets >= LEVEL2_AREAZAP_MAX_TARGETS ) +          return;        } - -      // enemy is already targetted -      if( foundOldTarget ) -        continue; - -      return enemy;      }    } - -  return NULL;  }  /* @@ -1155,30 +1075,19 @@ G_UpdateZapEffect  */  static void G_UpdateZapEffect( zap_t *zap )  { -  int       j; -  gentity_t *effect = zap->effectChannel; - -  effect->s.eType = ET_LEV2_ZAP_CHAIN; -  effect->classname = "lev2zapchain"; -  G_SetOrigin( effect, zap->creator->s.origin ); -  effect->s.misc = zap->creator->s.number; +  int i; +  int entityNums[ LEVEL2_AREAZAP_MAX_TARGETS + 1 ]; -  effect->s.time = effect->s.time2 = effect->s.constantLight = -1; +  entityNums[ 0 ] = zap->creator->s.number; -  for( j = 0; j < zap->numTargets; j++ ) -  { -    int number = zap->targets[ j ]->s.number; +  for( i = 0; i < zap->numTargets; i++ ) +    entityNums[ i + 1 ] = zap->targets[ i ]->s.number; -    switch( j ) -    { -      case 0: effect->s.time = number;          break; -      case 1: effect->s.time2 = number;         break; -      case 2: effect->s.constantLight = number; break; -      default:                                  break; -    } -  } +  BG_PackEntityNumbers( &zap->effectChannel->s, +                        entityNums, zap->numTargets + 1 ); -  trap_LinkEntity( effect ); +  VectorCopy( zap->creator->r.currentOrigin, zap->effectChannel->r.currentOrigin ); +  trap_LinkEntity( zap->effectChannel );  }  /* @@ -1188,38 +1097,48 @@ G_CreateNewZap  */  static void G_CreateNewZap( gentity_t *creator, gentity_t *target )  { -  int       i, j; -  zap_t     *zap; +  int   i; +  zap_t *zap;    for( i = 0; i < MAX_ZAPS; i++ )    {      zap = &zaps[ i ]; +    if( zap->used ) +      continue; -    if( !zap->used ) -    { -      zap->used = qtrue; +    zap->used = qtrue; +    zap->timeToLive = LEVEL2_AREAZAP_TIME; -      zap->timeToLive = LEVEL2_AREAZAP_TIME; -      zap->damageUsed = 0; +    zap->creator = creator; +    zap->targets[ 0 ] = target; +    zap->numTargets = 1; -      zap->creator = creator; +    // the zap chains only through living entities +    if( target->health > 0 ) +    { +      G_Damage( target, creator, creator, forward, +                target->r.currentOrigin, LEVEL2_AREAZAP_DMG, +                DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, +                MOD_LEVEL2_ZAP ); -      zap->targets[ 0 ] = target; -      zap->numTargets = 1; +      G_FindZapChainTargets( zap ); -      for( j = 1; j < MAX_ZAP_TARGETS && zap->targets[ j - 1 ]; j++ ) +      for( i = 1; i < zap->numTargets; i++ )        { -        zap->targets[ j ] = G_FindNewZapTarget( zap->targets[ j - 1 ] ); - -        if( zap->targets[ j ] ) -          zap->numTargets++; +        G_Damage( zap->targets[ i ], target, zap->creator, forward, target->r.currentOrigin, +                  LEVEL2_AREAZAP_DMG * ( 1 - pow( (zap->distances[ i ] / +                    LEVEL2_AREAZAP_CHAIN_RANGE ), LEVEL2_AREAZAP_CHAIN_FALLOFF ) ) + 1, +                  DAMAGE_NO_KNOCKBACK | DAMAGE_NO_LOCDAMAGE, +                  MOD_LEVEL2_ZAP );        } +    } -      zap->effectChannel = G_Spawn( ); -      G_UpdateZapEffect( zap ); +    zap->effectChannel = G_Spawn( ); +    zap->effectChannel->s.eType = ET_LEV2_ZAP_CHAIN; +    zap->effectChannel->classname = "lev2zapchain"; +    G_UpdateZapEffect( zap ); -      return; -    } +    return;    }  } @@ -1233,80 +1152,67 @@ void G_UpdateZaps( int msec )  {    int   i, j;    zap_t *zap; -  int   damage;    for( i = 0; i < MAX_ZAPS; i++ )    {      zap = &zaps[ i ]; +    if( !zap->used ) +      continue; -    if( zap->used ) +    zap->timeToLive -= msec; + +    // first, the disappearance of players is handled immediately in G_ClearPlayerZapEffects() + +    // the deconstruction or gibbing of a directly targeted buildable destroys the whole zap effect +    if( zap->timeToLive <= 0 || !zap->targets[ 0 ]->inuse )      { -      //check each target is valid -      for( j = 0; j < zap->numTargets; j++ ) -      { -        gentity_t *source; -        gentity_t *target = zap->targets[ j ]; - -        if( j == 0 ) -          source = zap->creator; -        else -          source = zap->targets[ j - 1 ]; - -        if( target->health <= 0 || !target->inuse || //early out -            Distance( source->s.origin, target->s.origin ) > LEVEL2_AREAZAP_RANGE ) -        { -          target = zap->targets[ j ] = G_FindNewZapTarget( source ); - -          //couldn't find a target, so forget about the rest of the chain -          if( !target ) -            zap->numTargets = j; -        } -      } +      G_FreeEntity( zap->effectChannel ); +      zap->used = qfalse; +      continue; +    } -      if( zap->numTargets ) -      { -        for( j = 0; j < zap->numTargets; j++ ) -        { -          gentity_t *source; -          gentity_t *target = zap->targets[ j ]; -          float     r = 1.0f / zap->numTargets; -          float     damageFraction = 2 * r - 2 * j * r * r - r * r; -          vec3_t    forward; - -          if( j == 0 ) -            source = zap->creator; -          else -            source = zap->targets[ j - 1 ]; - -          damage = ceil( ( (float)msec / LEVEL2_AREAZAP_TIME ) * -              LEVEL2_AREAZAP_DMG * damageFraction ); - -          // don't let a high msec value inflate the total damage -          if( damage + zap->damageUsed > LEVEL2_AREAZAP_DMG ) -            damage = LEVEL2_AREAZAP_DMG - zap->damageUsed; - -          VectorSubtract( target->s.origin, source->s.origin, forward ); -          VectorNormalize( forward ); - -          //do the damage -          if( damage ) -          { -            G_Damage( target, source, zap->creator, forward, target->s.origin, -                    damage, DAMAGE_NO_LOCDAMAGE, MOD_LEVEL2_ZAP ); -            zap->damageUsed += damage; -          } -        } -      } +    // the deconstruction or gibbing of chained buildables destroy the appropriate beams +    for( j = 1; j < zap->numTargets; j++ ) +    { +      if( !zap->targets[ j ]->inuse ) +        zap->targets[ j-- ] = zap->targets[ --zap->numTargets ]; +    } -      G_UpdateZapEffect( zap ); +    G_UpdateZapEffect( zap ); +  } +} -      zap->timeToLive -= msec; +/* +=============== +G_ClearPlayerZapEffects -      if( zap->timeToLive <= 0 || zap->numTargets == 0 || zap->creator->health <= 0 ) -      { -        zap->used = qfalse; -        G_FreeEntity( zap->effectChannel ); -      } +called from G_LeaveTeam() and TeleportPlayer() +=============== +*/ +void G_ClearPlayerZapEffects( gentity_t *player ) +{ +  int i, j; +  zap_t *zap; + +  for( i = 0; i < MAX_ZAPS; i++ ) +  { +    zap = &zaps[ i ]; +    if( !zap->used ) +      continue; + +    // the disappearance of the creator or the first target destroys the whole zap effect +    if( zap->creator == player || zap->targets[ 0 ] == player ) +    { +      G_FreeEntity( zap->effectChannel ); +      zap->used = qfalse; +      continue; +    } + +    // the disappearance of chained players destroy the appropriate beams +    for( j = 1; j < zap->numTargets; j++ ) +    { +      if( zap->targets[ j ] == player ) +        zap->targets[ j-- ] = zap->targets[ --zap->numTargets ];      }    }  } @@ -1319,32 +1225,16 @@ areaZapFire  void areaZapFire( gentity_t *ent )  {    trace_t   tr; -  vec3_t    end;    gentity_t *traceEnt; -  vec3_t    mins, maxs; - -  VectorSet( mins, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH, -LEVEL2_AREAZAP_WIDTH ); -  VectorSet( maxs, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH ); - -  // set aiming directions -  AngleVectors( ent->client->ps.viewangles, forward, right, up ); - -  CalcMuzzlePoint( ent, forward, right, up, muzzle ); -  VectorMA( muzzle, LEVEL2_AREAZAP_RANGE, forward, end ); +  G_WideTrace( &tr, ent, LEVEL2_AREAZAP_RANGE, LEVEL2_AREAZAP_WIDTH, LEVEL2_AREAZAP_WIDTH, &traceEnt ); -  G_UnlaggedOn( ent, muzzle, LEVEL2_AREAZAP_RANGE ); -  trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); -  G_UnlaggedOff( ); - -  if( tr.surfaceFlags & SURF_NOIMPACT ) +  if( traceEnt == NULL )      return; -  traceEnt = &g_entities[ tr.entityNum ]; - -  if( ( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) || +  if( ( traceEnt->client && traceEnt->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) ||        ( traceEnt->s.eType == ET_BUILDABLE && -        BG_FindTeamForBuildable( traceEnt->s.modelindex ) == BIT_HUMANS ) ) && traceEnt->health > 0 ) +        BG_Buildable( traceEnt->s.modelindex )->team == TEAM_HUMANS ) )    {      G_CreateNewZap( ent, traceEnt );    } @@ -1366,61 +1256,51 @@ CheckPounceAttack  */  qboolean CheckPounceAttack( gentity_t *ent )  { -  trace_t   tr; -  gentity_t *tent; +  trace_t tr;    gentity_t *traceEnt; -  int       damage; - -  if( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) -  { -    ent->client->allowedToPounce = qfalse; -    ent->client->pmext.pouncePayload = 0; -  } +  int damage, timeMax, pounceRange, payload; -  if( !ent->client->allowedToPounce ) +  if( ent->client->pmext.pouncePayload <= 0 )      return qfalse; -  if( ent->client->ps.weaponTime ) -    return qfalse; +  // In case the goon lands on his target, he gets one shot after landing +  payload = ent->client->pmext.pouncePayload; +  if( !( ent->client->ps.pm_flags & PMF_CHARGE ) ) +    ent->client->pmext.pouncePayload = 0; -  G_WideTrace( &tr, ent, LEVEL3_POUNCE_RANGE, LEVEL3_POUNCE_WIDTH, &traceEnt ); +  // Calculate muzzle point +  AngleVectors( ent->client->ps.viewangles, forward, right, up ); +  CalcMuzzlePoint( ent, forward, right, up, muzzle ); +  // Trace from muzzle to see what we hit +  pounceRange = ent->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_RANGE : +                                                       LEVEL3_POUNCE_UPG_RANGE; +  G_WideTrace( &tr, ent, pounceRange, LEVEL3_POUNCE_WIDTH, +               LEVEL3_POUNCE_WIDTH, &traceEnt );    if( traceEnt == NULL )      return qfalse; -  // send blood impact -  if( traceEnt->takedamage && traceEnt->client ) -  { -    tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT ); -    tent->s.otherEntityNum = traceEnt->s.number; -    tent->s.eventParm = DirToByte( tr.plane.normal ); -    tent->s.weapon = ent->s.weapon; -    tent->s.generic1 = ent->s.generic1; //weaponMode -  } +  // Send blood impact +  if( traceEnt->takedamage ) +    WideBloodSpurt( ent, traceEnt, &tr );    if( !traceEnt->takedamage )      return qfalse; - -  damage = (int)( ( (float)ent->client->pmext.pouncePayload -    / (float)LEVEL3_POUNCE_SPEED ) * LEVEL3_POUNCE_DMG ); - +     +  // Deal damage +  timeMax = ent->client->ps.weapon == WP_ALEVEL3 ? LEVEL3_POUNCE_TIME : +                                                   LEVEL3_POUNCE_TIME_UPG; +  damage = payload * LEVEL3_POUNCE_DMG / timeMax;    ent->client->pmext.pouncePayload = 0; -    G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, -      DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE ); - -  ent->client->allowedToPounce = qfalse; +            DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE );    return qtrue;  }  void bounceBallFire( gentity_t *ent )  { -  gentity_t *m; - -  m = fire_bounceBall( ent, muzzle, forward ); - -//  VectorAdd( m->s.pos.trDelta, ent->client->ps.velocity, m->s.pos.trDelta );  // "real" physics +  fire_bounceBall( ent, muzzle, forward );  } @@ -1434,39 +1314,96 @@ LEVEL4  /*  =============== -ChargeAttack +G_ChargeAttack  ===============  */ -void ChargeAttack( gentity_t *ent, gentity_t *victim ) +void G_ChargeAttack( gentity_t *ent, gentity_t *victim )  { -  gentity_t *tent;    int       damage; -  vec3_t    forward, normal; +  int       i; +  vec3_t    forward; -  if( level.time < victim->chargeRepeat ) +  if( ent->client->ps.stats[ STAT_MISC ] <= 0 || +      !( ent->client->ps.stats[ STAT_STATE ] & SS_CHARGING ) || +      ent->client->ps.weaponTime )      return; -  victim->chargeRepeat = level.time + LEVEL4_CHARGE_REPEAT; - -  VectorSubtract( victim->s.origin, ent->s.origin, forward ); +  VectorSubtract( victim->r.currentOrigin, ent->r.currentOrigin, forward );    VectorNormalize( forward ); -  VectorNegate( forward, normal ); -  if( victim->client ) +  if( !victim->takedamage ) +    return; + +  // For buildables, track the last MAX_TRAMPLE_BUILDABLES_TRACKED buildables +  //  hit, and do not do damage if the current buildable is in that list +  //  in order to prevent dancing over stuff to kill it very quickly +  if( !victim->client )    { -    tent = G_TempEntity( victim->s.origin, EV_MISSILE_HIT ); -    tent->s.otherEntityNum = victim->s.number; -    tent->s.eventParm = DirToByte( normal ); -    tent->s.weapon = ent->s.weapon; -    tent->s.generic1 = ent->s.generic1; //weaponMode +    for( i = 0; i < MAX_TRAMPLE_BUILDABLES_TRACKED; i++ ) +    { +      if( ent->client->trampleBuildablesHit[ i ] == victim - g_entities ) +        return; +    } + +    ent->client->trampleBuildablesHit[ +      ent->client->trampleBuildablesHitPos++ % MAX_TRAMPLE_BUILDABLES_TRACKED ] = +      victim - g_entities;    } -  if( !victim->takedamage ) +  WideBloodSpurt( ent, victim, NULL ); + +  damage = LEVEL4_TRAMPLE_DMG * ent->client->ps.stats[ STAT_MISC ] / +           LEVEL4_TRAMPLE_DURATION; + +  G_Damage( victim, ent, ent, forward, victim->r.currentOrigin, damage, +            DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_TRAMPLE ); + +  ent->client->ps.weaponTime += LEVEL4_TRAMPLE_REPEAT; +} + +/* +=============== +G_CrushAttack + +Should only be called if there was an impact between a tyrant and another player +=============== +*/ +void G_CrushAttack( gentity_t *ent, gentity_t *victim ) +{ +  vec3_t dir; +  float jump; +  int damage; + +  if( !victim->takedamage || +      ent->client->ps.origin[ 2 ] + ent->r.mins[ 2 ] < +      victim->r.currentOrigin[ 2 ] + victim->r.maxs[ 2 ] || +      ( victim->client && +        victim->client->ps.groundEntityNum == ENTITYNUM_NONE ) )      return; -  damage = (int)( ( (float)ent->client->ps.stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME ) * LEVEL4_CHARGE_DMG ); +  // Deal velocity based damage to target +  jump = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->jumpMagnitude; +  damage = ( ent->client->pmext.fallVelocity + jump ) * +           -LEVEL4_CRUSH_DAMAGE_PER_V; + +  if( damage < 0 ) +    damage = 0; +     +  // Players also get damaged periodically +  if( victim->client && +      ent->client->lastCrushTime + LEVEL4_CRUSH_REPEAT < level.time ) +  { +    ent->client->lastCrushTime = level.time; +    damage += LEVEL4_CRUSH_DAMAGE; +  } +   +  if( damage < 1 ) +    return; -  G_Damage( victim, ent, ent, forward, victim->s.origin, damage, 0, MOD_LEVEL4_CHARGE ); +  // Crush the victim over a period of time +  VectorSubtract( victim->r.currentOrigin, ent->client->ps.origin, dir ); +  G_Damage( victim, ent, ent, dir, victim->r.currentOrigin, damage, +            DAMAGE_NO_LOCDAMAGE, MOD_LEVEL4_CRUSH );  }  //====================================================================== @@ -1480,10 +1417,12 @@ set muzzle location relative to pivoting eye  */  void CalcMuzzlePoint( gentity_t *ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint )  { -  VectorCopy( ent->s.pos.trBase, muzzlePoint ); -  muzzlePoint[ 2 ] += ent->client->ps.viewheight; +  vec3_t normal; + +  VectorCopy( ent->client->ps.origin, muzzlePoint ); +  BG_GetClientNormal( &ent->client->ps, normal ); +  VectorMA( muzzlePoint, ent->client->ps.viewheight, normal, muzzlePoint );    VectorMA( muzzlePoint, 1, forward, muzzlePoint ); -  VectorMA( muzzlePoint, 1, right, muzzlePoint );    // snap to integer coordinates for more efficient network bandwidth usage    SnapVector( muzzlePoint );  } @@ -1548,18 +1487,18 @@ void FireWeapon2( gentity_t *ent )      case WP_ALEVEL1_UPG:        poisonCloud( ent );        break; -    case WP_ALEVEL2_UPG: -      areaZapFire( ent ); -      break;      case WP_LUCIFER_CANNON:        LCChargeFire( ent, qtrue );        break; +    case WP_ALEVEL2_UPG: +      areaZapFire( ent ); +      break; +      case WP_ABUILD:      case WP_ABUILD2:      case WP_HBUILD: -    case WP_HBUILD2:        cancelBuildFire( ent );        break;      default: @@ -1574,13 +1513,11 @@ FireWeapon  */  void FireWeapon( gentity_t *ent )  { -  if( level.paused ) return; -    if( ent->client )    {      // set aiming directions      AngleVectors( ent->client->ps.viewangles, forward, right, up ); -    CalcMuzzlePoint( ent, forward, right, up, muzzle ); +    CalcMuzzlePoint( ent, forward, right, up, muzzle );        }    else    { @@ -1592,21 +1529,32 @@ void FireWeapon( gentity_t *ent )    switch( ent->s.weapon )    {      case WP_ALEVEL1: +      meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_WIDTH, +                   LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW ); +      break;      case WP_ALEVEL1_UPG: -      meleeAttack( ent, LEVEL1_CLAW_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW ); +      meleeAttack( ent, LEVEL1_CLAW_U_RANGE, LEVEL1_CLAW_WIDTH, LEVEL1_CLAW_WIDTH, +                   LEVEL1_CLAW_DMG, MOD_LEVEL1_CLAW );        break;      case WP_ALEVEL3: +      meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_WIDTH, +                   LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW ); +      break;      case WP_ALEVEL3_UPG: -      meleeAttack( ent, LEVEL3_CLAW_RANGE, LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW ); +      meleeAttack( ent, LEVEL3_CLAW_UPG_RANGE, LEVEL3_CLAW_WIDTH, +                   LEVEL3_CLAW_WIDTH, LEVEL3_CLAW_DMG, MOD_LEVEL3_CLAW );        break;      case WP_ALEVEL2: -      meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW ); +      meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_WIDTH, +                   LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );        break;      case WP_ALEVEL2_UPG: -      meleeAttack( ent, LEVEL2_CLAW_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW ); +      meleeAttack( ent, LEVEL2_CLAW_U_RANGE, LEVEL2_CLAW_WIDTH, LEVEL2_CLAW_WIDTH, +                   LEVEL2_CLAW_DMG, MOD_LEVEL2_CLAW );        break;      case WP_ALEVEL4: -      meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW ); +      meleeAttack( ent, LEVEL4_CLAW_RANGE, LEVEL4_CLAW_WIDTH, +                   LEVEL4_CLAW_HEIGHT, LEVEL4_CLAW_DMG, MOD_LEVEL4_CLAW );        break;      case WP_BLASTER: @@ -1661,11 +1609,9 @@ void FireWeapon( gentity_t *ent )        buildFire( ent, MN_A_BUILD );        break;      case WP_HBUILD: -    case WP_HBUILD2:        buildFire( ent, MN_H_BUILD );        break;      default:        break;    }  } - diff --git a/src/game/g_weapondrop.c b/src/game/g_weapondrop.c new file mode 100644 index 0000000..11884ee --- /dev/null +++ b/src/game/g_weapondrop.c @@ -0,0 +1,199 @@ +// +// Ported + rewritten ioq3 item-drop. +// +// blowFish +// +#include "g_local.h" + +#define DISABLE_TOUCH_TIME 1000 +#define MISSILE_PRESTEP_TIME  50 + +// +// Pickup Weapon +// +// ent - The "weapon" being picked up +// other - The client who picked it up +// +void Pickup_Weapon (gentity_t *ent, gentity_t *other) +{ +  int w = ent->s.modelindex; + +  if ( w == WP_NONE ) +    return; + +  other->client->ps.stats[ STAT_WEAPON ] = w; +  other->client->ps.ammo = ent->item.ammo; +  other->client->ps.clips = ent->item.clips; +  G_ForceWeaponChange( other, w ); +} + +// +// Touch Weapon +// +// ent - The "weapon" being picked up +// other - The client who picked it up +// +void Touch_Weapon (gentity_t *ent, gentity_t *other, trace_t *trace) +{ +  if( !other->client +    || other->client->pers.teamSelection == TEAM_NONE +    || other->client->pers.teamSelection == TEAM_ALIENS ) +    return; + +  if( (other->client->lastDropTime + DISABLE_TOUCH_TIME) > level.time) +    return; + +  if ( other->health < 1 ) +    return; + +  Pickup_Weapon(ent, other); + +  // dropped items will not respawn +  if ( ent->flags & FL_DROPPED_ITEM ) +  { +    ent->freeAfterEvent = qtrue; +  } + +  ent->r.svFlags |= SVF_NOCLIENT; +  ent->s.eFlags |= EF_NODRAW; +  ent->r.contents = 0; + +  trap_LinkEntity( ent ); +} + +#define ITEM_RADIUS 15 + +// +// Launch Weapon +// +// Spawn a weapon and toss it into the world. +// +gentity_t *LaunchWeapon (gentity_t* client, weapon_t weap, vec3_t origin, vec3_t velocity) +{ +    gentity_t   *dropped; + +    dropped = G_Spawn(); + +    dropped->s.eType = ET_WEAPON_DROP; +    dropped->s.modelindex = weap; // store weapon number in modelindex +    dropped->s.modelindex2 = 1; // This is non-zero is it's a dropped item + +    dropped->classname = BG_Weapon(weap)->name; +    VectorSet (dropped->r.mins, -ITEM_RADIUS, -ITEM_RADIUS, -ITEM_RADIUS); +    VectorSet (dropped->r.maxs, ITEM_RADIUS, ITEM_RADIUS, ITEM_RADIUS); +    dropped->r.contents = CONTENTS_TRIGGER; + +    dropped->item.ammo = client->client->ps.ammo; +    dropped->item.clips = client->client->ps.clips; + +    dropped->touch = Touch_Weapon; + +    G_SetOrigin( dropped, origin ); +    dropped->s.pos.trType = TR_GRAVITY; +    dropped->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME; +    VectorCopy( velocity, dropped->s.pos.trDelta ); + +    dropped->s.eFlags |= EF_BOUNCE_HALF; +    dropped->think = G_FreeEntity; +    dropped->nextthink = level.time + 30000; + +    dropped->flags = FL_DROPPED_ITEM; + +    trap_LinkEntity (dropped); + +    return dropped; +} + +// +// Drop Weapon +// +// Spawns an weapon and tosses it forward +// +gentity_t *G_DropWeapon (gentity_t *ent, weapon_t w, float angle) +{ +    vec3_t  velocity; +    vec3_t  angles; + +    // set aiming directions +    VectorCopy( ent->s.apos.trBase, angles ); +    angles[YAW] += angle; +    angles[PITCH] = 0;  // always forward + +    AngleVectors( angles, velocity, NULL, NULL ); +    VectorScale( velocity, 150, velocity ); +    velocity[2] += 200 + crandom() * 50; + +    ent->client->lastDropTime = level.time; +    return LaunchWeapon( ent, w, ent->s.pos.trBase, velocity ); +} + +// +// Run Weapon Drops +// +void G_RunWeaponDrop (gentity_t *ent) +{ +	vec3_t		origin; +	trace_t		tr; +	int			contents; +	int			mask; + +	// if its groundentity has been set to none, it may have been pushed off an edge +	if ( ent->s.groundEntityNum == ENTITYNUM_NONE ) +    { +		if ( ent->s.pos.trType != TR_GRAVITY ) +        { +			ent->s.pos.trType = TR_GRAVITY; +			ent->s.pos.trTime = level.time; +		} +	} + +	if ( ent->s.pos.trType == TR_STATIONARY ) +    { +		// check think function +		G_RunThink( ent ); +		return; +	} + +	// get current position +	BG_EvaluateTrajectory( &ent->s.pos, level.time, origin ); + +	// trace a line from the previous position to the current position +	if ( ent->clipmask ) +    { +		mask = ent->clipmask; +	} +    else +    { +		mask = MASK_PLAYERSOLID & ~CONTENTS_BODY; +	} + +	trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, +		ent->r.ownerNum, mask ); + +	VectorCopy( tr.endpos, ent->r.currentOrigin ); + +	if ( tr.startsolid ) +    { +		tr.fraction = 0; +	} + +	trap_LinkEntity( ent );	// FIXME: avoid this for stationary? + +	// check think function +	G_RunThink( ent ); + +	if ( tr.fraction == 1 ) +    { +		return; +	} + +	// if it is in a nodrop volume, remove it +	contents = trap_PointContents( ent->r.currentOrigin, -1 ); +	if ( contents & CONTENTS_NODROP ) +    { +		G_FreeEntity( ent ); +		return; +	} + +    G_BounceMissile( ent, &tr ); +} diff --git a/src/game/tremulous.h b/src/game/tremulous.h index 423d465..f5f5e16 100644 --- a/src/game/tremulous.h +++ b/src/game/tremulous.h @@ -1,13 +1,14 @@  /*  ===========================================================================  Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub  This file is part of Tremulous.  Tremulous is free software; you can redistribute it  and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License,  or (at your option) any later version.  Tremulous is distributed in the hope that it will be @@ -16,11 +17,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  GNU General Public License for more details.  You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA +along with Tremulous; if not, see <https://www.gnu.org/licenses/> +  ===========================================================================  */ +#ifndef _TREMULOUS_H_ +#define _TREMULOUS_H_  /*   * ALIEN weapons @@ -41,86 +44,101 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define ABUILDER_CLAW_WIDTH         4.0f  #define ABUILDER_CLAW_REPEAT        1000  #define ABUILDER_CLAW_K_SCALE       1.0f -#define ABUILDER_BASE_DELAY         17000 -#define ABUILDER_ADV_DELAY          12000  #define ABUILDER_BLOB_DMG           ADM(4)  #define ABUILDER_BLOB_REPEAT        1000  #define ABUILDER_BLOB_SPEED         800.0f  #define ABUILDER_BLOB_SPEED_MOD     0.5f -#define ABUILDER_BLOB_TIME          5000 +#define ABUILDER_BLOB_TIME          2000 -#define LEVEL0_BITE_DMG             ADM(48) +#define LEVEL0_BITE_DMG             ADM(36)  #define LEVEL0_BITE_RANGE           64.0f  #define LEVEL0_BITE_WIDTH           6.0f  #define LEVEL0_BITE_REPEAT          500  #define LEVEL0_BITE_K_SCALE         1.0f  #define LEVEL1_CLAW_DMG             ADM(32) -#define LEVEL1_CLAW_RANGE           96.0f +#define LEVEL1_CLAW_RANGE           64.0f +#define LEVEL1_CLAW_U_RANGE         LEVEL1_CLAW_RANGE + 3.0f  #define LEVEL1_CLAW_WIDTH           10.0f  #define LEVEL1_CLAW_REPEAT          600  #define LEVEL1_CLAW_U_REPEAT        500  #define LEVEL1_CLAW_K_SCALE         1.0f  #define LEVEL1_CLAW_U_K_SCALE       1.0f -#define LEVEL1_GRAB_RANGE           64.0f +#define LEVEL1_GRAB_RANGE           96.0f +#define LEVEL1_GRAB_U_RANGE         LEVEL1_GRAB_RANGE + 3.0f  #define LEVEL1_GRAB_TIME            300 -#define LEVEL1_GRAB_U_TIME          450 +#define LEVEL1_GRAB_U_TIME          300  #define LEVEL1_PCLOUD_DMG           ADM(4) -#define LEVEL1_PCLOUD_RANGE         200.0f +#define LEVEL1_PCLOUD_RANGE         120.0f  #define LEVEL1_PCLOUD_REPEAT        2000  #define LEVEL1_PCLOUD_TIME          10000 -#define LEVEL1_PCLOUD_BOOST_TIME    5000 -#define LEVEL1_REGEN_RANGE          200.0f  #define LEVEL1_REGEN_MOD            2.0f +#define LEVEL1_UPG_REGEN_MOD        3.0f +#define LEVEL1_REGEN_SCOREINC       AVM(100) // score added for healing per 10s +#define LEVEL1_UPG_REGEN_SCOREINC   AVM(200)  #define LEVEL2_CLAW_DMG             ADM(40) -#define LEVEL2_CLAW_RANGE           96.0f -#define LEVEL2_CLAW_WIDTH           12.0f +#define LEVEL2_CLAW_RANGE           80.0f +#define LEVEL2_CLAW_U_RANGE         LEVEL2_CLAW_RANGE + 2.0f +#define LEVEL2_CLAW_WIDTH           14.0f  #define LEVEL2_CLAW_REPEAT          500  #define LEVEL2_CLAW_K_SCALE         1.0f  #define LEVEL2_CLAW_U_REPEAT        400  #define LEVEL2_CLAW_U_K_SCALE       1.0f -#define LEVEL2_AREAZAP_DMG          ADM(80) +#define LEVEL2_AREAZAP_DMG          ADM(60)  #define LEVEL2_AREAZAP_RANGE        200.0f +#define LEVEL2_AREAZAP_CHAIN_RANGE  150.0f +#define LEVEL2_AREAZAP_CHAIN_FALLOFF 8.0f  #define LEVEL2_AREAZAP_WIDTH        15.0f  #define LEVEL2_AREAZAP_REPEAT       1500  #define LEVEL2_AREAZAP_TIME         1000 -#define LEVEL2_AREAZAP_MAX_TARGETS  3 +#define LEVEL2_AREAZAP_MAX_TARGETS  5  #define LEVEL2_WALLJUMP_MAXSPEED    1000.0f  #define LEVEL3_CLAW_DMG             ADM(80) -#define LEVEL3_CLAW_RANGE           96.0f -#define LEVEL3_CLAW_WIDTH           16.0f -#define LEVEL3_CLAW_REPEAT          700 +#define LEVEL3_CLAW_RANGE           80.0f +#define LEVEL3_CLAW_UPG_RANGE       LEVEL3_CLAW_RANGE + 3.0f           +#define LEVEL3_CLAW_WIDTH           12.0f +#define LEVEL3_CLAW_REPEAT          900  #define LEVEL3_CLAW_K_SCALE         1.0f -#define LEVEL3_CLAW_U_REPEAT        600 +#define LEVEL3_CLAW_U_REPEAT        800  #define LEVEL3_CLAW_U_K_SCALE       1.0f  #define LEVEL3_POUNCE_DMG           ADM(100) -#define LEVEL3_POUNCE_RANGE         72.0f -#define LEVEL3_POUNCE_WIDTH         16.0f -#define LEVEL3_POUNCE_SPEED         700 -#define LEVEL3_POUNCE_UPG_SPEED     800 -#define LEVEL3_POUNCE_SPEED_MOD     0.75f -#define LEVEL3_POUNCE_CHARGE_TIME   700 -#define LEVEL3_POUNCE_TIME          400 +#define LEVEL3_POUNCE_RANGE         48.0f +#define LEVEL3_POUNCE_UPG_RANGE     LEVEL3_POUNCE_RANGE + 3.0f +#define LEVEL3_POUNCE_WIDTH         14.0f +#define LEVEL3_POUNCE_TIME          800      // msec for full Dragoon pounce +#define LEVEL3_POUNCE_TIME_UPG      800      // msec for full Adv. Dragoon pounce +#define LEVEL3_POUNCE_TIME_MIN      200      // msec before which pounce cancels   +#define LEVEL3_POUNCE_REPEAT        400      // msec before a new pounce starts +#define LEVEL3_POUNCE_SPEED_MOD     0.75f    // walking speed modifier for pounce charging +#define LEVEL3_POUNCE_JUMP_MAG      700      // Dragoon pounce jump power +#define LEVEL3_POUNCE_JUMP_MAG_UPG  800      // Adv. Dragoon pounce jump power  #define LEVEL3_BOUNCEBALL_DMG       ADM(110) -#define LEVEL3_BOUNCEBALL_REPEAT    1000 +#define LEVEL3_BOUNCEBALL_REPEAT    1200  #define LEVEL3_BOUNCEBALL_SPEED     1000.0f +#define LEVEL3_BOUNCEBALL_RADIUS    75 +#define LEVEL3_BOUNCEBALL_REGEN     15000    // msec until new barb  #define LEVEL4_CLAW_DMG             ADM(100) -#define LEVEL4_CLAW_RANGE           128.0f -#define LEVEL4_CLAW_WIDTH           20.0f -#define LEVEL4_CLAW_REPEAT          750 +#define LEVEL4_CLAW_RANGE           100.0f +#define LEVEL4_CLAW_WIDTH           14.0f +#define LEVEL4_CLAW_HEIGHT          20.0f +#define LEVEL4_CLAW_REPEAT          800  #define LEVEL4_CLAW_K_SCALE         1.0f -#define LEVEL4_CHARGE_SPEED         2.0f -#define LEVEL4_CHARGE_TIME          3000 -#define LEVEL4_CHARGE_CHARGE_TIME   1500 -#define LEVEL4_MIN_CHARGE_TIME      750 -#define LEVEL4_CHARGE_CHARGE_RATIO  (LEVEL4_CHARGE_TIME/LEVEL4_CHARGE_CHARGE_TIME) -#define LEVEL4_CHARGE_REPEAT        1000 -#define LEVEL4_CHARGE_DMG           ADM(110) +#define LEVEL4_TRAMPLE_DMG             ADM(111) +#define LEVEL4_TRAMPLE_SPEED           2.0f +#define LEVEL4_TRAMPLE_CHARGE_MIN      375   // minimum msec to start a charge +#define LEVEL4_TRAMPLE_CHARGE_MAX      1000  // msec to maximum charge stored +#define LEVEL4_TRAMPLE_CHARGE_TRIGGER  3000  // msec charge starts on its own +#define LEVEL4_TRAMPLE_DURATION        3000  // msec trample lasts on full charge +#define LEVEL4_TRAMPLE_STOP_PENALTY    1     // charge lost per msec when stopped +#define LEVEL4_TRAMPLE_REPEAT          100   // msec before a trample will rehit a player +#define LEVEL4_CRUSH_DAMAGE_PER_V      0.5f  // damage per falling velocity +#define LEVEL4_CRUSH_DAMAGE            120   // to players only +#define LEVEL4_CRUSH_REPEAT            500   // player damage repeat  /*   * ALIEN classes @@ -138,68 +156,66 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define ALIEN_VALUE_MODIFIER        1.0f  #define AVM(h)                      ((int)((float)h*ALIEN_VALUE_MODIFIER)) -#define ABUILDER_SPEED              0.8f -#define ABUILDER_VALUE              AVM(200) +#define ABUILDER_SPEED              0.9f +#define ABUILDER_VALUE              AVM(240)  #define ABUILDER_HEALTH             AHM(50) -#define ABUILDER_REGEN              2 +#define ABUILDER_REGEN              (0.04f * ABUILDER_HEALTH)  #define ABUILDER_COST               0 -#define ABUILDER_UPG_SPEED          1.0f -#define ABUILDER_UPG_VALUE          AVM(250) +#define ABUILDER_UPG_SPEED          0.9f +#define ABUILDER_UPG_VALUE          AVM(300)  #define ABUILDER_UPG_HEALTH         AHM(75) -#define ABUILDER_UPG_REGEN          3 +#define ABUILDER_UPG_REGEN          (0.04f * ABUILDER_UPG_HEALTH)  #define ABUILDER_UPG_COST           0 -#define LEVEL0_SPEED                1.3f -#define LEVEL0_VALUE                AVM(175) +#define LEVEL0_SPEED                1.4f +#define LEVEL0_VALUE                AVM(180)  #define LEVEL0_HEALTH               AHM(25) -#define LEVEL0_REGEN                1 +#define LEVEL0_REGEN                (0.05f * LEVEL0_HEALTH)  #define LEVEL0_COST                 0  #define LEVEL1_SPEED                1.25f -#define LEVEL1_VALUE                AVM(225) -#define LEVEL1_HEALTH               AHM(75) -#define LEVEL1_REGEN                2 +#define LEVEL1_VALUE                AVM(270) +#define LEVEL1_HEALTH               AHM(60) +#define LEVEL1_REGEN                (0.03f * LEVEL1_HEALTH)  #define LEVEL1_COST                 1  #define LEVEL1_UPG_SPEED            1.25f -#define LEVEL1_UPG_VALUE            AVM(275) -#define LEVEL1_UPG_HEALTH           AHM(100) -#define LEVEL1_UPG_REGEN            3 +#define LEVEL1_UPG_VALUE            AVM(330) +#define LEVEL1_UPG_HEALTH           AHM(80) +#define LEVEL1_UPG_REGEN            (0.03f * LEVEL1_UPG_HEALTH)  #define LEVEL1_UPG_COST             1  #define LEVEL2_SPEED                1.2f -#define LEVEL2_VALUE                AVM(350) +#define LEVEL2_VALUE                AVM(420)  #define LEVEL2_HEALTH               AHM(150) -#define LEVEL2_REGEN                4 +#define LEVEL2_REGEN                (0.03f * LEVEL2_HEALTH)  #define LEVEL2_COST                 1  #define LEVEL2_UPG_SPEED            1.2f -#define LEVEL2_UPG_VALUE            AVM(450) +#define LEVEL2_UPG_VALUE            AVM(540)  #define LEVEL2_UPG_HEALTH           AHM(175) -#define LEVEL2_UPG_REGEN            5 +#define LEVEL2_UPG_REGEN            (0.03f * LEVEL2_UPG_HEALTH)  #define LEVEL2_UPG_COST             1  #define LEVEL3_SPEED                1.1f -#define LEVEL3_VALUE                AVM(500) +#define LEVEL3_VALUE                AVM(600)  #define LEVEL3_HEALTH               AHM(200) -#define LEVEL3_REGEN                6 +#define LEVEL3_REGEN                (0.03f * LEVEL3_HEALTH)  #define LEVEL3_COST                 1  #define LEVEL3_UPG_SPEED            1.1f -#define LEVEL3_UPG_VALUE            AVM(600) +#define LEVEL3_UPG_VALUE            AVM(720)  #define LEVEL3_UPG_HEALTH           AHM(250) -#define LEVEL3_UPG_REGEN            7 +#define LEVEL3_UPG_REGEN            (0.03f * LEVEL3_UPG_HEALTH)  #define LEVEL3_UPG_COST             1  #define LEVEL4_SPEED                1.2f -#define LEVEL4_VALUE                AVM(800) -#define LEVEL4_HEALTH               AHM(400) -#define LEVEL4_REGEN                7 +#define LEVEL4_VALUE                AVM(960) +#define LEVEL4_HEALTH               AHM(350) +#define LEVEL4_REGEN                (0.025f * LEVEL4_HEALTH)  #define LEVEL4_COST                 2 - -  /*   * ALIEN buildables   * @@ -216,6 +232,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define ALIEN_BHLTH_MODIFIER        1.0f  #define ABHM(h)                     ((int)((float)h*ALIEN_BHLTH_MODIFIER)) +#define ALIEN_BVALUE_MODIFIER       90.0f +#define ABVM(h)                     ((int)((float)h*ALIEN_BVALUE_MODIFIER))  #define CREEP_BASESIZE              700  #define CREEP_TIMEOUT               1000 @@ -223,44 +241,53 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define CREEP_ARMOUR_MODIFIER       0.75f  #define CREEP_SCALEDOWN_TIME        3000 +#define PCLOUD_MODIFIER             0.5f +#define PCLOUD_ARMOUR_MODIFIER      0.75f +  #define ASPAWN_BP                   10  #define ASPAWN_BT                   15000  #define ASPAWN_HEALTH               ABHM(250)  #define ASPAWN_REGEN                8  #define ASPAWN_SPLASHDAMAGE         50 -#define ASPAWN_SPLASHRADIUS         50 +#define ASPAWN_SPLASHRADIUS         100  #define ASPAWN_CREEPSIZE            120 -#define ASPAWN_VALUE                150 +#define ASPAWN_VALUE                ABVM(ASPAWN_BP) -#define BARRICADE_BP                10 +#define BARRICADE_BP                8  #define BARRICADE_BT                20000 -#define BARRICADE_HEALTH            ABHM(200) +#define BARRICADE_HEALTH            ABHM(300)  #define BARRICADE_REGEN             14  #define BARRICADE_SPLASHDAMAGE      50 -#define BARRICADE_SPLASHRADIUS      50 +#define BARRICADE_SPLASHRADIUS      100  #define BARRICADE_CREEPSIZE         120 +#define BARRICADE_SHRINKPROP        0.25f +#define BARRICADE_SHRINKTIMEOUT     500 +#define BARRICADE_VALUE             ABVM(BARRICADE_BP)  #define BOOSTER_BP                  12  #define BOOSTER_BT                  15000  #define BOOSTER_HEALTH              ABHM(150)  #define BOOSTER_REGEN               8  #define BOOSTER_SPLASHDAMAGE        50 -#define BOOSTER_SPLASHRADIUS        50 +#define BOOSTER_SPLASHRADIUS        100  #define BOOSTER_CREEPSIZE           120 -#define BOOSTER_INTERVAL            30000 //time in msec between uses (per player) -#define BOOSTER_REGEN_MOD           2.0f -#define BOOST_TIME                  30000 +#define BOOSTER_REGEN_MOD           3.0f +#define BOOSTER_VALUE               ABVM(BOOSTER_BP) +#define BOOST_TIME                  20000 +#define BOOST_WARN_TIME             15000  #define ACIDTUBE_BP                 8  #define ACIDTUBE_BT                 15000  #define ACIDTUBE_HEALTH             ABHM(125)  #define ACIDTUBE_REGEN              10 -#define ACIDTUBE_SPLASHDAMAGE       6 -#define ACIDTUBE_SPLASHRADIUS       300 +#define ACIDTUBE_SPLASHDAMAGE       50 +#define ACIDTUBE_SPLASHRADIUS       100  #define ACIDTUBE_CREEPSIZE          120 +#define ACIDTUBE_DAMAGE             8  #define ACIDTUBE_RANGE              300.0f -#define ACIDTUBE_REPEAT             3000 -#define ACIDTUBE_K_SCALE            1.0f +#define ACIDTUBE_REPEAT             300 +#define ACIDTUBE_REPEAT_ANIM        2000 +#define ACIDTUBE_VALUE              ABVM(ACIDTUBE_BP)  #define HIVE_BP                     12  #define HIVE_BT                     20000 @@ -269,12 +296,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define HIVE_SPLASHDAMAGE           30  #define HIVE_SPLASHRADIUS           200  #define HIVE_CREEPSIZE              120 -#define HIVE_RANGE                  400.0f -#define HIVE_REPEAT                 5000 +#define HIVE_SENSE_RANGE            500.0f +#define HIVE_LIFETIME               3000 +#define HIVE_REPEAT                 3000  #define HIVE_K_SCALE                1.0f -#define HIVE_DMG                    50 -#define HIVE_SPEED                  240.0f +#define HIVE_DMG                    80 +#define HIVE_SPEED                  320.0f  #define HIVE_DIR_CHANGE_PERIOD      500 +#define HIVE_VALUE                  ABVM(HIVE_BP)  #define TRAPPER_BP                  8  #define TRAPPER_BT                  12000 @@ -285,7 +314,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define TRAPPER_CREEPSIZE           30  #define TRAPPER_RANGE               400  #define TRAPPER_REPEAT              1000 -#define TRAPPER_K_SCALE             1.0f +#define TRAPPER_VALUE               ABVM(TRAPPER_BP)  #define LOCKBLOB_SPEED              650.0f  #define LOCKBLOB_LOCKTIME           5000  #define LOCKBLOB_DOT                0.85f // max angle = acos( LOCKBLOB_DOT ) @@ -300,17 +329,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define OVERMIND_CREEPSIZE          120  #define OVERMIND_ATTACK_RANGE       150.0f  #define OVERMIND_ATTACK_REPEAT      1000 -#define OVERMIND_VALUE              300 - -#define HOVEL_BP                    0 -#define HOVEL_BT                    15000 -#define HOVEL_HEALTH                ABHM(375) -#define HOVEL_REGEN                 20 -#define HOVEL_SPLASHDAMAGE          20 -#define HOVEL_SPLASHRADIUS          200 -#define HOVEL_CREEPSIZE             120 - - +#define OVERMIND_VALUE              ABVM(30)  /*   * ALIEN misc @@ -320,14 +339,21 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA   */  #define ALIENSENSE_RANGE            1000.0f +#define REGEN_BOOST_RANGE           200.0f -#define ALIEN_POISON_TIME           5000 +#define ALIEN_POISON_TIME           10000  #define ALIEN_POISON_DMG            5  #define ALIEN_POISON_DIVIDER        (1.0f/1.32f) //about 1.0/(time`th root of damage)  #define ALIEN_SPAWN_REPEAT_TIME     10000  #define ALIEN_REGEN_DAMAGE_TIME     2000 //msec since damage that regen starts again +#define ALIEN_REGEN_NOCREEP_MOD     (1.0f/3.0f) //regen off creep + +#define ALIEN_MAX_FRAGS             9 +#define ALIEN_MAX_CREDITS           (ALIEN_MAX_FRAGS*ALIEN_CREDITS_PER_KILL) +#define ALIEN_CREDITS_PER_KILL      400 +#define ALIEN_TK_SUICIDE_PENALTY    350  /*   * HUMAN weapons @@ -347,7 +373,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define BLASTER_K_SCALE             1.0f  #define BLASTER_SPREAD              200  #define BLASTER_SPEED               1400 -#define BLASTER_DMG                 HDM(9) +#define BLASTER_DMG                 HDM(10) +#define BLASTER_SIZE                5  #define RIFLE_CLIPSIZE              30  #define RIFLE_MAXCLIPS              6 @@ -361,8 +388,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define PAINSAW_PRICE               100  #define PAINSAW_REPEAT              75  #define PAINSAW_K_SCALE             1.0f -#define PAINSAW_DAMAGE              HDM(15) -#define PAINSAW_RANGE               40.0f +#define PAINSAW_DAMAGE              HDM(11) +#define PAINSAW_RANGE               64.0f +#define PAINSAW_WIDTH               0.0f +#define PAINSAW_HEIGHT              8.0f  #define GRENADE_PRICE               200  #define GRENADE_REPEAT              0 @@ -373,13 +402,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define SHOTGUN_PRICE               150  #define SHOTGUN_SHELLS              8 -#define SHOTGUN_PELLETS             8 //used to sync server and client side +#define SHOTGUN_PELLETS             11 //used to sync server and client side  #define SHOTGUN_MAXCLIPS            3  #define SHOTGUN_REPEAT              1000  #define SHOTGUN_K_SCALE             1.0f  #define SHOTGUN_RELOAD              2000 -#define SHOTGUN_SPREAD              900 -#define SHOTGUN_DMG                 HDM(7) +#define SHOTGUN_SPREAD              700 +#define SHOTGUN_DMG                 HDM(5) +#define SHOTGUN_RANGE               (8192 * 12)  #define LASGUN_PRICE                250  #define LASGUN_AMMO                 200 @@ -391,7 +421,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define MDRIVER_PRICE               350  #define MDRIVER_CLIPSIZE            5  #define MDRIVER_MAXCLIPS            4 -#define MDRIVER_DMG                 HDM(38) +#define MDRIVER_DMG                 HDM(40)  #define MDRIVER_REPEAT              1000  #define MDRIVER_K_SCALE             1.0f  #define MDRIVER_RELOAD              2000 @@ -400,64 +430,66 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define CHAINGUN_BULLETS            300  #define CHAINGUN_REPEAT             80  #define CHAINGUN_K_SCALE            1.0f -#define CHAINGUN_SPREAD             1000 +#define CHAINGUN_SPREAD             900  #define CHAINGUN_DMG                HDM(6) -#define PRIFLE_PRICE                400 -#define PRIFLE_CLIPS                50 -#define PRIFLE_MAXCLIPS             4 +#define FLAMER_PRICE                400 +#define FLAMER_GAS                  200 +#define FLAMER_REPEAT               200 +#define FLAMER_K_SCALE              2.0f +#define FLAMER_DMG                  HDM(20) +#define FLAMER_SPLASHDAMAGE         HDM(10) +#define FLAMER_RADIUS               50       // splash radius +#define FLAMER_SIZE                 15        // missile bounding box +#define FLAMER_LIFETIME             700.0f +#define FLAMER_SPEED                500.0f +#define FLAMER_LAG                  0.65f    // the amount of player velocity that is added to the fireball + +#define PRIFLE_PRICE                450 +#define PRIFLE_CLIPS                40 +#define PRIFLE_MAXCLIPS             5  #define PRIFLE_REPEAT               100  #define PRIFLE_K_SCALE              1.0f  #define PRIFLE_RELOAD               2000  #define PRIFLE_DMG                  HDM(9) -#define PRIFLE_SPEED                1000 - -#define FLAMER_PRICE                450 -#define FLAMER_GAS                  150 -#define FLAMER_REPEAT               200 -#define FLAMER_K_SCALE              1.0f -#define FLAMER_DMG                  HDM(20) -#define FLAMER_RADIUS               50 -#define FLAMER_LIFETIME             800.0f -#define FLAMER_SPEED                200.0f -#define FLAMER_LAG                  0.65f  //the amount of player velocity that is added to the fireball +#define PRIFLE_SPEED                1200 +#define PRIFLE_SIZE                 5  #define LCANNON_PRICE               600 -#define LCANNON_AMMO                90 -#define LCANNON_REPEAT              500 +#define LCANNON_AMMO                80  #define LCANNON_K_SCALE             1.0f -#define LCANNON_CHARGEREPEAT        1000 -#define LCANNON_RELOAD              2000 +#define LCANNON_REPEAT              500 +#define LCANNON_RELOAD              0  #define LCANNON_DAMAGE              HDM(265) -#define LCANNON_RADIUS              150 -#define LCANNON_SECONDARY_DAMAGE    HDM(27) -#define LCANNON_SECONDARY_RADIUS    75 -#define LCANNON_SPEED               350 -#define LCANNON_CHARGE_TIME         2000 -#define LCANNON_TOTAL_CHARGE        255 -#define LCANNON_MIN_CHARGE          50 +#define LCANNON_RADIUS              150      // primary splash damage radius +#define LCANNON_SIZE                5        // missile bounding box radius +#define LCANNON_SECONDARY_DAMAGE    HDM(30) +#define LCANNON_SECONDARY_RADIUS    75       // secondary splash damage radius +#define LCANNON_SECONDARY_SPEED     1400 +#define LCANNON_SECONDARY_RELOAD    2000 +#define LCANNON_SECONDARY_REPEAT    1000 +#define LCANNON_SPEED               700 +#define LCANNON_CHARGE_TIME_MAX     3000 +#define LCANNON_CHARGE_TIME_MIN     100 +#define LCANNON_CHARGE_TIME_WARN    2000 +#define LCANNON_CHARGE_AMMO         10       // ammo cost of a full charge shot  #define HBUILD_PRICE                0  #define HBUILD_REPEAT               1000 -#define HBUILD_DELAY                17500  #define HBUILD_HEALRATE             18 -#define HBUILD2_PRICE               0 -#define HBUILD2_REPEAT              1000 -#define HBUILD2_DELAY               15000 - - -  /*   * HUMAN upgrades   */  #define LIGHTARMOUR_PRICE           70  #define LIGHTARMOUR_POISON_PROTECTION 1 +#define LIGHTARMOUR_PCLOUD_PROTECTION 1000  #define HELMET_PRICE                90  #define HELMET_RANGE                1000.0f -#define HELMET_POISON_PROTECTION    2  +#define HELMET_POISON_PROTECTION    1 +#define HELMET_PCLOUD_PROTECTION    1000  #define MEDKIT_PRICE                0 @@ -471,19 +503,13 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define JETPACK_DISABLE_CHANCE      0.3f  #define BSUIT_PRICE                 400 -#define BSUIT_POISON_PROTECTION     4 - -#define MGCLIP_PRICE                0 - -#define CGAMMO_PRICE                0 - -#define GAS_PRICE                   0 +#define BSUIT_POISON_PROTECTION     3 +#define BSUIT_PCLOUD_PROTECTION     3000  #define MEDKIT_POISON_IMMUNITY_TIME 0  #define MEDKIT_STARTUP_TIME         4000  #define MEDKIT_STARTUP_SPEED        5 -  /*   * HUMAN buildables   * @@ -492,7 +518,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA   * _SPLASHDAMGE   - the amount of damage caused by this buildable when it blows up   * _SPLASHRADIUS  - the radius around which it does this damage   * - * REACTOR_BASESIZE - the maximum distance a buildable can be from an reactor + * REACTOR_BASESIZE - the maximum distance a buildable can be from a reactor   * REPEATER_BASESIZE - the maximum distance a buildable can be from a repeater   * HUMAN_BHLTH_MODIFIER - overall health modifier for coarse tuning   * @@ -500,6 +526,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define HUMAN_BHLTH_MODIFIER        1.0f  #define HBHM(h)                     ((int)((float)h*HUMAN_BHLTH_MODIFIER)) +#define HUMAN_BVALUE_MODIFIER       240.0f +#define HBVM(h)                     ((int)((float)h*(float)HUMAN_BVALUE_MODIFIER)) // remember these are measured in credits not frags (c.f. ALIEN_CREDITS_PER_KILL)  #define REACTOR_BASESIZE            1000  #define REPEATER_BASESIZE           500 @@ -510,31 +538,30 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define HSPAWN_HEALTH               HBHM(310)  #define HSPAWN_SPLASHDAMAGE         50  #define HSPAWN_SPLASHRADIUS         100 -#define HSPAWN_VALUE                1 +#define HSPAWN_VALUE                HBVM(HSPAWN_BP)  #define MEDISTAT_BP                 8  #define MEDISTAT_BT                 10000  #define MEDISTAT_HEALTH             HBHM(190)  #define MEDISTAT_SPLASHDAMAGE       50  #define MEDISTAT_SPLASHRADIUS       100 +#define MEDISTAT_VALUE              HBVM(MEDISTAT_BP)  #define MGTURRET_BP                 8  #define MGTURRET_BT                 10000  #define MGTURRET_HEALTH             HBHM(190)  #define MGTURRET_SPLASHDAMAGE       100  #define MGTURRET_SPLASHRADIUS       100 -#define MGTURRET_ANGULARSPEED       8  //degrees/think ~= 200deg/sec -#define MGTURRET_ACCURACYTOLERANCE  MGTURRET_ANGULARSPEED / 1.5f //angular difference for turret to fire +#define MGTURRET_ANGULARSPEED       12 +#define MGTURRET_ACCURACY_TO_FIRE   0  #define MGTURRET_VERTICALCAP        30  // +/- maximum pitch -#define MGTURRET_REPEAT             100 +#define MGTURRET_REPEAT             150  #define MGTURRET_K_SCALE            1.0f -#define MGTURRET_RANGE              300.0f +#define MGTURRET_RANGE              400.0f  #define MGTURRET_SPREAD             200 -#define MGTURRET_DMG                HDM(4) -#define MGTURRET_DCC_ANGULARSPEED       10 -#define MGTURRET_DCC_ACCURACYTOLERANCE  MGTURRET_DCC_ANGULARSPEED / 1.5f -#define MGTURRET_GRAB_ANGULARSPEED      3 -#define MGTURRET_GRAB_ACCURACYTOLERANCE MGTURRET_GRAB_ANGULARSPEED / 1.5f +#define MGTURRET_DMG                HDM(8) +#define MGTURRET_SPINUP_TIME        750 // time between target sighted and fire +#define MGTURRET_VALUE              HBVM(MGTURRET_BP)  #define TESLAGEN_BP                 10  #define TESLAGEN_BT                 15000 @@ -543,20 +570,26 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define TESLAGEN_SPLASHRADIUS       100  #define TESLAGEN_REPEAT             250  #define TESLAGEN_K_SCALE            4.0f -#define TESLAGEN_RANGE              250 -#define TESLAGEN_DMG                HDM(9) +#define TESLAGEN_RANGE              200 +#define TESLAGEN_DMG                HDM(10) +#define TESLAGEN_VALUE              HBVM(TESLAGEN_BP)  #define DC_BP                       8  #define DC_BT                       10000  #define DC_HEALTH                   HBHM(190)  #define DC_SPLASHDAMAGE             50  #define DC_SPLASHRADIUS             100 +#define DC_ATTACK_PERIOD            10000 // how often to spam "under attack" +#define DC_HEALRATE                 4 +#define DC_RANGE                    1000 +#define DC_VALUE                    HBVM(DC_BP)  #define ARMOURY_BP                  10  #define ARMOURY_BT                  10000 -#define ARMOURY_HEALTH              HBHM(280) +#define ARMOURY_HEALTH              HBHM(420)  #define ARMOURY_SPLASHDAMAGE        50  #define ARMOURY_SPLASHRADIUS        100 +#define ARMOURY_VALUE               HBVM(ARMOURY_BP)  #define REACTOR_BP                  0  #define REACTOR_BT                  20000 @@ -566,14 +599,17 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define REACTOR_ATTACK_RANGE        100.0f  #define REACTOR_ATTACK_REPEAT       1000  #define REACTOR_ATTACK_DAMAGE       40 -#define REACTOR_VALUE               2 +#define REACTOR_ATTACK_DCC_REPEAT   1000 +#define REACTOR_ATTACK_DCC_RANGE    150.0f +#define REACTOR_ATTACK_DCC_DAMAGE   40 +#define REACTOR_VALUE               HBVM(30) -#define REPEATER_BP                 0 +#define REPEATER_BP                 4  #define REPEATER_BT                 10000  #define REPEATER_HEALTH             HBHM(250)  #define REPEATER_SPLASHDAMAGE       50  #define REPEATER_SPLASHRADIUS       100 -#define REPEATER_INACTIVE_TIME      90000 +#define REPEATER_VALUE              HBVM(REPEATER_BP)  /*   * HUMAN misc @@ -583,13 +619,33 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define HUMAN_JOG_MODIFIER          1.0f  #define HUMAN_BACK_MODIFIER         0.8f  #define HUMAN_SIDE_MODIFIER         0.9f +#define HUMAN_DODGE_SIDE_MODIFIER   2.9f +#define HUMAN_DODGE_SLOWED_MODIFIER 0.9f +#define HUMAN_DODGE_UP_MODIFIER     0.5f +#define HUMAN_DODGE_TIMEOUT         500 +#define HUMAN_LAND_FRICTION         3.0f -#define STAMINA_STOP_RESTORE        25 +#define STAMINA_STOP_RESTORE        30  #define STAMINA_WALK_RESTORE        15 -#define STAMINA_SPRINT_TAKE         8 -#define STAMINA_LARMOUR_TAKE        4 +#define STAMINA_MEDISTAT_RESTORE    30 // stacked on STOP or WALK +#define STAMINA_SPRINT_TAKE         6 +#define STAMINA_JUMP_TAKE           250 +#define STAMINA_DODGE_TAKE          250 +#define STAMINA_MAX                 1000 +#define STAMINA_BREATHING_LEVEL     0 +#define STAMINA_SLOW_LEVEL          -500 +#define STAMINA_BLACKOUT_LEVEL      -800  #define HUMAN_SPAWN_REPEAT_TIME     10000 +#define HUMAN_REGEN_DAMAGE_TIME     2000 //msec since damage before dcc repairs + +#define HUMAN_MAX_CREDITS           2000 +#define HUMAN_TK_SUICIDE_PENALTY    150 + +#define HUMAN_BUILDER_SCOREINC      50       // builders receive this many points every 10 seconds +#define ALIEN_BUILDER_SCOREINC      AVM(100)  // builders receive this many points every 10 seconds + +#define HUMAN_BUILDABLE_INACTIVE_TIME 90000  /*   * Misc @@ -599,29 +655,27 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA  #define MAX_FALL_DISTANCE           120.0f //the fall distance at which maximum damage is dealt  #define AVG_FALL_DISTANCE           ((MIN_FALL_DISTANCE+MAX_FALL_DISTANCE)/2.0f) -#define HUMAN_MAXED                 900   //a human with a strong selection of weapons/upgrades -#define HUMAN_MAX_CREDITS           2000 -#define ALIEN_MAX_KILLS             9 -#define ALIEN_MAX_SINGLE_KILLS      3 - -#define FREEKILL_PERIOD             120000 //msec -#define FREEKILL_ALIEN              1 +#define DEFAULT_FREEKILL_PERIOD     "120" //seconds +#define FREEKILL_ALIEN              ALIEN_CREDITS_PER_KILL  #define FREEKILL_HUMAN              LEVEL0_VALUE -#define DEFAULT_ALIEN_BUILDPOINTS   "130" -#define DEFAULT_ALIEN_STAGE2_THRESH "20" -#define DEFAULT_ALIEN_STAGE3_THRESH "40" +#define DEFAULT_ALIEN_BUILDPOINTS   "150" +#define DEFAULT_ALIEN_QUEUE_TIME    "12000" +#define DEFAULT_ALIEN_STAGE2_THRESH "12000" +#define DEFAULT_ALIEN_STAGE3_THRESH "24000"  #define DEFAULT_ALIEN_MAX_STAGE     "2" -#define DEFAULT_HUMAN_BUILDPOINTS   "130" -#define DEFAULT_HUMAN_STAGE2_THRESH "20" -#define DEFAULT_HUMAN_STAGE3_THRESH "40" +#define DEFAULT_HUMAN_BUILDPOINTS   "100" +#define DEFAULT_HUMAN_QUEUE_TIME    "8000" +#define DEFAULT_HUMAN_REPEATER_BUILDPOINTS "20" +#define DEFAULT_HUMAN_REPEATER_QUEUE_TIME "2000" +#define DEFAULT_HUMAN_REPEATER_MAX_ZONES "500" +#define DEFAULT_HUMAN_STAGE2_THRESH "6000" +#define DEFAULT_HUMAN_STAGE3_THRESH "12000"  #define DEFAULT_HUMAN_MAX_STAGE     "2"  #define DAMAGE_FRACTION_FOR_KILL    0.5f //how much damage players (versus structures) need to                                           //do to increment the stage kill counters +                                          +#define MAXIMUM_BUILD_TIME          20000 // used for pie timer -// g_suddenDeathMode settings -#define SDMODE_BP                   0 -#define SDMODE_NO_BUILD             1 -#define SDMODE_SELECTIVE            2 -#define SDMODE_NO_DECON             3 +#endif  | 
