From bf23ecf17f432cf8e47302ef7464612c17be9bbe Mon Sep 17 00:00:00 2001 From: Tim Angus Date: Sat, 10 Dec 2005 03:23:37 +0000 Subject: * Move the game source from mod/src/ to src/ --- src/game/bg_lib.c | 1940 +++++++++++++++++ src/game/bg_lib.h | 92 + src/game/bg_local.h | 80 + src/game/bg_misc.c | 5248 ++++++++++++++++++++++++++++++++++++++++++++++ src/game/bg_pmove.c | 3471 ++++++++++++++++++++++++++++++ src/game/bg_public.h | 1299 ++++++++++++ src/game/bg_slidemove.c | 408 ++++ src/game/g_active.c | 1524 ++++++++++++++ src/game/g_buildable.c | 2987 ++++++++++++++++++++++++++ src/game/g_client.c | 1593 ++++++++++++++ src/game/g_cmds.c | 2305 ++++++++++++++++++++ src/game/g_combat.c | 1322 ++++++++++++ src/game/g_local.h | 1139 ++++++++++ src/game/g_main.c | 2207 +++++++++++++++++++ src/game/g_maprotation.c | 709 +++++++ src/game/g_mem.c | 201 ++ src/game/g_misc.c | 411 ++++ src/game/g_missile.c | 803 +++++++ src/game/g_mover.c | 2468 ++++++++++++++++++++++ src/game/g_physics.c | 161 ++ src/game/g_ptr.c | 167 ++ src/game/g_public.h | 419 ++++ src/game/g_session.c | 150 ++ src/game/g_spawn.c | 701 +++++++ src/game/g_svcmds.c | 581 +++++ src/game/g_syscalls.asm | 61 + src/game/g_syscalls.c | 264 +++ src/game/g_target.c | 432 ++++ src/game/g_team.c | 249 +++ src/game/g_trigger.c | 1037 +++++++++ src/game/g_utils.c | 1071 ++++++++++ src/game/g_weapon.c | 1553 ++++++++++++++ src/game/q_math.c | 1482 +++++++++++++ src/game/q_shared.c | 1286 ++++++++++++ src/game/q_shared.h | 1422 +++++++++++++ src/game/surfaceflags.h | 84 + src/game/tremulous.h | 572 +++++ 37 files changed, 41899 insertions(+) create mode 100644 src/game/bg_lib.c create mode 100644 src/game/bg_lib.h create mode 100644 src/game/bg_local.h create mode 100644 src/game/bg_misc.c create mode 100644 src/game/bg_pmove.c create mode 100644 src/game/bg_public.h create mode 100644 src/game/bg_slidemove.c create mode 100644 src/game/g_active.c create mode 100644 src/game/g_buildable.c create mode 100644 src/game/g_client.c create mode 100644 src/game/g_cmds.c create mode 100644 src/game/g_combat.c create mode 100644 src/game/g_local.h create mode 100644 src/game/g_main.c create mode 100644 src/game/g_maprotation.c create mode 100644 src/game/g_mem.c create mode 100644 src/game/g_misc.c create mode 100644 src/game/g_missile.c create mode 100644 src/game/g_mover.c create mode 100644 src/game/g_physics.c create mode 100644 src/game/g_ptr.c create mode 100644 src/game/g_public.h create mode 100644 src/game/g_session.c create mode 100644 src/game/g_spawn.c create mode 100644 src/game/g_svcmds.c create mode 100644 src/game/g_syscalls.asm create mode 100644 src/game/g_syscalls.c create mode 100644 src/game/g_target.c create mode 100644 src/game/g_team.c create mode 100644 src/game/g_trigger.c create mode 100644 src/game/g_utils.c create mode 100644 src/game/g_weapon.c create mode 100644 src/game/q_math.c create mode 100644 src/game/q_shared.c create mode 100644 src/game/q_shared.h create mode 100644 src/game/surfaceflags.h create mode 100644 src/game/tremulous.h (limited to 'src/game') diff --git a/src/game/bg_lib.c b/src/game/bg_lib.c new file mode 100644 index 00000000..d940c6d8 --- /dev/null +++ b/src/game/bg_lib.c @@ -0,0 +1,1940 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_lib,c -- standard C library replacement routines used by code +// compiled for the virtual machine + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +#include "q_shared.h" + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "bg_lib.h" + +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = + "$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". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + register TYPE *pi = (TYPE *) (parmi); \ + register TYPE *pj = (TYPE *) (parmj); \ + do { \ + register TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static void +swapfunc(a, b, n, swaptype) + char *a, *b; + int n, swaptype; +{ + if(swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static char * +med3(a, b, c, cmp) + char *a, *b, *c; + cmp_t *cmp; +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); +} + +void +qsort(a, n, es, cmp) + void *a; + size_t n, es; + cmp_t *cmp; +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype, swap_cnt; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = cmp(pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) + qsort(a, r / es, es, cmp); + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp);*/ +} + +//================================================================================== + + +// 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; + + s = string; + while( *s ) + s++; + + return s - string; +} + + +char *strcat( char *strDestination, const char *strSource ) +{ + char *s; + + s = strDestination; + while( *s ) + s++; + + while( *strSource ) + *s++ = *strSource++; + + *s = 0; + return strDestination; +} + +char *strcpy( char *strDestination, const char *strSource ) +{ + char *s; + + s = strDestination; + + while( *strSource ) + *s++ = *strSource++; + + *s = 0; + return strDestination; +} + + +int strcmp( const char *string1, const char *string2 ) +{ + while( *string1 == *string2 && *string1 && *string2 ) + { + string1++; + 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-- ) + { + p = (char *)&string[ i ]; + + if( *p == c ) + return (char *)p; + } + + return (char *)0; +} + +char *strchr( const char *string, int c ) +{ + while( *string ) + { + if( *string == c ) + return ( char * )string; + + string++; + } + return (char *)0; +} + +char *strstr( const char *string, const char *strCharSet ) +{ + while( *string ) + { + int i; + + for( i = 0; strCharSet[ i ]; i++ ) + { + if( string[ i ] != strCharSet[ i ] ) + break; + } + + if( !strCharSet[ i ] ) + return (char *)string; + + string++; + } + return (char *)0; +} + +#endif // bk001211 + +#if defined ( Q3_VM ) + +int tolower( int c ) +{ + if( c >= 'A' && c <= 'Z' ) + c += 'a' - 'A'; + + return c; +} + + +int toupper( int c ) +{ + if( c >= 'a' && c <= 'z' ) + c += 'A' - 'a'; + + return c; +} + +#endif + +void *memmove( void *dest, const void *src, size_t count ) +{ + int i; + + if( dest > src ) + { + for( i = count - 1; i >= 0; i-- ) + ( (char *)dest )[ i ] = ( (char *)src )[ i ]; + } + else + { + for( i = 0; i < count; i++ ) + ( (char *)dest )[ i ] = ( (char *)src )[ i ]; + } + + return dest; +} + + +#if 0 + +double floor( double x ) { + return (int)(x + 0x40000000) - 0x40000000; +} + +void *memset( void *dest, int c, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = c; + } + return dest; +} + +void *memcpy( void *dest, const void *src, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = ((char *)src)[count]; + } + return dest; +} + +char *strncpy( char *strDest, const char *strSource, size_t count ) { + char *s; + + s = strDest; + while ( *strSource && count ) { + *s++ = *strSource++; + count--; + } + while ( count-- ) { + *s++ = 0; + } + return strDest; +} + +double sqrt( double x ) { + float y; + float delta; + float maxError; + + if ( x <= 0 ) { + return 0; + } + + // initial guess + y = x / 2; + + // refine + maxError = x * 0.001; + + do { + delta = ( y * y ) - x; + y -= delta / ( 2 * y ); + } while ( delta > maxError || delta < -maxError ); + + return y; +} + + +float sintable[1024] = { +0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, +0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, +0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, +0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, +0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, +0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, +0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, +0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, +0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, +0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, +0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, +0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, +0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, +0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, +0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, +0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, +0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, +0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, +0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, +0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, +0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, +0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, +0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, +0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, +0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, +0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, +0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, +0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, +0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, +0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, +0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, +0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, +0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, +0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, +0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, +0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, +0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, +0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, +0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, +0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, +0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, +0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, +0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, +0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, +0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, +0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, +0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, +0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, +0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, +0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, +0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, +0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, +0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, +0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, +0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, +0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, +0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, +0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, +0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, +0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, +0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, +0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, +0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, +0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, +0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, +0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, +0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, +0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, +0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, +0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, +0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, +0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, +0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, +0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, +0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, +0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, +0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, +0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, +0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, +0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, +0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, +0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, +0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, +0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, +0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, +0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, +0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, +0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, +0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, +0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, +0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, +0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, +0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, +0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, +0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, +0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, +0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, +0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, +0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, +0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, +0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, +0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, +0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, +0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, +0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, +0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, +0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, +0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, +0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, +0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, +0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, +0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, +0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, +0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, +0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, +0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, +0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, +0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, +0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, +0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, +0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, +0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, +0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, +0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, +0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, +0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, +0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, +0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 +}; + +double sin( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 0: + return sintable[index]; + case 1: + return sintable[1023-index]; + case 2: + return -sintable[index]; + case 3: + return -sintable[1023-index]; + } + return 0; +} + + +double cos( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 3: + return sintable[index]; + case 0: + return sintable[1023-index]; + case 1: + return -sintable[index]; + case 2: + return -sintable[1023-index]; + } + return 0; +} + + +/* +void create_acostable( void ) { + int i; + FILE *fp; + float a; + + fp = fopen("c:\\acostable.txt", "w"); + fprintf(fp, "float acostable[] = {"); + for (i = 0; i < 1024; i++) { + if (!(i & 7)) + fprintf(fp, "\n"); + a = acos( (float) -1 + i / 512 ); + fprintf(fp, "%1.8f,", a); + } + fprintf(fp, "\n}\n"); + fclose(fp); +} +*/ + + +float acostable[] = { +3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422, +2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629, +2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962, +2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724, +2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926, +2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688, +2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735, +2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133, +2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440, +2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769, +2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409, +2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238, +2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096, +2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722, +2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034, +2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622, +2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379, +2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237, +2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964, +2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010, +2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388, +2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580, +2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460, +2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238, +2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408, +2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709, +2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092, +2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692, +2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805, +2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865, +2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435, +2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185, +2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884, +2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387, +2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628, +2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610, +2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399, +2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119, +1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942, +1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088, +1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818, +1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433, +1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266, +1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683, +1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076, +1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866, +1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495, +1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429, +1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151, +1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164, +1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985, +1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148, +1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198, +1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692, +1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199, +1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297, +1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571, +1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615, +1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028, +1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416, +1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388, +1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556, +1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536, +1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945, +1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403, +1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526, +1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933, +1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240, +1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060, +1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004, +1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677, +1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682, +1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612, +1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056, +1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594, +1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795, +1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222, +1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422, +1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932, +1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273, +1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951, +1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456, +1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257, +1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803, +1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519, +1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806, +1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037, +1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553, +1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663, +1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638, +1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709, +1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061, +1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831, +1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098, +1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882, +1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136, +1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735, +1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471, +1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039, +0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030, +0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912, +0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017, +0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524, +0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430, +0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531, +0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385, +0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277, +0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172, +0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655, +0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865, +0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402, +0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217, +0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469, +0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332, +0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753, +0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106, +0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735, +0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288, +0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758, +0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028, +0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573, +0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652, +0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513, +0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209, +0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574, +0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250, +0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575, +0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018, +}; + +double acos( double x ) { + int index; + + if (x < -1) + x = -1; + if (x > 1) + x = 1; + index = (float) (1.0 + x) * 511.9; + return acostable[index]; +} + + +double atan2( double y, double x ) { + float base; + float temp; + float dir; + float test; + int i; + + if ( x < 0 ) { + if ( y >= 0 ) { + // quad 1 + base = M_PI / 2; + temp = x; + x = y; + y = -temp; + } else { + // quad 2 + base = M_PI; + x = -x; + y = -y; + } + } else { + if ( y < 0 ) { + // quad 3 + base = 3 * M_PI / 2; + temp = x; + x = -y; + y = temp; + } + } + + if ( y > x ) { + base += M_PI/2; + temp = x; + x = y; + y = temp; + dir = -1; + } else { + dir = 1; + } + + // calcualte angle in octant 0 + if ( x == 0 ) { + return base; + } + y /= x; + + for ( i = 0 ; i < 512 ; i++ ) { + test = sintable[i] / sintable[1023-i]; + if ( test > y ) { + break; + } + } + + return base + dir * i * ( M_PI/2048); +} + + +#endif + +#ifdef Q3_VM +// bk001127 - guarded this tan replacement +// ld: undefined versioned symbol name tan@@GLIBC_2.0 +double tan( double x ) +{ + return sin( x ) / cos( x ); +} + +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +typedef union +{ + float value; + unsigned int word; +} ieee_float_shape_type; + +/* Get a 32 bit int from a float. */ + +#define GET_FLOAT_WORD(i,d) \ +do { \ + ieee_float_shape_type gf_u; \ + gf_u.value = (d); \ + (i) = gf_u.word; \ +} while (0) + +/* Set a float from a 32 bit int. */ + +#define SET_FLOAT_WORD(d,i) \ +do { \ + ieee_float_shape_type sf_u; \ + sf_u.word = (i); \ + (d) = sf_u.value; \ +} while (0) + +/* A union which permits us to convert between a float and a 32 bit + int. */ + +//acos +static const float +pi = 3.1415925026e+00, /* 0x40490fda */ +pio2_hi = 1.5707962513e+00, /* 0x3fc90fda */ +pio2_lo = 7.5497894159e-08, /* 0x33a22168 */ +pS0 = 1.6666667163e-01, /* 0x3e2aaaab */ +pS1 = -3.2556581497e-01, /* 0xbea6b090 */ +pS2 = 2.0121252537e-01, /* 0x3e4e0aa8 */ +pS3 = -4.0055535734e-02, /* 0xbd241146 */ +pS4 = 7.9153501429e-04, /* 0x3a4f7f04 */ +pS5 = 3.4793309169e-05, /* 0x3811ef08 */ +qS1 = -2.4033949375e+00, /* 0xc019d139 */ +qS2 = 2.0209457874e+00, /* 0x4001572d */ +qS3 = -6.8828397989e-01, /* 0xbf303361 */ +qS4 = 7.7038154006e-02; /* 0x3d9dc62e */ + +/* +================== +acos +================== +*/ +double acos( double x ) +{ + float z, subp, p, q, r, w, s, c, df; + int hx, ix; + + GET_FLOAT_WORD( hx, x ); + ix = hx & 0x7fffffff; + + if( ix == 0x3f800000 ) + { // |x|==1 + if( hx > 0 ) + return 0.0; // acos(1) = 0 + else + return pi + (float)2.0 * pio2_lo; // acos(-1)= pi + } + else if( ix > 0x3f800000 ) + { // |x| >= 1 + return (x-x)/(x-x); // acos(|x|>1) is NaN + } + + if( ix < 0x3f000000 ) + { // |x| < 0.5 + if( ix <= 0x23000000 ) + return pio2_hi + pio2_lo;//if|x|<2**-57 + + z = x * x; + subp = pS3 + z * ( pS4 + z * pS5 ); + // chop up expression to keep mac register based stack happy + p = z * ( pS0 + z * ( pS1 + z * ( pS2 + z * subp ) ) ); + q = 1.0 + z * ( qS1 + z * ( qS2 + z * ( qS3 + z * qS4 ) ) ); + r = p / q; + return pio2_hi - ( x - ( pio2_lo - x * r ) ); + } + else if( hx < 0 ) + { // x < -0.5 + z = ( 1.0 + x ) * (float)0.5; + subp = pS3 + z * ( pS4 + z * pS5 ); + // chop up expression to keep mac register based stack happy + p = z * ( pS0 + z * ( pS1 + z * ( pS2 + z * subp ) ) ); + q = 1.0 + z * ( qS1 + z * ( qS2 + z * ( qS3 + z * qS4 ) ) ); + s = sqrt( z ); + r = p / q; + w = r * s - pio2_lo; + return pi - (float)2.0 * ( s + w ); + } + else + { // x > 0.5 + int idf; + z = ( 1.0 - x ) * (float)0.5; + s = sqrt( z ); + df = s; + GET_FLOAT_WORD( idf, df ); + SET_FLOAT_WORD( df, idf & 0xfffff000 ); + c = ( z - df * df ) / ( s + df ); + subp = pS3 + z * ( pS4 + z * pS5 ); + // chop up expression to keep mac register based stack happy + p = z * ( pS0 + z * ( pS1 + z * ( pS2 + z * subp ) ) ); + q = 1.0 + z * ( qS1 + z * ( qS2 + z * ( qS3 + z * qS4 ) ) ); + r = p / q; + w = r * s + c; + return (double)( 2.0 * ( df + w ) ); + } +} + +//pow +static const float +bp[ ] = { 1.0, 1.5, }, +dp_h[ ] = { 0.0, 5.84960938e-01, }, /* 0x3f15c000 */ +dp_l[ ] = { 0.0, 1.56322085e-06, }, /* 0x35d1cfdc */ +huge = 1.0e+30, +tiny = 1.0e-30, +zero = 0.0, +one = 1.0, +two = 2.0, +two24 = 16777216.0, /* 0x4b800000 */ +two25 = 3.355443200e+07, /* 0x4c000000 */ +twom25 = 2.9802322388e-08, /* 0x33000000 */ + /* poly coefs for (3/2)*(log(x)-2s-2/3*s**3 */ +L1 = 6.0000002384e-01, /* 0x3f19999a */ +L2 = 4.2857143283e-01, /* 0x3edb6db7 */ +L3 = 3.3333334327e-01, /* 0x3eaaaaab */ +L4 = 2.7272811532e-01, /* 0x3e8ba305 */ +L5 = 2.3066075146e-01, /* 0x3e6c3255 */ +L6 = 2.0697501302e-01, /* 0x3e53f142 */ +P1 = 1.6666667163e-01, /* 0x3e2aaaab */ +P2 = -2.7777778450e-03, /* 0xbb360b61 */ +P3 = 6.6137559770e-05, /* 0x388ab355 */ +P4 = -1.6533901999e-06, /* 0xb5ddea0e */ +P5 = 4.1381369442e-08, /* 0x3331bb4c */ +lg2 = 6.9314718246e-01, /* 0x3f317218 */ +lg2_h = 6.93145752e-01, /* 0x3f317200 */ +lg2_l = 1.42860654e-06, /* 0x35bfbe8c */ +ovt = 4.2995665694e-08, /* -(128-log2(ovfl+.5ulp)) */ +cp = 9.6179670095e-01, /* 0x3f76384f =2/(3ln2) */ +cp_h = 9.6179199219e-01, /* 0x3f763800 =head of cp */ +cp_l = 4.7017383622e-06, /* 0x369dc3a0 =tail of cp_h */ +ivln2 = 1.4426950216e+00, /* 0x3fb8aa3b =1/ln2 */ +ivln2_h = 1.4426879883e+00, /* 0x3fb8aa00 =16b 1/ln2*/ +ivln2_l = 7.0526075433e-06; /* 0x36eca570 =1/ln2 tail*/ + +/* +================== +copysignf +================== +*/ +static float copysignf( float x, float y ) +{ + unsigned int ix, iy; + + GET_FLOAT_WORD( ix, x ); + GET_FLOAT_WORD( iy, y ); + SET_FLOAT_WORD( x, ( ix & 0x7fffffff ) | ( iy & 0x80000000 ) ); + return x; +} + +/* +================== +__scalbnf +================== +*/ +static float __scalbnf( float x, int n ) +{ + int k, ix; + + GET_FLOAT_WORD( ix, x ); + + k = ( ix & 0x7f800000 ) >> 23; /* extract exponent */ + + if( k == 0 ) + { /* 0 or subnormal x */ + if( ( ix & 0x7fffffff ) == 0 ) + return x; /* +-0 */ + + x *= two25; + GET_FLOAT_WORD( ix, x ); + k = ( ( ix & 0x7f800000 ) >> 23 ) - 25; + } + if( k == 0xff ) + return x+x; /* NaN or Inf */ + + k = k + n; + + if( n > 50000 || k > 0xfe ) + return huge * copysignf( huge, x ); /* overflow */ + if ( n < -50000 ) + return tiny * copysignf( tiny, x ); /*underflow*/ + if( k > 0 ) /* normal result */ + { + SET_FLOAT_WORD( x, ( ix & 0x807fffff ) | ( k << 23 ) ); + return x; + } + if( k <= -25 ) + return tiny * copysignf( tiny, x ); /*underflow*/ + + k += 25; /* subnormal result */ + SET_FLOAT_WORD( x, ( ix & 0x807fffff ) | ( k << 23 ) ); + return x * twom25; +} + +/* +================== +pow +================== +*/ +float pow( float x, float y ) +{ + float z, ax, z_h, z_l, p_h, p_l; + float y1, subt1, t1, t2, subr, r, s, t, u, v, w; + int i, j, k, yisint, n; + int hx, hy, ix, iy, is; + + /*TA: for some reason the Q3 VM goes apeshit when x = 1.0 + and y > 1.0. Curiously this doesn't happen with gcc + hence this hack*/ + if( x == 1.0 ) + return x; + + GET_FLOAT_WORD( hx, x ); + GET_FLOAT_WORD( hy, y ); + ix = hx & 0x7fffffff; + iy = hy & 0x7fffffff; + + /* y==zero: x**0 = 1 */ + if( iy == 0 ) + return one; + + /* +-NaN return x+y */ + if( ix > 0x7f800000 || iy > 0x7f800000 ) + return x + y; + + /* determine if y is an odd int when x < 0 + * yisint = 0 ... y is not an integer + * yisint = 1 ... y is an odd int + * yisint = 2 ... y is an even int + */ + yisint = 0; + if( hx < 0 ) + { + if( iy >= 0x4b800000 ) + yisint = 2; /* even integer y */ + else if( iy >= 0x3f800000 ) + { + k = ( iy >> 23 ) - 0x7f; /* exponent */ + j = iy >> ( 23 - k ); + if( ( j << ( 23 - k ) ) == iy ) + yisint = 2 - ( j & 1 ); + } + } + + /* special value of y */ + if( iy == 0x7f800000 ) + { /* y is +-inf */ + if( ix == 0x3f800000 ) + return y - y; /* inf**+-1 is NaN */ + else if( ix > 0x3f800000 )/* (|x|>1)**+-inf = inf,0 */ + return ( hy >= 0 ) ? y : zero; + else /* (|x|<1)**-,+inf = inf,0 */ + return ( hy < 0 ) ? -y : zero; + } + + if( iy == 0x3f800000 ) + { /* y is +-1 */ + if( hy < 0 ) + return one / x; + else + return x; + } + + if( hy == 0x40000000 ) + return x * x; /* y is 2 */ + + if( hy == 0x3f000000 ) + { /* y is 0.5 */ + if( hx >= 0 ) /* x >= +0 */ + return sqrt( x ); + } + + ax = fabs( x ); + + /* special value of x */ + if( ix == 0x7f800000 || ix == 0 || ix == 0x3f800000 ) + { + z = ax; /*x is +-0,+-inf,+-1*/ + if( hy < 0 ) + z = one / z; /* z = (1/|x|) */ + if( hx < 0 ) + { + if( ( ( ix - 0x3f800000 ) | yisint ) == 0 ) + z = ( z - z ) / ( z - z ); /* (-1)**non-int is NaN */ + else if( yisint == 1 ) + z = -z; /* (x<0)**odd = -(|x|**odd) */ + } + + return z; + } + + /* (x<0)**(non-int) is NaN */ + if( ( ( ( (unsigned int)hx >> 31 ) - 1 ) | yisint ) == 0 ) + return ( x - x ) / ( x - x ); + + /* |y| is huge */ + if( iy > 0x4d000000 ) + { /* if |y| > 2**27 */ + /* over/underflow if x is not close to one */ + if( ix < 0x3f7ffff8 ) + return ( hy < 0 ) ? huge * huge : tiny * tiny; + + if( ix > 0x3f800007 ) + return ( hy > 0 ) ? huge * huge : tiny * tiny; + /* now |1-x| is tiny <= 2**-20, suffice to compute + log(x) by x-x^2/2+x^3/3-x^4/4 */ + t = x - 1; /* t has 20 trailing zeros */ + w = ( t * t ) * ( (float)0.5 - t * ( (float)0.333333333333 - t * (float)0.25 ) ); + u = ivln2_h * t; /* ivln2_h has 16 sig. bits */ + v = t * ivln2_l - w * ivln2; + t1 = u + v; + GET_FLOAT_WORD( is, t1 ); + SET_FLOAT_WORD( t1, is & 0xfffff000 ); + t2 = v - ( t1 - u ); + } + else + { + float s2, s_h, s_l, t_h, t_l; + n = 0; + /* take care subnormal number */ + if( ix < 0x00800000 ) + { + ax *= two24; + n -= 24; + GET_FLOAT_WORD( ix, ax ); + } + + n += ( ( ix ) >> 23 ) - 0x7f; + j = ix & 0x007fffff; + + /* determine interval */ + ix = j | 0x3f800000; /* normalize ix */ + if( j <= 0x1cc471 ) + k = 0; /* |x|> 1 ) | 0x20000000 ) + 0x0040000 + ( k << 21 ) ); + t_l = ax - ( t_h - bp[ k ] ); + s_l = v * ( ( u - s_h * t_h ) - s_h * t_l ); + /* compute log(ax) */ + s2 = s * s; + subr = L3 + s2 * ( L4 + s2 * ( L5 + s2 * L6 ) ); + // chop up expression to keep mac register based stack happy + r = s2 * s2 * ( L1 + s2 * ( L2 + s2 * subr ) ); + r += s_l * ( s_h + s ); + s2 = s_h * s_h; + t_h = (float)3.0 + s2 + r; + GET_FLOAT_WORD( is, t_h ); + SET_FLOAT_WORD( t_h, is & 0xfffff000 ); + t_l = r - ( ( t_h - (float)3.0 ) - s2 ); + /* u+v = s*(1+...) */ + u = s_h * t_h; + v = s_l * t_h + t_l * s; + /* 2/(3log2)*(s+...) */ + p_h = u + v; + GET_FLOAT_WORD( is, p_h ); + SET_FLOAT_WORD( p_h, is & 0xfffff000 ); + p_l = v - ( p_h - u ); + z_h = cp_h * p_h; /* cp_h+cp_l = 2/(3*log2) */ + z_l = cp_l * p_h + p_l * cp + dp_l[ k ]; + /* log2(ax) = (s+..)*2/(3*log2) = n + dp_h + z_h + z_l */ + t = (float)n; + t1 = ( ( ( z_h + z_l ) + dp_h[ k ] ) + t ); + GET_FLOAT_WORD( is, t1 ); + SET_FLOAT_WORD( t1, is & 0xfffff000 ); + t2 = z_l - ( ( ( t1 - t ) - dp_h[ k ] ) - z_h ); + } + + s = one; /* s (sign of result -ve**odd) = -1 else = 1 */ + if( ( ( ( (unsigned int)hx >> 31 ) - 1 ) | ( yisint - 1 ) ) == 0 ) + s = -one; /* (-ve)**(odd int) */ + + /* split up y into y1+y2 and compute (y1+y2)*(t1+t2) */ + GET_FLOAT_WORD( is, y ); + SET_FLOAT_WORD( y1, is & 0xfffff000 ); + p_l = ( y - y1 ) * t1 + y * t2; + p_h = y1 * t1; + z = p_l + p_h; + GET_FLOAT_WORD( j, z ); + + if( j > 0x43000000 ) /* if z > 128 */ + return s * huge * huge; /* overflow */ + else if( j == 0x43000000 ) + { /* if z == 128 */ + if( p_l + ovt > z - p_h ) + return s * huge * huge; /* overflow */ + } + else if( ( j & 0x7fffffff ) > 0x43160000 ) /* z <= -150 */ + return s * tiny * tiny; /* underflow */ + else if( (unsigned int)j == 0xc3160000 ) + { /* z == -150 */ + if( p_l <= z - p_h ) + return s * tiny * tiny; /* underflow */ + } + + /* + * compute 2**(p_h+p_l) + */ + i = j & 0x7fffffff; + k = ( i >> 23 ) - 0x7f; + n = 0; + + if( i > 0x3f000000 ) + { /* if |z| > 0.5, set n = [z+0.5] */ + n = j + ( 0x00800000 >> ( k + 1 ) ); + k = ( ( n & 0x7fffffff ) >> 23 ) - 0x7f; /* new k for n */ + SET_FLOAT_WORD( t, n & ~( 0x007fffff >> k ) ); + n = ( ( n & 0x007fffff ) | 0x00800000 ) >> ( 23 - k ); + + if( j < 0 ) + n = -n; + + p_h -= t; + } + + t = p_l + p_h; + GET_FLOAT_WORD( is, t ); + SET_FLOAT_WORD( t, is & 0xfffff000 ); + u = t * lg2_h; + v = ( p_l - ( t - p_h ) ) * lg2 + t * lg2_l; + z = u + v; + w = v - ( z - u ); + t = z * z; + subt1 = P3 + t * ( P4 + t * P5 ); + // chop up expression to keep mac register based stack happy + t1 = z - t * ( P1 + t * ( P2 + t * subt1 ) ); + r = ( z * t1 ) / ( t1 - two ) - ( w + z * w ); + z = one - ( r - z ); + GET_FLOAT_WORD( j, z ); + j += (n << 23 ); + + if( ( j >> 23 ) <= 0 ) + z = __scalbnf( z, n ); /* subnormal output */ + else + SET_FLOAT_WORD( z, j ); + + return s * z; +} + +#endif + + + +static int randSeed = 0; + +void srand( unsigned seed ) +{ + randSeed = seed; +} + +int rand( void ) +{ + randSeed = ( 69069 * randSeed + 1 ); + return randSeed & 0x7fff; +} + +double atof( const char *string ) +{ + float sign; + float value; + int c; + + // skip whitespace + while( *string <= ' ' ) + { + if( !*string ) + return 0; + + string++; + } + + // check sign + switch( *string ) + { + case '+': + string++; + sign = 1; + break; + + case '-': + string++; + sign = -1; + break; + + default: + sign = 1; + break; + } + + // read digits + value = 0; + c = string[ 0 ]; + + if( c != '.' ) + { + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value = value * 10 + c; + } while( 1 ); + } + else + string++; + + // check for decimal point + if( c == '.' ) + { + double fraction; + + fraction = 0.1; + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while( 1 ); + + } + + // not handling 10e10 notation... + + return value * sign; +} + +double _atof( const char **stringPtr ) +{ + const char *string; + float sign; + float value; + int c = '0'; // bk001211 - uninitialized use possible + + string = *stringPtr; + + // skip whitespace + while( *string <= ' ' ) + { + if( !*string ) + { + *stringPtr = string; + return 0; + } + + string++; + } + + // check sign + switch( *string ) + { + case '+': + string++; + sign = 1; + break; + + case '-': + string++; + sign = -1; + break; + + default: + sign = 1; + break; + } + + // read digits + value = 0; + if( string[ 0 ] != '.' ) + { + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value = value * 10 + c; + } while( 1 ); + } + + // check for decimal point + if( c == '.' ) + { + double fraction; + + fraction = 0.1; + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while( 1 ); + + } + + // not handling 10e10 notation... + *stringPtr = string; + + return value * sign; +} + + +#if defined ( Q3_VM ) + +int atoi( const char *string ) +{ + int sign; + int value; + int c; + + // skip whitespace + while( *string <= ' ' ) + { + if( !*string ) + return 0; + + string++; + } + + // check sign + switch( *string ) + { + case '+': + string++; + sign = 1; + break; + + case '-': + string++; + sign = -1; + break; + + default: + sign = 1; + break; + } + + // read digits + value = 0; + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value = value * 10 + c; + } while( 1 ); + + // not handling 10e10 notation... + + return value * sign; +} + + +int _atoi( const char **stringPtr ) +{ + int sign; + int value; + int c; + const char *string; + + string = *stringPtr; + + // skip whitespace + while( *string <= ' ' ) + { + if( !*string ) + return 0; + + string++; + } + + // check sign + switch( *string ) + { + case '+': + string++; + sign = 1; + break; + + case '-': + string++; + sign = -1; + break; + + default: + sign = 1; + break; + } + + // read digits + value = 0; + do + { + c = *string++; + if( c < '0' || c > '9' ) + break; + + c -= '0'; + value = value * 10 + c; + } while( 1 ); + + // not handling 10e10 notation... + + *stringPtr = string; + + return value * sign; +} + +int abs( int n ) +{ + return n < 0 ? -n : n; +} + +double fabs( double x ) +{ + return x < 0 ? -x : x; +} + + + +//========================================================= + + +#define ALT 0x00000001 /* alternate form */ +#define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */ +#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 to_digit(c) ((c) - '0') +#define is_digit(c) ((unsigned)to_digit(c) <= 9) +#define to_char(n) ((n) + '0') + +void AddInt( char **buf_p, int val, int width, int flags ) +{ + char text[ 32 ]; + int digits; + int signedVal; + char *buf; + + digits = 0; + signedVal = val; + if( val < 0 ) + val = -val; + + do + { + text[ digits++ ] = '0' + val % 10; + val /= 10; + } while( val ); + + if( signedVal < 0 ) + text[ digits++ ] = '-'; + + buf = *buf_p; + + if( !( flags & LADJUST ) ) + { + while( digits < width ) + { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + width--; + } + } + + while( digits-- ) + { + *buf++ = text[ digits ]; + width--; + } + + if( flags & LADJUST ) + { + while( width-- ) + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + } + + *buf_p = buf; +} + +void AddFloat( char **buf_p, float fval, int width, int prec ) +{ + char text[ 32 ]; + int digits; + float signedVal; + char *buf; + int val; + + // get the sign + signedVal = fval; + if( fval < 0 ) + fval = -fval; + + // write the float number + digits = 0; + val = (int)fval; + + do + { + text[ digits++ ] = '0' + val % 10; + val /= 10; + } while( val ); + + if( signedVal < 0 ) + text[digits++] = '-'; + + buf = *buf_p; + + while( digits < width ) + *buf++ = ' '; + width--; + + while( digits-- ) + *buf++ = text[ digits ]; + + *buf_p = buf; + + if( prec < 0 ) + prec = 6; + + // write the fraction + digits = 0; + + while( digits < prec ) + { + fval -= (int)fval; + fval *= 10.0; + val = (int)fval; + text[ digits++ ] = '0' + val % 10; + } + + if( digits > 0 ) + { + buf = *buf_p; + *buf++ = '.'; + for( prec = 0; prec < digits; prec++ ) + *buf++ = text[ prec ]; + + *buf_p = buf; + } +} + +void AddVec3_t( char **buf_p, vec3_t v, int width, int prec ) +{ + char *buf; + + buf = *buf_p; + + *buf++ = '['; + + AddFloat( &buf, v[ 0 ], width, prec ); + buf += width; + *buf++ = ' '; + + AddFloat( &buf, v[ 1 ], width, prec ); + buf += width; + *buf++ = ' '; + + AddFloat( &buf, v[ 2 ], width, prec ); + buf += width; + *buf++ = ']'; + + *buf_p = buf; +} + +void AddString( char **buf_p, char *string, int width, int prec ) +{ + int size; + char *buf; + + buf = *buf_p; + + if( string == NULL ) + { + string = "(null)"; + prec = -1; + } + + if( prec >= 0 ) + { + for( size = 0; size < prec; size++ ) + { + if( string[ size ] == '\0' ) + break; + } + } + else + size = strlen( string ); + + width -= size; + + while( size-- ) + *buf++ = *string++; + + while( width-- > 0 ) + *buf++ = ' '; + + *buf_p = buf; +} + +/* +vsprintf + +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 ) + { + // run through the format string until we hit a '%' or '\0' + for( ch = *fmt; ( ch = *fmt ) != '\0' && ch != '%'; fmt++ ) + *buf_p++ = ch; + + if( ch == '\0' ) + goto done; + + // skip over the '%' + fmt++; + + // reset formatting state + flags = 0; + width = 0; + prec = -1; + sign = '\0'; + +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 ) ); + + width = n; + goto reswitch; + + case 'c': + *buf_p++ = (char)*arg; + arg++; + break; + + case 'd': + case 'i': + AddInt( &buf_p, *arg, width, flags ); + arg++; + break; + + 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; + + case 's': + AddString( &buf_p, (char *)*arg, width, prec ); + arg++; + break; + + case 'v': + AddVec3_t( &buf_p, (vec_t *)*arg, width, prec ); + arg++; + break; + + case '%': + *buf_p++ = ch; + break; + + default: + *buf_p++ = (char)*arg; + arg++; + break; + } + } + +done: + *buf_p = 0; + return buf_p - buffer; +} + + +/* this is really crappy */ +int sscanf( const char *buffer, const char *fmt, ... ) +{ + int cmd; + int **arg; + int count; + + arg = (int **)&fmt + 1; + count = 0; + + while( *fmt ) + { + if( fmt[ 0 ] != '%' ) + { + fmt++; + continue; + } + + cmd = fmt[ 1 ]; + fmt += 2; + + switch( cmd ) + { + case 'i': + case 'd': + case 'u': + **arg = _atoi( &buffer ); + break; + case 'f': + *(float *)*arg = _atof( &buffer ); + break; + } + + arg++; + } + + return count; +} + +#endif diff --git a/src/game/bg_lib.h b/src/game/bg_lib.h new file mode 100644 index 00000000..61d64025 --- /dev/null +++ b/src/game/bg_lib.h @@ -0,0 +1,92 @@ +// bg_lib.h -- standard C library replacement routines used by code +// compiled for the virtual machine + +// This file is NOT included on native builds +#ifndef BG_LIB_H +#define BG_LIB_H + +#ifndef NULL +#define NULL ((void *)0) +#endif + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +typedef int size_t; + +typedef char * va_list; +#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) +#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) +#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 SHRT_MIN (-32768) /* minimum (signed) short value */ +#define SHRT_MAX 32767 /* maximum (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 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 ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ + +// Misc functions +typedef int cmp_t( const void *, const void * ); +void qsort( void *a, size_t n, size_t es, cmp_t *cmp ); +void srand( unsigned seed ); +int rand( void ); + +// String functions +size_t strlen( const char *string ); +char *strcat( char *strDestination, const char *strSource ); +char *strcpy( char *strDestination, const char *strSource ); +int strcmp( const char *string1, const char *string2 ); +char *strchr( const char *string, int c ); +char *strrchr( const char *string, int c ); +char *strstr( const char *string, const char *strCharSet ); +char *strncpy( char *strDest, const char *strSource, size_t count ); +int tolower( int c ); +int toupper( int c ); + +double atof( const char *string ); +double _atof( const char **stringPtr ); +int atoi( const char *string ); +int _atoi( const char **stringPtr ); + +int vsprintf( char *buffer, const char *fmt, va_list argptr ); +int sscanf( const char *buffer, const char *fmt, ... ); + +// Memory functions +void *memmove( void *dest, const void *src, size_t count ); +void *memset( void *dest, int c, size_t count ); +void *memcpy( void *dest, const void *src, size_t count ); + +// Math functions +double ceil( double x ); +double floor( double x ); +double sqrt( double x ); +double sin( double x ); +double cos( double x ); +double atan2( double y, double x ); +double tan( double x ); +int abs( int n ); +double fabs( double x ); +double acos( double x ); +float pow( float x, float y ); + +#endif // BG_LIB_H diff --git a/src/game/bg_local.h b/src/game/bg_local.h new file mode 100644 index 00000000..7e5ea46f --- /dev/null +++ b/src/game/bg_local.h @@ -0,0 +1,80 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_local.h -- local definitions for the bg (both games) files + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes + +#define STEPSIZE 18 + +#define TIMER_LAND 130 +#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 + + +// all of the locals will be zeroed before each +// pmove, just to make damn sure we don't have +// any differences when running on client or server +typedef struct +{ + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + qboolean ladder; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +extern pmove_t *pm; +extern pml_t pml; + +// movement parameters +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 int c_pmove; + +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepEvent( vec3_t from, vec3_t to, vec3_t normal ); +qboolean PM_StepSlideMove( qboolean gravity, qboolean predictive ); +qboolean PM_PredictStepMove( void ); diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c new file mode 100644 index 00000000..727190af --- /dev/null +++ b/src/game/bg_misc.c @@ -0,0 +1,5248 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_misc.c -- both games misc functions, all completely stateless + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +#include "q_shared.h" +#include "bg_public.h" + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t + +buildableAttributes_t bg_buildableList[ ] = +{ + { + BA_A_SPAWN, //int buildNum; + "eggpod", //char *buildName; + "Egg", //char *humanName; + "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 + 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; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + ASPAWN_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.5f, //float minNormal; + qtrue, //qboolean invertNormal; + qfalse, //qboolean creepTest; + ASPAWN_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qfalse //qboolean reactorTest; + }, + { + 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; + 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; + MOD_ASPAWN, //int meansOfDeath; + BIT_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + BARRICADE_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.707f, //float minNormal; + qfalse, //qboolean invertNormal; + qtrue, //qboolean creepTest; + BARRICADE_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qfalse //qboolean reactorTest; + }, + { + 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; + 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; + MOD_ASPAWN, //int meansOfDeath; + BIT_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + BOOSTER_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.707f, //float minNormal; + qfalse, //qboolean invertNormal; + qtrue, //qboolean creepTest; + BOOSTER_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qfalse //qboolean reactorTest; + }, + { + BA_A_ACIDTUBE, //int buildNum; + "acid_tube", //char *buildName; + "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; + TR_GRAVITY, //trType_t traj; + 0.0, //float bounce; + ACIDTUBE_BP, //int buildPoints; + ( 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; + BANIM_IDLE1, //int idleAnim; + 200, //int nextthink; + ACIDTUBE_BT, //int buildTime; + qfalse, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.0f, //float minNormal; + qtrue, //qboolean invertNormal; + qtrue, //qboolean creepTest; + ACIDTUBE_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qfalse //qboolean reactorTest; + }, + { + 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 reactorTest; + }, + { + BA_A_TRAPPER, //int buildNum; + "trapper", //char *buildName; + "Trapper", //char *humanName; + "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 + 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; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + TRAPPER_BT, //int buildTime; + qfalse, //qboolean usable; + TRAPPER_RANGE, //int turretRange; + TRAPPER_REPEAT, //int turretFireSpeed; + WP_LOCKBLOB_LAUNCHER, //weapon_t turretProjType; + 0.0f, //float minNormal; + qtrue, //qboolean invertNormal; + qtrue, //qboolean creepTest; + TRAPPER_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qfalse //qboolean reactorTest; + }, + { + 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; + 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; + MOD_ASPAWN, //int meansOfDeath; + BIT_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + OVERMIND_ATTACK_REPEAT,//int nextthink; + OVERMIND_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; + OVERMIND_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qtrue //qboolean reactorTest; + }, + { + 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; + 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; + MOD_ASPAWN, //int meansOfDeath; + BIT_ALIENS, //int team; + ( 1 << WP_ABUILD )|( 1 << WP_ABUILD2 ), //weapon_t buildWeapon; + BANIM_IDLE1, //int idleAnim; + 150, //int nextthink; + HOVEL_BT, //int buildTime; + qtrue, //qboolean usable; + 0, //int turretRange; + 0, //int turretFireSpeed; + WP_NONE, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qtrue, //qboolean creepTest; + HOVEL_CREEPSIZE, //int creepSize; + qfalse, //qboolean dccTest; + qtrue //qboolean reactorTest; + }, + { + BA_H_SPAWN, //int buildNum; + "telenode", //char *buildName; + "Telenode", //char *humanName; + "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 + 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; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + HSPAWN_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; + qfalse //qboolean reactorTest; + }, + { + 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; + qfalse //qboolean reactorTest; + }, + { + BA_H_MGTURRET, //int buildNum; + "mgturret", //char *buildName; + "Machinegun Turret", //char *humanName; + "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 + 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; + BANIM_IDLE1, //int idleAnim; + 50, //int nextthink; + MGTURRET_BT, //int buildTime; + qfalse, //qboolean usable; + MGTURRET_RANGE, //int turretRange; + MGTURRET_REPEAT, //int turretFireSpeed; + WP_MGTURRET, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qfalse, //qboolean dccTest; + qfalse //qboolean reactorTest; + }, + { + BA_H_TESLAGEN, //int buildNum; + "tesla", //char *buildName; + "Tesla Generator", //char *humanName; + "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 + 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; + BANIM_IDLE1, //int idleAnim; + 150, //int nextthink; + TESLAGEN_BT, //int buildTime; + qfalse, //qboolean usable; + TESLAGEN_RANGE, //int turretRange; + TESLAGEN_REPEAT, //int turretFireSpeed; + WP_TESLAGEN, //weapon_t turretProjType; + 0.95f, //float minNormal; + qfalse, //qboolean invertNormal; + qfalse, //qboolean creepTest; + 0, //int creepSize; + qtrue, //qboolean dccTest; + qfalse //qboolean reactorTest; + }, + { + BA_H_DCC, //int buildNum; + "dcc", //char *buildName; + "Defence Computer", //char *humanName; + "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 + 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; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + DC_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; + qfalse //qboolean reactorTest; + }, + { + 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; + 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; + BIT_HUMANS, //int team; + ( 1 << WP_HBUILD )|( 1 << WP_HBUILD2 ), //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 reactorTest; + }, + { + BA_H_REACTOR, //int buildNum; + "reactor", //char *buildName; + "Reactor", //char *humanName; + "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 + 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; + BANIM_IDLE1, //int idleAnim; + REACTOR_ATTACK_REPEAT, //int nextthink; + REACTOR_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; + qtrue //qboolean reactorTest; + }, + { + BA_H_REPEATER, //int buildNum; + "repeater", //char *buildName; + "Repeater", //char *humanName; + "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 + 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; + BANIM_IDLE1, //int idleAnim; + 100, //int nextthink; + REPEATER_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 reactorTest; + } +}; + +int bg_numBuildables = sizeof( bg_buildableList ) / sizeof( bg_buildableList[ 0 ] ); + +//separate from bg_buildableList to work around char struct init bug +buildableAttributeOverrides_t bg_buildableOverrideList[ BA_NUM_BUILDABLES ]; + +/* +============== +BG_FindBuildNumForName +============== +*/ +int BG_FindBuildNumForName( char *name ) +{ + int i; + + for( i = 0; i < bg_numBuildables; i++ ) + { + if( !Q_stricmp( bg_buildableList[ i ].buildName, name ) ) + return bg_buildableList[ i ].buildNum; + } + + //wimp out + return BA_NONE; +} + +/* +============== +BG_FindBuildNumForEntityName +============== +*/ +int BG_FindBuildNumForEntityName( char *name ) +{ + int i; + + for( i = 0; i < bg_numBuildables; i++ ) + { + if( !Q_stricmp( bg_buildableList[ i ].entityName, name ) ) + return bg_buildableList[ i ].buildNum; + } + + //wimp out + return BA_NONE; +} + +/* +============== +BG_FindNameForBuildNum +============== +*/ +char *BG_FindNameForBuildable( int bclass ) +{ + 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; +} + +/* +============== +BG_FindEntityNameForBuildNum +============== +*/ +char *BG_FindEntityNameForBuildable( int bclass ) +{ + int i; + + for( i = 0; i < bg_numBuildables; i++ ) + { + if( bg_buildableList[ i ].buildNum == bclass ) + return bg_buildableList[ i ].entityName; + } + + //wimp out + return 0; +} + +/* +============== +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; +} + +/* +============== +BG_FindModelScaleForBuildable +============== +*/ +float BG_FindModelScaleForBuildable( int bclass ) +{ + 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; +} + +/* +============== +BG_FindBBoxForBuildable +============== +*/ +void BG_FindBBoxForBuildable( int bclass, 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; + } + } + + if( mins != NULL ) + VectorCopy( bg_buildableList[ 0 ].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 BA_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_FindOverrideForBuildable +============== +*/ +static buildableAttributeOverrides_t *BG_FindOverrideForBuildable( int bclass ) +{ + return &bg_buildableOverrideList[ bclass ]; +} + +/* +====================== +BG_ParseBuildableFile + +Parses a configuration file describing a builable +====================== +*/ +static qboolean BG_ParseBuildableFile( const char *filename, buildableAttributeOverrides_t *bao ) +{ + char *text_p; + int i; + int len; + char *token; + char text[ 20000 ]; + fileHandle_t f; + float scale; + + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len <= 0 ) + return qfalse; + + if( len >= sizeof( text ) - 1 ) + { + Com_Printf( S_COLOR_RED "ERROR: Buildable file %s too long\n", filename ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "model" ) ) + { + int index = 0; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + index = atoi( token ); + + if( index < 0 ) + index = 0; + else if( index > 3 ) + index = 3; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( bao->models[ index ], token, sizeof( bao->models[ 0 ] ) ); + + continue; + } + else if( !Q_stricmp( token, "modelScale" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + scale = atof( token ); + + if( scale < 0.0f ) + scale = 0.0f; + + bao->modelScale = scale; + + continue; + } + else if( !Q_stricmp( token, "mins" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + bao->mins[ i ] = atof( token ); + } + + continue; + } + else if( !Q_stricmp( token, "maxs" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + bao->maxs[ i ] = atof( token ); + } + + continue; + } + else if( !Q_stricmp( token, "zOffset" ) ) + { + float offset; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + offset = atof( token ); + + bao->zOffset = offset; + + continue; + } + + + Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token ); + return qfalse; + } + + return qtrue; +} + +/* +=============== +BG_InitBuildableOverrides + +Set any overrides specfied by file +=============== +*/ +void BG_InitBuildableOverrides( void ) +{ + int i; + buildableAttributeOverrides_t *bao; + + for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ ) + { + bao = BG_FindOverrideForBuildable( i ); + + BG_ParseBuildableFile( va( "overrides/buildables/%s.cfg", BG_FindNameForBuildable( i ) ), bao ); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +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; + 0, //int health; + 0.0f, //float fallDamage; + 0, //int regenRate; + 0, //int abilities; + WP_NONE, //weapon_t startWeapon + 0.0f, //float buildDist; + 90, //int fov; + 0.000f, //float bob; + 1.0f, //float bobCycle; + 0, //int steptime; + 600, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + 0, //int cost; + 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; + 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 + 95.0f, //float buildDist; + 80, //int fov; + 0.001f, //float bob; + 2.0f, //float bobCycle; + 150, //int steptime; + ABUILDER_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 195.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { 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; + 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 + 105.0f, //float buildDist; + 110, //int fov; + 0.001f, //float bob; + 2.0f, //float bobCycle; + 100, //int steptime; + ABUILDER_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_ALIEN_LEVEL0, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + ABUILDER_UPG_COST, //int cost; + 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; + 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 + 0.0f, //float buildDist; + 140, //int fov; + 0.0f, //float bob; + 2.5f, //float bobCycle; + 25, //int steptime; + LEVEL0_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 400.0f, //float stopSpeed; + 250.0f, //float jumpMagnitude; + 2.0f, //float knockbackScale; + { PCL_ALIEN_LEVEL1, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL0_COST, //int cost; + 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; + 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 + 0.0f, //float buildDist; + 120, //int fov; + 0.001f, //float bob; + 1.8f, //float bobCycle; + 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; + 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_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; + 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 + 0.0f, //float buildDist; + 120, //int fov; + 0.001f, //float bob; + 1.8f, //float bobCycle; + 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; + 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; + 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 + 0.0f, //float buildDist; + 90, //int fov; + 0.001f, //float bob; + 1.5f, //float bobCycle; + 80, //int steptime; + LEVEL2_SPEED, //float speed; + 10.0f, //float acceleration; + 2.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 400.0f, //float jumpMagnitude; + 0.8f, //float knockbackScale; + { 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; + 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 + 0.0f, //float buildDist; + 90, //int fov; + 0.001f, //float bob; + 1.5f, //float bobCycle; + 80, //int steptime; + LEVEL2_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 2.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 400.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; + 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 + 0.0f, //float buildDist; + 110, //int fov; + 0.0005f, //float bob; + 1.3f, //float bobCycle; + 90, //int steptime; + LEVEL3_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 200.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 0.5f, //float knockbackScale; + { 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; + 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 + 0.0f, //float buildDist; + 110, //int fov; + 0.0005f, //float bob; + 1.3f, //float bobCycle; + 90, //int steptime; + LEVEL3_UPG_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 200.0f, //float stopSpeed; + 270.0f, //float jumpMagnitude; + 0.4f, //float knockbackScale; + { PCL_ALIEN_LEVEL4, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL3_UPG_COST, //int cost; + 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; + 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 + 0.0f, //float buildDist; + 90, //int fov; + 0.001f, //float bob; + 1.1f, //float bobCycle; + 100, //int steptime; + LEVEL4_SPEED, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 170.0f, //float jumpMagnitude; + 0.1f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + LEVEL4_COST, //int cost; + 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; + 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 + 110.0f, //float buildDist; + 90, //int fov; + 0.002f, //float bob; + 1.0f, //float bobCycle; + 100, //int steptime; + 1.0f, //float speed; + 10.0f, //float acceleration; + 1.0f, //float airAcceleration; + 6.0f, //float friction; + 100.0f, //float stopSpeed; + 220.0f, //float jumpMagnitude; + 1.0f, //float knockbackScale; + { PCL_NONE, PCL_NONE, PCL_NONE }, //int children[ 3 ]; + 0, //int cost; + 0 //int value; + }, + { + //this isn't a real class, but a dummy to force the client to precache the model + //FIXME: one day do this in a less hacky fashion + PCL_HUMAN_BSUIT, "human_bsuit", "bsuit", + + "keel", + 1.0f, + "default", + 1.0f, + + "bsuit", ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), { 0, 0, 0 }, { 0, 0, 0, }, + { 0, 0, 0, }, { 0, 0, 0, }, { 0, 0, 0, }, 0.0f, 0, 0, 0, 0.0f, 0, 0, WP_NONE, 0.0f, 0, + 0.0f, 1.0f, 0, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 270.0f, 1.0f, { PCL_NONE, PCL_NONE, PCL_NONE }, 0, 0 + } +}; + +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; + } + + 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; + + 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; +} + +/* +============== +BG_FindModelScaleForClass +============== +*/ +float BG_FindModelScaleForClass( int pclass ) +{ + int i; + + if( bg_classOverrideList[ pclass ].modelScale != 0.0f ) + return bg_classOverrideList[ pclass ].modelScale; + + for( i = 0; i < bg_numPclasses; i++ ) + { + if( bg_classList[ i ].classNum == pclass ) + { + return bg_classList[ i ].modelScale; + } + } + + Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindModelScaleForClass( %d )\n", pclass ); + return 1.0f; +} + +/* +============== +BG_FindSkinNameForClass +============== +*/ +char *BG_FindSkinNameForClass( int pclass ) +{ + 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; +} + +/* +============== +BG_FindShadowScaleForClass +============== +*/ +float BG_FindShadowScaleForClass( int pclass ) +{ + int i; + + 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; +} + +/* +============== +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; +} + +/* +============== +BG_FindStagesForClass +============== +*/ +qboolean BG_FindStagesForClass( int pclass, stage_t stage ) +{ + 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; +} + +/* +============== +BG_FindBBoxForClass +============== +*/ +void BG_FindBBoxForClass( int pclass, 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; + } + } + + if( mins != NULL ) + VectorCopy( bg_classList[ 0 ].mins, mins ); + + if( maxs != NULL ) + VectorCopy( bg_classList[ 0 ].maxs, maxs ); + + if( cmaxs != NULL ) + VectorCopy( bg_classList[ 0 ].crouchMaxs, cmaxs ); + + if( dmins != NULL ) + VectorCopy( bg_classList[ 0 ].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; + + for( i = 0; i < bg_numPclasses; i++ ) + { + if( bg_classList[ i ].classNum == pclass ) + { + if( viewheight != NULL ) + *viewheight = bg_classList[ i ].viewheight; + + if( cViewheight != NULL ) + *cViewheight = bg_classList[ i ].crouchViewheight; + + return; + } + } + + if( viewheight != NULL ) + *viewheight = bg_classList[ 0 ].viewheight; + + if( cViewheight != NULL ) + *cViewheight = bg_classList[ 0 ].crouchViewheight; +} + +/* +============== +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; +} + +/* +============== +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 ) +{ + int i; + + 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; +} + +/* +============== +BG_ClassCanEvolveFromTo +============== +*/ +int BG_ClassCanEvolveFromTo( int fclass, int tclass, int credits, int num ) +{ + int i, j, cost; + + cost = BG_FindCostOfClass( tclass ); + + //base case + if( credits < cost ) + return -1; + + if( fclass == PCL_NONE || tclass == PCL_NONE ) + return -1; + + for( i = 0; i < bg_numPclasses; i++ ) + { + if( bg_classList[ i ].classNum == fclass ) + { + for( j = 0; j < 3; j++ ) + if( bg_classList[ i ].children[ j ] == tclass ) + return num + cost; + + for( j = 0; j < 3; j++ ) + { + int sub; + + 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; + } + + return -1; //may as well return by this point + } + } + + return -1; +} + +/* +============== +BG_FindValueOfClass +============== +*/ +int BG_FindValueOfClass( int pclass ) +{ + int i; + + for( i = 0; i < bg_numPclasses; 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; + + for( i = 0; i < bg_numPclasses; i++ ) + { + if( bg_classList[ i ].classNum == pclass ) + { + return bg_classList[ i ].cost; + } + } + + Com_Printf( S_COLOR_YELLOW "WARNING: fallthrough in BG_FindCostOfClass\n" ); + return 0; +} + +/* +============== +BG_FindOverrideForClass +============== +*/ +static classAttributeOverrides_t *BG_FindOverrideForClass( int pclass ) +{ + return &bg_classOverrideList[ pclass ]; +} + +/* +====================== +BG_ParseClassFile + +Parses a configuration file describing a class +====================== +*/ +static qboolean BG_ParseClassFile( const char *filename, classAttributeOverrides_t *cao ) +{ + char *text_p; + int i; + int len; + char *token; + char text[ 20000 ]; + fileHandle_t f; + float scale = 0.0f; + + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len <= 0 ) + return qfalse; + + if( len >= sizeof( text ) - 1 ) + { + Com_Printf( S_COLOR_RED "ERROR: Class file %s too long\n", filename ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "model" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( cao->modelName, token, sizeof( cao->modelName ) ); + + continue; + } + else if( !Q_stricmp( token, "skin" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( cao->skinName, token, sizeof( cao->skinName ) ); + + continue; + } + else if( !Q_stricmp( token, "hud" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( cao->hudName, token, sizeof( cao->hudName ) ); + + continue; + } + else if( !Q_stricmp( token, "modelScale" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + scale = atof( token ); + + if( scale < 0.0f ) + scale = 0.0f; + + cao->modelScale = scale; + + continue; + } + else if( !Q_stricmp( token, "shadowScale" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + scale = atof( token ); + + if( scale < 0.0f ) + scale = 0.0f; + + cao->shadowScale = scale; + + continue; + } + else if( !Q_stricmp( token, "mins" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cao->mins[ i ] = atof( token ); + } + + continue; + } + else if( !Q_stricmp( token, "maxs" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cao->maxs[ i ] = atof( token ); + } + + continue; + } + else if( !Q_stricmp( token, "deadMins" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cao->deadMins[ i ] = atof( token ); + } + + continue; + } + else if( !Q_stricmp( token, "deadMaxs" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cao->deadMaxs[ i ] = atof( token ); + } + + continue; + } + else if( !Q_stricmp( token, "crouchMaxs" ) ) + { + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + cao->crouchMaxs[ i ] = atof( token ); + } + + continue; + } + else if( !Q_stricmp( token, "zOffset" ) ) + { + float offset; + + token = COM_Parse( &text_p ); + if( !token ) + break; + + offset = atof( token ); + + cao->zOffset = offset; + + continue; + } + else if( !Q_stricmp( token, "name" ) ) + { + token = COM_Parse( &text_p ); + if( !token ) + break; + + Q_strncpyz( cao->humanName, token, sizeof( cao->humanName ) ); + + continue; + } + + + Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token ); + return qfalse; + } + + return qtrue; +} + +/* +=============== +BG_InitClassOverrides + +Set any overrides specfied by file +=============== +*/ +void BG_InitClassOverrides( void ) +{ + int i; + classAttributeOverrides_t *cao; + + for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) + { + cao = BG_FindOverrideForClass( i ); + + BG_ParseClassFile( va( "overrides/classes/%s.cfg", BG_FindNameForClassNum( i ) ), cao ); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +weaponAttributes_t bg_weapons[ ] = +{ + { + WP_BLASTER, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + 0, //int slots; + "blaster", //char *weaponName; + "Blaster", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + BLASTER_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_MACHINEGUN, //int weaponNum; + RIFLE_PRICE, //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; + qfalse, //int usesEnergy; + RIFLE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + RIFLE_RELOAD, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_SHOTGUN, //int weaponNum; + SHOTGUN_PRICE, //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; + qfalse, //int usesEnergy; + SHOTGUN_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + SHOTGUN_RELOAD, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_FLAMER, //int weaponNum; + FLAMER_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "flamer", //char *weaponName; + "Flame Thrower", //char *weaponHumanName; + FLAMER_GAS, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + FLAMER_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_CHAINGUN, //int weaponNum; + CHAINGUN_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "chaingun", //char *weaponName; + "Chaingun", //char *weaponHumanName; + CHAINGUN_BULLETS, //int maxAmmo; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + CHAINGUN_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_MASS_DRIVER, //int weaponNum; + MDRIVER_PRICE, //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; + 0, //int repeatRate2; + 0, //int repeatRate3; + MDRIVER_RELOAD, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qtrue, //qboolean canZoom; + 20.0f, //float zoomFov; + qtrue, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_PULSE_RIFLE, //int weaponNum; + PRIFLE_PRICE, //int price; + ( 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; + 0, //int repeatRate2; + 0, //int repeatRate3; + PRIFLE_RELOAD, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_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; + 0, //int maxClips; + qfalse, //int infiniteAmmo; + qtrue, //int usesEnergy; + LCANNON_REPEAT, //int repeatRate1; + LCANNON_CHARGEREPEAT, //int repeatRate2; + 0, //int repeatRate3; + LCANNON_RELOAD, //int reloadTime; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_LAS_GUN, //int weaponNum; + LASGUN_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; + qfalse, //int infiniteAmmo; + qtrue, //int usesEnergy; + LASGUN_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + LASGUN_RELOAD, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_PAIN_SAW, //int weaponNum; + PAINSAW_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "psaw", //char *weaponName; + "Pain Saw", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + PAINSAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_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; + qfalse, //int infiniteAmmo; + qfalse, //int usesEnergy; + GRENADE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_HBUILD, //int weaponNum; + HBUILD_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; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + HBUILD_REPEAT, //int repeatRate1; + HBUILD_REPEAT, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + HBUILD_DELAY, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_HBUILD2, //int weaponNum; + HBUILD2_PRICE, //int price; + ( 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; + 0, //int repeatRate3; + 0, //int reloadTime; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + HBUILD2_DELAY, //int buildDelay; + WUT_HUMANS //WUTeam_t team; + }, + { + WP_ABUILD, //int weaponNum; + 0, //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; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + ABUILDER_BUILD_REPEAT,//int repeatRate1; + ABUILDER_BUILD_REPEAT,//int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + 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; + "abuild2", //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; + qtrue, //qboolean hasAltMode; + qtrue, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qtrue, //qboolean purchasable; + ABUILDER_ADV_DELAY, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_ALEVEL0, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "bite", //char *weaponName; + "Bite", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL0_BITE_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_ALEVEL3, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "pounce", //char *weaponName; + "Claw and pounce", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL3_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_ALEVEL3_UPG, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "pounce_upgrade", //char *weaponName; + "Claw and pounce (upgrade)", //char *weaponHumanName; + 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; + qfalse, //qboolean hasAltMode; + qtrue, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_ALEVEL1, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "grabandclaw", //char *weaponName; + "Claws", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL1_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_ALEVEL1_UPG, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "grabandclaw_upgrade",//char *weaponName; + "Claws Upgrade", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL1_CLAW_U_REPEAT, //int repeatRate1; + LEVEL1_PCLOUD_REPEAT, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_ALEVEL2, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "areazap", //char *weaponName; + "Area Zap", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL2_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_ALEVEL2_UPG, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "directzap", //char *weaponName; + "Directed Zap", //char *weaponHumanName; + 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; + 0, //int reloadTime; + qtrue, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_ALEVEL4, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "charge", //char *weaponName; + "Charge", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + LEVEL4_CLAW_REPEAT, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_LOCKBLOB_LAUNCHER, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "lockblob", //char *weaponName; + "Lock Blob", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + 500, //int repeatRate1; + 500, //int repeatRate2; + 500, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_HIVE, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "hive", //char *weaponName; + "Hive", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + 500, //int repeatRate1; + 500, //int repeatRate2; + 500, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_ALIENS //WUTeam_t team; + }, + { + WP_MGTURRET, //int weaponNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_WEAPON, //int slots; + "mgturret", //char *weaponName; + "Machinegun Turret", //char *weaponHumanName; + 0, //int maxAmmo; + 0, //int maxClips; + qtrue, //int infiniteAmmo; + qfalse, //int usesEnergy; + 0, //int repeatRate1; + 0, //int repeatRate2; + 0, //int repeatRate3; + 0, //int reloadTime; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 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; + qfalse, //qboolean hasAltMode; + qfalse, //qboolean hasThirdMode; + qfalse, //qboolean canZoom; + 90.0f, //float zoomFov; + qfalse, //qboolean purchasable; + 0, //int buildDelay; + WUT_HUMANS //WUTeam_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; + } + } + + return 100; +} + +/* +============== +BG_FindStagesForWeapon +============== +*/ +qboolean BG_FindStagesForWeapon( int weapon, stage_t stage ) +{ + int i; + + for( i = 0; i < bg_numWeapons; i++ ) + { + if( bg_weapons[ i ].weaponNum == weapon ) + { + if( bg_weapons[ i ].stages & ( 1 << stage ) ) + return qtrue; + else + return qfalse; + } + } + + 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; +} + +/* +============== +BG_FindWeaponNumForName +============== +*/ +int BG_FindWeaponNumForName( char *name ) +{ + 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; +} + +/* +============== +BG_FindHumanNameForWeapon +============== +*/ +char *BG_FindHumanNameForWeapon( int weapon ) +{ + int i; + + for( i = 0; i < bg_numWeapons; i++ ) + { + if( bg_weapons[ i ].weaponNum == weapon ) + return bg_weapons[ i ].weaponHumanName; + } + + //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_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_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; +} + +//////////////////////////////////////////////////////////////////////////////// + +upgradeAttributes_t bg_upgrades[ ] = +{ + { + UP_LIGHTARMOUR, //int upgradeNum; + LIGHTARMOUR_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_TORSO|SLOT_ARMS|SLOT_LEGS, //int slots; + "larmour", //char *upgradeName; + "Light Armour", //char *upgradeHumanName; + "icons/iconu_larmour", + qtrue, //qboolean purchasable + WUT_HUMANS //WUTeam_t team; + }, + { + UP_HELMET, //int upgradeNum; + HELMET_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_HEAD, //int slots; + "helmet", //char *upgradeName; + "Helmet", //char *upgradeHumanName; + "icons/iconu_helmet", + qtrue, //qboolean purchasable + WUT_HUMANS //WUTeam_t team; + }, + { + UP_MEDKIT, //int upgradeNum; + MEDKIT_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_NONE, //int slots; + "medkit", //char *upgradeName; + "Medkit", //char *upgradeHumanName; + "icons/iconu_atoxin", + qfalse, //qboolean purchasable + WUT_HUMANS //WUTeam_t team; + }, + { + UP_BATTPACK, //int upgradeNum; + BATTPACK_PRICE, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_BACKPACK, //int slots; + "battpack", //char *upgradeName; + "Battery Pack", //char *upgradeHumanName; + "icons/iconu_battpack", + qtrue, //qboolean purchasable + WUT_HUMANS //WUTeam_t team; + }, + { + UP_JETPACK, //int upgradeNum; + JETPACK_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_BACKPACK, //int slots; + "jetpack", //char *upgradeName; + "Jet Pack", //char *upgradeHumanName; + "icons/iconu_jetpack", + qtrue, //qboolean purchasable + WUT_HUMANS //WUTeam_t team; + }, + { + UP_BATTLESUIT, //int upgradeNum; + 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; + "icons/iconu_bsuit", + qtrue, //qboolean purchasable + WUT_HUMANS //WUTeam_t team; + }, + { + UP_GRENADE, //int upgradeNum; + GRENADE_PRICE, //int price; + ( 1 << S2 )|( 1 << S3 ),//int stages + SLOT_NONE, //int slots; + "gren", //char *upgradeName; + "Grenade", //char *upgradeHumanName; + 0, + qtrue, //qboolean purchasable + WUT_HUMANS //WUTeam_t team; + }, + { + UP_AMMO, //int upgradeNum; + 0, //int price; + ( 1 << S1 )|( 1 << S2 )|( 1 << S3 ), //int stages + SLOT_NONE, //int slots; + "ammo", //char *upgradeName; + "Ammunition", //char *upgradeHumanName; + 0, + qtrue, //qboolean purchasable + WUT_HUMANS //WUTeam_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; + } + } + + return 100; +} + +/* +============== +BG_FindStagesForUpgrade +============== +*/ +qboolean BG_FindStagesForUpgrade( int upgrade, stage_t stage ) +{ + int i; + + for( i = 0; i < bg_numUpgrades; i++ ) + { + if( bg_upgrades[ i ].upgradeNum == upgrade ) + { + if( bg_upgrades[ i ].stages & ( 1 << stage ) ) + return qtrue; + else + return qfalse; + } + } + + return qfalse; +} + +/* +============== +BG_FindSlotsForUpgrade +============== +*/ +int BG_FindSlotsForUpgrade( int upgrade ) +{ + int i; + + for( i = 0; i < bg_numUpgrades; i++ ) + { + if( bg_upgrades[ i ].upgradeNum == upgrade ) + { + return bg_upgrades[ i ].slots; + } + } + + return SLOT_NONE; +} + +/* +============== +BG_FindNameForUpgrade +============== +*/ +char *BG_FindNameForUpgrade( int upgrade ) +{ + int i; + + for( i = 0; i < bg_numUpgrades; i++ ) + { + if( bg_upgrades[ i ].upgradeNum == upgrade ) + return bg_upgrades[ i ].upgradeName; + } + + //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_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; +} + +//////////////////////////////////////////////////////////////////////////////// + +/* +================ +BG_EvaluateTrajectory + +================ +*/ +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) +{ + float deltaTime; + float phase; + + switch( tr->trType ) + { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorCopy( tr->trBase, result ); + break; + + case TR_LINEAR: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float)tr->trDuration; + phase = sin( deltaTime * M_PI * 2 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; + + case TR_LINEAR_STOP: + if( atTime > tr->trTime + tr->trDuration ) + atTime = tr->trTime + tr->trDuration; + + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + if( deltaTime < 0 ) + deltaTime = 0; + + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[ 2 ] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + + case TR_BUOYANCY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[ 2 ] += 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); + break; + } +} + +/* +================ +BG_EvaluateTrajectoryDelta + +For determining velocity at a given time +================ +*/ +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) +{ + float deltaTime; + float phase; + + switch( tr->trType ) + { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float)tr->trDuration; + phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos + phase *= 0.5; + VectorScale( tr->trDelta, phase, result ); + break; + + case TR_LINEAR_STOP: + if( atTime > tr->trTime + tr->trDuration ) + { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[ 2 ] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + + case TR_BUOYANCY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[ 2 ] += DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +char *eventnames[ ] = +{ + "EV_NONE", + + "EV_FOOTSTEP", + "EV_FOOTSTEP_METAL", + "EV_FOOTSTEP_SQUELCH", + "EV_FOOTSPLASH", + "EV_FOOTWADE", + "EV_SWIM", + + "EV_STEP_4", + "EV_STEP_8", + "EV_STEP_12", + "EV_STEP_16", + + "EV_STEPDN_4", + "EV_STEPDN_8", + "EV_STEPDN_12", + "EV_STEPDN_16", + + "EV_FALL_SHORT", + "EV_FALL_MEDIUM", + "EV_FALL_FAR", + "EV_FALLING", + + "EV_JUMP", + "EV_WATER_TOUCH", // foot touches + "EV_WATER_LEAVE", // foot leaves + "EV_WATER_UNDER", // head touches + "EV_WATER_CLEAR", // head leaves + + "EV_NOAMMO", + "EV_CHANGE_WEAPON", + "EV_FIRE_WEAPON", + "EV_FIRE_WEAPON2", + "EV_FIRE_WEAPON3", + + "EV_PLAYER_RESPAWN", //TA: for fovwarp effects + "EV_PLAYER_TELEPORT_IN", + "EV_PLAYER_TELEPORT_OUT", + + "EV_GRENADE_BOUNCE", // eventParm will be the soundindex + + "EV_GENERAL_SOUND", + "EV_GLOBAL_SOUND", // no attenuation + + "EV_BULLET_HIT_FLESH", + "EV_BULLET_HIT_WALL", + + "EV_SHOTGUN", + + "EV_MISSILE_HIT", + "EV_MISSILE_MISS", + "EV_MISSILE_MISS_METAL", + "EV_TESLATRAIL", + "EV_BULLET", // otherEntity is the shooter + + "EV_LEV1_GRAB", + "EV_LEV4_CHARGE_PREPARE", + "EV_LEV4_CHARGE_START", + + "EV_PAIN", + "EV_DEATH1", + "EV_DEATH2", + "EV_DEATH3", + "EV_OBITUARY", + + "EV_GIB_PLAYER", // gib a previously living 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_HUMAN_BUILDABLE_EXPLOSION", + "EV_ALIEN_BUILDABLE_EXPLOSION", + "EV_ALIEN_ACIDTUBE", + + "EV_MEDKIT_USED", + + "EV_ALIEN_EVOLVE", + "EV_ALIEN_EVOLVE_FAILED", + + "EV_DEBUG_LINE", + "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_DCC_ATTACK", //TA: dcc under attack + + "EV_RPTUSE_SOUND" //TA: trigger a sound +}; + +/* +=============== +BG_AddPredictableEventToPlayerstate + +Handles the sequence numbers +=============== +*/ + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) +{ +#ifdef _DEBUG + { + char buf[ 256 ]; + trap_Cvar_VariableStringBuffer( "showevents", buf, sizeof( buf ) ); + + if( atof( buf ) != 0 ) + { +#ifdef QAGAME + Com_Printf( " game event svt %5d -> %5d: num = %20s parm %d\n", + ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[ 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); +#endif + } + } +#endif + ps->events[ ps->eventSequence & ( MAX_PS_EVENTS - 1 ) ] = newEvent; + ps->eventParms[ ps->eventSequence & ( MAX_PS_EVENTS - 1 ) ] = eventParm; + ps->eventSequence++; +} + + +/* +======================== +BG_PlayerStateToEntityState + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) +{ + int i; + + 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 ) + s->eType = ET_INVISIBLE; + else + s->eType = ET_PLAYER; + + s->number = ps->clientNum; + + s->pos.trType = TR_INTERPOLATE; + VectorCopy( ps->origin, s->pos.trBase ); + + if( snap ) + SnapVector( s->pos.trBase ); + + //set the trDelta for flag direction + VectorCopy( ps->velocity, s->pos.trDelta ); + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + + 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->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + if( ps->stats[STAT_HEALTH] <= 0 ) + s->eFlags |= EF_DEAD; + else + s->eFlags &= ~EF_DEAD; + + if( ps->stats[ STAT_STATE ] & SS_BLOBLOCKED ) + s->eFlags |= EF_BLOBLOCKED; + else + s->eFlags &= ~EF_BLOBLOCKED; + + if( ps->externalEvent ) + { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } + else if( ps->entityEventSequence < ps->eventSequence ) + { + int seq; + + if( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS ) + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + + seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 ); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + //store items held and active items in otherEntityNum + s->modelindex = 0; + s->modelindex2 = 0; + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, ps->stats ) ) + { + s->modelindex |= 1 << i; + + if( BG_UpgradeIsActive( i, ps->stats ) ) + s->modelindex2 |= 1 << i; + } + } + + //TA: use powerups field to store team/class info: + s->powerups = ps->stats[ STAT_PTEAM ] | ( ps->stats[ STAT_PCLASS ] << 8 ); + + //TA: have to get the surfNormal thru 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; + + if( s->generic1 <= WPM_NONE || s->generic1 >= WPM_NUM_WEAPONMODES ) + s->generic1 = WPM_PRIMARY; +} + + +/* +======================== +BG_PlayerStateToEntityStateExtraPolate + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) +{ + int i; + + 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 ) + s->eType = ET_INVISIBLE; + else + s->eType = ET_PLAYER; + + s->number = ps->clientNum; + + s->pos.trType = TR_LINEAR_STOP; + VectorCopy( ps->origin, s->pos.trBase ); + + if( snap ) + SnapVector( s->pos.trBase ); + + // set the trDelta for flag direction and linear prediction + VectorCopy( ps->velocity, s->pos.trDelta ); + // set the time for linear prediction + s->pos.trTime = time; + // set maximum extra polation time + s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + 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->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + + if( ps->stats[STAT_HEALTH] <= 0 ) + s->eFlags |= EF_DEAD; + else + s->eFlags &= ~EF_DEAD; + + if( ps->stats[ STAT_STATE ] & SS_BLOBLOCKED ) + s->eFlags |= EF_BLOBLOCKED; + else + s->eFlags &= ~EF_BLOBLOCKED; + + if( ps->externalEvent ) + { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } + else if( ps->entityEventSequence < ps->eventSequence ) + { + int seq; + + if( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS ) + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + + seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 ); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + //store items held and active items in otherEntityNum + s->modelindex = 0; + s->modelindex2 = 0; + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, ps->stats ) ) + { + s->modelindex |= 1 << i; + + if( BG_UpgradeIsActive( i, ps->stats ) ) + s->modelindex2 |= 1 << i; + } + } + + //TA: use powerups field to store team/class info: + s->powerups = ps->stats[ STAT_PTEAM ] | ( ps->stats[ STAT_PCLASS ] << 8 ); + + //TA: have to get the surfNormal thru 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; + + if( s->generic1 <= WPM_NONE || s->generic1 >= WPM_NUM_WEAPONMODES ) + s->generic1 = WPM_PRIMARY; +} + +/* +======================== +BG_UnpackAmmoArray + +Extract the ammo quantity from the array +======================== +*/ +void BG_UnpackAmmoArray( int weapon, int psAmmo[ ], int psAmmo2[ ], int *ammo, int *clips ) +{ + int ammoarray[ 32 ]; + int i; + + for( i = 0; i <= 15; i++ ) + ammoarray[ i ] = psAmmo[ i ]; + + for( i = 16; i <= 31; i++ ) + ammoarray[ i ] = psAmmo2[ i - 16 ]; + + if( ammo != NULL ) + *ammo = ammoarray[ weapon ] & 0x0FFF; + + if( clips != NULL ) + *clips = ( ammoarray[ weapon ] >> 12 ) & 0x0F; +} + +/* +======================== +BG_PackAmmoArray + +Pack the ammo quantity into the array +======================== +*/ +void BG_PackAmmoArray( int weapon, int psAmmo[ ], int psAmmo2[ ], int ammo, int clips ) +{ + int weaponvalue; + + weaponvalue = ammo | ( clips << 12 ); + + if( weapon <= 15 ) + psAmmo[ weapon ] = weaponvalue; + else if( weapon >= 16 ) + psAmmo2[ weapon - 16 ] = weaponvalue; +} + +/* +======================== +BG_WeaponIsFull + +Check if a weapon has full ammo +======================== +*/ +qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int psAmmo[ ], int psAmmo2[ ] ) +{ + int maxAmmo, maxClips; + int ammo, clips; + + BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips ); + BG_UnpackAmmoArray( weapon, psAmmo, psAmmo2, &ammo, &clips ); + + if( BG_InventoryContainsUpgrade( UP_BATTPACK, stats ) ) + maxAmmo = (int)( (float)maxAmmo * BATTPACK_MODIFIER ); + + return ( maxAmmo == ammo ) && ( maxClips == clips ); +} + +/* +======================== +BG_AddWeaponToInventory + +Give a player a weapon +======================== +*/ +void BG_AddWeaponToInventory( 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 ); + + stats[ STAT_SLOTS ] |= BG_FindSlotsForWeapon( weapon ); +} + +/* +======================== +BG_RemoveWeaponToInventory + +Take a weapon from a player +======================== +*/ +void BG_RemoveWeaponFromInventory( 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; + + stats[ STAT_SLOTS ] &= ~BG_FindSlotsForWeapon( weapon ); +} + +/* +======================== +BG_InventoryContainsWeapon + +Does the player hold a weapon? +======================== +*/ +qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] ) +{ + int weaponList; + + weaponList = ( stats[ STAT_WEAPONS ] & 0x0000FFFF ) | ( ( stats[ STAT_WEAPONS2 ] << 16 ) & 0xFFFF0000 ); + + return( weaponList & ( 1 << weapon ) ); +} + +/* +======================== +BG_AddUpgradeToInventory + +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 ); +} + +/* +======================== +BG_RemoveUpgradeFromInventory + +Take an upgrade from the player +======================== +*/ +void BG_RemoveUpgradeFromInventory( int item, int stats[ ] ) +{ + stats[ STAT_ITEMS ] &= ~( 1 << item ); + + stats[ STAT_SLOTS ] &= ~BG_FindSlotsForUpgrade( item ); +} + +/* +======================== +BG_InventoryContainsUpgrade + +Does the player hold an upgrade? +======================== +*/ +qboolean BG_InventoryContainsUpgrade( int item, int stats[ ] ) +{ + return( stats[ STAT_ITEMS ] & ( 1 << item ) ); +} + +/* +======================== +BG_ActivateUpgrade + +Activates an upgrade +======================== +*/ +void BG_ActivateUpgrade( int item, int stats[ ] ) +{ + stats[ STAT_ACTIVEITEMS ] |= ( 1 << item ); +} + +/* +======================== +BG_DeactivateUpgrade + +Deactivates an upgrade +======================== +*/ +void BG_DeactivateUpgrade( int item, int stats[ ] ) +{ + stats[ STAT_ACTIVEITEMS ] &= ~( 1 << item ); +} + +/* +======================== +BG_UpgradeIsActive + +Is this upgrade active? +======================== +*/ +qboolean BG_UpgradeIsActive( int item, int stats[ ] ) +{ + return( stats[ STAT_ACTIVEITEMS ] & ( 1 << item ) ); +} + +/* +=============== +BG_RotateAxis + +Shared axis rotation function +=============== +*/ +qboolean BG_RotateAxis( vec3_t surfNormal, vec3_t inAxis[ 3 ], + vec3_t outAxis[ 3 ], qboolean inverse, qboolean ceiling ) +{ + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f }; + vec3_t localNormal, xNormal; + float rotAngle; + + //the grapplePoint being a surfNormal rotation Normal hack... see above :) + if( ceiling ) + { + VectorCopy( ceilingNormal, localNormal ); + VectorCopy( surfNormal, xNormal ); + } + else + { + //cross the reference normal and the surface normal to get the rotation axis + VectorCopy( surfNormal, localNormal ); + CrossProduct( localNormal, refNormal, xNormal ); + VectorNormalize( xNormal ); + } + + //can't rotate with no rotation vector + if( VectorLength( xNormal ) != 0.0f ) + { + rotAngle = RAD2DEG( acos( DotProduct( localNormal, refNormal ) ) ); + + if( inverse ) + rotAngle = -rotAngle; + + AngleNormalize180( rotAngle ); + + //hmmm could get away with only one rotation and some clever stuff later... but i'm lazy + RotatePointAroundVector( outAxis[ 0 ], xNormal, inAxis[ 0 ], -rotAngle ); + RotatePointAroundVector( outAxis[ 1 ], xNormal, inAxis[ 1 ], -rotAngle ); + RotatePointAroundVector( outAxis[ 2 ], xNormal, inAxis[ 2 ], -rotAngle ); + } + else + return qfalse; + + return qtrue; +} + +/* +=============== +BG_PositionBuildableRelativeToPlayer + +Find a place to build a buildable +=============== +*/ +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 ) +{ + vec3_t forward, entityOrigin, targetOrigin; + 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 ); + + VectorCopy( ps->viewangles, angles ); + VectorCopy( ps->origin, playerOrigin ); + buildDist = BG_FindBuildDistForClass( ps->stats[ STAT_PCLASS ] ); + + AngleVectors( angles, forward, NULL, NULL ); + ProjectPointOnPlane( forward, forward, playerNormal ); + VectorNormalize( forward ); + + VectorMA( playerOrigin, buildDist, forward, entityOrigin ); + + VectorCopy( entityOrigin, targetOrigin ); + + //so buildings can be placed facing slopes + VectorMA( entityOrigin, 32, playerNormal, entityOrigin ); + + //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 ); + vectoangles( forward, outAngles ); +} + +/* +=============== +BG_GetValueOfHuman + +Returns the kills value of some human player +=============== +*/ +int BG_GetValueOfHuman( playerState_t *ps ) +{ + int i, worth = 0; + float portion; + + for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) + { + if( BG_InventoryContainsUpgrade( i, ps->stats ) ) + worth += BG_FindPriceForUpgrade( i ); + } + + for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) + { + if( BG_InventoryContainsWeapon( i, ps->stats ) ) + worth += BG_FindPriceForWeapon( i ); + } + + portion = worth / (float)HUMAN_MAXED; + + if( portion < 0.01f ) + portion = 0.01f; + else if( portion > 1.0f ) + portion = 1.0f; + + return ceil( ALIEN_MAX_SINGLE_KILLS * portion ); +} + +/* +=============== +atof_neg + +atof with an allowance for negative values +=============== +*/ +float atof_neg( char *token, qboolean allowNegative ) +{ + float value; + + value = atof( token ); + + if( !allowNegative && value < 0.0f ) + value = 1.0f; + + return value; +} + +/* +=============== +atoi_neg + +atoi with an allowance for negative values +=============== +*/ +int atoi_neg( char *token, qboolean allowNegative ) +{ + int value; + + value = atoi( token ); + + if( !allowNegative && value < 0 ) + value = 1; + + return value; +} + +/* +=============== +BG_ParseCSVEquipmentList +=============== +*/ +void BG_ParseCSVEquipmentList( const char *string, weapon_t *weapons, int weaponsSize, + upgrade_t *upgrades, int upgradesSize ) +{ + char buffer[ MAX_STRING_CHARS ]; + int i = 0, j = 0; + char *p, *q; + qboolean EOS = qfalse; + + Q_strncpyz( buffer, string, MAX_STRING_CHARS ); + + p = q = buffer; + + while( *p != '\0' ) + { + //skip to first , or EOS + while( *p != ',' && *p != '\0' ) + p++; + + if( *p == '\0' ) + EOS = qtrue; + + *p = '\0'; + + //strip leading whitespace + while( *q == ' ' ) + q++; + + if( weaponsSize ) + weapons[ i ] = BG_FindWeaponNumForName( q ); + + if( upgradesSize ) + upgrades[ j ] = BG_FindUpgradeNumForName( q ); + + if( weaponsSize && weapons[ i ] == WP_NONE && + upgradesSize && upgrades[ j ] == UP_NONE ) + Com_Printf( S_COLOR_YELLOW "WARNING: unknown equipment %s\n", q ); + else if( weaponsSize && weapons[ i ] != WP_NONE ) + i++; + else if( upgradesSize && upgrades[ j ] != UP_NONE ) + j++; + + if( !EOS ) + { + p++; + q = p; + } + else + break; + + if( i == ( weaponsSize - 1 ) || j == ( upgradesSize - 1 ) ) + break; + } + + if( weaponsSize ) + weapons[ i ] = WP_NONE; + + if( upgradesSize ) + upgrades[ j ] = UP_NONE; +} + +/* +=============== +BG_ParseCSVClassList +=============== +*/ +void BG_ParseCSVClassList( const char *string, pClass_t *classes, int classesSize ) +{ + 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' ) + { + //skip to first , or EOS + while( *p != ',' && *p != '\0' ) + p++; + + if( *p == '\0' ) + EOS = qtrue; + + *p = '\0'; + + //strip leading whitespace + while( *q == ' ' ) + q++; + + classes[ i ] = BG_FindClassNumForName( q ); + + if( classes[ i ] == PCL_NONE ) + Com_Printf( S_COLOR_YELLOW "WARNING: unknown class %s\n", q ); + else + i++; + + if( !EOS ) + { + p++; + q = p; + } + else + break; + } + + classes[ i ] = PCL_NONE; +} + +/* +=============== +BG_ParseCSVBuildableList +=============== +*/ +void BG_ParseCSVBuildableList( const char *string, buildable_t *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' ) + { + //skip to first , or EOS + while( *p != ',' && *p != '\0' ) + p++; + + if( *p == '\0' ) + EOS = qtrue; + + *p = '\0'; + + //strip leading whitespace + while( *q == ' ' ) + q++; + + buildables[ i ] = BG_FindClassNumForName( q ); + + if( buildables[ i ] == BA_NONE ) + Com_Printf( S_COLOR_YELLOW "WARNING: unknown buildable %s\n", q ); + else + i++; + + if( !EOS ) + { + p++; + q = p; + } + else + break; + } + + buildables[ i ] = BA_NONE; +} diff --git a/src/game/bg_pmove.c b/src/game/bg_pmove.c new file mode 100644 index 00000000..88432f81 --- /dev/null +++ b/src/game/bg_pmove.c @@ -0,0 +1,3471 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_pmove.c -- both games player movement code +// takes a playerstate and a usercmd as input and returns a modifed playerstate + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +pmove_t *pm; +pml_t pml; + +// movement parameters +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; + +float pm_friction = 6.0f; +float pm_waterfriction = 1.0f; +float pm_flightfriction = 6.0f; +float pm_spectatorfriction = 5.0f; + +int c_pmove = 0; + +/* +=============== +PM_AddEvent + +=============== +*/ +void PM_AddEvent( int newEvent ) +{ + BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); +} + +/* +=============== +PM_AddTouchEnt +=============== +*/ +void PM_AddTouchEnt( int entityNum ) +{ + int i; + + if( entityNum == ENTITYNUM_WORLD ) + return; + + if( pm->numtouch == MAXTOUCH ) + return; + + // see if it is already added + for( i = 0 ; i < pm->numtouch ; i++ ) + { + if( pm->touchents[ i ] == entityNum ) + return; + } + + // add it + pm->touchents[ pm->numtouch ] = entityNum; + pm->numtouch++; +} + +/* +=================== +PM_StartTorsoAnim +=================== +*/ +static void PM_StartTorsoAnim( int anim ) +{ + if( pm->ps->pm_type >= PM_DEAD ) + return; + + pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + +/* +=================== +PM_StartLegsAnim +=================== +*/ +static void PM_StartLegsAnim( int anim ) +{ + if( pm->ps->pm_type >= PM_DEAD ) + return; + + //legsTimer is clamped too tightly for nonsegmented models + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + if( pm->ps->legsTimer > 0 ) + return; // a high priority animation is running + } + else + { + if( pm->ps->torsoTimer > 0 ) + return; // a high priority animation is running + } + + pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + +/* +=================== +PM_ContinueLegsAnim +=================== +*/ +static void PM_ContinueLegsAnim( int anim ) +{ + if( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) + return; + + //legsTimer is clamped too tightly for nonsegmented models + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + if( pm->ps->legsTimer > 0 ) + return; // a high priority animation is running + } + else + { + if( pm->ps->torsoTimer > 0 ) + return; // a high priority animation is running + } + + PM_StartLegsAnim( anim ); +} + +/* +=================== +PM_ContinueTorsoAnim +=================== +*/ +static void PM_ContinueTorsoAnim( int anim ) +{ + if( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) + return; + + if( pm->ps->torsoTimer > 0 ) + return; // a high priority animation is running + + PM_StartTorsoAnim( anim ); +} + +/* +=================== +PM_ForceLegsAnim +=================== +*/ +static void PM_ForceLegsAnim( int anim ) +{ + //legsTimer is clamped too tightly for nonsegmented models + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + pm->ps->legsTimer = 0; + else + pm->ps->torsoTimer = 0; + + PM_StartLegsAnim( anim ); +} + + +/* +================== +PM_ClipVelocity + +Slide off of the impacting surface +================== +*/ +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) +{ + 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( " " ); +} + + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +static void PM_Friction( void ) +{ + vec3_t vec; + float *vel; + float speed, newspeed, control; + float drop; + + vel = pm->ps->velocity; + + //TA: 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? + return; + } + + drop = 0; + + // apply ground friction + if( pm->waterlevel <= 1 ) + { + if( ( pml.walking || pml.ladder ) && !( pml.groundTrace.surfaceFlags & SURF_SLICK ) ) + { + // if getting knocked back, no friction + if( !( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) ) + { + float stopSpeed = BG_FindStopSpeedForClass( pm->ps->stats[ STAT_PCLASS ] ); + + control = speed < stopSpeed ? stopSpeed : speed; + drop += control * BG_FindFrictionForClass( pm->ps->stats[ STAT_PCLASS ] ) * pml.frametime; + } + } + } + + // apply water friction even if just wading + if( pm->waterlevel ) + drop += speed * pm_waterfriction * pm->waterlevel * pml.frametime; + + // apply flying friction + if( pm->ps->pm_type == PM_JETPACK ) + drop += speed * pm_flightfriction * pml.frametime; + + if( pm->ps->pm_type == PM_SPECTATOR ) + drop += speed * pm_spectatorfriction * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if( newspeed < 0 ) + newspeed = 0; + + newspeed /= speed; + + vel[ 0 ] = vel[ 0 ] * newspeed; + vel[ 1 ] = vel[ 1 ] * newspeed; + vel[ 2 ] = vel[ 2 ] * newspeed; +} + + +/* +============== +PM_Accelerate + +Handles user intended acceleration +============== +*/ +static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) +{ +#if 1 + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct( pm->ps->velocity, wishdir ); + addspeed = wishspeed - currentspeed; + if( addspeed <= 0 ) + return; + + accelspeed = accel * pml.frametime * wishspeed; + if( accelspeed > addspeed ) + accelspeed = addspeed; + + for( i = 0; i < 3; i++ ) + pm->ps->velocity[ i ] += accelspeed * wishdir[ i ]; +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + vec3_t wishVelocity; + vec3_t pushDir; + float pushLen; + float canPush; + + VectorScale( wishdir, wishspeed, wishVelocity ); + VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); + pushLen = VectorNormalize( pushDir ); + + canPush = accel * pml.frametime * wishspeed; + if( canPush > pushLen ) + canPush = pushLen; + + VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); +#endif +} + + + +/* +============ +PM_CmdScale + +Returns the scale factor to apply to cmd movements +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 ) +{ + 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_STATE ] & SS_SPEEDBOOST ) + modifier *= HUMAN_SPRINT_MODIFIER; + else + modifier *= HUMAN_JOG_MODIFIER; + + if( cmd->forwardmove < 0 ) + { + //can't run backwards + modifier *= HUMAN_BACK_MODIFIER; + } + else if( cmd->rightmove ) + { + //can't move that fast sideways + modifier *= HUMAN_SIDE_MODIFIER; + } + + //must have +ve stamina to jump + if( pm->ps->stats[ STAT_STAMINA ] < 0 ) + 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_STATE ] & SS_CREEPSLOWED ) + { + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, pm->ps->stats ) || + BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) ) + modifier *= CREEP_ARMOUR_MODIFIER; + else + modifier *= CREEP_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 ) ); + + //slow player if charging up for a pounce + if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) && + cmd->buttons & BUTTON_ATTACK2 ) + modifier *= LEVEL3_POUNCE_SPEED_MOD; + + //slow the player if slow locked + if( pm->ps->stats[ STAT_STATE ] & SS_SLOWLOCKED ) + modifier *= ABUILDER_BLOB_SPEED_MOD; + + 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 ); + + if( abs( cmd->upmove ) > max ) + max = abs( cmd->upmove ); + + if( !max ) + return 0; + + total = sqrt( cmd->forwardmove * cmd->forwardmove + + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); + + scale = (float)pm->ps->speed * max / ( 127.0 * total ) * modifier; + + return scale; +} + + +/* +================ +PM_SetMovementDir + +Determine the rotation of the legs reletive +to the facing dir +================ +*/ +static void PM_SetMovementDir( void ) +{ + if( pm->cmd.forwardmove || pm->cmd.rightmove ) + { + if( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) + pm->ps->movementDir = 0; + else if( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) + pm->ps->movementDir = 1; + else if( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) + pm->ps->movementDir = 2; + else if( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) + pm->ps->movementDir = 3; + else if( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) + pm->ps->movementDir = 4; + else if( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) + pm->ps->movementDir = 5; + else if( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) + pm->ps->movementDir = 6; + else if( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) + pm->ps->movementDir = 7; + } + else + { + // if they aren't actively going directly sideways, + // change the animation to the diagonal so they + // don't stop too crooked + if( pm->ps->movementDir == 2 ) + pm->ps->movementDir = 1; + else if( pm->ps->movementDir == 6 ) + pm->ps->movementDir = 7; + } +} + + +/* +============= +PM_CheckCharge +============= +*/ +static void PM_CheckCharge( void ) +{ + if( pm->ps->weapon != WP_ALEVEL4 ) + return; + + if( pm->cmd.buttons & BUTTON_ATTACK2 && + !( pm->ps->stats[ STAT_STATE ] & SS_CHARGING ) ) + { + pm->ps->pm_flags &= ~PMF_CHARGE; + return; + } + + if( pm->ps->stats[ STAT_MISC ] > 0 ) + pm->ps->pm_flags |= PMF_CHARGE; + else + pm->ps->pm_flags &= ~PMF_CHARGE; +} + +/* +============= +PM_CheckPounce +============= +*/ +static qboolean PM_CheckPounce( void ) +{ + if( pm->ps->weapon != WP_ALEVEL3 && + pm->ps->weapon != WP_ALEVEL3_UPG ) + return qfalse; + + if( pm->cmd.buttons & BUTTON_ATTACK2 ) + { + pm->ps->pm_flags &= ~PMF_CHARGE; + return qfalse; + } + + if( pm->ps->pm_flags & PMF_CHARGE ) + return qfalse; + + if( pm->ps->stats[ STAT_MISC ] == 0 ) + return qfalse; + + pml.groundPlane = qfalse; // jumping away + 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 ); + + PM_AddEvent( EV_JUMP ); + + if( pm->cmd.forwardmove >= 0 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMP ); + else + PM_ForceLegsAnim( NSPA_JUMP ); + + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMPB ); + else + PM_ForceLegsAnim( NSPA_JUMPBACK ); + + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + return qtrue; +} + +/* +============= +PM_CheckWallJump +============= +*/ +static qboolean PM_CheckWallJump( void ) +{ + vec3_t dir, forward, right; + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + float normalFraction = 1.5f; + float cmdFraction = 1.0f; + float upFraction = 1.5f; + + 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; + + if( pm->ps->pm_flags & PMF_TIME_WALLJUMP ) + return qfalse; + + // must wait for jump to be released + 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; + } + + 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; + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + + ProjectPointOnPlane( forward, pml.forward, pm->ps->grapplePoint ); + ProjectPointOnPlane( right, pml.right, pm->ps->grapplePoint ); + + VectorScale( pm->ps->grapplePoint, normalFraction, dir ); + + if( pm->cmd.forwardmove > 0 ) + VectorMA( dir, cmdFraction, forward, dir ); + else if( pm->cmd.forwardmove < 0 ) + VectorMA( dir, -cmdFraction, forward, dir ); + + if( pm->cmd.rightmove > 0 ) + VectorMA( dir, cmdFraction, right, dir ); + else if( pm->cmd.rightmove < 0 ) + VectorMA( dir, -cmdFraction, right, dir ); + + VectorMA( dir, upFraction, refNormal, dir ); + VectorNormalize( dir ); + + VectorMA( pm->ps->velocity, BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ), + dir, pm->ps->velocity ); + + //for a long run of wall jumps the velocity can get pretty large, this caps it + if( VectorLength( pm->ps->velocity ) > LEVEL2_WALLJUMP_MAXSPEED ) + { + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, LEVEL2_WALLJUMP_MAXSPEED, pm->ps->velocity ); + } + + PM_AddEvent( EV_JUMP ); + + if( pm->cmd.forwardmove >= 0 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMP ); + else + PM_ForceLegsAnim( NSPA_JUMP ); + + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMPB ); + else + PM_ForceLegsAnim( NSPA_JUMPBACK ); + + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + return qtrue; +} + +/* +============= +PM_CheckJump +============= +*/ +static qboolean PM_CheckJump( void ) +{ + if( BG_FindJumpMagnitudeForClass( pm->ps->stats[ STAT_PCLASS ] ) == 0.0f ) + return qfalse; + + if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) ) + return PM_CheckWallJump( ); + + //can't jump and pounce at the same time + if( ( pm->ps->weapon == WP_ALEVEL3 || + pm->ps->weapon == WP_ALEVEL3_UPG ) && + pm->ps->stats[ STAT_MISC ] > 0 ) + return qfalse; + + //can't jump and charge at the same time + if( ( pm->ps->weapon == WP_ALEVEL4 ) && + pm->ps->stats[ STAT_MISC ] > 0 ) + return qfalse; + + if( ( pm->ps->stats[ STAT_PTEAM ] == PTE_HUMANS ) && + ( pm->ps->stats[ STAT_STAMINA ] < 0 ) ) + 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; + + //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; + } + + 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; + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + + //TA: jump away from wall + if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) + { + vec3_t normal = { 0, 0, -1 }; + + 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 ] ); + + PM_AddEvent( EV_JUMP ); + + if( pm->cmd.forwardmove >= 0 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMP ); + else + PM_ForceLegsAnim( NSPA_JUMP ); + + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMPB ); + else + PM_ForceLegsAnim( NSPA_JUMPBACK ); + + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + return qtrue; +} + +/* +============= +PM_CheckWaterJump +============= +*/ +static qboolean PM_CheckWaterJump( void ) +{ + vec3_t spot; + int cont; + vec3_t flatforward; + + if( pm->ps->pm_time ) + return qfalse; + + // check for water jump + if( pm->waterlevel != 2 ) + return qfalse; + + flatforward[ 0 ] = pml.forward[ 0 ]; + flatforward[ 1 ] = pml.forward[ 1 ]; + flatforward[ 2 ] = 0; + VectorNormalize( flatforward ); + + VectorMA( pm->ps->origin, 30, flatforward, spot ); + spot[ 2 ] += 4; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + + if( !( cont & CONTENTS_SOLID ) ) + return qfalse; + + spot[ 2 ] += 16; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + + if( cont ) + return qfalse; + + // jump out of water + VectorScale( pml.forward, 200, pm->ps->velocity ); + pm->ps->velocity[ 2 ] = 350; + + pm->ps->pm_flags |= PMF_TIME_WATERJUMP; + pm->ps->pm_time = 2000; + + return qtrue; +} + +//============================================================================ + + +/* +=================== +PM_WaterJumpMove + +Flying out of the water +=================== +*/ +static void PM_WaterJumpMove( void ) +{ + // waterjump has no control, but falls + + PM_StepSlideMove( qtrue, qfalse ); + + pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime; + if( pm->ps->velocity[ 2 ] < 0 ) + { + // cancel as soon as we are falling down again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +static void PM_WaterMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + if( PM_CheckWaterJump( ) ) + { + PM_WaterJumpMove(); + return; + } +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if (pm->ps->velocity[2] > -300) { + if ( pm->watertype == CONTENTS_WATER ) { + pm->ps->velocity[2] = 100; + } else if (pm->watertype == CONTENTS_SLIME) { + pm->ps->velocity[2] = 80; + } else { + pm->ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction( ); + + scale = PM_CmdScale( &pm->cmd ); + // + // 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; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + if( wishspeed > pm->ps->speed * pm_swimScale ) + wishspeed = pm->ps->speed * pm_swimScale; + + PM_Accelerate( wishdir, wishspeed, pm_wateraccelerate ); + + // make sure we can go up slopes easily under water + if( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) + { + vel = VectorLength( pm->ps->velocity ); + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + } + + PM_SlideMove( qfalse ); +} + +/* +=================== +PM_JetPackMove + +Only with the jetpack +=================== +*/ +static void PM_JetPackMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + //normal slowdown + PM_Friction( ); + + scale = PM_CmdScale( &pm->cmd ); + + // user intentions + for( i = 0; i < 2; i++ ) + wishvel[ i ] = scale * pml.forward[ i ] * pm->cmd.forwardmove + scale * pml.right[ i ] * pm->cmd.rightmove; + + if( pm->cmd.upmove > 0.0f ) + wishvel[ 2 ] = JETPACK_FLOAT_SPEED; + if( pm->cmd.upmove < 0.0f ) + wishvel[ 2 ] = -JETPACK_SINK_SPEED; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate ); + + PM_StepSlideMove( qfalse, qfalse ); + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_LAND ); + else + PM_ContinueLegsAnim( NSPA_LAND ); +} + + + + +/* +=================== +PM_FlyMove + +Only with the flight powerup +=================== +*/ +static void PM_FlyMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + // normal slowdown + PM_Friction( ); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if( !scale ) + { + wishvel[ 0 ] = 0; + wishvel[ 1 ] = 0; + wishvel[ 2 ] = 0; + } + 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; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + PM_Accelerate( wishdir, wishspeed, pm_flyaccelerate ); + + PM_StepSlideMove( qfalse, qfalse ); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +static void PM_AirMove( void ) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + + PM_Friction( ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir( ); + + // project moves down to flat plane + pml.forward[ 2 ] = 0; + pml.right[ 2 ] = 0; + VectorNormalize( pml.forward ); + VectorNormalize( pml.right ); + + for( i = 0; i < 2; i++ ) + wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove; + + wishvel[ 2 ] = 0; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + // not on ground, so little effect on velocity + PM_Accelerate( wishdir, wishspeed, + BG_FindAirAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ) ); + + // 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_StepSlideMove( qtrue, qfalse ); +} + +/* +=================== +PM_ClimbMove + +=================== +*/ +static void PM_ClimbMove( void ) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + float vel; + + if( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) + { + // begin swimming + PM_WaterMove( ); + return; + } + + + if( PM_CheckJump( ) || PM_CheckPounce( ) ) + { + // jumped away + if( pm->waterlevel > 1 ) + PM_WaterMove( ); + else + PM_AirMove( ); + + return; + } + + PM_Friction( ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // 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 ); + // + VectorNormalize( pml.forward ); + VectorNormalize( pml.right ); + + for( i = 0; i < 3; i++ ) + wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove; + + // when going up or down slopes the wish velocity should Not be zero +// wishvel[2] = 0; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + // clamp the speed lower if ducking + if( pm->ps->pm_flags & PMF_DUCKED ) + { + if( wishspeed > pm->ps->speed * pm_duckScale ) + wishspeed = pm->ps->speed * pm_duckScale; + } + + // clamp the speed lower if wading or walking on the bottom + if( pm->waterlevel ) + { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; + if( wishspeed > pm->ps->speed * waterScale ) + wishspeed = pm->ps->speed * waterScale; + } + + // 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 ] ); + else + accelerate = BG_FindAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ); + + PM_Accelerate( wishdir, wishspeed, accelerate ); + + if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime; + + vel = VectorLength( pm->ps->velocity ); + + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + // don't decrease velocity when going up or down a slope + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + + // don't do anything if standing still + if( !pm->ps->velocity[ 0 ] && !pm->ps->velocity[ 1 ] && !pm->ps->velocity[ 2 ] ) + return; + + PM_StepSlideMove( qfalse, qfalse ); +} + + +/* +=================== +PM_WalkMove + +=================== +*/ +static void PM_WalkMove( void ) +{ + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + float vel; + + if( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) + { + // begin swimming + PM_WaterMove( ); + return; + } + + + if( PM_CheckJump( ) || PM_CheckPounce( ) ) + { + // jumped away + if( pm->waterlevel > 1 ) + PM_WaterMove( ); + else + PM_AirMove( ); + + return; + } + + //charging + PM_CheckCharge( ); + + PM_Friction( ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir( ); + + // project moves down to flat plane + pml.forward[ 2 ] = 0; + 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 ); + // + VectorNormalize( pml.forward ); + VectorNormalize( pml.right ); + + for( i = 0; i < 3; i++ ) + wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove; + + // when going up or down slopes the wish velocity should Not be zero +// wishvel[2] = 0; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + // clamp the speed lower if ducking + if( pm->ps->pm_flags & PMF_DUCKED ) + { + if( wishspeed > pm->ps->speed * pm_duckScale ) + wishspeed = pm->ps->speed * pm_duckScale; + } + + // clamp the speed lower if wading or walking on the bottom + if( pm->waterlevel ) + { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; + if( wishspeed > pm->ps->speed * waterScale ) + wishspeed = pm->ps->speed * waterScale; + } + + // 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 ] ); + else + accelerate = BG_FindAccelerationForClass( pm->ps->stats[ STAT_PCLASS ] ); + + PM_Accelerate( wishdir, wishspeed, accelerate ); + + //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); + //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); + + if( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) + pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime; + else + { + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + } + + vel = VectorLength( pm->ps->velocity ); + + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + // don't decrease velocity when going up or down a slope + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + + // don't do anything if standing still + if( !pm->ps->velocity[ 0 ] && !pm->ps->velocity[ 1 ] ) + return; + + PM_StepSlideMove( qfalse, qfalse ); + + //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); + +} + + +/* +=================== +PM_LadderMove + +Basically a rip of PM_WaterMove with a few changes +=================== +*/ +static void PM_LadderMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + PM_Friction( ); + + scale = PM_CmdScale( &pm->cmd ); + + 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 ); + + if( wishspeed > pm->ps->speed * pm_swimScale ) + wishspeed = pm->ps->speed * pm_swimScale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + //slanty ladders + if( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0.0f ) + { + vel = VectorLength( pm->ps->velocity ); + + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, vel, pm->ps->velocity ); + } + + PM_SlideMove( qfalse ); +} + + +/* +============= +PM_CheckLadder + +Check to see if the player is on a ladder or not +============= +*/ +static void PM_CheckLadder( void ) +{ + vec3_t forward, end; + trace_t trace; + + //test if class can use ladders + if( !BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_CANUSELADDERS ) ) + { + pml.ladder = qfalse; + return; + } + + VectorCopy( pml.forward, forward ); + forward[ 2 ] = 0.0f; + + VectorMA( pm->ps->origin, 1.0f, forward, end ); + + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, MASK_PLAYERSOLID ); + + if( ( trace.fraction < 1.0f ) && ( trace.surfaceFlags & SURF_LADDER ) ) + pml.ladder = qtrue; + else + pml.ladder = qfalse; +} + + +/* +============== +PM_DeadMove +============== +*/ +static void PM_DeadMove( void ) +{ + float forward; + + if( !pml.walking ) + return; + + // extra friction + + forward = VectorLength( pm->ps->velocity ); + forward -= 20; + + if( forward <= 0 ) + VectorClear( pm->ps->velocity ); + else + { + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, forward, pm->ps->velocity ); + } +} + + +/* +=============== +PM_NoclipMove +=============== +*/ +static void PM_NoclipMove( void ) +{ + float speed, drop, friction, control, newspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + + // friction + + speed = VectorLength( pm->ps->velocity ); + + if( speed < 1 ) + { + VectorCopy( vec3_origin, pm->ps->velocity ); + } + else + { + drop = 0; + + friction = pm_friction * 1.5; // extra friction + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control * friction * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + + if( newspeed < 0 ) + newspeed = 0; + + newspeed /= speed; + + VectorScale( pm->ps->velocity, newspeed, pm->ps->velocity ); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + for( i = 0; i < 3; i++ ) + wishvel[ i ] = pml.forward[ i ] * fmove + pml.right[ i ] * smove; + + wishvel[ 2 ] += pm->cmd.upmove; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + // move + VectorMA( pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin ); +} + +//============================================================================ + +/* +================ +PM_FootstepForSurface + +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; + + if( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) + return 0; + + if( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) + return EV_FOOTSTEP_METAL; + + return EV_FOOTSTEP; +} + + +/* +================= +PM_CrashLand + +Check for hard landings that generate sound events +================= +*/ +static void PM_CrashLand( void ) +{ + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + + // decide which landing animation to use + if( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_LANDB ); + else + PM_ForceLegsAnim( NSPA_LANDBACK ); + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_LAND ); + else + PM_ForceLegsAnim( NSPA_LAND ); + } + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + pm->ps->legsTimer = TIMER_LAND; + else + pm->ps->torsoTimer = TIMER_LAND; + + // calculate the exact velocity on landing + dist = pm->ps->origin[ 2 ] - pml.previous_origin[ 2 ]; + vel = pml.previous_velocity[ 2 ]; + acc = -pm->ps->gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if( den < 0 ) + return; + + t = (-b - sqrt( den ) ) / ( 2 * a ); + + 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; + + // reduce falling damage if there is standing water + if( pm->waterlevel == 2 ) + delta *= 0.25; + + if( pm->waterlevel == 1 ) + delta *= 0.5; + + if( delta < 1 ) + return; + + // create a local entity event to play the sound + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + if( !( pml.groundTrace.surfaceFlags & SURF_NODAMAGE ) ) + { + pm->ps->stats[ STAT_FALLDIST ] = delta; + + if( delta > AVG_FALL_DISTANCE ) + { + 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 ) + PM_AddEvent( EV_FALL_MEDIUM ); + } + else + { + if( delta > 7 ) + PM_AddEvent( EV_FALL_SHORT ); + else + PM_AddEvent( PM_FootstepForSurface( ) ); + } + } + + // start footstep cycle over + pm->ps->bobCycle = 0; +} + + +/* +============= +PM_CorrectAllSolid +============= +*/ +static int PM_CorrectAllSolid( trace_t *trace ) +{ + int i, j, k; + vec3_t point; + + if( pm->debugLevel ) + Com_Printf("%i:allsolid\n", c_pmove); + + // jitter around + for( i = -1; i <= 1; i++ ) + { + for( j = -1; j <= 1; j++ ) + { + for( k = -1; k <= 1; k++ ) + { + VectorCopy( pm->ps->origin, point ); + point[ 0 ] += (float)i; + point[ 1 ] += (float)j; + point[ 2 ] += (float)k; + pm->trace( trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + + if( !trace->allsolid ) + { + point[ 0 ] = pm->ps->origin[ 0 ]; + point[ 1 ] = pm->ps->origin[ 1 ]; + point[ 2 ] = pm->ps->origin[ 2 ] - 0.25; + + pm->trace( trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + pml.groundTrace = *trace; + return qtrue; + } + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + return qfalse; +} + + +/* +============= +PM_GroundTraceMissed + +The ground trace didn't hit a surface, so we are in freefall +============= +*/ +static void PM_GroundTraceMissed( void ) +{ + trace_t trace; + vec3_t point; + + if( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + // we just transitioned into freefall + if( pm->debugLevel ) + Com_Printf( "%i:lift\n", c_pmove ); + + // if they aren't in a jumping animation and the ground is a ways away, force into it + // if we didn't do the trace, the player would be backflipping down staircases + VectorCopy( pm->ps->origin, point ); + point[ 2 ] -= 64.0f; + + pm->trace( &trace, pm->ps->origin, NULL, NULL, point, pm->ps->clientNum, pm->tracemask ); + if( trace.fraction == 1.0f ) + { + if( pm->cmd.forwardmove >= 0 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMP ); + else + PM_ForceLegsAnim( NSPA_JUMP ); + + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMPB ); + else + PM_ForceLegsAnim( NSPA_JUMPBACK ); + + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + } + } + + if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_TAKESFALLDAMAGE ) ) + { + if( pm->ps->velocity[ 2 ] < FALLING_THRESHOLD && pml.previous_velocity[ 2 ] >= FALLING_THRESHOLD ) + PM_AddEvent( EV_FALLING ); + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + + +/* +============= +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; + + //used for delta correction + vec3_t traceCROSSsurf, traceCROSSref, surfCROSSref; + float traceDOTsurf, traceDOTref, surfDOTref, rTtDOTrTsTt; + float traceANGsurf, traceANGref, surfANGref; + vec3_t horizontal = { 1.0f, 0.0f, 0.0f }; //arbituary vector perpendicular to refNormal + vec3_t refTOtrace, refTOsurfTOtrace, tempVec; + int rTtANGrTsTt; + 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 ); + + //construct a vector which reflects the direction the player is looking wrt the surface normal + ProjectPointOnPlane( movedir, pml.forward, surfNormal ); + VectorNormalize( movedir ); + + VectorCopy( movedir, lookdir ); + + 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 ); + } + + for( i = 0; i <= 4; i++ ) + { + switch ( i ) + { + case 0: + //we are going to step this frame so skip the transition test + if( PM_PredictStepMove( ) ) + continue; + + //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 ); + break; + + case 1: + //trace straight down anto "ground" surface + VectorMA( pm->ps->origin, -0.25f, surfNormal, point ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + break; + + case 2: + if( pml.groundPlane != qfalse && PM_PredictStepMove( ) ) + { + //step down + VectorMA( pm->ps->origin, -STEPSIZE, surfNormal, point ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + } + else + continue; + break; + + case 3: + //trace "underneath" BBOX so we can traverse angles > 180deg + if( pml.groundPlane != qfalse ) + { + VectorMA( pm->ps->origin, -16.0f, surfNormal, point ); + VectorMA( point, -16.0f, movedir, point ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + } + else + continue; + break; + + case 4: + //fall back so we don't have to modify PM_GroundTrace too much + VectorCopy( pm->ps->origin, point ); + point[ 2 ] = pm->ps->origin[ 2 ] - 0.25f; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + break; + } + + //if we hit something + if( trace.fraction < 1.0f && !( trace.surfaceFlags & ( SURF_SKY | SURF_SLICK ) ) && + !( trace.entityNum != ENTITYNUM_WORLD && i != 4 ) ) + { + if( i == 2 || i == 3 ) + { + if( i == 2 ) + PM_StepEvent( pm->ps->origin, trace.endpos, surfNormal ); + + VectorCopy( trace.endpos, pm->ps->origin ); + } + + //calculate a bunch of stuff... + CrossProduct( trace.plane.normal, surfNormal, traceCROSSsurf ); + VectorNormalize( traceCROSSsurf ); + + CrossProduct( trace.plane.normal, refNormal, traceCROSSref ); + VectorNormalize( traceCROSSref ); + + CrossProduct( surfNormal, refNormal, surfCROSSref ); + VectorNormalize( surfCROSSref ); + + //calculate angle between surf and trace + traceDOTsurf = DotProduct( trace.plane.normal, surfNormal ); + traceANGsurf = RAD2DEG( acos( traceDOTsurf ) ); + + if( traceANGsurf > 180.0f ) + traceANGsurf -= 180.0f; + + //calculate angle between trace and ref + traceDOTref = DotProduct( trace.plane.normal, refNormal ); + traceANGref = RAD2DEG( acos( traceDOTref ) ); + + if( traceANGref > 180.0f ) + traceANGref -= 180.0f; + + //calculate angle between surf and ref + surfDOTref = DotProduct( surfNormal, refNormal ); + surfANGref = RAD2DEG( acos( surfDOTref ) ); + + if( surfANGref > 180.0f ) + surfANGref -= 180.0f; + + //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 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 ) ) + { + //behold the evil mindfuck from hell + //it has fucked mind like nothing has fucked mind before + + //calculate reference rotated through to trace plane + RotatePointAroundVector( refTOtrace, traceCROSSref, horizontal, -traceANGref ); + + //calculate reference rotated through to surf plane then to trace plane + RotatePointAroundVector( tempVec, surfCROSSref, horizontal, -surfANGref ); + RotatePointAroundVector( refTOsurfTOtrace, traceCROSSsurf, tempVec, -traceANGsurf ); + + //calculate angle between refTOtrace and refTOsurfTOtrace + rTtDOTrTsTt = DotProduct( refTOtrace, refTOsurfTOtrace ); + rTtANGrTsTt = ANGLE2SHORT( RAD2DEG( acos( rTtDOTrTsTt ) ) ); + + if( rTtANGrTsTt > 32768 ) + rTtANGrTsTt -= 32768; + + CrossProduct( refTOtrace, refTOsurfTOtrace, tempVec ); + VectorNormalize( tempVec ); + if( DotProduct( trace.plane.normal, tempVec ) > 0.0f ) + rTtANGrTsTt = -rTtANGrTsTt; + + //phew! - correct the angle + pm->ps->delta_angles[ YAW ] -= rTtANGrTsTt; + } + + //construct a plane dividing the surf and trace normals + CrossProduct( traceCROSSsurf, surfNormal, abc ); + VectorNormalize( abc ); + d = DotProduct( abc, pm->ps->origin ); + + //construct a point representing where the player is looking + VectorAdd( pm->ps->origin, lookdir, point ); + + //check whether point is on one side of the plane, if so invert the correction angle + if( ( abc[ 0 ] * point[ 0 ] + abc[ 1 ] * point[ 1 ] + abc[ 2 ] * point[ 2 ] - d ) > 0 ) + traceANGsurf = -traceANGsurf; + + //find the . product of the lookdir and traceCROSSsurf + if( ( ldDOTtCs = DotProduct( lookdir, traceCROSSsurf ) ) < 0.0f ) + { + VectorInverse( traceCROSSsurf ); + ldDOTtCs = DotProduct( lookdir, traceCROSSsurf ); + } + + //set the correction angle + traceANGsurf *= 1.0f - ldDOTtCs; + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_WALLCLIMBINGFOLLOW ) ) + { + //correct the angle + pm->ps->delta_angles[ PITCH ] -= ANGLE2SHORT( traceANGsurf ); + } + + //transition from wall to ceiling + //normal for subsequent viewangle rotations + if( VectorCompare( trace.plane.normal, ceilingNormal ) ) + { + CrossProduct( surfNormal, trace.plane.normal, pm->ps->grapplePoint ); + VectorNormalize( pm->ps->grapplePoint ); + pm->ps->stats[ STAT_STATE ] |= SS_WALLCLIMBINGCEILING; + } + + //transition from ceiling to wall + //we need to do some different angle correction here cos GPISROTVEC + if( VectorCompare( surfNormal, ceilingNormal ) ) + { + vectoangles( trace.plane.normal, toAngles ); + vectoangles( pm->ps->grapplePoint, surfAngles ); + + pm->ps->delta_angles[ 1 ] -= ANGLE2SHORT( ( ( surfAngles[ 1 ] - toAngles[ 1 ] ) * 2 ) - 180.0f ); + } + } + + pml.groundTrace = trace; + + //so everything knows where we're wallclimbing (ie client side) + 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 ) ) + { + //so we know what surface we're stuck to + VectorCopy( trace.plane.normal, pm->ps->grapplePoint ); + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; + } + + //IMPORTANT: break out of the for loop if we've hit something + break; + } + else if( trace.allsolid ) + { + // do something corrective if the trace starts in a solid... + if( !PM_CorrectAllSolid( &trace ) ) + return; + } + } + + if( trace.fraction >= 1.0f ) + { + // if the trace didn't hit anything, we are in free fall + PM_GroundTraceMissed( ); + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->eFlags &= ~EF_WALLCLIMB; + + //just transided from ceiling to floor... apply delta correction + if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) + { + vec3_t forward, rotated, angles; + + AngleVectors( pm->ps->viewangles, forward, NULL, NULL ); + + RotatePointAroundVector( rotated, pm->ps->grapplePoint, forward, 180.0f ); + vectoangles( rotated, angles ); + + pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( angles[ YAW ] - pm->ps->viewangles[ YAW ] ); + } + + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; + + //we get very bizarre effects if we don't do this :0 + VectorCopy( refNormal, pm->ps->grapplePoint ); + return; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // 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_time = 0; + } + + pm->ps->groundEntityNum = trace.entityNum; + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + PM_AddTouchEnt( trace.entityNum ); +} + + +/* +============= +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( pm->ps->persistant[ PERS_STATE ] & PS_WALLCLIMBINGTOGGLE ) + { + //toggle wall climbing if holding crouch + if( pm->cmd.upmove < 0 && !( pm->ps->pm_flags & PMF_CROUCH_HELD ) ) + { + if( !( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) + pm->ps->stats[ STAT_STATE ] |= SS_WALLCLIMBING; + else if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; + + pm->ps->pm_flags |= PMF_CROUCH_HELD; + } + else if( pm->cmd.upmove >= 0 ) + pm->ps->pm_flags &= ~PMF_CROUCH_HELD; + } + else + { + if( pm->cmd.upmove < 0 ) + pm->ps->stats[ STAT_STATE ] |= SS_WALLCLIMBING; + else if( pm->cmd.upmove >= 0 ) + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; + } + + if( pm->ps->pm_type == PM_DEAD ) + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; + + if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) + { + PM_GroundClimbTrace( ); + return; + } + + //just transided from ceiling to floor... apply delta correction + if( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) + { + vec3_t forward, rotated, angles; + + AngleVectors( pm->ps->viewangles, forward, NULL, NULL ); + + RotatePointAroundVector( rotated, pm->ps->grapplePoint, forward, 180.0f ); + vectoangles( rotated, angles ); + + pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( angles[ YAW ] - pm->ps->viewangles[ YAW ] ); + } + } + + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; + pm->ps->stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING; + pm->ps->eFlags &= ~EF_WALLCLIMB; + + point[ 0 ] = pm->ps->origin[ 0 ]; + point[ 1 ] = pm->ps->origin[ 1 ]; + point[ 2 ] = pm->ps->origin[ 2 ] - 0.25f; + + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + + pml.groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if( trace.allsolid ) + if( !PM_CorrectAllSolid( &trace ) ) + return; + + //make sure that the surfNormal is reset to the ground + VectorCopy( refNormal, pm->ps->grapplePoint ); + + // if the trace didn't hit anything, we are in free fall + if( trace.fraction == 1.0f ) + { + qboolean steppedDown = qfalse; + + // try to step down + if( pml.groundPlane != qfalse && PM_PredictStepMove( ) ) + { + //step down + point[ 0 ] = pm->ps->origin[ 0 ]; + point[ 1 ] = pm->ps->origin[ 1 ]; + point[ 2 ] = pm->ps->origin[ 2 ] - STEPSIZE; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask ); + + //if we hit something + if( trace.fraction < 1.0f ) + { + PM_StepEvent( pm->ps->origin, trace.endpos, refNormal ); + VectorCopy( trace.endpos, pm->ps->origin ); + steppedDown = qtrue; + } + } + + if( !steppedDown ) + { + PM_GroundTraceMissed( ); + 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; + } + } + + // check if getting thrown off the ground + if( pm->ps->velocity[ 2 ] > 0.0f && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10.0f ) + { + if( pm->debugLevel ) + Com_Printf( "%i:kickoff\n", c_pmove ); + + // go into jump animation + if( pm->cmd.forwardmove >= 0 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMP ); + else + PM_ForceLegsAnim( NSPA_JUMP ); + + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ForceLegsAnim( LEGS_JUMPB ); + else + PM_ForceLegsAnim( NSPA_JUMPBACK ); + + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // slopes that are too steep will not be considered onground + if( trace.plane.normal[ 2 ] < MIN_WALK_NORMAL ) + { + if( pm->debugLevel ) + Com_Printf( "%i:steep\n", c_pmove ); + + // FIXME: if they can't slide down the slope, let them + // walk (sharp crevices) + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qtrue; + pml.walking = qfalse; + return; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // 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_time = 0; + } + + if( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + // just hit the ground + if( pm->debugLevel ) + Com_Printf( "%i:Land\n", c_pmove ); + + if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_TAKESFALLDAMAGE ) ) + PM_CrashLand( ); + + // 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; + } + } + + pm->ps->groundEntityNum = trace.entityNum; + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + PM_AddTouchEnt( trace.entityNum ); +} + + +/* +============= +PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving +============= +*/ +static void PM_SetWaterLevel( void ) +{ + vec3_t point; + int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + pm->waterlevel = 0; + pm->watertype = 0; + + point[ 0 ] = pm->ps->origin[ 0 ]; + point[ 1 ] = pm->ps->origin[ 1 ]; + point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + 1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if( cont & MASK_WATER ) + { + sample2 = pm->ps->viewheight - MINS_Z; + sample1 = sample2 / 2; + + pm->watertype = cont; + pm->waterlevel = 1; + point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + sample1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if( cont & MASK_WATER ) + { + pm->waterlevel = 2; + point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + sample2; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if( cont & MASK_WATER ) + pm->waterlevel = 3; + } + } +} + + + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->ps->viewheight +============== +*/ +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; + + pm->mins[ 0 ] = PCmins[ 0 ]; + pm->mins[ 1 ] = PCmins[ 1 ]; + + pm->maxs[ 0 ] = PCmaxs[ 0 ]; + pm->maxs[ 1 ] = PCmaxs[ 1 ]; + + pm->mins[ 2 ] = PCmins[ 2 ]; + + if( pm->ps->pm_type == PM_DEAD ) + { + pm->maxs[ 2 ] = -8; + pm->ps->viewheight = 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 ) ) + { + // duck + pm->ps->pm_flags |= PMF_DUCKED; + } + else + { + // stand up if possible + if( pm->ps->pm_flags & PMF_DUCKED ) + { + // try to stand up + pm->maxs[ 2 ] = PCmaxs[ 2 ]; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if( !trace.allsolid ) + pm->ps->pm_flags &= ~PMF_DUCKED; + } + } + + 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_Footsteps +=============== +*/ +static void PM_Footsteps( void ) +{ + float bobmove; + int old; + qboolean footstep; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) && ( pml.groundPlane ) ) + { + //TA: 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 ] ); + } + else + pm->xyspeed = sqrt( pm->ps->velocity[ 0 ] * pm->ps->velocity[ 0 ] + + pm->ps->velocity[ 1 ] * pm->ps->velocity[ 1 ] ); + + if( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + // airborne leaves position in cycle intact, but doesn't advance + if( pm->waterlevel > 1 ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_SWIM ); + else + PM_ContinueLegsAnim( NSPA_SWIM ); + } + + return; + } + + // if not trying to move + if( !pm->cmd.forwardmove && !pm->cmd.rightmove ) + { + if( pm->xyspeed < 5 ) + { + pm->ps->bobCycle = 0; // start at beginning of cycle again + if( pm->ps->pm_flags & PMF_DUCKED ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_IDLECR ); + else + PM_ContinueLegsAnim( NSPA_STAND ); + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_IDLE ); + else + PM_ContinueLegsAnim( NSPA_STAND ); + } + } + return; + } + + + footstep = qfalse; + + if( pm->ps->pm_flags & PMF_DUCKED ) + { + bobmove = 0.5; // ducked characters bob much faster + + if( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_BACKCR ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKLEFT ); + else + PM_ContinueLegsAnim( NSPA_WALKBACK ); + } + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_WALKCR ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKLEFT ); + else + PM_ContinueLegsAnim( NSPA_WALK ); + } + } + + // ducked characters never play footsteps + } + else + { + if( !( pm->cmd.buttons & BUTTON_WALKING ) ) + { + bobmove = 0.4f; // faster speeds bob faster + + if( pm->ps->weapon == WP_ALEVEL4 && pm->ps->pm_flags & PMF_CHARGE ) + PM_ContinueLegsAnim( NSPA_CHARGE ); + else if( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_BACK ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_RUNRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_RUNLEFT ); + else + PM_ContinueLegsAnim( NSPA_RUNBACK ); + } + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_RUN ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_RUNRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_RUNLEFT ); + else + PM_ContinueLegsAnim( NSPA_RUN ); + } + } + + footstep = qtrue; + } + else + { + bobmove = 0.3f; // walking bobs slow + if( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_BACKWALK ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKLEFT ); + else + PM_ContinueLegsAnim( NSPA_WALKBACK ); + } + } + else + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_ContinueLegsAnim( LEGS_WALK ); + else + { + if( pm->cmd.rightmove > 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKRIGHT ); + else if( pm->cmd.rightmove < 0 && !pm->cmd.forwardmove ) + PM_ContinueLegsAnim( NSPA_WALKLEFT ); + else + PM_ContinueLegsAnim( NSPA_WALK ); + } + } + } + } + + bobmove *= BG_FindBobCycleForClass( pm->ps->stats[ STAT_PCLASS ] ); + + if( pm->ps->stats[ STAT_STATE ] & SS_SPEEDBOOST ) + bobmove *= HUMAN_SPRINT_MODIFIER; + + // check for footstep / splash sounds + old = pm->ps->bobCycle; + pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; + + // if we just crossed a cycle boundary, play an apropriate footstep event + if( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) + { + if( pm->waterlevel == 0 ) + { + // on ground will only play sounds if running + if( footstep && !pm->noFootsteps ) + PM_AddEvent( PM_FootstepForSurface( ) ); + } + else if( pm->waterlevel == 1 ) + { + // splashing + PM_AddEvent( EV_FOOTSPLASH ); + } + else if( pm->waterlevel == 2 ) + { + // wading / swimming at surface + PM_AddEvent( EV_SWIM ); + } + else if( pm->waterlevel == 3 ) + { + // no sound when completely underwater + } + } +} + +/* +============== +PM_WaterEvents + +Generate sound events for entering and leaving water +============== +*/ +static void PM_WaterEvents( void ) +{ + // FIXME? + // + // if just entered a water volume, play a sound + // + if( !pml.previous_waterlevel && pm->waterlevel ) + PM_AddEvent( EV_WATER_TOUCH ); + + // + // if just completely exited a water volume, play a sound + // + if( pml.previous_waterlevel && !pm->waterlevel ) + PM_AddEvent( EV_WATER_LEAVE ); + + // + // check for head just going under water + // + if( pml.previous_waterlevel != 3 && pm->waterlevel == 3 ) + PM_AddEvent( EV_WATER_UNDER ); + + // + // check for head just coming out of water + // + if( pml.previous_waterlevel == 3 && pm->waterlevel != 3 ) + PM_AddEvent( EV_WATER_CLEAR ); +} + + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +static void PM_BeginWeaponChange( int weapon ) +{ + if( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) + return; + + if( !BG_InventoryContainsWeapon( weapon, pm->ps->stats ) && weapon != WP_NONE ) + return; + + if( pm->ps->weaponstate == WEAPON_DROPPING ) + return; + + PM_AddEvent( EV_CHANGE_WEAPON ); + pm->ps->weaponstate = WEAPON_DROPPING; + pm->ps->weaponTime += 200; + pm->ps->persistant[ PERS_NEWWEAPON ] = weapon; + + //reset build weapon + pm->ps->stats[ STAT_BUILDABLE ] = BA_NONE; + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_StartTorsoAnim( TORSO_DROP ); +} + + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +static void PM_FinishWeaponChange( void ) +{ + int weapon; + + weapon = pm->ps->persistant[ PERS_NEWWEAPON ]; + if( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) + weapon = WP_NONE; + + if( !BG_InventoryContainsWeapon( weapon, pm->ps->stats ) ) + weapon = WP_NONE; + + pm->ps->weapon = weapon; + pm->ps->weaponstate = WEAPON_RAISING; + pm->ps->weaponTime += 250; + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + PM_StartTorsoAnim( TORSO_RAISE ); +} + + +/* +============== +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 ); + } +} + + +/* +============== +PM_Weapon + +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; + + // don't allow attack until all buttons are up + if( pm->ps->pm_flags & PMF_RESPAWNED ) + return; + + // ignore if spectator + if( pm->ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) + return; + + if( pm->ps->stats[ STAT_STATE ] & SS_INFESTING ) + return; + + if( pm->ps->stats[ STAT_STATE ] & SS_HOVELING ) + return; + + // check for dead player + if( pm->ps->stats[ STAT_HEALTH ] <= 0 ) + { + pm->ps->weapon = WP_NONE; + return; + } + + // make weapon function + if( pm->ps->weaponTime > 0 ) + pm->ps->weaponTime -= pml.msec; + + // 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 ) + { + //TA: 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 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 ) + { + //if trying to toggle an upgrade, toggle it + if( BG_InventoryContainsUpgrade( pm->cmd.weapon - 32, pm->ps->stats ) ) //sanity check + { + if( BG_UpgradeIsActive( pm->cmd.weapon - 32, pm->ps->stats ) ) + BG_DeactivateUpgrade( pm->cmd.weapon - 32, pm->ps->stats ); + else + BG_ActivateUpgrade( pm->cmd.weapon - 32, pm->ps->stats ); + } + } + pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + } + } + else + pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; + + //something external thinks a weapon change is necessary + 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->weaponTime > 0 ) + return; + + // change weapon if time + if( pm->ps->weaponstate == WEAPON_DROPPING ) + { + PM_FinishWeaponChange( ); + return; + } + + 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 ) + PM_ContinueTorsoAnim( TORSO_STAND2 ); + else + PM_ContinueTorsoAnim( TORSO_STAND ); + } + + return; + } + + // start the animation even if out of ammo + + BG_UnpackAmmoArray( pm->ps->weapon, pm->ps->ammo, pm->ps->powerups, &ammo, &clips ); + BG_FindAmmoForWeapon( pm->ps->weapon, NULL, &maxClips ); + + // check for out of ammo + if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) + { + PM_AddEvent( EV_NOAMMO ); + pm->ps->weaponTime += 200; + return; + } + + //done reloading so give em some ammo + if( pm->ps->weaponstate == WEAPON_RELOADING ) + { + if( maxClips > 0 ) + { + clips--; + BG_FindAmmoForWeapon( pm->ps->weapon, &ammo, NULL ); + } + + if( BG_FindUsesEnergyForWeapon( pm->ps->weapon ) && + BG_InventoryContainsUpgrade( UP_BATTPACK, pm->ps->stats ) ) + ammo = (int)( (float)ammo * BATTPACK_MODIFIER ); + + BG_PackAmmoArray( pm->ps->weapon, pm->ps->ammo, pm->ps->powerups, ammo, clips ); + + //allow some time for the weapon to be raised + pm->ps->weaponstate = WEAPON_RAISING; + PM_StartTorsoAnim( TORSO_RAISE ); + pm->ps->weaponTime += 250; + return; + } + + // check for end of clip + if( ( !ammo || pm->ps->pm_flags & PMF_WEAPON_RELOAD ) && clips ) + { + pm->ps->pm_flags &= ~PMF_WEAPON_RELOAD; + + pm->ps->weaponstate = WEAPON_RELOADING; + + //drop the weapon + PM_StartTorsoAnim( TORSO_DROP ); + + addTime = BG_FindReloadTimeForWeapon( pm->ps->weapon ); + + pm->ps->weaponTime += addTime; + return; + } + + //check if non-auto primary/secondary attacks are permited + switch( pm->ps->weapon ) + { + 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; + + 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; + 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; + + if( ( attack1 || pm->ps->stats[ STAT_MISC ] == 0 ) && !attack2 && !attack3 ) + { + if( pm->ps->stats[ STAT_MISC ] < LCANNON_TOTAL_CHARGE ) + { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + else + attack1 = !attack1; + } + + //erp this looks confusing + if( pm->ps->stats[ STAT_MISC ] > 0 ) + attack1 = !attack1; + 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; + pm->ps->weaponstate = WEAPON_READY; + return; + } + break; + } + + //TA: fire events for non auto weapons + if( attack3 ) + { + if( BG_WeaponHasThirdMode( pm->ps->weapon ) ) + { + //hacky special case for slowblob + if( pm->ps->weapon == WP_ALEVEL3_UPG && !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 ); + } + else + { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + } + else if( attack2 ) + { + if( BG_WeaponHasAltMode( pm->ps->weapon ) ) + { + pm->ps->generic1 = WPM_SECONDARY; + PM_AddEvent( EV_FIRE_WEAPON2 ); + addTime = BG_FindRepeatRate2ForWeapon( pm->ps->weapon ); + } + else + { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + } + else if( attack1 ) + { + pm->ps->generic1 = WPM_PRIMARY; + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = BG_FindRepeatRate1ForWeapon( pm->ps->weapon ); + } + + //TA: fire events for autohit weapons + if( pm->autoWeaponHit[ pm->ps->weapon ] ) + { + switch( pm->ps->weapon ) + { + case WP_ALEVEL0: + pm->ps->generic1 = WPM_PRIMARY; + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = BG_FindRepeatRate1ForWeapon( pm->ps->weapon ); + break; + + case WP_ALEVEL3: + case WP_ALEVEL3_UPG: + pm->ps->generic1 = WPM_SECONDARY; + PM_AddEvent( EV_FIRE_WEAPON2 ); + addTime = BG_FindRepeatRate2ForWeapon( pm->ps->weapon ); + break; + + default: + break; + } + } + + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + //FIXME: this should be an option in the client weapon.cfg + switch( pm->ps->weapon ) + { + case WP_FLAMER: + if( pm->ps->weaponstate == WEAPON_READY ) + { + PM_StartTorsoAnim( TORSO_ATTACK ); + } + break; + + case WP_BLASTER: + PM_StartTorsoAnim( TORSO_ATTACK2 ); + break; + + default: + PM_StartTorsoAnim( TORSO_ATTACK ); + 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; + + if( num == 0 ) + PM_ForceLegsAnim( NSPA_ATTACK1 ); + else if( num == 1 ) + PM_ForceLegsAnim( NSPA_ATTACK2 ); + else if( num == 2 ) + PM_ForceLegsAnim( NSPA_ATTACK3 ); + } + else + { + if( attack1 ) + PM_ForceLegsAnim( NSPA_ATTACK1 ); + else if( attack2 ) + PM_ForceLegsAnim( NSPA_ATTACK2 ); + else if( attack3 ) + PM_ForceLegsAnim( NSPA_ATTACK3 ); + } + + pm->ps->torsoTimer = TIMER_ATTACK; + } + + pm->ps->weaponstate = WEAPON_FIRING; + + // take an ammo away if not infinite + if( !BG_FindInfinteAmmoForWeapon( pm->ps->weapon ) ) + { + //special case for lCanon + if( pm->ps->weapon == WP_LUCIFER_CANNON && attack1 ) + { + 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; + } + else + ammo--; + + BG_PackAmmoArray( pm->ps->weapon, pm->ps->ammo, pm->ps->powerups, ammo, clips ); + } + else if( pm->ps->weapon == WP_ALEVEL3_UPG && attack3 ) + { + //special case for slowblob + ammo--; + BG_PackAmmoArray( pm->ps->weapon, pm->ps->ammo, pm->ps->powerups, ammo, clips ); + } + + //FIXME: predicted angles miss a problem?? + if( pm->ps->weapon == WP_CHAINGUN ) + { + if( pm->ps->pm_flags & PMF_DUCKED || + BG_InventoryContainsUpgrade( UP_BATTLESUIT, pm->ps->stats ) ) + { + pm->ps->delta_angles[ PITCH ] -= ANGLE2SHORT( ( ( random() * 0.5 ) - 0.125 ) * ( 30 / (float)addTime ) ); + pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( ( ( random() * 0.5 ) - 0.25 ) * ( 30.0 / (float)addTime ) ); + } + else + { + pm->ps->delta_angles[ PITCH ] -= ANGLE2SHORT( ( ( random() * 8 ) - 2 ) * ( 30.0 / (float)addTime ) ); + pm->ps->delta_angles[ YAW ] -= ANGLE2SHORT( ( ( random() * 8 ) - 4 ) * ( 30.0 / (float)addTime ) ); + } + } + + pm->ps->weaponTime += addTime; +} + +/* +================ +PM_Animate +================ +*/ +static void PM_Animate( void ) +{ + if( pm->cmd.buttons & BUTTON_GESTURE ) + { + if( !( pm->ps->persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) + { + if( pm->ps->torsoTimer == 0 ) + { + PM_StartTorsoAnim( TORSO_GESTURE ); + pm->ps->torsoTimer = TIMER_GESTURE; + + PM_AddEvent( EV_TAUNT ); + } + } + else + { + if( pm->ps->torsoTimer == 0 ) + { + PM_ForceLegsAnim( NSPA_GESTURE ); + pm->ps->torsoTimer = TIMER_GESTURE; + + PM_AddEvent( EV_TAUNT ); + } + } + } +} + + +/* +================ +PM_DropTimers +================ +*/ +static void PM_DropTimers( void ) +{ + // drop misc timing counter + if( pm->ps->pm_time ) + { + if( pml.msec >= pm->ps->pm_time ) + { + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } + else + pm->ps->pm_time -= pml.msec; + } + + // drop animation counter + if( pm->ps->legsTimer > 0 ) + { + pm->ps->legsTimer -= pml.msec; + + if( pm->ps->legsTimer < 0 ) + pm->ps->legsTimer = 0; + } + + if( pm->ps->torsoTimer > 0 ) + { + pm->ps->torsoTimer -= pml.msec; + + if( pm->ps->torsoTimer < 0 ) + pm->ps->torsoTimer = 0; + } +} + + +/* +================ +PM_UpdateViewAngles + +This can be used as another entry point when only the viewangles +are being updated instead of a full move +================ +*/ +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) +{ + short temp[ 3 ]; + int i; + vec3_t axis[ 3 ], rotaxis[ 3 ]; + vec3_t tempang; + + if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION ) + return; // no view changes at all + + if( ps->pm_type != PM_SPECTATOR && ps->stats[ STAT_HEALTH ] <= 0 ) + return; // no view changes at all + + // circularly clamp the angles with deltas + for( i = 0; i < 3; i++ ) + { + temp[ i ] = cmd->angles[ i ] + ps->delta_angles[ i ]; + + if( i == PITCH ) + { + // don't let the player look up or down more than 90 degrees + if( temp[ i ] > 16000 ) + { + ps->delta_angles[ i ] = 16000 - cmd->angles[ i ]; + temp[ i ] = 16000; + } + else if( temp[ i ] < -16000 ) + { + ps->delta_angles[ i ] = -16000 - cmd->angles[ i ]; + temp[ i ] = -16000; + } + } + tempang[ i ] = SHORT2ANGLE( temp[ i ] ); + } + + //convert viewangles -> axis + AnglesToAxis( tempang, axis ); + + if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) || + !BG_RotateAxis( ps->grapplePoint, axis, rotaxis, qfalse, + ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) ) + AxisCopy( axis, rotaxis ); + + //convert the new axis back to angles + AxisToAngles( rotaxis, tempang ); + + //force angles to -180 <= x <= 180 + for( i = 0; i < 3; i++ ) + { + while( tempang[ i ] > 180 ) + tempang[ i ] -= 360; + + while( tempang[ i ] < 180 ) + tempang[ i ] += 360; + } + + //actually set the viewangles + for( i = 0; i < 3; i++ ) + ps->viewangles[ i ] = tempang[ i ]; + + //pull the view into the lock point + if( ps->pm_type == PM_GRABBED && !BG_InventoryContainsUpgrade( UP_BATTLESUIT, ps->stats ) ) + { + vec3_t dir, angles; + + ByteToDir( ps->stats[ STAT_VIEWLOCK ], dir ); + vectoangles( dir, angles ); + + for( i = 0; i < 3; i++ ) + { + float diff = AngleSubtract( ps->viewangles[ i ], angles[ i ] ); + + while( diff > 180.0f ) + diff -= 360.0f; + while( diff < -180.0f ) + diff += 360.0f; + + if( diff < -90.0f ) + ps->delta_angles[ i ] += ANGLE2SHORT( fabs( diff ) - 90.0f ); + else if( diff > 90.0f ) + ps->delta_angles[ i ] -= ANGLE2SHORT( fabs( diff ) - 90.0f ); + + if( diff < 0.0f ) + ps->delta_angles[ i ] += ANGLE2SHORT( fabs( diff ) * 0.05f ); + else if( diff > 0.0f ) + ps->delta_angles[ i ] -= ANGLE2SHORT( fabs( diff ) * 0.05f ); + } + } +} + + +/* +================ +PmoveSingle + +================ +*/ +void trap_SnapVector( float *v ); + +void PmoveSingle( pmove_t *pmove ) +{ + int ammo, clips; + + pm = pmove; + + BG_UnpackAmmoArray( pm->ps->weapon, pm->ps->ammo, pm->ps->powerups, &ammo, &clips ); + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint fot the previous frame + c_pmove++; + + // clear results + pm->numtouch = 0; + pm->watertype = 0; + pm->waterlevel = 0; + + if( pm->ps->stats[ STAT_HEALTH ] <= 0 ) + pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies + + // make sure walking button is clear if they are running, to avoid + // proxy no-footsteps cheats + 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->eFlags |= EF_FIRING; + else + pm->ps->eFlags &= ~EF_FIRING; + + // 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->eFlags |= EF_FIRING2; + else + pm->ps->eFlags &= ~EF_FIRING2; + + // 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->eFlags |= EF_FIRING3; + else + pm->ps->eFlags &= ~EF_FIRING3; + + + // clear the respawned flag if attack and use are cleared + if( pm->ps->stats[STAT_HEALTH] > 0 && + !( pm->cmd.buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) ) + pm->ps->pm_flags &= ~PMF_RESPAWNED; + + // if talk button is down, dissallow all other input + // this is to prevent any possible intercept proxy from + // adding fake talk balloons + if( pmove->cmd.buttons & BUTTON_TALK ) + { + pmove->cmd.buttons = BUTTON_TALK; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + } + + // clear all pmove local vars + memset( &pml, 0, sizeof( pml ) ); + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; + + if( pml.msec < 1 ) + pml.msec = 1; + else if( pml.msec > 200 ) + pml.msec = 200; + + pm->ps->commandTime = pmove->cmd.serverTime; + + // save old org in case we get stuck + VectorCopy( pm->ps->origin, pml.previous_origin ); + + // save old velocity for crashlanding + VectorCopy( pm->ps->velocity, pml.previous_velocity ); + + pml.frametime = pml.msec * 0.001; + + AngleVectors( pm->ps->viewangles, pml.forward, pml.right, pml.up ); + + if( pm->cmd.upmove < 10 ) + { + // not holding jump + pm->ps->pm_flags &= ~PMF_JUMP_HELD; + } + + // decide if backpedaling animations should be used + if( pm->cmd.forwardmove < 0 ) + pm->ps->pm_flags |= PMF_BACKWARDS_RUN; + 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 ) + { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + + if( pm->ps->pm_type == PM_SPECTATOR ) + { + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + PM_CheckDuck( ); + PM_FlyMove( ); + PM_DropTimers( ); + return; + } + + if( pm->ps->pm_type == PM_NOCLIP ) + { + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + PM_NoclipMove( ); + PM_DropTimers( ); + return; + } + + 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 ) + return; // no movement at all + + // set watertype, and waterlevel + PM_SetWaterLevel( ); + pml.previous_waterlevel = pmove->waterlevel; + + // set mins, maxs, and viewheight + PM_CheckDuck( ); + + PM_CheckLadder( ); + + // set groundentity + PM_GroundTrace( ); + + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + if( pm->ps->pm_type == PM_DEAD || pm->ps->pm_type == PM_GRABBED ) + PM_DeadMove( ); + + PM_DropTimers( ); + + if( pm->ps->pm_type == PM_JETPACK ) + PM_JetPackMove( ); + else if( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) + PM_WaterJumpMove( ); + else if( pm->waterlevel > 1 ) + PM_WaterMove( ); + else if( pml.ladder ) + PM_LadderMove( ); + else if( pml.walking ) + { + if( BG_ClassHasAbility( pm->ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) && + ( pm->ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) ) + PM_ClimbMove( ); //TA: walking on any surface + else + PM_WalkMove( ); // walking on ground + } + else + PM_AirMove( ); + + PM_Animate( ); + + // 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 ); + + PM_SetWaterLevel( ); + + // weapons + PM_Weapon( ); + + // torso animation + PM_TorsoAnimation( ); + + // footstep events / legs animations + PM_Footsteps( ); + + // entering / leaving water splashes + PM_WaterEvents( ); + + // snap some parts of playerstate to save network bandwidth + trap_SnapVector( pm->ps->velocity ); +} + + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void Pmove( pmove_t *pmove ) +{ + int finalTime; + + finalTime = pmove->cmd.serverTime; + + if( finalTime < pmove->ps->commandTime ) + return; // should not happen + + if( finalTime > pmove->ps->commandTime + 1000 ) + pmove->ps->commandTime = finalTime - 1000; + + pmove->ps->pmove_framecount = ( pmove->ps->pmove_framecount + 1 ) & ( ( 1 << PS_PMOVEFRAMECOUNTBITS ) - 1 ); + + // chop the move up if it is too long, to prevent framerate + // dependent behavior + while( pmove->ps->commandTime != finalTime ) + { + int msec; + + msec = finalTime - pmove->ps->commandTime; + + if( pmove->pmove_fixed ) + { + if( msec > pmove->pmove_msec ) + msec = pmove->pmove_msec; + } + else + { + if( msec > 66 ) + 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 new file mode 100644 index 00000000..fa84b440 --- /dev/null +++ b/src/game/bg_public.h @@ -0,0 +1,1299 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_public.h -- definitions shared by both the server game and client game modules + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +//tremulous balance header +#include "tremulous.h" + +// because games can change separately from the main system version, we need a +// second version that must match between game and cgame +#define GAME_VERSION "tremulous" + +#define DEFAULT_GRAVITY 800 + +#define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present + +#define VOTE_TIME 30000 // 30 seconds before vote times out + +#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 + +// +// config strings are a general means of communicating variable length strings +// from the server to all connected clients. +// + +// 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 +#define CS_SCORES1 6 +#define CS_SCORES2 7 +#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_FLAGSTATUS 23 // string indicating flag status in CTF +#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_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_PARTICLE_FILES (CS_LOCATIONS+MAX_LOCATIONS) + +#define CS_MAX (CS_PARTICLE_FILES+MAX_PARTICLE_FILES) + +#if (CS_MAX) > MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +typedef enum +{ + GENDER_MALE, + GENDER_FEMALE, + GENDER_NEUTER +} gender_t; + +/* +=================================================================================== + +PMOVE MODULE + +The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t +and some other output data. Used for local prediction on the client game and true +movement on the server game. +=================================================================================== +*/ + +typedef enum +{ + PM_NORMAL, // can accelerate and turn + 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_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 +} pmtype_t; + +typedef enum +{ + WEAPON_READY, + WEAPON_RAISING, + WEAPON_DROPPING, + WEAPON_FIRING, + 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_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK|PMF_TIME_WALLJUMP) + +#define MAXTOUCH 32 +typedef struct +{ + // state (in / out) + playerState_t *ps; + + // command (in) + usercmd_t cmd; + 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 + + int framecount; + + // results (out) + int numtouch; + int touchents[ MAXTOUCH ]; + + vec3_t mins, maxs; // bounding box size + + int watertype; + int waterlevel; + + float xyspeed; + + // for fixed msec Pmove + int pmove_fixed; + int pmove_msec; + + // callbacks to test the world + // these will be different functions during game and cgame + /*void (*trace)( trace_t *results, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask );*/ + void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, + const vec3_t end, int passEntityNum, int contentMask ); + + + int (*pointcontents)( const vec3_t point, int passEntityNum ); +} pmove_t; + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); +void Pmove( pmove_t *pmove ); + +//=================================================================================== + + +// player_state->stats[] indexes +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 +} 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 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 BOOST_TIME 20000 + +// player_state->persistant[] indexes +// these fields are the only part of player_state that isn't +// cleared on respawn +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_RANK, + PERS_TEAM, + 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 +} persEnum_t; + +#define PS_WALLCLIMBINGFOLLOW 0x00000001 +#define PS_WALLCLIMBINGTOGGLE 0x00000002 +#define PS_NONSEGMODEL 0x00000004 + +// 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 + +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; + +typedef enum +{ + HI_NONE, + + HI_TELEPORTER, + HI_MEDKIT, + + HI_NUM_HOLDABLE +} holdable_t; + +typedef enum +{ + WPM_NONE, + + WPM_PRIMARY, + WPM_SECONDARY, + WPM_TERTIARY, + + WPM_NUM_WEAPONMODES +} weaponMode_t; + +typedef enum +{ + WP_NONE, + + WP_ALEVEL0, + WP_ALEVEL1, + WP_ALEVEL1_UPG, + WP_ALEVEL2, + WP_ALEVEL2_UPG, + WP_ALEVEL3, + WP_ALEVEL3_UPG, + WP_ALEVEL4, + + WP_BLASTER, + WP_MACHINEGUN, + WP_PAIN_SAW, + WP_SHOTGUN, + WP_LAS_GUN, + WP_MASS_DRIVER, + WP_CHAINGUN, + WP_PULSE_RIFLE, + WP_FLAMER, + WP_LUCIFER_CANNON, + WP_GRENADE, + + WP_LOCKBLOB_LAUNCHER, + WP_HIVE, + WP_TESLAGEN, + WP_MGTURRET, + + //build weapons must remain in a block + WP_ABUILD, + WP_ABUILD2, + WP_HBUILD2, + WP_HBUILD, + //ok? + + WP_NUM_WEAPONS +} weapon_t; + +typedef enum +{ + UP_NONE, + + UP_LIGHTARMOUR, + UP_HELMET, + UP_MEDKIT, + UP_BATTPACK, + UP_JETPACK, + UP_BATTLESUIT, + UP_GRENADE, + + UP_AMMO, + + UP_NUM_UPGRADES +} upgrade_t; + +typedef enum +{ + WUT_NONE, + + WUT_ALIENS, + WUT_HUMANS, + + WUT_NUM_TEAMS +} WUTeam_t; + +//TA: bitmasks for upgrade slots +#define SLOT_NONE 0x00000000 +#define SLOT_HEAD 0x00000001 +#define SLOT_TORSO 0x00000002 +#define SLOT_ARMS 0x00000004 +#define SLOT_LEGS 0x00000008 +#define SLOT_BACKPACK 0x00000010 +#define SLOT_WEAPON 0x00000020 +#define SLOT_SIDEARM 0x00000040 + +typedef enum +{ + BA_NONE, + + BA_A_SPAWN, + BA_A_OVERMIND, + + BA_A_BARRICADE, + BA_A_ACIDTUBE, + BA_A_TRAPPER, + BA_A_BOOSTER, + BA_A_HIVE, + + BA_A_HOVEL, + + BA_H_SPAWN, + + BA_H_MGTURRET, + BA_H_TESLAGEN, + + BA_H_ARMOURY, + BA_H_DCC, + BA_H_MEDISTAT, + + BA_H_REACTOR, + BA_H_REPEATER, + + BA_NUM_BUILDABLES +} buildable_t; + +typedef enum +{ + BIT_NONE, + + BIT_ALIENS, + BIT_HUMANS, + + BIT_NUM_TEAMS +} buildableTeam_t; + +#define B_HEALTH_BITS 5 +#define B_HEALTH_SCALE (float)((1<persistant[PERS_PLAYEREVENTS]) +#define PLAYEREVENT_DENIEDREWARD 0x0001 +#define PLAYEREVENT_GAUNTLETREWARD 0x0002 +#define PLAYEREVENT_HOLYSHIT 0x0004 + +// entityState_t->event values +// entity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. + +// two bits at the top of the entityState->event field +// will be incremented with each change in the event so +// that an identical event started twice in a row can +// be distinguished. And off the value with ~EV_EVENT_BITS +// to retrieve the actual event number +#define EV_EVENT_BIT1 0x00000100 +#define EV_EVENT_BIT2 0x00000200 +#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) + +#define EVENT_VALID_MSEC 300 + +typedef enum +{ + EV_NONE, + + EV_FOOTSTEP, + EV_FOOTSTEP_METAL, + EV_FOOTSTEP_SQUELCH, + EV_FOOTSPLASH, + EV_FOOTWADE, + EV_SWIM, + + EV_STEP_4, + EV_STEP_8, + EV_STEP_12, + EV_STEP_16, + + EV_STEPDN_4, + EV_STEPDN_8, + EV_STEPDN_12, + EV_STEPDN_16, + + EV_FALL_SHORT, + EV_FALL_MEDIUM, + EV_FALL_FAR, + EV_FALLING, + + EV_JUMP, + EV_WATER_TOUCH, // foot touches + EV_WATER_LEAVE, // foot leaves + EV_WATER_UNDER, // head touches + EV_WATER_CLEAR, // head leaves + + EV_NOAMMO, + EV_CHANGE_WEAPON, + EV_FIRE_WEAPON, + EV_FIRE_WEAPON2, + EV_FIRE_WEAPON3, + + EV_PLAYER_RESPAWN, //TA: for fovwarp effects + EV_PLAYER_TELEPORT_IN, + EV_PLAYER_TELEPORT_OUT, + + EV_GRENADE_BOUNCE, // eventParm will be the soundindex + + EV_GENERAL_SOUND, + EV_GLOBAL_SOUND, // no attenuation + + EV_BULLET_HIT_FLESH, + EV_BULLET_HIT_WALL, + + EV_SHOTGUN, + + EV_MISSILE_HIT, + EV_MISSILE_MISS, + EV_MISSILE_MISS_METAL, + EV_TESLATRAIL, + EV_BULLET, // otherEntity is the shooter + + EV_LEV1_GRAB, + EV_LEV4_CHARGE_PREPARE, + EV_LEV4_CHARGE_START, + + EV_PAIN, + EV_DEATH1, + EV_DEATH2, + EV_DEATH3, + EV_OBITUARY, + + EV_GIB_PLAYER, // gib a previously living 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_HUMAN_BUILDABLE_EXPLOSION, + EV_ALIEN_BUILDABLE_EXPLOSION, + EV_ALIEN_ACIDTUBE, + + EV_MEDKIT_USED, + + EV_ALIEN_EVOLVE, + EV_ALIEN_EVOLVE_FAILED, + + EV_DEBUG_LINE, + 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_DCC_ATTACK, //TA: dcc under attack + + EV_RPTUSE_SOUND //TA: trigger a sound +} entity_event_t; + +typedef enum +{ + MN_TEAM, + MN_A_TEAMFULL, + MN_H_TEAMFULL, + + //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, + + //alien build + MN_A_SPWNWARN, + MN_A_OVERMIND, + MN_A_NOASSERT, + 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_NOSLOTS, + MN_H_NOFUNDS, + MN_H_ITEMHELD, + + //human build + MN_H_REPEATER, + MN_H_NOPOWER, + MN_H_NOTPOWERED, + MN_H_NODCC, + MN_H_REACTOR, + MN_H_NOROOM, + MN_H_NORMAL, + MN_H_TNODEWARN, + MN_H_RPTWARN, + MN_H_RPTWARN2 +} dynMenu_t; + +// animations +typedef enum +{ + BOTH_DEATH1, + BOTH_DEAD1, + BOTH_DEATH2, + BOTH_DEAD2, + BOTH_DEATH3, + BOTH_DEAD3, + + TORSO_GESTURE, + + TORSO_ATTACK, + TORSO_ATTACK2, + + TORSO_DROP, + TORSO_RAISE, + + TORSO_STAND, + TORSO_STAND2, + + LEGS_WALKCR, + LEGS_WALK, + LEGS_RUN, + LEGS_BACK, + LEGS_SWIM, + + LEGS_JUMP, + LEGS_LAND, + + LEGS_JUMPB, + LEGS_LANDB, + + LEGS_IDLE, + LEGS_IDLECR, + + LEGS_TURN, + + TORSO_GETFLAG, + TORSO_GUARDBASE, + TORSO_PATROL, + TORSO_FOLLOWME, + TORSO_AFFIRMATIVE, + TORSO_NEGATIVE, + + MAX_PLAYER_ANIMATIONS, + + LEGS_BACKCR, + LEGS_BACKWALK, + FLAG_RUN, + FLAG_STAND, + FLAG_STAND2RUN, + + MAX_PLAYER_TOTALANIMATIONS +} playerAnimNumber_t; + +// nonsegmented animations +typedef enum +{ + NSPA_STAND, + + NSPA_GESTURE, + + NSPA_WALK, + NSPA_RUN, + NSPA_RUNBACK, + NSPA_CHARGE, + + NSPA_RUNLEFT, + NSPA_WALKLEFT, + NSPA_RUNRIGHT, + NSPA_WALKRIGHT, + + NSPA_SWIM, + + NSPA_JUMP, + NSPA_LAND, + NSPA_JUMPBACK, + NSPA_LANDBACK, + + NSPA_TURN, + + NSPA_ATTACK1, + NSPA_ATTACK2, + NSPA_ATTACK3, + + NSPA_PAIN1, + NSPA_PAIN2, + + NSPA_DEATH1, + NSPA_DEAD1, + NSPA_DEATH2, + NSPA_DEAD2, + NSPA_DEATH3, + NSPA_DEAD3, + + MAX_NONSEG_PLAYER_ANIMATIONS, + + NSPA_WALKBACK, + + MAX_NONSEG_PLAYER_TOTALANIMATIONS +} nonSegPlayerAnimNumber_t; + +//TA: for buildable animations +typedef enum +{ + BANIM_NONE, + + BANIM_CONSTRUCT1, + BANIM_CONSTRUCT2, + + BANIM_IDLE1, + BANIM_IDLE2, + BANIM_IDLE3, + + BANIM_ATTACK1, + BANIM_ATTACK2, + + BANIM_SPAWN1, + BANIM_SPAWN2, + + BANIM_PAIN1, + BANIM_PAIN2, + + BANIM_DESTROY1, + BANIM_DESTROY2, + BANIM_DESTROYED, + + MAX_BUILDABLE_ANIMATIONS +} buildableAnimNumber_t; + +typedef struct animation_s +{ + int firstFrame; + int numFrames; + int loopFrames; // 0 to numFrames + int frameLerp; // msec between frames + int initialLerp; // msec to get to first frame + int reversed; // true if animation is reversed + int flipflop; // true if animation should flipflop back to base +} animation_t; + + +// flip the togglebit every time an animation +// changes so a restart of the same anim can be detected +#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 + +// How many players on the overlay +#define TEAM_MAXOVERLAY 32 + +//TA: player classes +typedef enum +{ + PCL_NONE, + + //builder classes + PCL_ALIEN_BUILDER0, + PCL_ALIEN_BUILDER0_UPG, + + //offensive classes + PCL_ALIEN_LEVEL0, + PCL_ALIEN_LEVEL1, + PCL_ALIEN_LEVEL1_UPG, + PCL_ALIEN_LEVEL2, + PCL_ALIEN_LEVEL2_UPG, + PCL_ALIEN_LEVEL3, + PCL_ALIEN_LEVEL3_UPG, + PCL_ALIEN_LEVEL4, + + //human class + PCL_HUMAN, + PCL_HUMAN_BSUIT, + + PCL_NUM_CLASSES +} pClass_t; + + +//TA: player teams +typedef enum +{ + PTE_NONE, + PTE_ALIENS, + PTE_HUMANS, + + PTE_NUM_TEAMS +} pTeam_t; + + +// means of death +typedef enum +{ + MOD_UNKNOWN, + MOD_SHOTGUN, + MOD_BLASTER, + MOD_PAINSAW, + MOD_MACHINEGUN, + MOD_CHAINGUN, + MOD_PRIFLE, + MOD_MDRIVER, + MOD_LASGUN, + MOD_LCANNON, + MOD_LCANNON_SPLASH, + MOD_FLAMER, + MOD_FLAMER_SPLASH, + MOD_GRENADE, + MOD_WATER, + MOD_SLIME, + MOD_LAVA, + MOD_CRUSH, + MOD_TELEFRAG, + MOD_FALLING, + MOD_SUICIDE, + MOD_TARGET_LASER, + MOD_TRIGGER_HURT, + + MOD_ABUILDER_CLAW, + MOD_LEVEL0_BITE, + MOD_LEVEL1_CLAW, + MOD_LEVEL1_PCLOUD, + MOD_LEVEL3_CLAW, + MOD_LEVEL3_POUNCE, + MOD_LEVEL3_BOUNCEBALL, + MOD_LEVEL2_CLAW, + MOD_LEVEL2_ZAP, + MOD_LEVEL4_CLAW, + MOD_LEVEL4_CHARGE, + + MOD_SLOWBLOB, + MOD_POISON, + MOD_SWARM, + + MOD_HSPAWN, + MOD_TESLAGEN, + MOD_MGTURRET, + MOD_REACTOR, + + MOD_ASPAWN, + MOD_ATUBE, + MOD_OVERMIND +} meansOfDeath_t; + + +//--------------------------------------------------------- + +//TA: player class record +typedef struct +{ + int classNum; + + char *className; + char *humanName; + + char *modelName; + float modelScale; + char *skinName; + float shadowScale; + + char *hudName; + + 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; + + int abilities; + + weapon_t startWeapon; + + float buildDist; + + int fov; + float bob; + float bobCycle; + int steptime; + + float speed; + float acceleration; + float airAcceleration; + float friction; + float stopSpeed; + float jumpMagnitude; + float knockbackScale; + + int children[ 3 ]; + int cost; + int value; +} classAttributes_t; + +typedef struct +{ + char modelName[ MAX_QPATH ]; + float modelScale; + char skinName[ MAX_QPATH ]; + float shadowScale; + char hudName[ MAX_QPATH ]; + char humanName[ MAX_STRING_CHARS ]; + + vec3_t mins; + vec3_t maxs; + vec3_t crouchMaxs; + vec3_t deadMins; + vec3_t deadMaxs; + float zOffset; +} classAttributeOverrides_t; + +//stages +typedef enum +{ + S1, + S2, + S3 +} stage_t; + +#define MAX_BUILDABLE_MODELS 4 + +//TA: buildable item record +typedef struct +{ + int buildNum; + + char *buildName; + char *humanName; + char *entityName; + + char *models[ MAX_BUILDABLE_MODELS ]; + float modelScale; + + vec3_t mins; + vec3_t maxs; + float zOffset; + + trType_t traj; + float bounce; + + int buildPoints; + int stages; + + int health; + int regenRate; + + int splashDamage; + int splashRadius; + + int meansOfDeath; + + int team; + weapon_t buildWeapon; + + int idleAnim; + + int nextthink; + int buildTime; + qboolean usable; + + int turretRange; + int turretFireSpeed; + weapon_t turretProjType; + + float minNormal; + qboolean invertNormal; + + qboolean creepTest; + int creepSize; + + qboolean dccTest; + qboolean reactorTest; +} buildableAttributes_t; + +typedef struct +{ + char models[ MAX_BUILDABLE_MODELS ][ MAX_QPATH ]; + + float modelScale; + vec3_t mins; + vec3_t maxs; + float zOffset; +} buildableAttributeOverrides_t; + +//TA: weapon record +typedef struct +{ + int weaponNum; + + int price; + int stages; + + int slots; + + char *weaponName; + char *weaponHumanName; + + int maxAmmo; + int maxClips; + qboolean infiniteAmmo; + qboolean usesEnergy; + + int repeatRate1; + int repeatRate2; + int repeatRate3; + int reloadTime; + + qboolean hasAltMode; + qboolean hasThirdMode; + + qboolean canZoom; + float zoomFov; + + qboolean purchasable; + + int buildDelay; + + WUTeam_t team; +} weaponAttributes_t; + +//TA: upgrade record +typedef struct +{ + int upgradeNum; + + int price; + int stages; + + int slots; + + char *upgradeName; + char *upgradeHumanName; + + char *icon; + + qboolean purchasable; + + WUTeam_t team; +} upgradeAttributes_t; + + +//TA: +void BG_UnpackAmmoArray( int weapon, int psAmmo[ ], int psAmmo2[ ], int *ammo, int *clips ); +void BG_PackAmmoArray( int weapon, int psAmmo[ ], int psAmmo2[ ], int ammo, int clips ); +qboolean BG_WeaponIsFull( weapon_t weapon, int stats[ ], int psAmmo[ ], int psAmmo2[ ] ); +void BG_AddWeaponToInventory( int weapon, int stats[ ] ); +void BG_RemoveWeaponFromInventory( int weapon, int stats[ ] ); +qboolean BG_InventoryContainsWeapon( int weapon, int stats[ ] ); +void BG_AddUpgradeToInventory( int item, int stats[ ] ); +void BG_RemoveUpgradeFromInventory( int item, int stats[ ] ); +qboolean BG_InventoryContainsUpgrade( int item, int stats[ ] ); +void BG_ActivateUpgrade( int item, int stats[ ] ); +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_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_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 ); +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 ); +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 ); +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 ); +WUTeam_t BG_FindTeamForUpgrade( int upgrade ); + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY) + + +// +// entityState_t->eType +// +typedef enum +{ + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + + ET_BUILDABLE, //TA: buildable type + + ET_MISSILE, + ET_MOVER, + ET_BEAM, + ET_PORTAL, + ET_SPEAKER, + ET_PUSH_TRIGGER, + ET_TELEPORT_TRIGGER, + ET_INVISIBLE, + ET_GRAPPLE, // grapple hooked on wall + + ET_CORPSE, + ET_PARTICLE_SYSTEM, + ET_ANIMMAPOBJ, + ET_MODELDOOR, + ET_LIGHTFLARE, + ET_LEV2_ZAP_CHAIN, + + 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_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); + +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); + +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); + +#define ARENAS_PER_TIER 4 +#define MAX_ARENAS 1024 +#define MAX_ARENAS_TEXT 8192 + +#define MAX_BOTS 1024 +#define MAX_BOTS_TEXT 8192 + +//TA: conceptually should live in q_shared.h +void AxisToAngles( vec3_t axis[3], vec3_t angles ); +#define Vector2Set(v, x, y) ((v)[0]=(x), (v)[1]=(y)) +float pointToLineDistance( const vec3_t point, const vec3_t p1, const vec3_t p2 ); +#define MAX(x,y) (x)>(y)?(x):(y) +#define MIN(x,y) (x)<(y)?(x):(y) + + +// Ridah +void GetPerpendicularViewVector( const vec3_t point, const vec3_t p1, + const vec3_t p2, vec3_t up ); +void ProjectPointOntoVector( vec3_t point, vec3_t vStart, + vec3_t vEnd, vec3_t vProj ); +float VectorDistance( vec3_t v1, vec3_t v2 ); +// done. + +//call roundf in place of round on non VM platforms +#ifdef Q3_VM +#define roundf round +#else +#define round roundf +#endif + +#define M_ROOT3 1.732050808f +float VectorMinComponent( vec3_t v ); +float VectorMaxComponent( vec3_t v ); +float round( float v ); + +float atof_neg( char *token, qboolean allowNegative ); +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_ParseCSVBuildableList( const char *string, buildable_t *buildables, int buildablesSize ); diff --git a/src/game/bg_slidemove.c b/src/game/bg_slidemove.c new file mode 100644 index 00000000..47f1ec04 --- /dev/null +++ b/src/game/bg_slidemove.c @@ -0,0 +1,408 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_slidemove.c -- part of bg_pmove functionality + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +/* + +input: origin, velocity, bounds, groundPlane, trace function + +output: origin, velocity, impacts, stairup boolean + +*/ + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 +qboolean PM_SlideMove( qboolean gravity ) +{ + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + vec3_t clipVelocity; + int i, j, k; + trace_t trace; + vec3_t end; + float time_left; + float into; + vec3_t endVelocity; + vec3_t endClipVelocity; + + numbumps = 4; + + VectorCopy( pm->ps->velocity, primal_velocity ); + + if( gravity ) + { + VectorCopy( pm->ps->velocity, endVelocity ); + endVelocity[ 2 ] -= pm->ps->gravity * pml.frametime; + pm->ps->velocity[ 2 ] = ( pm->ps->velocity[ 2 ] + endVelocity[ 2 ] ) * 0.5; + primal_velocity[ 2 ] = endVelocity[ 2 ]; + + if( pml.groundPlane ) + { + // slide along the ground plane + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + + time_left = pml.frametime; + + // never turn against the ground plane + if( pml.groundPlane ) + { + numplanes = 1; + VectorCopy( pml.groundTrace.plane.normal, planes[ 0 ] ); + } + else + numplanes = 0; + + // never turn against original velocity + VectorNormalize2( pm->ps->velocity, planes[ numplanes ] ); + numplanes++; + + for( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) + { + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // see if we can make it there + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask ); + + if( trace.allsolid ) + { + // entity is completely trapped in another solid + pm->ps->velocity[ 2 ] = 0; // don't build up falling damage, but allow sideways acceleration + return qtrue; + } + + if( trace.fraction > 0 ) + { + // actually covered some distance + VectorCopy( trace.endpos, pm->ps->origin ); + } + + if( trace.fraction == 1 ) + break; // moved the entire distance + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + time_left -= time_left * trace.fraction; + + if( numplanes >= MAX_CLIP_PLANES ) + { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for( i = 0 ; i < numplanes ; i++ ) + { + if( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) + { + VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + + if( i < numplanes ) + continue; + + VectorCopy( trace.plane.normal, planes[ numplanes ] ); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for( i = 0; i < numplanes; i++ ) + { + into = DotProduct( pm->ps->velocity, planes[ i ] ); + if( into >= 0.1 ) + continue; // move doesn't interact with the plane + + // see how hard we are hitting things + if( -into > pml.impactSpeed ) + pml.impactSpeed = -into; + + // slide along the plane + PM_ClipVelocity( pm->ps->velocity, planes[ i ], clipVelocity, OVERCLIP ); + + // slide along the plane + PM_ClipVelocity( endVelocity, planes[ i ], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for( j = 0; j < numplanes; j++ ) + { + if( j == i ) + continue; + + if( DotProduct( clipVelocity, planes[ j ] ) >= 0.1 ) + 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 ); + + // see if it goes back into the first clip plane + if( DotProduct( clipVelocity, planes[ i ] ) >= 0 ) + continue; + + // slide the original velocity along the crease + CrossProduct( planes[ i ], planes[ j ], dir ); + VectorNormalize( dir ); + d = DotProduct( dir, pm->ps->velocity ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct( planes[ i ], planes[ j ], dir); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the the new move enters + for( k = 0; k < numplanes; k++ ) + { + if( k == i || k == j ) + continue; + + if( DotProduct( clipVelocity, planes[ k ] ) >= 0.1 ) + continue; // move doesn't interact with the plane + + // stop dead at a tripple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if( gravity ) + VectorCopy( endVelocity, pm->ps->velocity ); + + // don't change velocity if in a timer (FIXME: is this correct?) + if( pm->ps->pm_time ) + VectorCopy( primal_velocity, pm->ps->velocity ); + + return ( bumpcount != 0 ); +} + +/* +================== +PM_StepEvent +================== +*/ +void PM_StepEvent( vec3_t from, vec3_t to, vec3_t normal ) +{ + float size; + vec3_t delta, dNormal; + + VectorSubtract( from, to, delta ); + VectorCopy( delta, dNormal ); + VectorNormalize( dNormal ); + + size = DotProduct( normal, dNormal ) * VectorLength( delta ); + + if( size > 0.0f ) + { + if( size > 2.0f ) + { + if( size < 7.0f ) + PM_AddEvent( EV_STEPDN_4 ); + else if( size < 11.0f ) + PM_AddEvent( EV_STEPDN_8 ); + else if( size < 15.0f ) + PM_AddEvent( EV_STEPDN_12 ); + else + PM_AddEvent( EV_STEPDN_16 ); + } + } + else + { + size = fabs( size ); + + if( size > 2.0f ) + { + if( size < 7.0f ) + PM_AddEvent( EV_STEP_4 ); + else if( size < 11.0f ) + PM_AddEvent( EV_STEP_8 ); + else if( size < 15.0f ) + PM_AddEvent( EV_STEP_12 ); + else + PM_AddEvent( EV_STEP_16 ); + } + } + + if( pm->debugLevel ) + Com_Printf( "%i:stepped\n", c_pmove ); +} + +/* +================== +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; + vec3_t up, down; + 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 ); + + VectorCopy( pm->ps->origin, start_o ); + VectorCopy( pm->ps->velocity, start_v ); + + if( PM_SlideMove( gravity ) == 0 ) + { + VectorCopy( start_o, down ); + VectorMA( down, -STEPSIZE, normal, down ); + pm->trace( &trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask ); + + //we can step down + if( trace.fraction > 0.01f && trace.fraction < 1.0f && + !trace.allsolid && pml.groundPlane != qfalse ) + { + if( pm->debugLevel ) + Com_Printf( "%d: step down\n", c_pmove ); + + stepped = qtrue; + } + } + else + { + VectorCopy( start_o, down ); + VectorMA( down, -STEPSIZE, normal, down ); + pm->trace( &trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask ); + // never step up when you still have up velocity + if( DotProduct( trace.plane.normal, pm->ps->velocity ) > 0.0f && + ( trace.fraction == 1.0f || DotProduct( trace.plane.normal, normal ) < 0.7f ) ) + { + return stepped; + } + + VectorCopy( pm->ps->origin, down_o ); + VectorCopy( pm->ps->velocity, down_v ); + + VectorCopy( start_o, up ); + VectorMA( up, STEPSIZE, normal, up ); + + // test the player position if they were a stepheight higher + pm->trace( &trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask ); + if( trace.allsolid ) + { + if( pm->debugLevel ) + Com_Printf( "%i:bend can't step\n", c_pmove ); + + return stepped; // can't step up + } + + VectorSubtract( trace.endpos, start_o, step_v ); + VectorCopy( step_v, step_vNormal ); + VectorNormalize( step_vNormal ); + + stepSize = DotProduct( normal, step_vNormal ) * VectorLength( step_v ); + // try slidemove from this position + VectorCopy( trace.endpos, pm->ps->origin ); + VectorCopy( start_v, pm->ps->velocity ); + + if( PM_SlideMove( gravity ) == 0 ) + { + if( pm->debugLevel ) + Com_Printf( "%d: step up\n", c_pmove ); + + stepped = qtrue; + } + + // push down the final amount + VectorCopy( pm->ps->origin, down ); + VectorMA( down, -stepSize, normal, down ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask ); + + if( !trace.allsolid ) + VectorCopy( trace.endpos, pm->ps->origin ); + + if( trace.fraction < 1.0f ) + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + + if( !predictive && stepped ) + PM_StepEvent( start_o, pm->ps->origin, normal ); + + return stepped; +} + +/* +================== +PM_PredictStepMove +================== +*/ +qboolean PM_PredictStepMove( void ) +{ + vec3_t velocity, origin; + float impactSpeed; + qboolean stepped = qfalse; + + VectorCopy( pm->ps->velocity, velocity ); + VectorCopy( pm->ps->origin, origin ); + impactSpeed = pml.impactSpeed; + + if( PM_StepSlideMove( qfalse, qtrue ) ) + stepped = qtrue; + + VectorCopy( velocity, pm->ps->velocity ); + VectorCopy( origin, pm->ps->origin ); + pml.impactSpeed = impactSpeed; + + return stepped; +} diff --git a/src/game/g_active.c b/src/game/g_active.c new file mode 100644 index 00000000..fc66ccff --- /dev/null +++ b/src/game/g_active.c @@ -0,0 +1,1524 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +#include "g_local.h" + +/* +=============== +G_DamageFeedback + +Called just before a snapshot is sent to the given player. +Totals up all damage and generates both the player_state_t +damage values to that client for pain blends and kicks, and +global pain sound events for all clients. +=============== +*/ +void P_DamageFeedback( gentity_t *player ) +{ + gclient_t *client; + float count; + vec3_t angles; + + client = player->client; + if( client->ps.pm_type == PM_DEAD ) + return; + + // total points of damage shot at the player this frame + count = client->damage_blood + client->damage_armor; + if( count == 0 ) + return; // didn't take any damage + + if( count > 255 ) + count = 255; + + // send the information to the client + + // world damage (falling, slime, etc) uses a special code + // to make the blend blob centered instead of positional + if( client->damage_fromWorld ) + { + client->ps.damagePitch = 255; + client->ps.damageYaw = 255; + + client->damage_fromWorld = qfalse; + } + else + { + vectoangles( client->damage_from, angles ); + client->ps.damagePitch = angles[ PITCH ] / 360.0 * 256; + client->ps.damageYaw = angles[ YAW ] / 360.0 * 256; + } + + // play an apropriate pain sound + if( ( level.time > player->pain_debounce_time ) && !( player->flags & FL_GODMODE ) ) + { + player->pain_debounce_time = level.time + 700; + G_AddEvent( player, EV_PAIN, player->health ); + client->ps.damageEvent++; + } + + + client->ps.damageCount = count; + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_knockback = 0; +} + + + +/* +============= +P_WorldEffects + +Check for lava / slime contents and drowning +============= +*/ +void P_WorldEffects( gentity_t *ent ) +{ + int waterlevel; + + if( ent->client->noclip ) + { + ent->client->airOutTime = level.time + 12000; // don't need air + return; + } + + waterlevel = ent->waterlevel; + + // + // check for drowning + // + if( waterlevel == 3 ) + { + // if out of air, start drowning + if( ent->client->airOutTime < level.time) + { + // drown! + ent->client->airOutTime += 1000; + if( ent->health > 0 ) + { + // take more damage the longer underwater + ent->damage += 2; + if( ent->damage > 15 ) + ent->damage = 15; + + // 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 ) + G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp1.wav" ) ); + else + G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp2.wav" ) ); + + // don't play a normal pain sound + ent->pain_debounce_time = level.time + 200; + + G_Damage( ent, NULL, NULL, NULL, NULL, + ent->damage, DAMAGE_NO_ARMOR, MOD_WATER ); + } + } + } + else + { + ent->client->airOutTime = level.time + 12000; + ent->damage = 2; + } + + // + // check for sizzle damage (move to pmove?) + // + if( waterlevel && + ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) ) + { + if( ent->health > 0 && + ent->pain_debounce_time <= level.time ) + { + if( ent->watertype & CONTENTS_LAVA ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, + 30 * waterlevel, 0, MOD_LAVA ); + } + + if( ent->watertype & CONTENTS_SLIME ) + { + G_Damage( ent, NULL, NULL, NULL, NULL, + 10 * waterlevel, 0, MOD_SLIME ); + } + } + } +} + + + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) +{ + if( ent->waterlevel && ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) ) + ent->client->ps.loopSound = level.snd_fry; + else + ent->client->ps.loopSound = 0; +} + + + +//============================================================== + +/* +============== +ClientImpacts +============== +*/ +void ClientImpacts( gentity_t *ent, pmove_t *pm ) +{ + int i, j; + trace_t trace; + gentity_t *other; + + 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 ] ]; + + if( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) + ent->touch( ent, other, &trace ); + + //charge attack + if( ent->client->ps.weapon == WP_ALEVEL4 && + ent->client->ps.stats[ STAT_MISC ] > 0 && + ent->client->charging ) + ChargeAttack( ent, other ); + + if( !other->touch ) + continue; + + other->touch( other, ent, &trace ); + } +} + +/* +============ +G_TouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_TouchTriggers( gentity_t *ent ) +{ + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs; + vec3_t pmins, pmaxs; + static vec3_t range = { 10, 10, 10 }; + + if( !ent->client ) + return; + + // dead clients don't activate triggers! + if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 ) + return; + + BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], + pmins, pmaxs, NULL, NULL, NULL ); + + VectorAdd( ent->client->ps.origin, pmins, mins ); + VectorAdd( ent->client->ps.origin, pmaxs, maxs ); + + VectorSubtract( mins, range, mins ); + VectorAdd( maxs, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + + if( !hit->touch && !ent->touch ) + continue; + + if( !( hit->r.contents & CONTENTS_TRIGGER ) ) + 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( hit->s.eType != ET_TELEPORT_TRIGGER && + // this is ugly but adding a new ET_? type will + // most likely cause network incompatibilities + hit->touch != Touch_DoorTrigger ) + { + //check for manually triggered doors + manualTriggerSpectator( hit, ent ); + continue; + } + } + + if( !trap_EntityContact( mins, maxs, hit ) ) + continue; + + memset( &trace, 0, sizeof( trace ) ); + + if( hit->touch ) + hit->touch( hit, ent, &trace ); + + if( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) + ent->touch( ent, hit, &trace ); + } + + // if we didn't touch a jump pad this pmove frame + if( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) + { + ent->client->ps.jumppad_frame = 0; + ent->client->ps.jumppad_ent = 0; + } +} + +/* +================= +SpectatorThink +================= +*/ +void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) +{ + pmove_t pm; + gclient_t *client; + + client = ent->client; + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + + if( client->sess.spectatorState != SPECTATOR_FOLLOW ) + { + if( client->sess.spectatorState == SPECTATOR_LOCKED ) + client->ps.pm_type = PM_FREEZE; + else + client->ps.pm_type = PM_SPECTATOR; + + client->ps.speed = BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] ); + + 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.weapon = WP_NONE; + + // set up for pmove + memset( &pm, 0, sizeof( pm ) ); + pm.ps = &client->ps; + pm.cmd = *ucmd; + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + + // perform a pmove + Pmove( &pm ); + + // save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + G_TouchTriggers( ent ); + trap_UnlinkEntity( ent ); + + if( ( client->buttons & BUTTON_ATTACK ) && !( client->oldbuttons & BUTTON_ATTACK ) ) + { + //if waiting in a queue remove from the queue + if( 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; + } + else if( 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 + if( client->ps.pm_flags & PMF_QUEUED ) + { + if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + client->ps.persistant[ PERS_QUEUEPOS ] = + G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); + } + else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + client->ps.persistant[ PERS_QUEUEPOS ] = + G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); + } + } + } + + if( ( client->buttons & BUTTON_USE_HOLDABLE ) && !( client->oldbuttons & BUTTON_USE_HOLDABLE ) ) + Cmd_Follow_f( ent, qtrue ); +} + + + +/* +================= +ClientInactivityTimer + +Returns qfalse if the client is dropped +================= +*/ +qboolean ClientInactivityTimer( gclient_t *client ) +{ + if( ! g_inactivity.integer ) + { + // give everyone some time, so if the operator sets g_inactivity during + // gameplay, everyone isn't kicked + client->inactivityTime = level.time + 60 * 1000; + client->inactivityWarning = qfalse; + } + else if( client->pers.cmd.forwardmove || + client->pers.cmd.rightmove || + client->pers.cmd.upmove || + ( client->pers.cmd.buttons & BUTTON_ATTACK ) ) + { + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->inactivityWarning = qfalse; + } + else if( !client->pers.localClient ) + { + if( level.time > client->inactivityTime ) + { + trap_DropClient( client - level.clients, "Dropped due to inactivity" ); + return qfalse; + } + + if( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) + { + client->inactivityWarning = qtrue; + G_SendCommandFromServer( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); + } + } + + return qtrue; +} + +/* +================== +ClientTimerActions + +Actions that happen once a second +================== +*/ +void ClientTimerActions( gentity_t *ent, int msec ) +{ + gclient_t *client; + usercmd_t *ucmd; + int aForward, aRight; + + 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; + + while ( client->time100 >= 100 ) + { + client->time100 -= 100; + + //if not trying to run then not trying to sprint + if( aForward <= 64 ) + 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 ) && ucmd->upmove >= 0 ) + { + //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( ( aForward <= 64 && aForward > 5 ) || ( aRight <= 64 && aRight > 5 ) ) + { + //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( aForward <= 5 && aRight <= 5 ) + { + //restore stamina faster + 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->ps.stats[ STAT_MISC ] > 0 ) + { + client->allowedToPounce = qtrue; + client->pouncePayload = client->ps.stats[ STAT_MISC ]; + } + + client->ps.stats[ STAT_MISC ] = 0; + } + + 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 ) + { + 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 ) + { + 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 ) + { + 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; + + BG_UnpackAmmoArray( WP_LUCIFER_CANNON, client->ps.ammo, client->ps.powerups, &ammo, NULL ); + + 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 ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + case WP_HBUILD2: + //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; + + if( G_itemFits( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT, + dist, dummy ) == IBE_NONE ) + client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT; + else + client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT; + } + + //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; + break; + + default: + break; + } + + if( client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ) + { + int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime ); + + if( remainingStartupTime < 0 ) + { + if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && + ent->client->medKitHealthToRestore && + ent->client->ps.pm_type != PM_DEAD ) + { + ent->client->medKitHealthToRestore--; + ent->health++; + } + else + ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; + } + else + { + if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && + ent->client->medKitHealthToRestore && + ent->client->ps.pm_type != PM_DEAD ) + { + //partial increase + if( level.time > client->medKitIncrementTime ) + { + ent->client->medKitHealthToRestore--; + ent->health++; + + client->medKitIncrementTime = level.time + + ( remainingStartupTime / MEDKIT_STARTUP_SPEED ); + } + } + else + ent->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; + } + } + } + + while( client->time1000 >= 1000 ) + { + 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 i; + int seconds = ( ( level.time - client->lastPoisonTime ) / 1000 ) + 1; + int damage = ALIEN_POISON_DMG, damage2 = 0; + + for( i = 0; i < seconds; i++ ) + { + if( i == seconds - 1 ) + damage2 = damage; + + damage *= ALIEN_POISON_DIVIDER; + } + + damage = damage2 - damage; + + G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL, NULL, + damage, 0, MOD_POISON ); + } + + //replenish alien health + if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + int entityList[ MAX_GENTITIES ]; + vec3_t range = { LEVEL4_REGEN_RANGE, LEVEL4_REGEN_RANGE, LEVEL4_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_LEVEL4 ) + { + modifier = LEVEL4_REGEN_MOD; + break; + } + else if( boostEntity->s.eType == ET_BUILDABLE && + boostEntity->s.modelindex == BA_A_BOOSTER && + boostEntity->spawned ) + { + modifier = BOOSTER_REGEN_MOD; + break; + } + } + + if( ent->health > 0 && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] && + ( ent->lastDamageTime + ALIEN_REGEN_DAMAGE_TIME ) < level.time ) + ent->health += BG_FindRegenRateForClass( client->ps.stats[ STAT_PCLASS ] ) * modifier; + + if( ent->health > client->ps.stats[ STAT_MAX_HEALTH ] ) + ent->health = client->ps.stats[ STAT_MAX_HEALTH ]; + } + } + + while( client->time10000 >= 10000 ) + { + client->time10000 -= 10000; + + if( client->ps.weapon == WP_ALEVEL3_UPG ) + { + int ammo, maxAmmo; + + BG_FindAmmoForWeapon( WP_ALEVEL3_UPG, &maxAmmo, NULL ); + BG_UnpackAmmoArray( WP_ALEVEL3_UPG, client->ps.ammo, client->ps.powerups, &ammo, NULL ); + + if( ammo < maxAmmo ) + { + ammo++; + BG_PackAmmoArray( WP_ALEVEL3_UPG, client->ps.ammo, client->ps.powerups, ammo, 0 ); + } + } + } +} + +/* +==================== +ClientIntermissionThink +==================== +*/ +void ClientIntermissionThink( gclient_t *client ) +{ + client->ps.eFlags &= ~EF_TALK; + client->ps.eFlags &= ~EF_FIRING; + client->ps.eFlags &= ~EF_FIRING2; + + // the level will exit when everyone wants to or after timeouts + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = client->pers.cmd.buttons; + if( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) + client->readyToExit = 1; +} + + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +void ClientEvents( gentity_t *ent, int oldEventSequence ) +{ + int i; + int event; + gclient_t *client; + int damage; + vec3_t dir; + vec3_t point, mins; + float fallDistance; + pClass_t class; + + client = ent->client; + class = client->ps.stats[ STAT_PCLASS ]; + + if( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) + oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; + + for( i = oldEventSequence; i < client->ps.eventSequence; i++ ) + { + event = client->ps.events[ i & ( MAX_PS_EVENTS - 1 ) ]; + + switch( event ) + { + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + if( ent->s.eType != ET_PLAYER ) + break; // not in the player model + + fallDistance = ( (float)client->ps.stats[ STAT_FALLDIST ] - MIN_FALL_DISTANCE ) / + ( MAX_FALL_DISTANCE - MIN_FALL_DISTANCE ); + + if( fallDistance < 0.0f ) + fallDistance = 0.0f; + else if( fallDistance > 1.0f ) + fallDistance = 1.0f; + + damage = (int)( (float)BG_FindHealthForClass( class ) * + BG_FindFallDamageForClass( class ) * fallDistance ); + + VectorSet( dir, 0, 0, 1 ); + BG_FindBBoxForClass( class, mins, NULL, NULL, NULL, NULL ); + mins[ 0 ] = mins[ 1 ] = 0.0f; + VectorAdd( client->ps.origin, mins, point ); + + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage( ent, NULL, NULL, dir, point, damage, DAMAGE_NO_LOCDAMAGE, MOD_FALLING ); + break; + + case EV_FIRE_WEAPON: + FireWeapon( ent ); + break; + + case EV_FIRE_WEAPON2: + FireWeapon2( ent ); + break; + + case EV_FIRE_WEAPON3: + FireWeapon3( ent ); + break; + + case EV_NOAMMO: + //if we just ran out of grenades, remove the inventory item + if( ent->s.weapon == WP_GRENADE ) + { + int j; + + BG_RemoveWeaponFromInventory( ent->s.weapon, ent->client->ps.stats ); + + //switch to the first non blaster weapon + for( j = WP_NONE + 1; j < WP_NUM_WEAPONS; j++ ) + { + if( j == WP_BLASTER ) + continue; + + if( BG_InventoryContainsWeapon( j, ent->client->ps.stats ) ) + { + G_ForceWeaponChange( ent, j ); + break; + } + } + + //only got the blaster to switch to + if( j == WP_NUM_WEAPONS ) + G_ForceWeaponChange( ent, WP_BLASTER ); + + //update ClientInfo + ClientUserinfoChanged( ent->client->ps.clientNum ); + } + + break; + + default: + break; + } + } +} + + +/* +============== +SendPendingPredictableEvents +============== +*/ +void SendPendingPredictableEvents( playerState_t *ps ) +{ + gentity_t *t; + int event, seq; + int extEvent, number; + + // if there are still events pending + if( ps->entityEventSequence < ps->eventSequence ) + { + // create a temporary entity for this event which is sent to everyone + // except the client who generated the event + seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 ); + event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + // set external event to zero before calling BG_PlayerStateToEntityState + extEvent = ps->externalEvent; + ps->externalEvent = 0; + // create temporary entity for event + t = G_TempEntity( ps->origin, event ); + number = t->s.number; + BG_PlayerStateToEntityState( ps, &t->s, qtrue ); + t->s.number = number; + t->s.eType = ET_EVENTS + event; + t->s.eFlags |= EF_PLAYER_EVENT; + t->s.otherEntityNum = ps->clientNum; + // send to everyone except the client who generated the event + t->r.svFlags |= SVF_NOTSINGLECLIENT; + t->r.singleClient = ps->clientNum; + // set back external event + ps->externalEvent = extEvent; + } +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame on fast clients. + +If "g_synchronousClients 1" is set, this will be called exactly +once for each server frame, which makes for smooth demo recording. +============== +*/ +void ClientThink_real( gentity_t *ent ) +{ + gclient_t *client; + pmove_t pm; + int oldEventSequence; + int msec; + usercmd_t *ucmd; + + client = ent->client; + + // don't think if the client is not yet connected (and thus not yet spawned in) + if( client->pers.connected != CON_CONNECTED ) + return; + + // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; + + // 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" ); + } + + msec = ucmd->serverTime - client->ps.commandTime; + // following others may result in bad times, but we still want + // to check for follow toggles + if( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) + return; + + if( msec > 200 ) + msec = 200; + + if( pmove_msec.integer < 8 ) + trap_Cvar_Set( "pmove_msec", "8" ); + else if( pmove_msec.integer > 33 ) + trap_Cvar_Set( "pmove_msec", "33" ); + + if( pmove_fixed.integer || client->pers.pmoveFixed ) + { + ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; + //if (ucmd->serverTime - client->ps.commandTime <= 0) + // return; + } + + // + // check for exiting intermission + // + if( level.intermissiontime ) + { + ClientIntermissionThink( client ); + return; + } + + // spectators don't do much + if( client->sess.sessionTeam == TEAM_SPECTATOR ) + { + if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) + return; + + SpectatorThink( ent, ucmd ); + return; + } + + G_UpdatePTRConnection( client ); + + // check for inactivity timer, but never drop the local client of a non-dedicated server + if( !ClientInactivityTimer( client ) ) + return; + + 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; + else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) + client->ps.pm_type = PM_JETPACK; + else + client->ps.pm_type = PM_NORMAL; + + 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 && + client->lastLockTime + 5000 < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED; + + if( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED && + client->lastLockTime + 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; + + if( client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED && + client->lastPoisonCloudedTime + LEVEL1_PCLOUD_TIME < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED; + + if( client->ps.stats[ STAT_STATE ] & SS_POISONED && + client->lastPoisonTime + ALIEN_POISON_TIME < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + + client->ps.gravity = g_gravity.value; + + if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) && + 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 || + ( 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 ) + { + //remove anti toxin + BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); + BG_RemoveUpgradeFromInventory( UP_MEDKIT, client->ps.stats ); + + client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; + client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME; + + client->ps.stats[ STAT_STATE ] |= SS_MEDKIT_ACTIVE; + client->lastMedKitTime = level.time; + client->medKitHealthToRestore = + client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ]; + client->medKitIncrementTime = level.time + + ( MEDKIT_STARTUP_TIME / MEDKIT_STARTUP_SPEED ); + + G_AddEvent( ent, EV_MEDKIT_USED, 0 ); + } + } + + if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) && + BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) ) + { + int lastWeapon = ent->s.weapon; + + //remove grenade + BG_DeactivateUpgrade( UP_GRENADE, client->ps.stats ); + BG_RemoveUpgradeFromInventory( UP_GRENADE, client->ps.stats ); + + //M-M-M-M-MONSTER HACK + ent->s.weapon = WP_GRENADE; + FireWeapon( ent ); + ent->s.weapon = lastWeapon; + } + + // set speed + client->ps.speed = g_speed.value * BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] ); + + if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time ) + client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED; + + //randomly disable the jet pack if damaged + if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && + BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) + { + if( ent->lastDamageTime + JETPACK_DISABLE_TIME > level.time ) + { + if( random( ) > JETPACK_DISABLE_CHANCE ) + client->ps.pm_type = PM_NORMAL; + } + + //switch jetpack off if no reactor + if( !level.reactorPresent ) + BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); + } + + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + memset( &pm, 0, sizeof( pm ) ); + + if( !( ucmd->buttons & BUTTON_TALK ) ) //&& client->ps.weaponTime <= 0 ) //TA: erk more server load + { + 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; + } + + pm.ps = &client->ps; + 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.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + pm.debugLevel = g_debugMove.integer; + pm.noFootsteps = 0; + + pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_msec = pmove_msec.integer; + + VectorCopy( client->ps.origin, client->oldOrigin ); + + // moved from after Pmove -- potentially the cause of + // future triggering bugs + if( !ent->client->noclip ) + G_TouchTriggers( ent ); + + Pmove( &pm ); + + // save results of pmove + if( ent->client->ps.eventSequence != oldEventSequence ) + ent->eventTime = level.time; + + 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 ); + + SendPendingPredictableEvents( &ent->client->ps ); + + if( !( ent->client->ps.eFlags & EF_FIRING ) ) + client->fireHeld = qfalse; // for grapple + if( !( ent->client->ps.eFlags & EF_FIRING2 ) ) + client->fire2Held = qfalse; + + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + VectorCopy( pm.mins, ent->r.mins ); + VectorCopy( pm.maxs, ent->r.maxs ); + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + + // execute client events + ClientEvents( ent, oldEventSequence ); + + // link entity now, after any personal teleporters have been used + trap_LinkEntity( 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 ); + + // touch other objects + ClientImpacts( ent, &pm ); + + // save results of triggers and client events + if( ent->client->ps.eventSequence != oldEventSequence ) + ent->eventTime = level.time; + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + if( ( client->buttons & BUTTON_GETFLAG ) && !( client->oldbuttons & BUTTON_GETFLAG ) && + 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; + + //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; + + //client leaves hovel + client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; + + //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 ); + } + } + 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; + int j; + qboolean upgrade = qfalse; + + //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 ]; + + 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 + { + //no entity in front of player - do a small area search + + 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++ ) + { + 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; + } + } + + if( i == num && client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + for( j = PCL_NONE + 1; j < PCL_NUM_CLASSES; j++ ) + { + if( BG_ClassCanEvolveFromTo( client->ps.stats[ STAT_PCLASS ], j, + client->ps.persistant[ PERS_CREDIT ], 0 ) >= 0 && + BG_FindStagesForClass( j, g_alienStage.integer ) && G_ClassIsAllowed( j ) ) + { + upgrade = qtrue; + break; + } + } + + if( upgrade ) + { + //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 ); + } + } + } + } + } + + // check for respawning + if( client->ps.stats[ STAT_HEALTH ] <= 0 ) + { + // wait for the attack button to be pressed + if( level.time > client->respawnTime ) + { + // forcerespawn is to prevent users from waiting out powerups + if( g_forcerespawn.integer > 0 && + ( level.time - client->respawnTime ) > 0 ) + { + respawn( ent ); + return; + } + + // pressing attack or use is the normal respawn method + if( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) + { + respawn( ent ); + } + } + return; + } + + if( level.framenum > client->retriggerArmouryMenu && client->retriggerArmouryMenu ) + { + G_TriggerMenu( client->ps.clientNum, MN_H_ARMOURY ); + + client->retriggerArmouryMenu = 0; + } + + // Give clients some credit periodically + if( ent->client->lastKillTime + FREEKILL_PERIOD < level.time ) + { + if( g_suddenDeathTime.integer && + ( level.time - level.startTime >= g_suddenDeathTime.integer * 60000 ) ) + { + //gotta love logic like this eh? + } + else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + G_AddCreditToClient( ent->client, FREEKILL_ALIEN, qtrue ); + else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + G_AddCreditToClient( ent->client, FREEKILL_HUMAN, qtrue ); + + ent->client->lastKillTime = level.time; + } + + // 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; + } +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +void ClientThink( int clientNum ) +{ + gentity_t *ent; + + ent = g_entities + clientNum; + trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); + + // mark the time we got info, so we can display the + // phone jack if they don't get any for a while + ent->client->lastCmdTime = level.time; + + if( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) + ClientThink_real( ent ); +} + + +void G_RunClient( gentity_t *ent ) +{ + if( !( ent->r.svFlags & SVF_BOT ) && !g_synchronousClients.integer ) + return; + + ent->client->pers.cmd.serverTime = level.time; + ClientThink_real( ent ); +} + + +/* +================== +SpectatorClientEndFrame + +================== +*/ +void SpectatorClientEndFrame( gentity_t *ent ) +{ + gclient_t *cl; + int clientNum, flags; + + // 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 ) + { + cl = &level.clients[ clientNum ]; + + if( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) + { + flags = ( cl->ps.eFlags & ~( EF_VOTED | EF_TEAMVOTED ) ) | + ( ent->client->ps.eFlags & ( EF_VOTED | EF_TEAMVOTED ) ); + ent->client->ps = cl->ps; + ent->client->ps.pm_flags |= PMF_FOLLOW; + ent->client->ps.eFlags = flags; + } + } + } +} + +/* +============== +ClientEndFrame + +Called at the end of each server frame for each connected client +A fast client will have multiple ClientThink for each ClientEdFrame, +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 ) + { + SpectatorClientEndFrame( ent ); + return; + } + + pers = &ent->client->pers; + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if( level.intermissiontime ) + return; + + // burn from lava, etc + P_WorldEffects( ent ); + + // apply all the damage taken this frame + P_DamageFeedback( ent ); + + // add the EF_CONNECTION flag if we haven't gotten commands recently + if( level.time - ent->client->lastCmdTime > 1000 ) + ent->s.eFlags |= EF_CONNECTION; + else + ent->s.eFlags &= ~EF_CONNECTION; + + ent->client->ps.stats[ STAT_HEALTH ] = ent->health; // FIXME: get rid of ent->health... + + G_SetClientSound( ent ); + + // set the latest infor + 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 ); + + SendPendingPredictableEvents( &ent->client->ps ); +} + + diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c new file mode 100644 index 00000000..083e0662 --- /dev/null +++ b/src/game/g_buildable.c @@ -0,0 +1,2987 @@ +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +#include "g_local.h" + +/* +================ +G_setBuildableAnim + +Triggers an animation client side +================ +*/ +void G_setBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim, qboolean force ) +{ + int localAnim = anim; + + if( force ) + localAnim |= ANIM_FORCEBIT; + + localAnim |= ( ( ent->s.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ); + + ent->s.legsAnim = localAnim; +} + +/* +================ +G_setIdleBuildableAnim + +Set the animation to use whilst no other animations are running +================ +*/ +void G_setIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim ) +{ + ent->s.torsoAnim = anim; +} + +/* +=============== +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 ) +{ + float displacement; + vec3_t mins, maxs; + vec3_t cmins, cmaxs; + vec3_t localOrigin; + trace_t tr; + + BG_FindBBoxForBuildable( 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; + 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_SHOT ); + + 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 ); + + 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_SHOT ); + + if( tr.entityNum == ENTITYNUM_NONE ) + { + if( spawnOrigin != NULL ) + VectorCopy( localOrigin, spawnOrigin ); + + return NULL; + } + else + return &g_entities[ tr.entityNum ]; + } + + return NULL; +} + +/* +================ +G_NumberOfDependants + +Return number of entities that depend on this one +================ +*/ +static int G_NumberOfDependants( gentity_t *self ) +{ + int i, n = 0; + gentity_t *ent; + + for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->parentNode == self ) + n++; + } + + return n; +} + +#define POWER_REFRESH_TIME 2000 + +/* +================ +findPower + +attempt to find power for self, return qtrue if successful +================ +*/ +static qboolean findPower( gentity_t *self ) +{ + int i; + gentity_t *ent; + gentity_t *closestPower = NULL; + int distance = 0; + int minDistance = 10000; + vec3_t temp_v; + qboolean foundPower = qfalse; + + if( self->biteam != BIT_HUMANS ) + return qfalse; + + //reactor is always powered + if( self->s.modelindex == BA_H_REACTOR ) + return qtrue; + + //if this already has power then stop now + if( self->parentNode && self->parentNode->powered ) + return qtrue; + + //reset parent + self->parentNode = NULL; + + //iterate through entities + for ( i = 1, 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( ( ent->s.modelindex == BA_H_REACTOR || ent->s.modelindex == BA_H_REPEATER ) && + ent->spawned ) + { + VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + distance = VectorLength( temp_v ); + if( distance < minDistance && ent->powered ) + { + closestPower = ent; + minDistance = distance; + foundPower = qtrue; + } + } + } + + //if there were no power items nearby give up + if( !foundPower ) + return qfalse; + + //bleh + if( ( closestPower->s.modelindex == BA_H_REACTOR && ( minDistance <= REACTOR_BASESIZE ) ) || + ( closestPower->s.modelindex == BA_H_REPEATER && ( minDistance <= REPEATER_BASESIZE ) && + closestPower->powered ) ) + { + self->parentNode = closestPower; + + return qtrue; + } + else + return qfalse; +} + +/* +================ +G_isPower + +Simple wrapper to findPower to check if a location has power +================ +*/ +qboolean G_isPower( vec3_t origin ) +{ + gentity_t dummy; + + dummy.parentNode = NULL; + dummy.biteam = BIT_HUMANS; + VectorCopy( origin, dummy.s.origin ); + + return findPower( &dummy ); +} + +/* +================ +findDCC + +attempt to find a controlling DCC for self, return qtrue if successful +================ +*/ +static qboolean findDCC( gentity_t *self ) +{ + int i; + gentity_t *ent; + gentity_t *closestDCC = NULL; + int distance = 0; + int minDistance = 10000; + vec3_t temp_v; + qboolean foundDCC = qfalse; + + if( self->biteam != BIT_HUMANS ) + return qfalse; + + //if this already has dcc then stop now + if( self->dccNode && self->dccNode->powered ) + return qtrue; + + //reset parent + self->dccNode = NULL; + + //iterate through entities + for( i = 1, 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 ) + { + VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + distance = VectorLength( temp_v ); + if( distance < minDistance && ent->powered ) + { + closestDCC = ent; + minDistance = distance; + foundDCC = qtrue; + } + } + } + + //if there were no power items nearby give up + if( !foundDCC ) + return qfalse; + + self->dccNode = closestDCC; + + return qtrue; +} + +/* +================ +G_isDCC + +simple wrapper to findDCC to check for a dcc +================ +*/ +qboolean G_isDCC( void ) +{ + gentity_t dummy; + + dummy.dccNode = NULL; + dummy.biteam = BIT_HUMANS; + + return findDCC( &dummy ); +} + +/* +================ +findOvermind + +Attempt to find an overmind for self +================ +*/ +static qboolean findOvermind( gentity_t *self ) +{ + int i; + gentity_t *ent; + + if( self->biteam != BIT_ALIENS ) + return qfalse; + + //if this already has overmind then stop now + if( self->overmindNode && self->overmindNode->health > 0 ) + return qtrue; + + //reset parent + self->overmindNode = NULL; + + //iterate through entities + for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + 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 qfalse; +} + +/* +================ +G_isOvermind + +Simple wrapper to findOvermind to check if a location has an overmind +================ +*/ +qboolean G_isOvermind( void ) +{ + gentity_t dummy; + + dummy.overmindNode = NULL; + dummy.biteam = BIT_ALIENS; + + return findOvermind( &dummy ); +} + +/* +================ +findCreep + +attempt to find creep for self, return qtrue if successful +================ +*/ +static qboolean findCreep( gentity_t *self ) +{ + int i; + gentity_t *ent; + gentity_t *closestSpawn = NULL; + int distance = 0; + int minDistance = 10000; + vec3_t temp_v; + + //don't check for creep if flying through the air + if( self->s.groundEntityNum == -1 ) + 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 ) + { + for ( i = 1, 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 ) + { + VectorSubtract( self->s.origin, ent->s.origin, temp_v ); + distance = VectorLength( temp_v ); + if( distance < minDistance ) + { + closestSpawn = ent; + minDistance = distance; + } + } + } + + if( minDistance <= CREEP_BASESIZE ) + { + self->parentNode = closestSpawn; + return qtrue; + } + else + return qfalse; + } + + //if we haven't returned by now then we must already have a valid parent + return qtrue; +} + +/* +================ +isCreep + +simple wrapper to findCreep to check if a location has creep +================ +*/ +static qboolean isCreep( vec3_t origin ) +{ + gentity_t dummy; + + dummy.parentNode = NULL; + VectorCopy( origin, dummy.s.origin ); + + return findCreep( &dummy ); +} + +/* +================ +creepSlow + +Set any nearby humans' SS_CREEPSLOWED flag +================ +*/ +static void creepSlow( gentity_t *self ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range; + vec3_t mins, maxs; + int i, num; + gentity_t *enemy; + buildable_t buildable = self->s.modelindex; + float creepSize = (float)BG_FindCreepSizeForBuildable( buildable ); + + VectorSet( range, creepSize, creepSize, creepSize ); + + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + + //find humans + 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->client->ps.groundEntityNum != ENTITYNUM_NONE && + G_Visible( self, enemy ) ) + { + enemy->client->ps.stats[ STAT_STATE ] |= SS_CREEPSLOWED; + enemy->client->lastCreepSlowTime = level.time; + } + } +} + +/* +================ +nullDieFunction + +hack to prevent compilers complaining about function pointer -> NULL conversion +================ +*/ +static void nullDieFunction( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +{ +} + +/* +================ +freeBuildable +================ +*/ +static void freeBuildable( gentity_t *self ) +{ + G_FreeEntity( self ); +} + + +//================================================================================== + + + +/* +================ +A_CreepRecede + +Called when an alien spawn dies +================ +*/ +void A_CreepRecede( gentity_t *self ) +{ + //if the creep just died begin the 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 ) ) ) ) ); + } + + //creep is still receeding + if( ( self->timestamp + 10000 ) > level.time ) + self->nextthink = level.time + 500; + else //creep has died + G_FreeEntity( self ); +} + + + + +//================================================================================== + + + + +/* +================ +ASpawn_Melt + +Called when an alien spawn dies +================ +*/ +void ASpawn_Melt( 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; + + 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 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->r.contents = 0; //stop collisions... + trap_LinkEntity( self ); //...requires a relink +} + +/* +================ +ASpawn_Die + +Called when an alien spawn dies +================ +*/ +void ASpawn_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->think = ASpawn_Blast; + + if( self->spawned ) + self->nextthink = level.time + 5000; + else + self->nextthink = level.time; //blast immediately + + self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + + if( attacker && attacker->client && 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 ); + } +} + +/* +================ +ASpawn_Think + +think function for Alien Spawn +================ +*/ +void ASpawn_Think( gentity_t *self ) +{ + gentity_t *ent; + + 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_A_SPAWN, NULL ) ) != NULL ) + { + if( ent->s.eType == ET_BUILDABLE || ent->s.number == ENTITYNUM_WORLD || + ent->s.eType == ET_MOVER ) + { + G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + return; + } + + if( ent->s.eType == ET_CORPSE ) + G_FreeEntity( ent ); //quietly remove + } + } + } + + 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 ); +} + + + + + +//================================================================================== + + + + + +#define OVERMIND_ATTACK_PERIOD 10000 +#define OVERMIND_DYING_PERIOD 5000 +#define OVERMIND_SPAWNS_PERIOD 30000 + +/* +================ +AOvermind_Think + +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 ); + + if( self->spawned && ( self->health > 0 ) ) + { + //do some damage + 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 ) + { + 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 ); + } + } + + //low on spawns + if( level.numAlienSpawns <= 0 && level.time > self->overmindSpawnsTimer ) + { + self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD; + G_BroadcastEvent( EV_OVERMIND_SPAWNS, 0 ); + } + + //overmind dying + if( self->health < ( OVERMIND_HEALTH / 10.0f ) && level.time > self->overmindDyingTimer ) + { + self->overmindDyingTimer = level.time + OVERMIND_DYING_PERIOD; + G_BroadcastEvent( EV_OVERMIND_DYING, 0 ); + } + + //overmind under attack + if( self->health < self->lastHealth && level.time > self->overmindAttackTimer ) + { + self->overmindAttackTimer = level.time + OVERMIND_ATTACK_PERIOD; + G_BroadcastEvent( EV_OVERMIND_ATTACK, 0 ); + } + + self->lastHealth = self->health; + } + else + self->overmindSpawnsTimer = level.time + OVERMIND_SPAWNS_PERIOD; + + creepSlow( self ); + + self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +} + + + + + + +//================================================================================== + + + + + +/* +================ +ABarricade_Pain + +pain function for Alien Spawn +================ +*/ +void ABarricade_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + if( rand( ) % 1 ) + G_setBuildableAnim( self, BANIM_PAIN1, qfalse ); + else + G_setBuildableAnim( self, BANIM_PAIN2, qfalse ); +} + +/* +================ +ABarricade_Blast + +Called when an alien spawn dies +================ +*/ +void ABarricade_Blast( gentity_t *self ) +{ + 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 ); + + //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 + + self->r.contents = 0; //stop collisions... + trap_LinkEntity( self ); //...requires a relink +} + +/* +================ +ABarricade_Die + +Called when an alien spawn dies +================ +*/ +void ABarricade_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->think = ABarricade_Blast; + self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + + if( self->spawned ) + self->nextthink = level.time + 5000; + else + self->nextthink = level.time; //blast immediately +} + +/* +================ +ABarricade_Think + +Think function for Alien Barricade +================ +*/ +void ABarricade_Think( gentity_t *self ) +{ + //if there is no creep nearby die + if( !findCreep( self ) ) + { + G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + return; + } + + creepSlow( self ); + + self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +} + + + + +//================================================================================== + + + + +void AAcidTube_Think( gentity_t *self ); + +/* +================ +AAcidTube_Damage + +Damage function for Alien Acid Tube +================ +*/ +void AAcidTube_Damage( 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 ); + } + + creepSlow( self ); + + self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +} + +/* +================ +AAcidTube_Think + +Think function for Alien Acid Tube +================ +*/ +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; + + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + + //if there is no creep nearby die + if( !findCreep( self ) ) + { + G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + return; + } + + if( self->spawned && 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( !G_Visible( self, enemy ) ) + continue; + + if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + self->timestamp = level.time; + self->think = AAcidTube_Damage; + self->nextthink = level.time + 100; + G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + return; + } + } + } + + creepSlow( self ); + + self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); +} + + + + +//================================================================================== + + + + +/* +================ +AHive_Think + +Think function for Alien Hive +================ +*/ +void AHive_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->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + + //if there is no creep nearby die + if( !findCreep( self ) ) + { + 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 && 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->health <= 0 ) + continue; + + if( !G_Visible( self, enemy ) ) + continue; + + if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + 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 ); + return; + } + } + } + + creepSlow( self ); +} + + + + +//================================================================================== + + + + +#define HOVEL_TRACE_DEPTH 128.0f + +/* +================ +AHovel_Blocked + +Is this hovel entrance blocked? +================ +*/ +qboolean AHovel_Blocked( gentity_t *hovel, gentity_t *player, qboolean provideExit ) +{ + 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 ) * M_ROOT3 + + VectorMaxComponent( hovelMaxs ) * M_ROOT3 + 1.0f; + + VectorMA( hovel->s.origin, displacement, forward, origin ); + vectoangles( forward, angles ); + + VectorMA( origin, HOVEL_TRACE_DEPTH, normal, start ); + + //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 ); + + if( tr.startsolid ) + return qtrue; + + VectorCopy( tr.endpos, origin ); + + trap_Trace( &tr, origin, mins, maxs, origin, player->s.number, MASK_PLAYERSOLID ); + + if( provideExit ) + { + G_SetOrigin( player, origin ); + VectorCopy( origin, player->client->ps.origin ); + VectorCopy( vec3_origin, player->client->ps.velocity ); + SetClientViewAngle( player, angles ); + } + + if( tr.fraction < 1.0f ) + return qtrue; + else + return qfalse; +} + +/* +================ +APropHovel_Blocked + +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; + + VectorCopy( origin, hovel.s.origin ); + VectorCopy( angles, hovel.s.angles ); + VectorCopy( normal, hovel.s.origin2 ); + + return AHovel_Blocked( &hovel, player, qfalse ); +} + +/* +================ +AHovel_Use + +Called when an alien uses a hovel +================ +*/ +void AHovel_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + vec3_t hovelOrigin, hovelAngles, inverseNormal; + + if( self->spawned && 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; + } + + self->active = qtrue; + G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + + //prevent lerping + activator->client->ps.eFlags ^= EF_TELEPORT_BIT; + activator->client->ps.eFlags |= EF_NODRAW; + + activator->client->ps.stats[ STAT_STATE ] |= SS_HOVELING; + activator->client->hovel = self; + self->builder = activator; + + VectorCopy( self->s.pos.trBase, hovelOrigin ); + VectorMA( hovelOrigin, 128.0f, self->s.origin2, hovelOrigin ); + + VectorCopy( self->s.origin2, inverseNormal ); + VectorInverse( inverseNormal ); + vectoangles( inverseNormal, hovelAngles ); + + VectorCopy( activator->s.pos.trBase, activator->client->hovelOrigin ); + + G_SetOrigin( activator, hovelOrigin ); + VectorCopy( hovelOrigin, activator->client->ps.origin ); + SetClientViewAngle( activator, hovelAngles ); + } + } +} + + +/* +================ +AHovel_Think + +Think for alien hovel +================ +*/ +void AHovel_Think( gentity_t *self ) +{ + if( self->spawned ) + { + if( self->active ) + G_setIdleBuildableAnim( self, BANIM_IDLE2 ); + else + G_setIdleBuildableAnim( self, BANIM_IDLE1 ); + } + + 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; + + 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 + + //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_SetOrigin( builder, newOrigin ); + VectorCopy( newOrigin, builder->client->ps.origin ); + SetClientViewAngle( builder, newAngles ); + + //client leaves hovel + builder->client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; + } + + self->r.contents = 0; //stop collisions... + trap_LinkEntity( self ); //...requires a relink +} + + + + + +//================================================================================== + + + + +/* +================ +ABooster_Touch + +Called when an alien touches a booster +================ +*/ +void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) +{ + gclient_t *client = other->client; + + if( !self->spawned ) + return; + + if( !findOvermind( self ) ) + return; + + if( !client ) + return; + + if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + return; + + //only allow boostage once every 30 seconds + if( client->lastBoostedTime + BOOSTER_INTERVAL > level.time ) + return; + + if( !( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) ) + { + client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; + client->lastBoostedTime = level.time; + } +} + + + + +//================================================================================== + +#define TRAPPER_ACCURACY 10 // lower is better + +/* +================ +ATrapper_FireOnEnemy + +Used by ATrapper_Think to fire at enemy +================ +*/ +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 ); + int lowMsec = 0; + int highMsec = (int)( ( + ( ( distanceToTarget * LOCKBLOB_SPEED ) + + ( distanceToTarget * BG_FindSpeedForClass( enemy->client->ps.stats[ STAT_PCLASS ] ) ) ) / + ( LOCKBLOB_SPEED * LOCKBLOB_SPEED ) ) * 1000.0f ); + + VectorScale( enemy->acceleration, 1.0f / 2.0f, halfAcceleration ); + VectorScale( enemy->jerk, 1.0f / 3.0f, thirdJerk ); + + // highMsec and lowMsec can only move toward + // one another, so the loop must terminate + while( highMsec - lowMsec > TRAPPER_ACCURACY ) + { + int partitionMsec = ( highMsec + lowMsec ) / 2; + float time = (float)partitionMsec / 1000.0f; + float projectileDistance = LOCKBLOB_SPEED * time; + + VectorMA( enemy->s.pos.trBase, time, enemy->s.pos.trDelta, dirToTarget ); + VectorMA( dirToTarget, time * time, halfAcceleration, dirToTarget ); + VectorMA( dirToTarget, time * time * time, thirdJerk, dirToTarget ); + VectorSubtract( dirToTarget, self->s.pos.trBase, dirToTarget ); + distanceToTarget = VectorLength( dirToTarget ); + + if( projectileDistance < distanceToTarget ) + lowMsec = partitionMsec; + else if( projectileDistance > distanceToTarget ) + highMsec = partitionMsec; + else if( projectileDistance == distanceToTarget ) + break; // unlikely to happen + } + + VectorNormalize( dirToTarget ); + vectoangles( dirToTarget, self->turretAim ); + + //fire at target + FireWeapon( self ); + G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + self->count = level.time + firespeed; +} + +/* +================ +ATrapper_CheckTarget + +Used by ATrapper_Think to check enemies for validity +================ +*/ +qboolean ATrapper_CheckTarget( gentity_t *self, gentity_t *target, int range ) +{ + vec3_t distance; + trace_t trace; + + if( !target ) // Do we have a target? + return qfalse; + if( !target->inuse ) // Does the target still exist? + return qfalse; + if( target == self ) // is the target us? + return qfalse; + if( !target->client ) // is the target a bot or player? + return qfalse; + if( target->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) // one of us? + return qfalse; + if( target->client->sess.sessionTeam == TEAM_SPECTATOR ) // is the target alive? + return qfalse; + if( target->health <= 0 ) // is the target still alive? + return qfalse; + if( target->client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) // locked? + return qfalse; + + VectorSubtract( target->r.currentOrigin, self->r.currentOrigin, distance ); + if( VectorLength( distance ) > range ) // is the target within range? + return qfalse; + + //only allow a narrow field of "vision" + VectorNormalize( distance ); //is now direction of target + if( DotProduct( distance, self->s.origin2 ) < LOCKBLOB_DOT ) + return qfalse; + + trap_Trace( &trace, self->s.pos.trBase, NULL, NULL, target->s.pos.trBase, self->s.number, MASK_SHOT ); + if ( trace.contents & CONTENTS_SOLID ) // can we see the target? + return qfalse; + + return qtrue; +} + +/* +================ +ATrapper_FindEnemy + +Used by ATrapper_Think to locate enemy gentities +================ +*/ +void ATrapper_FindEnemy( gentity_t *ent, int range ) +{ + gentity_t *target; + + //iterate through entities + for( target = g_entities; target < &g_entities[ level.num_entities ]; target++ ) + { + //if target is not valid keep searching + if( !ATrapper_CheckTarget( ent, target, range ) ) + continue; + + //we found a target + ent->enemy = target; + return; + } + + //couldn't find a target + ent->enemy = NULL; +} + +/* +================ +ATrapper_Think + +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 ); + + creepSlow( self ); + + self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + + //if there is no creep nearby die + if( !findCreep( self ) ) + { + G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + return; + } + + if( self->spawned && findOvermind( self ) ) + { + //if the current target is not valid find a new one + if( !ATrapper_CheckTarget( self, self->enemy, range ) ) + ATrapper_FindEnemy( self, range ); + + //if a new target cannot be found don't do anything + if( !self->enemy ) + return; + + //if we are pointing at our target and we can fire shoot it + if( self->count < level.time ) + ATrapper_FireOnEnemy( self, firespeed, range ); + } +} + + + +//================================================================================== + + + +/* +================ +HRepeater_Think + +Think for human power repeater +================ +*/ +void HRepeater_Think( gentity_t *self ) +{ + int i; + qboolean reactor = qfalse; + gentity_t *ent; + + if( self->spawned ) + { + //iterate through entities + for ( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->s.modelindex == BA_H_REACTOR && ent->spawned ) + reactor = qtrue; + } + } + + if( G_NumberOfDependants( self ) == 0 ) + { + //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 ); + } + else + self->count = -1; + + self->powered = reactor; + + self->nextthink = level.time + POWER_REFRESH_TIME; +} + +/* +================ +HRepeater_Use + +Use for human power repeater +================ +*/ +void HRepeater_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if( self->health <= 0 ) + return; + + if( !self->spawned ) + return; + + if( other ) + G_GiveClientMaxAmmo( other, qtrue ); +} + + +#define DCC_ATTACK_PERIOD 10000 + +/* +================ +HReactor_Think + +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 mins, maxs; + int i, num; + gentity_t *enemy, *tent; + + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + + if( self->spawned && ( self->health > 0 ) ) + { + //do some damage + 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_ALIENS ) + { + 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 + } + } + + //reactor under attack + if( self->health < self->lastHealth && + level.time > level.humanBaseAttackTimer && G_isDCC( ) ) + { + 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 ); +} + +//================================================================================== + + + +/* +================ +HArmoury_Activate + +Called when a human activates an Armoury +================ +*/ +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 ) + return; + + //if this is powered then call the armoury menu + if( self->powered ) + G_TriggerMenu( activator->client->ps.clientNum, MN_H_ARMOURY ); + else + G_TriggerMenu( activator->client->ps.clientNum, MN_H_NOTPOWERED ); + } +} + +/* +================ +HArmoury_Think + +Think for armoury +================ +*/ +void HArmoury_Think( gentity_t *self ) +{ + //make sure we have power + self->nextthink = level.time + POWER_REFRESH_TIME; + + self->powered = findPower( self ); +} + + + + +//================================================================================== + + + + + +/* +================ +HDCC_Think + +Think for dcc +================ +*/ +void HDCC_Think( gentity_t *self ) +{ + //make sure we have power + self->nextthink = level.time + POWER_REFRESH_TIME; + + self->powered = findPower( self ); +} + + + + +//================================================================================== + +/* +================ +HMedistat_Think + +think function for Human Medistation +================ +*/ +void HMedistat_Think( gentity_t *self ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t mins, maxs; + int i, num; + gentity_t *player; + qboolean occupied = qfalse; + + self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + + //make sure we have power + if( !( self->powered = findPower( self ) ) ) + { + self->nextthink = level.time + POWER_REFRESH_TIME; + return; + } + + if( self->spawned ) + { + VectorAdd( self->s.origin, self->r.maxs, maxs ); + VectorAdd( self->s.origin, self->r.mins, mins ); + + mins[ 2 ] += fabs( self->r.mins[ 2 ] ) + self->r.maxs[ 2 ]; + maxs[ 2 ] += 60; //player height + + //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->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && + player->client->ps.pm_type != PM_DEAD && + self->enemy == player ) + occupied = qtrue; + } + } + + if( !occupied ) + { + self->enemy = NULL; + + //look for something to heal + for( i = 0; i < num; i++ ) + { + player = &g_entities[ entityList[ i ] ]; + + if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && + player->client->ps.pm_type != PM_DEAD ) + { + self->enemy = player; + + //start the heal anim + if( !self->active ) + { + G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + self->active = qtrue; + } + } + else if( !BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) ) + BG_AddUpgradeToInventory( UP_MEDKIT, player->client->ps.stats ); + } + } + } + + //nothing left to heal so go back to idling + if( !self->enemy && self->active ) + { + G_setBuildableAnim( self, BANIM_CONSTRUCT2, qtrue ); + G_setIdleBuildableAnim( self, BANIM_IDLE1 ); + + self->active = qfalse; + } + else if( self->enemy ) //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 && self->enemy->client->ps.stats[ STAT_STATE ] & SS_MEDKIT_ACTIVE ) + self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_MEDKIT_ACTIVE; + + 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 ); + } + } +} + + + + +//================================================================================== + + + + +/* +================ +HMGTurret_TrackEnemy + +Used by HMGTurret_Think to track enemy location +================ +*/ +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 ); + VectorNormalize( xNormal ); + rotAngle = RAD2DEG( acos( DotProduct( self->s.origin2, refNormal ) ) ); + RotatePointAroundVector( dttAdjusted, xNormal, dirToTarget, rotAngle ); + + vectoangles( dttAdjusted, angleToTarget ); + + angularDiff[ PITCH ] = AngleSubtract( self->s.angles2[ PITCH ], angleToTarget[ PITCH ] ); + 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; + else + self->s.angles2[ PITCH ] = angleToTarget[ PITCH ]; + + //disallow vertical movement past a certain limit + temp = fabs( self->s.angles2[ PITCH ] ); + if( temp > 180 ) + temp -= 360; + + if( temp < -MGTURRET_VERTICALCAP ) + 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; + else + self->s.angles2[ YAW ] = angleToTarget[ YAW ]; + + AngleVectors( self->s.angles2, dttAdjusted, NULL, NULL ); + RotatePointAroundVector( dirToTarget, xNormal, dttAdjusted, -rotAngle ); + vectoangles( dirToTarget, self->turretAim ); + + //if pointing at our target return true + if( abs( angleToTarget[ YAW ] - self->s.angles2[ YAW ] ) <= accuracyTolerance && + abs( angleToTarget[ PITCH ] - self->s.angles2[ PITCH ] ) <= accuracyTolerance ) + return qtrue; + + return qfalse; +} + + +/* +================ +HMGTurret_CheckTarget + +Used by HMGTurret_Think to check enemies for validity +================ +*/ +qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ignorePainted ) +{ + trace_t trace; + gentity_t *traceEnt; + + if( !target ) + return qfalse; + + if( !target->client ) + 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 ); + + traceEnt = &g_entities[ trace.entityNum ]; + + if( !traceEnt->client ) + return qfalse; + + if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) + return qfalse; + + return qtrue; +} + + +/* +================ +HMGTurret_FindEnemy + +Used by HMGTurret_Think to locate enemy gentities +================ +*/ +void HMGTurret_FindEnemy( gentity_t *self ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range; + vec3_t mins, maxs; + int i, num; + gentity_t *target; + + VectorSet( range, MGTURRET_RANGE, MGTURRET_RANGE, MGTURRET_RANGE ); + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + + //find aliens + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + 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, qfalse ) ) + continue; + + //we found a target + self->enemy = target; + return; + } + } + + if( self->dcced ) + { + //check again, this time ignoring painted targets + for( i = 0; i < num; i++ ) + { + 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; + } + } + } + + //couldn't find a target + self->enemy = NULL; +} + + +/* +================ +HMGTurret_Think + +Think function for MG turret +================ +*/ +void HMGTurret_Think( gentity_t *self ) +{ + int firespeed = BG_FindFireSpeedForBuildable( self->s.modelindex ); + + self->nextthink = level.time + BG_FindNextThinkForBuildable( self->s.modelindex ); + + //used for 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 = findPower( self ) ) ) + { + self->nextthink = level.time + POWER_REFRESH_TIME; + return; + } + + if( self->spawned ) + { + //find a dcc for self + self->dcced = 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 ); + } + + //if a new target cannot be found don't do anything + if( !self->enemy ) + return; + + self->enemy->targeted = self; + + //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->s.eFlags |= EF_FIRING; + G_AddEvent( self, EV_FIRE_WEAPON, 0 ); + G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + + self->count = level.time + firespeed; + } + } +} + + + + +//================================================================================== + + + + +/* +================ +HTeslaGen_Think + +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_FindNextThinkForBuildable( self->s.modelindex ); + + //if not powered don't do anything and check again for power next think + if( !( self->powered = findPower( self ) ) || !( self->dcced = findDCC( self ) ) ) + { + self->s.eFlags &= ~EF_FIRING; + self->nextthink = level.time + POWER_REFRESH_TIME; + return; + } + + if( self->spawned && self->count < level.time ) + { + //used to mark client side effects + self->s.eFlags &= ~EF_FIRING; + + VectorSet( range, TESLAGEN_RANGE, TESLAGEN_RANGE, TESLAGEN_RANGE ); + VectorAdd( self->s.origin, range, maxs ); + VectorSubtract( self->s.origin, range, mins ); + + //find aliens + 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_ALIENS && + enemy->health > 0 ) + { + VectorSubtract( enemy->s.pos.trBase, self->s.pos.trBase, dir ); + VectorNormalize( dir ); + vectoangles( dir, self->turretAim ); + + //fire at target + FireWeapon( self ); + } + } + + if( self->s.eFlags & EF_FIRING ) + { + G_AddEvent( self, EV_FIRE_WEAPON, 0 ); + + //doesn't really need an anim + //G_setBuildableAnim( self, BANIM_ATTACK1, qfalse ); + + self->count = level.time + TESLAGEN_REPEAT; + } + } +} + + + + +//================================================================================== + + + + +/* +================ +HSpawn_Disappear + +Called when a human spawn is destroyed before it is spawned +think function +================ +*/ +void HSpawn_Disappear( 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->s.eFlags |= EF_NODRAW; //don't draw the model once its destroyed + self->timestamp = level.time; + + self->think = freeBuildable; + self->nextthink = level.time + 100; + + self->r.contents = 0; //stop collisions... + trap_LinkEntity( self ); //...requires a relink +} + + +/* +================ +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->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; + + //do some radius damage + G_RadiusDamage( self->s.pos.trBase, self, self->splashDamage, + self->splashRadius, self, self->splashMethodOfDeath ); + + self->think = freeBuildable; + self->nextthink = level.time + 100; + + self->r.contents = 0; //stop collisions... + trap_LinkEntity( self ); //...requires a relink +} + + +/* +================ +HSpawn_die + +Called when a human spawn dies +================ +*/ +void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod ) +{ + //pretty events and cleanup + G_setBuildableAnim( self, BANIM_DESTROY1, qtrue ); + G_setIdleBuildableAnim( self, BANIM_DESTROYED ); + + self->die = nullDieFunction; + 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 + } + + if( attacker && attacker->client && 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 ); + } +} + +/* +================ +HSpawn_Think + +Think for human spawn +================ +*/ +void HSpawn_Think( gentity_t *self ) +{ + gentity_t *ent; + + //make sure we have power + self->powered = findPower( self ); + + 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( ent->s.eType == ET_BUILDABLE || ent->s.number == ENTITYNUM_WORLD || + ent->s.eType == ET_MOVER ) + { + G_Damage( self, NULL, NULL, NULL, NULL, 10000, 0, MOD_SUICIDE ); + return; + } + + if( ent->s.eType == ET_CORPSE ) + G_FreeEntity( ent ); //quietly remove + } + } + + //spawn under attack + if( self->health < self->lastHealth && + level.time > level.humanBaseAttackTimer && G_isDCC( ) ) + { + 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 ); +} + + + + +//================================================================================== + + +/* +============ +G_BuildableTouchTriggers + +Find all trigger entities that a buildable touches. +============ +*/ +void G_BuildableTouchTriggers( gentity_t *ent ) +{ + int i, num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs; + vec3_t bmins, bmaxs; + static vec3_t range = { 10, 10, 10 }; + + // dead buildables don't activate triggers! + if( ent->health <= 0 ) + return; + + BG_FindBBoxForBuildable( ent->s.modelindex, bmins, bmaxs ); + + VectorAdd( ent->s.origin, bmins, mins ); + VectorAdd( ent->s.origin, 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 ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + + if( !hit->touch ) + continue; + + if( !( hit->r.contents & CONTENTS_TRIGGER ) ) + continue; + + //ignore buildables not yet spawned + if( !ent->spawned ) + continue; + + if( !trap_EntityContact( mins, maxs, hit ) ) + continue; + + memset( &trace, 0, sizeof( trace ) ); + + if( hit->touch ) + hit->touch( hit, ent, &trace ); + } +} + + +/* +=============== +G_BuildableThink + +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 + + //toggle spawned flag for buildables + if( !ent->spawned ) + { + if( ent->buildTime + bTime < level.time ) + ent->spawned = qtrue; + } + + ent->s.generic1 = (int)( ( (float)ent->health / (float)bHealth ) * B_HEALTH_SCALE ); + + 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; + + ent->time1000 += msec; + + if( ent->time1000 >= 1000 ) + { + ent->time1000 -= 1000; + + if( !ent->spawned ) + 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 > bHealth ) + ent->health = bHealth; + } + + 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 + G_BuildableTouchTriggers( ent ); + + //fall back on normal physics routines + G_Physics( ent, msec ); +} + + +/* +=============== +G_BuildableRange + +Check whether a point is within some range of a type of buildable +=============== +*/ +qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range; + vec3_t mins, maxs; + int i, num; + gentity_t *ent; + + VectorSet( range, r, r, r ); + VectorAdd( origin, range, maxs ); + VectorSubtract( origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + ent = &g_entities[ entityList[ i ] ]; + + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->biteam == BIT_HUMANS && !ent->powered ) + continue; + + if( ent->s.modelindex == buildable && ent->spawned ) + return qtrue; + } + + return qfalse; +} + + +/* +================ +G_itemFits + +Checks to see if an item fits in a specific area +================ +*/ +itemBuildError_t G_itemFits( gentity_t *ent, buildable_t buildable, int distance, vec3_t origin ) +{ + vec3_t angles; + vec3_t entity_origin, normal; + vec3_t mins, maxs; + trace_t tr1, tr2, tr3; + int i; + itemBuildError_t reason = IBE_NONE; + gentity_t *tempent; + float minNormal; + qboolean invert; + int contents; + playerState_t *ps = &ent->client->ps; + + BG_FindBBoxForBuildable( 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 ); + + //this item does not fit here + if( tr2.fraction < 1.0 || tr3.fraction < 1.0 ) + return IBE_NOROOM; //NO other reason is allowed to override this + + VectorCopy( tr1.plane.normal, normal ); + minNormal = BG_FindMinNormalForBuildable( buildable ); + invert = BG_FindInvertNormalForBuildable( buildable ); + + //can we build at this angle? + if( !( normal[ 2 ] >= minNormal || ( invert && normal[ 2 ] <= -minNormal ) ) ) + return IBE_NORMAL; + + if( tr1.entityNum != ENTITYNUM_WORLD ) + return IBE_NORMAL; + + //check there is enough room to spawn from (presuming this is a spawn) + if( G_CheckSpawnPoint( -1, origin, normal, buildable, NULL ) != NULL ) + return IBE_NORMAL; + + contents = trap_PointContents( entity_origin, -1 ); + + if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + //alien criteria + + if( buildable == BA_A_HOVEL ) + { + 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( angles, origin, normal, ent ) ) + reason = IBE_HOVELEXIT; + } + + //check there is creep near by for building on + if( BG_FindCreepTestForBuildable( buildable ) ) + { + if( !isCreep( 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 ) + reason = IBE_PERMISSION; + + //look for a hivemind + 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 ) + 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 ) + { + 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; + } + } + } + + if( level.alienBuildPoints - BG_FindBuildPointsForBuildable( buildable ) < 0 ) + reason = IBE_NOASSERT; + } + else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + //human criteria + if( !G_isPower( entity_origin ) ) + { + //tell player to build a repeater to provide power + if( buildable != BA_H_REACTOR && buildable != BA_H_REPEATER ) + reason = IBE_REPEATER; + } + + //this buildable requires a DCC + if( BG_FindDCCTestForBuildable( buildable ) && !G_isDCC( ) ) + 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_isPower( entity_origin ) ) + reason = IBE_RPTWARN2; + } + + //check permission to build here + if( tr1.surfaceFlags & SURF_NOHUMANBUILD || tr1.surfaceFlags & SURF_NOBUILD || + contents & CONTENTS_NOHUMANBUILD || contents & CONTENTS_NOBUILD ) + reason = IBE_PERMISSION; + + //can we only build 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 == BA_H_REACTOR ) + { + reason = IBE_REACTOR; + break; + } + } + } + + if( level.humanBuildPoints - BG_FindBuildPointsForBuildable( buildable ) < 0 ) + reason = IBE_NOPOWER; + } + + return reason; +} + + +/* +================ +G_buildItem + +Spawns a buildable +================ +*/ +gentity_t *G_buildItem( gentity_t *builder, buildable_t buildable, vec3_t origin, vec3_t angles ) +{ + gentity_t *built; + vec3_t normal; + + //spawn the buildable + built = G_Spawn(); + + built->s.eType = ET_BUILDABLE; + + built->classname = BG_FindEntityNameForBuildable( buildable ); + + 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 ); + built->health = 1; + + built->splashDamage = BG_FindSplashDamageForBuildable( buildable ); + built->splashRadius = BG_FindSplashRadiusForBuildable( buildable ); + built->splashMethodOfDeath = BG_FindMODForBuildable( buildable ); + + built->nextthink = BG_FindNextThinkForBuildable( buildable ); + + built->takedamage = qtrue; + built->spawned = qfalse; + built->buildTime = built->s.time = level.time; + + //things that vary for each buildable that aren't in the dbase + switch( buildable ) + { + case BA_A_SPAWN: + built->die = ASpawn_Die; + built->think = ASpawn_Think; + built->pain = ASpawn_Pain; + break; + + case BA_A_BARRICADE: + built->die = ABarricade_Die; + built->think = ABarricade_Think; + built->pain = ABarricade_Pain; + break; + + case BA_A_BOOSTER: + built->die = ABarricade_Die; + built->think = ABarricade_Think; + built->pain = ABarricade_Pain; + built->touch = ABooster_Touch; + break; + + case BA_A_ACIDTUBE: + built->die = ABarricade_Die; + built->think = AAcidTube_Think; + built->pain = ASpawn_Pain; + break; + + case BA_A_HIVE: + built->die = ABarricade_Die; + built->think = AHive_Think; + built->pain = ASpawn_Pain; + break; + + case BA_A_TRAPPER: + built->die = ABarricade_Die; + built->think = ATrapper_Think; + built->pain = ASpawn_Pain; + break; + + case BA_A_OVERMIND: + built->die = ASpawn_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; + break; + + case BA_H_SPAWN: + built->die = HSpawn_Die; + built->think = HSpawn_Think; + break; + + case BA_H_MGTURRET: + built->die = HSpawn_Die; + built->think = HMGTurret_Think; + break; + + case BA_H_TESLAGEN: + built->die = HSpawn_Die; + built->think = HTeslaGen_Think; + break; + + case BA_H_ARMOURY: + built->think = HArmoury_Think; + built->die = HSpawn_Die; + built->use = HArmoury_Activate; + break; + + case BA_H_DCC: + built->think = HDCC_Think; + built->die = HSpawn_Die; + break; + + case BA_H_MEDISTAT: + built->think = HMedistat_Think; + built->die = HSpawn_Die; + break; + + case BA_H_REACTOR: + built->think = HReactor_Think; + built->die = HSpawn_Die; + built->use = HRepeater_Use; + built->powered = built->active = qtrue; + break; + + case BA_H_REPEATER: + built->think = HRepeater_Think; + built->die = HSpawn_Die; + built->use = HRepeater_Use; + built->count = -1; + break; + + default: + //erk + 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 ); + + if( builder->client ) + built->builtBy = builder->client->ps.clientNum; + else + built->builtBy = -1; + + G_SetOrigin( built, origin ); + VectorCopy( angles, built->s.angles ); + built->s.angles[ 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; + + if( builder->client && 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 ); + + //gently nudge the buildable onto the surface :) + VectorScale( normal, -50.0f, built->s.pos.trDelta ); + } + else + VectorSet( normal, 0.0f, 0.0f, 1.0f ); + + built->s.generic1 = (int)( ( (float)built->health / + (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_SCALE ); + + if( built->s.generic1 < 0 ) + built->s.generic1 = 0; + + if( ( built->powered = findPower( built ) ) ) + built->s.generic1 |= B_POWERED_TOGGLEBIT; + + if( ( built->dcced = findDCC( built ) ) ) + built->s.generic1 |= B_DCCED_TOGGLEBIT; + + built->s.generic1 &= ~B_SPAWNED_TOGGLEBIT; + + VectorCopy( normal, built->s.origin2 ); + + G_AddEvent( built, EV_BUILD_CONSTRUCT, 0 ); + + G_setIdleBuildableAnim( built, BG_FindAnimForBuildable( buildable ) ); + + if( built->builtBy >= 0 ) + G_setBuildableAnim( built, BANIM_CONSTRUCT1, qtrue ); + + trap_LinkEntity( built ); + + return built; +} + +/* +================= +G_ValidateBuild +================= +*/ +qboolean G_ValidateBuild( gentity_t *ent, buildable_t buildable ) +{ + float dist; + vec3_t origin; + + dist = BG_FindBuildDistForClass( ent->client->ps.stats[ STAT_PCLASS ] ); + + switch( G_itemFits( ent, buildable, dist, origin ) ) + { + case IBE_NONE: + G_buildItem( ent, buildable, origin, ent->s.apos.trBase ); + return qtrue; + + case IBE_NOASSERT: + G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOASSERT ); + return qfalse; + + case IBE_NOOVERMIND: + G_TriggerMenu( ent->client->ps.clientNum, MN_A_NOOVMND ); + return qfalse; + + case IBE_NOCREEP: + 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 ); + 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 ); + 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 ); + return qfalse; + + case IBE_REACTOR: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_REACTOR ); + return qfalse; + + case IBE_REPEATER: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_REPEATER ); + 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 ); + return qfalse; + + case IBE_NOPOWER: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOPOWER ); + return qfalse; + + case IBE_NODCC: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_NODCC ); + return qfalse; + + case IBE_SPWNWARN: + G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN ); + G_buildItem( ent, buildable, origin, ent->s.apos.trBase ); + return qtrue; + + case IBE_TNODEWARN: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN ); + G_buildItem( ent, buildable, origin, ent->s.apos.trBase ); + return qtrue; + + case IBE_RPTWARN: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN ); + G_buildItem( ent, buildable, origin, ent->s.apos.trBase ); + return qtrue; + + case IBE_RPTWARN2: + G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN2 ); + return qfalse; + + default: + break; + } + + return qfalse; +} + +/* +================ +FinishSpawningBuildable + +Traces down to find where an item should rest, instead of letting them +free fall from their spawn points +================ +*/ +void FinishSpawningBuildable( gentity_t *ent ) +{ + trace_t tr; + vec3_t dest; + gentity_t *built; + buildable_t buildable = ent->s.modelindex; + + built = G_buildItem( ent, buildable, ent->s.pos.trBase, ent->s.angles ); + G_FreeEntity( ent ); + + built->takedamage = qtrue; + built->spawned = qtrue; //map entities are already spawned + built->health = BG_FindHealthForBuildable( buildable ); + built->s.generic1 |= B_SPAWNED_TOGGLEBIT; + + // drop to floor + if( buildable != BA_NONE && BG_FindTrajectoryForBuildable( buildable ) == TR_BUOYANCY ) + VectorSet( dest, built->s.origin[ 0 ], built->s.origin[ 1 ], built->s.origin[ 2 ] + 4096 ); + else + VectorSet( dest, built->s.origin[ 0 ], built->s.origin[ 1 ], built->s.origin[ 2 ] - 4096 ); + + 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 "FinishSpawningBuildable: %s startsolid at %s\n", built->classname, vtos( built->s.origin ) ); + G_FreeEntity( built ); + return; + } + + //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 ); +} + +/* +============ +G_SpawnBuildable + +Sets the clipping size and plants the object on the floor. + +Items can't be immediately dropped to floor, because they might +be on an entity that hasn't spawned yet. +============ +*/ +void G_SpawnBuildable( gentity_t *ent, buildable_t buildable ) +{ + ent->s.modelindex = 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 = FinishSpawningBuildable; +} diff --git a/src/game/g_client.c b/src/game/g_client.c new file mode 100644 index 00000000..e86439c4 --- /dev/null +++ b/src/game/g_client.c @@ -0,0 +1,1593 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +#include "g_local.h" + +// g_client.c -- client functions that don't happen every frame + +static vec3_t playerMins = {-15, -15, -24}; +static vec3_t playerMaxs = {15, 15, 32}; + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_deathmatch( gentity_t *ent ) +{ + int i; + + G_SpawnInt( "nobots", "0", &i); + + if( i ) + ent->flags |= FL_NO_BOTS; + + G_SpawnInt( "nohumans", "0", &i ); + if( i ) + ent->flags |= FL_NO_HUMANS; +} + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +equivelant to info_player_deathmatch +*/ +void SP_info_player_start( gentity_t *ent ) +{ + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_player_intermission( gentity_t *ent ) +{ +} + +/*QUAKED info_alien_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_alien_intermission( gentity_t *ent ) +{ +} + +/*QUAKED info_human_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_human_intermission( gentity_t *ent ) +{ +} + +/* +=============== +G_AddCreditToClient +=============== +*/ +void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap ) +{ + if( !client ) + return; + + //if we're already at the max and trying to add credit then stop + if( cap ) + { + if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + if( client->ps.persistant[ PERS_CREDIT ] >= ALIEN_MAX_KILLS && + credit > 0 ) + return; + } + else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + if( client->ps.persistant[ PERS_CREDIT ] >= HUMAN_MAX_CREDITS && + credit > 0 ) + return; + } + } + + client->ps.persistant[ PERS_CREDIT ] += credit; + + if( cap ) + { + if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + if( client->ps.persistant[ PERS_CREDIT ] > ALIEN_MAX_KILLS ) + client->ps.persistant[ PERS_CREDIT ] = ALIEN_MAX_KILLS; + } + else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + if( client->ps.persistant[ PERS_CREDIT ] > HUMAN_MAX_CREDITS ) + client->ps.persistant[ PERS_CREDIT ] = HUMAN_MAX_CREDITS; + } + } + + if( client->ps.persistant[ PERS_CREDIT ] < 0 ) + client->ps.persistant[ PERS_CREDIT ] = 0; +} + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +SpotWouldTelefrag + +================ +*/ +qboolean SpotWouldTelefrag( gentity_t *spot ) +{ + int i, num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( spot->s.origin, playerMins, mins ); + VectorAdd( spot->s.origin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + //if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + if( hit->client ) + return qtrue; + } + + return qfalse; +} + +/* +================ +SelectNearestDeathmatchSpawnPoint + +Find the spot that we DON'T want to use +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *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; +} + + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *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 ]; +} + + +/* +=========== +SelectRandomFurthestSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) +{ + gentity_t *spot; + vec3_t delta; + float dist; + float list_dist[ 64 ]; + gentity_t *list_spot[ 64 ]; + int numSpots, rnd, i, j; + + numSpots = 0; + spot = NULL; + + while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) + { + if( SpotWouldTelefrag( spot ) ) + continue; + + VectorSubtract( spot->s.origin, avoidPoint, delta ); + dist = VectorLength( delta ); + + for( i = 0; i < numSpots; i++ ) + { + if( dist > list_dist[ i ] ) + { + if( numSpots >= 64 ) + numSpots = 64 - 1; + + for( j = numSpots; j > i; j-- ) + { + list_dist[ j ] = list_dist[ j - 1 ]; + list_spot[ j ] = list_spot[ j - 1 ]; + } + + list_dist[ i ] = dist; + list_spot[ i ] = spot; + numSpots++; + + if( numSpots > 64 ) + numSpots = 64; + + break; + } + } + + if( i >= numSpots && numSpots < 64 ) + { + list_dist[ numSpots ] = dist; + list_spot[ numSpots ] = spot; + numSpots++; + } + } + + if( !numSpots ) + { + spot = G_Find( NULL, FOFS( classname ), "info_player_deathmatch" ); + + if( !spot ) + G_Error( "Couldn't find a spawn point" ); + + VectorCopy( spot->s.origin, origin ); + origin[ 2 ] += 9; + VectorCopy( spot->s.angles, angles ); + return spot; + } + + // select a random spot from the spawn points furthest away + rnd = random( ) * ( numSpots / 2 ); + + VectorCopy( list_spot[ rnd ]->s.origin, origin ); + origin[ 2 ] += 9; + VectorCopy( list_spot[ rnd ]->s.angles, angles ); + + return list_spot[ rnd ]; +} + + +/* +================ +SelectAlienSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +gentity_t *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 ); +} + + +/* +================ +SelectHumanSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +gentity_t *SelectHumanSpawnPoint( vec3_t preference ) +{ + gentity_t *spot; + int count; + gentity_t *spots[ MAX_SPAWN_POINTS ]; + + if( level.numHumanSpawns <= 0 ) + return NULL; + + count = 0; + spot = NULL; + + while( ( spot = G_Find( spot, FOFS( classname ), + BG_FindEntityNameForBuildable( BA_H_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_H_SPAWN, NULL ) != NULL ) + continue; + + spots[ count ] = spot; + count++; + } + + if( !count ) + return NULL; + + return G_ClosestEnt( preference, spots, count ); +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) +{ + return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); +} + + +/* +=========== +SelectTremulousSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles ) +{ + gentity_t *spot = NULL; + + if( team == PTE_ALIENS ) + spot = SelectAlienSpawnPoint( preference ); + else if( team == PTE_HUMANS ) + spot = SelectHumanSpawnPoint( preference ); + + //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 ); + + VectorCopy( spot->s.angles, angles ); + angles[ ROLL ] = 0; + + return spot; + +} + + +/* +=========== +SelectInitialSpawnPoint + +Try to find a spawn point marked 'initial', otherwise +use normal spawn selection. +============ +*/ +gentity_t *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 SelectSpawnPoint( vec3_origin, origin, angles ); + } + + VectorCopy( spot->s.origin, origin ); + origin[ 2 ] += 9; + VectorCopy( spot->s.angles, angles ); + + return spot; +} + +/* +=========== +SelectSpectatorSpawnPoint + +============ +*/ +gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) +{ + FindIntermissionPoint( ); + + VectorCopy( level.intermission_origin, origin ); + VectorCopy( level.intermission_angle, angles ); + + return NULL; +} + + +/* +=========== +SelectAlienLockSpawnPoint + +Try to find a spawn point for alien intermission otherwise +use normal intermission spawn. +============ +*/ +gentity_t *SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles ) +{ + gentity_t *spot; + + spot = NULL; + spot = G_Find( spot, FOFS( classname ), "info_alien_intermission" ); + + if( !spot ) + return SelectSpectatorSpawnPoint( origin, angles ); + + VectorCopy( spot->s.origin, origin ); + VectorCopy( spot->s.angles, angles ); + + return spot; +} + + +/* +=========== +SelectHumanLockSpawnPoint + +Try to find a spawn point for human intermission otherwise +use normal intermission spawn. +============ +*/ +gentity_t *SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles ) +{ + gentity_t *spot; + + spot = NULL; + spot = G_Find( spot, FOFS( classname ), "info_human_intermission" ); + + if( !spot ) + return SelectSpectatorSpawnPoint( origin, angles ); + + VectorCopy( spot->s.origin, origin ); + VectorCopy( spot->s.angles, angles ); + + return spot; +} + + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +void BodySink( gentity_t *ent ) +{ + //run on first BodySink call + if( !ent->active ) + { + ent->active = qtrue; + + //sinking bodies can't be infested + ent->killedBy = ent->s.powerups = MAX_CLIENTS; + ent->timestamp = level.time; + } + + if( level.time - ent->timestamp > 6500 ) + { + G_FreeEntity( ent ); + return; + } + + ent->nextthink = level.time + 100; + ent->s.pos.trBase[ 2 ] -= 1; +} + + +/* +============= +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 ) +{ + gentity_t *body; + int contents; + vec3_t origin, dest; + trace_t tr; + float vDiff; + + VectorCopy( ent->r.currentOrigin, origin ); + + trap_UnlinkEntity( ent ); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( origin, -1 ); + if( contents & CONTENTS_NODROP ) + return; + + body = G_Spawn( ); + + VectorCopy( ent->s.apos.trBase, body->s.angles ); + 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->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; + + if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + body->classname = "humanCorpse"; + else + body->classname = "alienCorpse"; + + body->s.powerups = MAX_CLIENTS; + + body->think = BodySink; + body->nextthink = level.time + 20000; + + body->s.legsAnim = ent->s.legsAnim; + + if( !body->nonSegModel ) + { + switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) + { + case BOTH_DEATH1: + case BOTH_DEAD1: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; + break; + case BOTH_DEATH2: + case BOTH_DEAD2: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; + break; + case BOTH_DEATH3: + case BOTH_DEAD3: + default: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; + break; + } + } + else + { + switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) + { + case NSPA_DEATH1: + case NSPA_DEAD1: + body->s.legsAnim = NSPA_DEAD1; + break; + case NSPA_DEATH2: + case NSPA_DEAD2: + body->s.legsAnim = NSPA_DEAD2; + break; + case NSPA_DEATH3: + case NSPA_DEAD3: + default: + body->s.legsAnim = NSPA_DEAD3; + break; + } + } + + body->takedamage = qfalse; + + body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; + ent->health = 0; + + //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 ]; + + //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 ); + + 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 ); +} + +//====================================================================== + + +/* +================== +SetClientViewAngle + +================== +*/ +void SetClientViewAngle( gentity_t *ent, vec3_t angle ) +{ + int i; + + // set the delta angle + for( i = 0; i < 3; i++ ) + { + int cmdAngle; + + cmdAngle = ANGLE2SHORT( angle[ i ] ); + 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 ); +} + +/* +================ +respawn +================ +*/ +void respawn( gentity_t *ent ) +{ + SpawnCorpse( ent ); + + //TA: Clients can't respawn - they must go thru the class cmd + ClientSpawn( ent, NULL, NULL, NULL ); +} + +/* +================ +TeamCount + +Returns number of players on a team +================ +*/ +team_t TeamCount( int ignoreClientNum, int team ) +{ + int i; + int count = 0; + + for( i = 0 ; i < level.maxclients ; i++ ) + { + if( i == ignoreClientNum ) + continue; + + if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) + continue; + + if( level.clients[ i ].sess.sessionTeam == team ) + count++; + } + + return count; +} + + +/* +=========== +ClientCheckName +============ +*/ +static void ClientCleanName( const char *in, char *out, int outSize ) +{ + int len, colorlessLen; + char ch; + char *p; + int spaces; + + //save room for trailing null byte + outSize--; + + len = 0; + colorlessLen = 0; + p = out; + *p = 0; + spaces = 0; + + while( 1 ) + { + ch = *in++; + if( !ch ) + break; + + // don't allow leading spaces + if( !*p && ch == ' ' ) + continue; + + // check colors + if( ch == Q_COLOR_ESCAPE ) + { + // solo trailing carat is not a color prefix + if( !*in ) + break; + + // don't allow black in a name, period + if( ColorIndex( *in ) == 0 ) + { + in++; + continue; + } + + // make sure room in dest for both chars + if( len > outSize - 2 ) + break; + + *out++ = ch; + *out++ = *in++; + len += 2; + continue; + } + + // don't allow too many consecutive spaces + if( ch == ' ' ) + { + spaces++; + if( spaces > 3 ) + continue; + } + else + spaces = 0; + + if( len > outSize - 1 ) + break; + + *out++ = ch; + colorlessLen++; + len++; + } + + *out = 0; + + // don't allow empty names + if( *p == 0 || colorlessLen == 0 ) + Q_strncpyz( p, "UnnamedPlayer", outSize ); +} + + +/* +====================== +G_NonSegModel + +Reads an animation.cfg to check for nonsegmentation +====================== +*/ +static qboolean G_NonSegModel( const char *filename ) +{ + char *text_p; + int len; + char *token; + char text[ 20000 ]; + fileHandle_t f; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( !f ) + { + G_Printf( "File not found: %s\n", filename ); + return qfalse; + } + + if( len <= 0 ) + return qfalse; + + if( len >= sizeof( text ) - 1 ) + { + G_Printf( "File %s too long\n", filename ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + //EOF + if( !token[ 0 ] ) + break; + + if( !Q_stricmp( token, "nonsegmented" ) ) + return qtrue; + } + + return qfalse; +} + +/* +=========== +ClientUserInfoChanged + +Called from ClientConnect when the player first connects and +directly by the server system when the player updates a userinfo variable. + +The game can override any of the settings and call trap_SetUserinfo +if desired. +============ +*/ +void ClientUserinfoChanged( int clientNum ) +{ + gentity_t *ent; + int teamTask, teamLeader, health; + char *s; + char model[ MAX_QPATH ]; + char buffer[ MAX_QPATH ]; + char filename[ MAX_QPATH ]; + char oldname[ MAX_STRING_CHARS ]; + gclient_t *client; + char c1[ MAX_INFO_STRING ]; + char c2[ MAX_INFO_STRING ]; + char redTeam[ MAX_INFO_STRING ]; + char blueTeam[ MAX_INFO_STRING ]; + char userinfo[ MAX_INFO_STRING ]; + team_t team; + + ent = g_entities + clientNum; + client = ent->client; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if( !Info_Validate(userinfo) ) + strcpy( userinfo, "\\name\\badinfo" ); + + // 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; + + // set name + Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey( userinfo, "name" ); + ClientCleanName( s, client->pers.netname, sizeof( client->pers.netname ) ); + + if( client->sess.sessionTeam == TEAM_SPECTATOR ) + { + if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) + Q_strncpyz( client->pers.netname, "scoreboard", sizeof( client->pers.netname ) ); + } + + if( client->pers.connected == CON_CONNECTED ) + { + if( strcmp( oldname, client->pers.netname ) ) + { + G_SendCommandFromServer( -1, va( "print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, + client->pers.netname ) ); + } + } + + // set max health + health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + client->pers.maxHealth = health; + + 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 ) ) + { + Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( PCL_HUMAN_BSUIT ), + BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) ); + } + else 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 ) ); + } + else + { + Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( client->pers.classSelection ), + BG_FindSkinNameForClass( client->pers.classSelection ) ); + } + Q_strncpyz( model, buffer, sizeof( model ) ); + + //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 ) ); + + if( G_NonSegModel( filename ) ) + client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL; + } + + // wallwalk follow + s = Info_ValueForKey( userinfo, "cg_wwFollow" ); + + if( atoi( s ) ) + client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGFOLLOW; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGFOLLOW; + + // wallwalk toggle + s = Info_ValueForKey( userinfo, "cg_wwToggle" ); + + if( atoi( s ) ) + client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGTOGGLE; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE; + + // teamInfo + s = Info_ValueForKey( userinfo, "teamoverlay" ); + + if( ! *s || atoi( s ) != 0 ) + client->pers.teamInfo = qtrue; + else + client->pers.teamInfo = 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" ) ); + strcpy( redTeam, "humans" ); + strcpy( blueTeam, "aliens" ); + + if( client->ps.pm_flags & PMF_FOLLOW ) + team = PTE_NONE; + else + team = client->ps.stats[ STAT_PTEAM ]; + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + s = va( "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", + client->pers.netname, team, model, model, redTeam, blueTeam, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); + + trap_SetConfigstring( CS_PLAYERS + clientNum, s ); + + /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s );*/ +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and tournement +restarts. +============ +*/ +char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) +{ + char *value; + gclient_t *client; + char userinfo[ MAX_INFO_STRING ]; + gentity_t *ent; + + ent = &g_entities[ clientNum ]; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // 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" ); + if( G_FilterPacket( value ) ) + return "You are banned from this server."; + + // check for a password + value = Info_ValueForKey( userinfo, "password" ); + + if( g_password.string[ 0 ] && Q_stricmp( g_password.string, "none" ) && + strcmp( g_password.string, value ) != 0 ) + return "Invalid password"; + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + + memset( client, 0, sizeof(*client) ); + + client->pers.connected = CON_CONNECTING; + + // read or initialize the session data + if( firstTime || level.newSession ) + G_InitSessionData( client, userinfo ); + + G_ReadSessionData( client ); + + // get and distribute relevent paramters + G_LogPrintf( "ClientConnect: %i\n", clientNum ); + ClientUserinfoChanged( clientNum ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if( firstTime ) + G_SendCommandFromServer( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname ) ); + + // count current clients and rank for scoreboard + CalculateRanks( ); + + return NULL; +} + +/* +=========== +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 +============ +*/ +void ClientBegin( int clientNum ) +{ + gentity_t *ent; + gclient_t *client; + int flags; + + ent = g_entities + clientNum; + + client = level.clients + clientNum; + + if( ent->r.linked ) + trap_UnlinkEntity( ent ); + + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + client->pers.connected = CON_CONNECTED; + client->pers.enterTime = level.time; + client->pers.teamState.state = TEAM_BEGIN; + + // save eflags around this, because changing teams will + // cause this to happen with a valid entity, and we + // want to make sure the teleport bit is set right + // so the viewpoint doesn't interpolate through the + // world to the new position + flags = client->ps.eFlags; + memset( &client->ps, 0, sizeof( client->ps ) ); + client->ps.eFlags = flags; + + // locate ent at a spawn point + + ClientSpawn( ent, NULL, NULL, NULL ); + + G_InitCommandQueue( clientNum ); + + G_SendCommandFromServer( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); + + // request the clients PTR code + G_SendCommandFromServer( ent - g_entities, "ptrcrequest" ); + + G_LogPrintf( "ClientBegin: %i\n", clientNum ); + + // count current clients and rank for scoreboard + CalculateRanks( ); +} + +/* +=========== +ClientSpawn + +Called every time a client is placed fresh in the world: +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 ) +{ + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + clientPersistant_t saved; + clientSession_t savedSess; + int persistant[ MAX_PERSISTANT ]; + gentity_t *spawnPoint = NULL; + int flags; + int savedPing; + int teamLocal; + int eventSequence; + char userinfo[ MAX_INFO_STRING ]; + vec3_t up = { 0.0f, 0.0f, 1.0f }; + 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 ) + { + client->sess.sessionTeam = TEAM_SPECTATOR; + client->sess.spectatorState = SPECTATOR_FREE; + } + else if( client->pers.classSelection == PCL_NONE ) + { + client->sess.sessionTeam = TEAM_SPECTATOR; + client->sess.spectatorState = SPECTATOR_LOCKED; + } + + if( origin != NULL ) + VectorCopy( origin, spawn_origin ); + + if( angles != NULL ) + VectorCopy( angles, spawn_angles ); + + // 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( teamLocal == PTE_NONE ) + spawnPoint = SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); + else if( teamLocal == PTE_ALIENS ) + spawnPoint = SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); + else if( teamLocal == PTE_HUMANS ) + spawnPoint = SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + } + else + { + if( spawn == NULL ) + { + G_Error( "ClientSpawn: spawn is NULL\n" ); + return; + } + + spawnPoint = spawn; + + if( ent != spawn ) + { + //start spawn animation on spawnPoint + G_setBuildableAnim( spawnPoint, 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; + } + } + 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; + + // clear everything but the persistant data + + saved = client->pers; + savedSess = client->sess; + savedPing = client->ps.ping; + + for( i = 0; i < MAX_PERSISTANT; i++ ) + persistant[ i ] = client->ps.persistant[ i ]; + + eventSequence = client->ps.eventSequence; + memset( client, 0, sizeof( *client ) ); + + client->pers = saved; + client->sess = savedSess; + client->ps.ping = savedPing; + client->lastkilled_client = -1; + + for( i = 0; i < MAX_PERSISTANT; i++ ) + client->ps.persistant[ i ] = persistant[ i ]; + + client->ps.eventSequence = eventSequence; + + // increment the spawncount so the client will detect the respawn + client->ps.persistant[ PERS_SPAWN_COUNT ]++; + client->ps.persistant[ PERS_TEAM ] = client->sess.sessionTeam; + + client->airOutTime = level.time + 12000; + + trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); + client->ps.eFlags = flags; + + //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); + + 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; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + + //TA: 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.eFlags = flags; + client->ps.clientNum = index; + + BG_FindBBoxForClass( 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 ); + else + client->pers.maxHealth = 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 + weapon = WP_NONE; + + BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips ); + BG_AddWeaponToInventory( weapon, client->ps.stats ); + BG_PackAmmoArray( weapon, client->ps.ammo, client->ps.powerups, maxAmmo, maxClips ); + + ent->client->ps.stats[ STAT_PCLASS ] = ent->client->pers.classSelection; + ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection; + + ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; + ent->client->ps.stats[ STAT_STATE ] = 0; + VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); + + // health will count down towards max_health + ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25; + + //if evolving scale health + if( ent == spawn ) + { + ent->health *= ent->client->pers.evolveHealthFraction; + client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; + } + + //clear the credits array + for( i = 0; i < MAX_CLIENTS; i++ ) + ent->credits[ i ] = 0; + + client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, client->ps.origin ); + +#define UP_VEL 150.0f +#define F_VEL 50.0f + + //give aliens some spawn velocity + if( client->sess.sessionTeam != TEAM_SPECTATOR && + client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + if( ent == spawn ) + { + //evolution particle system + G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); + } + else + { + spawn_angles[ YAW ] += 180.0f; + AngleNormalize360( spawn_angles[ YAW ] ); + + if( spawnPoint->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 ); + VectorNormalize( dir ); + + VectorScale( dir, UP_VEL, client->ps.velocity ); + } + + G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); + } + } + else if( client->sess.sessionTeam != TEAM_SPECTATOR && + client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + spawn_angles[ YAW ] += 180.0f; + AngleNormalize360( spawn_angles[ YAW ] ); + } + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + SetClientViewAngle( ent, spawn_angles ); + + if( !( client->sess.sessionTeam == TEAM_SPECTATOR ) ) + { + /*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; + client->ps.weaponstate = WEAPON_READY; + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + client->lastKillTime = level.time; + + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->latched_buttons = 0; + + // set default animations + client->ps.torsoAnim = TORSO_STAND; + client->ps.legsAnim = LEGS_IDLE; + + if( level.intermissiontime ) + MoveClientToIntermission( ent ); + else + { + // fire the targets of the spawn point + if( !spawn ) + G_UseTargets( spawnPoint, 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; + } + } + } + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ent->client->pers.cmd.serverTime = level.time; + ClientThink( ent-g_entities ); + + // positively link the client, even if the command times are weird + if( client->sess.sessionTeam != TEAM_SPECTATOR ) + { + 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 + CalculateRanks( ); + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); +} + + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. + +This should NOT be called directly by any game logic, +call trap_DropClient(), which will call this and do +server system housekeeping. +============ +*/ +void ClientDisconnect( int clientNum ) +{ + gentity_t *ent; + gentity_t *tent; + int i; + + ent = g_entities + clientNum; + + if( !ent->client ) + return; + + // stop any following clients + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR && + level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && + level.clients[ i ].sess.spectatorClient == clientNum ) + { + if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) + G_StopFollowing( &g_entities[ i ] ); + } + } + + // send effect if they were completely connected + if( ent->client->pers.connected == CON_CONNECTED && + ent->client->sess.sessionTeam != TEAM_SPECTATOR ) + { + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = ent->s.clientNum; + } + + G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); + + 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; + + trap_SetConfigstring( CS_PLAYERS + clientNum, ""); + + CalculateRanks( ); +} diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c new file mode 100644 index 00000000..819fbb75 --- /dev/null +++ b/src/game/g_cmds.c @@ -0,0 +1,2305 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/* + * Portions Copyright (C) 2000-2001 Tim Angus + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the OSML - Open Source Modification License v1.0 as + * described in the file COPYING which is distributed with this source + * code. + * + * This program 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. + */ + +#include "g_local.h" + +/* +================== +G_SanitiseName + +Remove case and control characters from a player name +================== +*/ +void G_SanitiseName( char *in, char *out ) +{ + while( *in ) + { + if( *in == 27 ) + { + in += 2; // skip color code + continue; + } + + if( *in < 32 ) + { + in++; + continue; + } + + *out++ = tolower( *in++ ); + } + + *out = 0; +} + +/* +================== +G_ClientNumberFromString + +Returns a player number for either a number or name string +Returns -1 if invalid +================== +*/ +int G_ClientNumberFromString( gentity_t *to, char *s ) +{ + gclient_t *cl; + int idnum; + char s2[ MAX_STRING_CHARS ]; + char n2[ MAX_STRING_CHARS ]; + + // numeric values are just slot numbers + if( s[ 0 ] >= '0' && s[ 0 ] <= '9' ) + { + idnum = atoi( s ); + + if( idnum < 0 || idnum >= level.maxclients ) + { + G_SendCommandFromServer( to - g_entities, va( "print \"Bad client slot: %i\n\"", idnum ) ); + return -1; + } + + cl = &level.clients[ idnum ]; + + if( cl->pers.connected != CON_CONNECTED ) + { + G_SendCommandFromServer( to - g_entities, va( "print \"Client %i is not active\n\"", idnum ) ); + return -1; + } + + return idnum; + } + + // check for a name match + G_SanitiseName( s, s2 ); + + for( idnum = 0, cl = level.clients; idnum < level.maxclients; idnum++, cl++ ) + { + if( cl->pers.connected != CON_CONNECTED ) + continue; + + G_SanitiseName( cl->pers.netname, n2 ); + + if( !strcmp( n2, s2 ) ) + return idnum; + } + + G_SendCommandFromServer( to - g_entities, va( "print \"User %s is not on the server\n\"", s ) ); + return -1; +} + +/* +================== +ScoreboardMessage + +================== +*/ +void ScoreboardMessage( gentity_t *ent ) +{ + char entry[ 1024 ]; + char string[ 1400 ]; + int stringlength; + int i, j; + gclient_t *cl; + int numSorted; + weapon_t weapon = WP_NONE; + upgrade_t upgrade = UP_NONE; + + // send the latest information on all clients + string[ 0 ] = 0; + stringlength = 0; + + numSorted = level.numConnectedClients; + + for( i = 0; i < numSorted; i++ ) + { + int ping; + + cl = &level.clients[ level.sortedClients[ i ] ]; + + if( cl->pers.connected == CON_CONNECTING ) + ping = -1; + else + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + + if( cl->ps.stats[ STAT_HEALTH ] > 0 ) + { + weapon = 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 + { + weapon = WP_NONE; + upgrade = UP_NONE; + } + + Com_sprintf( entry, sizeof( entry ), + " %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 ) + break; + + strcpy( string + stringlength, entry ); + stringlength += j; + } + + G_SendCommandFromServer( ent-g_entities, va( "scores %i %i %i%s", i, + level.alienKills, level.humanKills, string ) ); +} + + +/* +================== +Cmd_Score_f + +Request current scoreboard information +================== +*/ +void Cmd_Score_f( gentity_t *ent ) +{ + ScoreboardMessage( ent ); +} + + + +/* +================== +CheatsOk +================== +*/ +qboolean CheatsOk( gentity_t *ent ) +{ + if( !g_cheats.integer ) + { + G_SendCommandFromServer( ent-g_entities, va( "print \"Cheats are not enabled on this server\n\"" ) ); + return qfalse; + } + + if( ent->health <= 0 ) + { + G_SendCommandFromServer( ent-g_entities, va( "print \"You must be alive to use this command\n\"" ) ); + return qfalse; + } + + return qtrue; +} + + +/* +================== +ConcatArgs +================== +*/ +char *ConcatArgs( int start ) +{ + int i, c, tlen; + static char line[ MAX_STRING_CHARS ]; + int len; + char arg[ MAX_STRING_CHARS ]; + + len = 0; + c = trap_Argc( ); + + for( i = start; i < c; i++ ) + { + trap_Argv( i, arg, sizeof( arg ) ); + tlen = strlen( arg ); + + if( len + tlen >= MAX_STRING_CHARS - 1 ) + break; + + memcpy( line + len, arg, tlen ); + len += tlen; + + if( i != c - 1 ) + { + line[ len ] = ' '; + len++; + } + } + + line[ len ] = 0; + + return line; +} + + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f( gentity_t *ent ) +{ + char *name; + qboolean give_all; + + if( !CheatsOk( ent ) ) + return; + + name = ConcatArgs( 1 ); + + if( Q_stricmp( name, "all" ) == 0 ) + give_all = qtrue; + else + give_all = qfalse; + + if( give_all || Q_stricmp( name, "health" ) == 0 ) + { + ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ]; + if( !give_all ) + return; + } + + if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) + { + int credits = atoi( name + 6 ); + + if( !credits ) + G_AddCreditToClient( ent->client, 1, qtrue ); + else + G_AddCreditToClient( ent->client, credits, qtrue ); + + if( !give_all ) + return; + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f( gentity_t *ent ) +{ + char *msg; + + if( !CheatsOk( ent ) ) + return; + + ent->flags ^= FL_GODMODE; + + if( !( ent->flags & FL_GODMODE ) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + G_SendCommandFromServer( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( gentity_t *ent ) +{ + char *msg; + + if( !CheatsOk( ent ) ) + return; + + ent->flags ^= FL_NOTARGET; + + if( !( ent->flags & FL_NOTARGET ) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + G_SendCommandFromServer( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( gentity_t *ent ) +{ + char *msg; + + if( !CheatsOk( ent ) ) + return; + + if( ent->client->noclip ) + msg = "noclip OFF\n"; + else + msg = "noclip ON\n"; + + ent->client->noclip = !ent->client->noclip; + + G_SendCommandFromServer( ent - g_entities, va( "print \"%s\"", msg ) ); +} + + +/* +================== +Cmd_LevelShot_f + +This is just to help generate the level pictures +for the menus. It goes to the intermission immediately +and sends over a command to the client to resize the view, +hide the scoreboard, and take a special screenshot +================== +*/ +void Cmd_LevelShot_f( gentity_t *ent ) +{ + if( !CheatsOk( ent ) ) + return; + + BeginIntermission( ); + G_SendCommandFromServer( ent - g_entities, "clientLevelShot" ); +} + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( gentity_t *ent ) +{ + if( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + return; + + if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_NONE ) + return; + + if( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) + return; + + if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) + { + G_SendCommandFromServer( ent-g_entities, "print \"Leave the hovel first (use your destroy key)\n\"" ); + return; + } + + if( ent->health <= 0 ) + return; + + if( g_cheats.integer ) + { + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; + player_die( ent, ent, ent, 100000, MOD_SUICIDE ); + } + else + { + if( ent->suicideTime == 0 ) + { + G_SendCommandFromServer( ent-g_entities, "print \"You will suicide in 20 seconds\n\"" ); + ent->suicideTime = level.time + 20000; + } + else if( ent->suicideTime > level.time ) + { + G_SendCommandFromServer( ent-g_entities, "print \"Suicide cancelled\n\"" ); + ent->suicideTime = 0; + } + } +} + +/* +================= +G_ChangeTeam +================= +*/ +void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam ) +{ + pTeam_t oldTeam = ent->client->pers.teamSelection; + + ent->client->pers.teamSelection = newTeam; + + if( oldTeam != newTeam ) + { + //if the client is in a queue make sure they are removed from it before changing + if( oldTeam == PTE_ALIENS ) + G_RemoveFromSpawnQueue( &level.alienSpawnQueue, ent->client->ps.clientNum ); + else if( oldTeam == PTE_HUMANS ) + G_RemoveFromSpawnQueue( &level.humanSpawnQueue, ent->client->ps.clientNum ); + + level.bankCredits[ ent->client->ps.clientNum ] = 0; + ent->client->ps.persistant[ PERS_CREDIT ] = 0; + ent->client->ps.persistant[ PERS_SCORE ] = 0; + ent->client->pers.classSelection = PCL_NONE; + ClientSpawn( ent, NULL, NULL, NULL ); + } + + ent->client->pers.joinedATeam = qtrue; + + //update ClientInfo + ClientUserinfoChanged( ent->client->ps.clientNum ); +} + +/* +================= +Cmd_Team_f +================= +*/ +void Cmd_Team_f( gentity_t *ent ) +{ + pTeam_t team; + char s[ MAX_TOKEN_CHARS ]; + + trap_Argv( 1, s, sizeof( s ) ); + + if( !strlen( s ) ) + { + G_SendCommandFromServer( ent-g_entities, va("print \"team: %i\n\"", ent->client->pers.teamSelection ) ); + return; + } + + if( !Q_stricmp( s, "spectate" ) ) + team = PTE_NONE; + else if( !Q_stricmp( s, "aliens" ) ) + { + if( g_teamForceBalance.integer && level.numAlienClients > level.numHumanClients ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_A_TEAMFULL ); + return; + } + + team = PTE_ALIENS; + } + else if( !Q_stricmp( s, "humans" ) ) + { + if( g_teamForceBalance.integer && level.numHumanClients > level.numAlienClients ) + { + G_TriggerMenu( ent->client->ps.clientNum, MN_H_TEAMFULL ); + return; + } + + team = PTE_HUMANS; + } + else if( !Q_stricmp( s, "auto" ) ) + { + if( level.numHumanClients > level.numAlienClients ) + team = PTE_ALIENS; + else if( level.numHumanClients < level.numAlienClients ) + team = PTE_HUMANS; + else + team = PTE_ALIENS + ( rand( ) % 2 ); + } + else + { + G_SendCommandFromServer( ent-g_entities, va( "print \"Unknown team: %s\n\"", s ) ); + return; + } + + G_ChangeTeam( ent, team ); + + if( team == PTE_ALIENS ) + G_SendCommandFromServer( -1, va( "print \"%s" S_COLOR_WHITE " joined the aliens\n\"", ent->client->pers.netname ) ); + else if( team == PTE_HUMANS ) + G_SendCommandFromServer( -1, va( "print \"%s" S_COLOR_WHITE " joined the humans\n\"", ent->client->pers.netname ) ); +} + + +/* +================== +G_Say +================== +*/ +static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message ) +{ + if( !other ) + return; + + if( !other->inuse ) + return; + + if( !other->client ) + return; + + if( other->client->pers.connected != CON_CONNECTED ) + return; + + if( mode == SAY_TEAM && !OnSameTeam( ent, other ) ) + return; + + G_SendCommandFromServer( other-g_entities, va( "%s \"%s%c%c%s\"", + mode == SAY_TEAM ? "tchat" : "chat", + name, Q_COLOR_ESCAPE, color, message ) ); +} + +#define EC "\x19" + +void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) +{ + int j; + gentity_t *other; + int color; + char name[ 64 ]; + // don't let text be too long for malicious reasons + char text[ MAX_SAY_TEXT ]; + char location[ 64 ]; + + switch( mode ) + { + default: + case SAY_ALL: + G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); + Com_sprintf( name, sizeof( name ), "%s%c%c"EC": ", ent->client->pers.netname, + Q_COLOR_ESCAPE, COLOR_WHITE ); + color = COLOR_GREEN; + break; + + case SAY_TEAM: + G_LogPrintf( "sayteam: %s: %s\n", 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 ); + color = COLOR_CYAN; + break; + + case SAY_TELL: + if( target && + target->client->ps.stats[ STAT_PTEAM ] == ent->client->ps.stats[ STAT_PTEAM ] && + 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; + } + + Q_strncpyz( text, chatText, sizeof( text ) ); + + if( target ) + { + G_SayTo( ent, target, mode, color, name, text ); + return; + } + + // echo the text to the console + if( g_dedicated.integer ) + G_Printf( "%s%s\n", name, text); + + // 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 ); + } +} + + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) +{ + char *p; + + if( trap_Argc( ) < 2 && !arg0 ) + return; + + if( arg0 ) + p = ConcatArgs( 0 ); + else + p = ConcatArgs( 1 ); + + 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 ) + return; + + trap_Argv( 1, arg, sizeof( arg ) ); + targetNum = atoi( arg ); + + if( targetNum < 0 || targetNum >= level.maxclients ) + return; + + target = &g_entities[ targetNum ]; + if( !target || !target->inuse || !target->client ) + return; + + p = ConcatArgs( 2 ); + + 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 && !( ent->r.svFlags & SVF_BOT ) ) + G_Say( ent, ent, SAY_TELL, p ); +} + +/* +================== +Cmd_Where_f +================== +*/ +void Cmd_Where_f( gentity_t *ent ) +{ + G_SendCommandFromServer( ent-g_entities, va( "print \"%s\n\"", vtos( ent->s.origin ) ) ); +} + +/* +================== +Cmd_CallVote_f +================== +*/ +void Cmd_CallVote_f( gentity_t *ent ) +{ + int i; + char arg1[ MAX_STRING_TOKENS ]; + char arg2[ MAX_STRING_TOKENS ]; + + if( !g_allowVote.integer ) + { + G_SendCommandFromServer( ent-g_entities, "print \"Voting not allowed here\n\"" ); + return; + } + + if( level.voteTime ) + { + G_SendCommandFromServer( ent-g_entities, "print \"A vote is already in progress\n\"" ); + return; + } + + if( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) + { + G_SendCommandFromServer( ent-g_entities, "print \"You have called the maximum number of votes\n\"" ); + return; + } + + if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_NONE ) + { + G_SendCommandFromServer( ent-g_entities, "print \"Not allowed to call a vote as spectator\n\"" ); + 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( arg1, ';' ) || strchr( arg2, ';' ) ) + { + G_SendCommandFromServer( ent-g_entities, "print \"Invalid vote string\n\"" ); + return; + } + + if( !Q_stricmp( arg1, "map_restart" ) ) { } + else if( !Q_stricmp( arg1, "nextmap" ) ) { } + else if( !Q_stricmp( arg1, "map" ) ) { } + else if( !Q_stricmp( arg1, "kick" ) ) { } + else if( !Q_stricmp( arg1, "clientkick" ) ) { } + else if( !Q_stricmp( arg1, "timelimit" ) ) { } + else + { + G_SendCommandFromServer( ent-g_entities, "print \"Invalid vote string\n\"" ); + G_SendCommandFromServer( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map , " + "kick , clientkick , " + "timelimit