diff options
author | Tim Angus <tim@ngus.net> | 2001-01-03 22:43:20 +0000 |
---|---|---|
committer | Tim Angus <tim@ngus.net> | 2001-01-03 22:43:20 +0000 |
commit | 7cc6c9cbe613b7022ad3b5ae1a8a9bd7811e5e6a (patch) | |
tree | d244805f389a78ce6f3d78fe2ea89ecd7a4ef9bf /src/game/g_utils.c | |
parent | 6f175bfee2d373b67a94261d26e3106d483099e8 (diff) |
1.27 upgrade
Diffstat (limited to 'src/game/g_utils.c')
-rw-r--r-- | src/game/g_utils.c | 718 |
1 files changed, 718 insertions, 0 deletions
diff --git a/src/game/g_utils.c b/src/game/g_utils.c new file mode 100644 index 00000000..b134a9df --- /dev/null +++ b/src/game/g_utils.c @@ -0,0 +1,718 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// g_utils.c -- misc utility functions for game module + +/* + * 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 GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* To assertain which portions are licensed under the GPL and which are + * licensed by Id Software, Inc. please run a diff between the equivalent + * versions of the "Tremulous" modification and the unmodified "Quake3" + * game source code. + */ + +#include "g_local.h" + +typedef struct { + char oldShader[MAX_QPATH]; + char newShader[MAX_QPATH]; + float timeOffset; +} shaderRemap_t; + +#define MAX_SHADER_REMAPS 128 + +int remapCount = 0; +shaderRemap_t remappedShaders[MAX_SHADER_REMAPS]; + +void AddRemap(const char *oldShader, const char *newShader, float timeOffset) { + int i; + + for (i = 0; i < remapCount; i++) { + if (Q_stricmp(oldShader, remappedShaders[i].oldShader) == 0) { + // found it, just update this one + strcpy(remappedShaders[i].newShader,newShader); + remappedShaders[i].timeOffset = timeOffset; + return; + } + } + if (remapCount < MAX_SHADER_REMAPS) { + strcpy(remappedShaders[remapCount].newShader,newShader); + strcpy(remappedShaders[remapCount].oldShader,oldShader); + remappedShaders[remapCount].timeOffset = timeOffset; + remapCount++; + } +} + +const char *BuildShaderStateConfig() { + static char buff[MAX_STRING_CHARS]; + char out[(MAX_QPATH * 2) + 5]; + int i; + + memset(buff, 0, MAX_STRING_CHARS); + for (i = 0; i < remapCount; i++) { + Com_sprintf(out, (MAX_QPATH * 2) + 5, "%s=%s:%5.2f@", remappedShaders[i].oldShader, remappedShaders[i].newShader, remappedShaders[i].timeOffset); + Q_strcat( buff, sizeof( buff ), out); + } + return buff; +} + + +/* +========================================================================= + +model / sound configstring indexes + +========================================================================= +*/ + +/* +================ +G_FindConfigstringIndex + +================ +*/ +int G_FindConfigstringIndex( char *name, int start, int max, qboolean create ) { + int i; + char s[MAX_STRING_CHARS]; + + if ( !name || !name[0] ) { + return 0; + } + + for ( i=1 ; i<max ; i++ ) { + trap_GetConfigstring( start + i, s, sizeof( s ) ); + if ( !s[0] ) { + break; + } + if ( !strcmp( s, name ) ) { + return i; + } + } + + if ( !create ) { + return 0; + } + + if ( i == max ) { + G_Error( "G_FindConfigstringIndex: overflow" ); + } + + trap_SetConfigstring( start + i, name ); + + return i; +} + + +int G_ModelIndex( char *name ) { + return G_FindConfigstringIndex (name, CS_MODELS, MAX_MODELS, qtrue); +} + +int G_SoundIndex( char *name ) { + return G_FindConfigstringIndex (name, CS_SOUNDS, MAX_SOUNDS, qtrue); +} + +//===================================================================== + + +/* +================ +G_TeamCommand + +Broadcasts a command to only a specific team +================ +*/ +void G_TeamCommand( team_t team, char *cmd ) { + int i; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.connected == CON_CONNECTED ) { + if ( level.clients[i].sess.sessionTeam == team ) { + trap_SendServerCommand( i, va("%s", cmd )); + } + } + } +} + + +/* +============= +G_Find + +Searches all active entities for the next one that holds +the matching string at fieldofs (use the FOFS() macro) in the structure. + +Searches beginning at the entity after from, or the beginning if NULL +NULL will be returned if the end of the list is reached. + +============= +*/ +gentity_t *G_Find (gentity_t *from, int fieldofs, const char *match) +{ + char *s; + + if (!from) + from = g_entities; + else + from++; + + for ( ; from < &g_entities[level.num_entities] ; from++) + { + if (!from->inuse) + continue; + s = *(char **) ((byte *)from + fieldofs); + if (!s) + continue; + if (!Q_stricmp (s, match)) + return from; + } + + return NULL; +} + + +/* +============= +G_PickTarget + +Selects a random entity from among the targets +============= +*/ +#define MAXCHOICES 32 + +gentity_t *G_PickTarget (char *targetname) +{ + gentity_t *ent = NULL; + int num_choices = 0; + gentity_t *choice[MAXCHOICES]; + + if (!targetname) + { + G_Printf("G_PickTarget called with NULL targetname\n"); + return NULL; + } + + while(1) + { + ent = G_Find (ent, FOFS(targetname), targetname); + if (!ent) + break; + choice[num_choices++] = ent; + if (num_choices == MAXCHOICES) + break; + } + + if (!num_choices) + { + G_Printf("G_PickTarget: target %s not found\n", targetname); + return NULL; + } + + return choice[rand() % num_choices]; +} + + +/* +============================== +G_UseTargets + +"activator" should be set to the entity that initiated the firing. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function + +============================== +*/ +void G_UseTargets( gentity_t *ent, gentity_t *activator ) { + gentity_t *t; + + if ( !ent ) { + return; + } + + if (ent->targetShaderName && ent->targetShaderNewName) { + float f = level.time * 0.001; + AddRemap(ent->targetShaderName, ent->targetShaderNewName, f); + trap_SetConfigstring(CS_SHADERSTATE, BuildShaderStateConfig()); + } + + if ( !ent->target ) { + return; + } + + t = NULL; + while ( (t = G_Find (t, FOFS(targetname), ent->target)) != NULL ) { + if ( t == ent ) { + G_Printf ("WARNING: Entity used itself.\n"); + } else { + if ( t->use ) { + t->use (t, ent, activator); + } + } + if ( !ent->inuse ) { + G_Printf("entity was removed while using targets\n"); + return; + } + } +} + + +/* +============= +TempVector + +This is just a convenience function +for making temporary vectors for function calls +============= +*/ +float *tv( float x, float y, float z ) { + static int index; + static vec3_t vecs[8]; + float *v; + + // use an array so that multiple tempvectors won't collide + // for a while + v = vecs[index]; + index = (index + 1)&7; + + v[0] = x; + v[1] = y; + v[2] = z; + + return v; +} + + +/* +============= +VectorToString + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos( const vec3_t v ) { + static int index; + static char str[8][32]; + char *s; + + // use an array so that multiple vtos won't collide + s = str[index]; + index = (index + 1)&7; + + Com_sprintf (s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]); + + return s; +} + + +/* +=============== +G_SetMovedir + +The editor only specifies a single value for angles (yaw), +but we have special constants to generate an up or down direction. +Angles will be cleared, because it is being used to represent a direction +instead of an orientation. +=============== +*/ +void G_SetMovedir( vec3_t angles, vec3_t movedir ) { + static vec3_t VEC_UP = {0, -1, 0}; + static vec3_t MOVEDIR_UP = {0, 0, 1}; + static vec3_t VEC_DOWN = {0, -2, 0}; + static vec3_t MOVEDIR_DOWN = {0, 0, -1}; + + if ( VectorCompare (angles, VEC_UP) ) { + VectorCopy (MOVEDIR_UP, movedir); + } else if ( VectorCompare (angles, VEC_DOWN) ) { + VectorCopy (MOVEDIR_DOWN, movedir); + } else { + AngleVectors (angles, movedir, NULL, NULL); + } + VectorClear( angles ); +} + + +float vectoyaw( const vec3_t vec ) { + float yaw; + + if (vec[YAW] == 0 && vec[PITCH] == 0) { + yaw = 0; + } else { + if (vec[PITCH]) { + yaw = ( atan2( vec[YAW], vec[PITCH]) * 180 / M_PI ); + } else if (vec[YAW] > 0) { + yaw = 90; + } else { + yaw = 270; + } + if (yaw < 0) { + yaw += 360; + } + } + + return yaw; +} + + +void G_InitGentity( gentity_t *e ) { + e->inuse = qtrue; + e->classname = "noclass"; + e->s.number = e - g_entities; + e->r.ownerNum = ENTITYNUM_NONE; +} + +/* +================= +G_Spawn + +Either finds a free entity, or allocates a new one. + + The slots from 0 to MAX_CLIENTS-1 are always reserved for clients, and will +never be used by anything else. + +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +gentity_t *G_Spawn( void ) { + int i, force; + gentity_t *e; + + e = NULL; // shut up warning + i = 0; // shut up warning + for ( force = 0 ; force < 2 ; force++ ) { + // if we go through all entities and can't find one to free, + // override the normal minimum times before use + e = &g_entities[MAX_CLIENTS]; + for ( i = MAX_CLIENTS ; i<level.num_entities ; i++, e++) { + if ( e->inuse ) { + continue; + } + + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if ( !force && e->freetime > level.startTime + 2000 && level.time - e->freetime < 1000 ) { + continue; + } + + // reuse this slot + G_InitGentity( e ); + return e; + } + if ( i != MAX_GENTITIES ) { + break; + } + } + if ( i == ENTITYNUM_MAX_NORMAL ) { + for (i = 0; i < MAX_GENTITIES; i++) { + G_Printf("%4i: %s\n", i, g_entities[i].classname); + } + G_Error( "G_Spawn: no free entities" ); + } + + // open up a new slot + level.num_entities++; + + // let the server system know that there are more entities + trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ), + &level.clients[0].ps, sizeof( level.clients[0] ) ); + + G_InitGentity( e ); + return e; +} + + +/* +================= +G_EntitiesFree +================= +*/ +qboolean G_EntitiesFree( void ) { + int i; + gentity_t *e; + + e = &g_entities[MAX_CLIENTS]; + for ( i = MAX_CLIENTS; i < level.num_entities; i++, e++) { + if ( e->inuse ) { + continue; + } + // slot available + return qtrue; + } + return qfalse; +} + + +/* +================= +G_FreeEntity + +Marks the entity as free +================= +*/ +void G_FreeEntity( gentity_t *ed ) { + trap_UnlinkEntity (ed); // unlink from world + + if ( ed->neverFree ) { + return; + } + + memset (ed, 0, sizeof(*ed)); + ed->classname = "freed"; + ed->freetime = level.time; + ed->inuse = qfalse; +} + +/* +================= +G_TempEntity + +Spawns an event entity that will be auto-removed +The origin will be snapped to save net bandwidth, so care +must be taken if the origin is right on a surface (snap towards start vector first) +================= +*/ +gentity_t *G_TempEntity( vec3_t origin, int event ) { + gentity_t *e; + vec3_t snapped; + + e = G_Spawn(); + e->s.eType = ET_EVENTS + event; + + e->classname = "tempEntity"; + e->eventTime = level.time; + e->freeAfterEvent = qtrue; + + VectorCopy( origin, snapped ); + SnapVector( snapped ); // save network bandwidth + G_SetOrigin( e, snapped ); + + // find cluster for PVS + trap_LinkEntity( e ); + + return e; +} + + + +/* +============================================================================== + +Kill box + +============================================================================== +*/ + +/* +================= +G_KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +void G_KillBox (gentity_t *ent) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; i<num ; i++) { + hit = &g_entities[touch[i]]; + if ( !hit->client ) { + continue; + } + + // nail it + G_Damage ( hit, ent, ent, NULL, NULL, + 100000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); + } + +} + +//============================================================================== + +/* +=============== +G_AddPredictableEvent + +Use for non-pmove events that would also be predicted on the +client side: jumppads and item pickups +Adds an event+parm and twiddles the event counter +=============== +*/ +void G_AddPredictableEvent( gentity_t *ent, int event, int eventParm ) { + if ( !ent->client ) { + return; + } + BG_AddPredictableEventToPlayerstate( event, eventParm, &ent->client->ps ); +} + + +/* +=============== +G_AddEvent + +Adds an event+parm and twiddles the event counter +=============== +*/ +void G_AddEvent( gentity_t *ent, int event, int eventParm ) { + int bits; + + if ( !event ) { + G_Printf( "G_AddEvent: zero event added for entity %i\n", ent->s.number ); + return; + } + + // clients need to add the event in playerState_t instead of entityState_t + if ( ent->client ) { + bits = ent->client->ps.externalEvent & EV_EVENT_BITS; + bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS; + ent->client->ps.externalEvent = event | bits; + ent->client->ps.externalEventParm = eventParm; + ent->client->ps.externalEventTime = level.time; + } else { + bits = ent->s.event & EV_EVENT_BITS; + bits = ( bits + EV_EVENT_BIT1 ) & EV_EVENT_BITS; + ent->s.event = event | bits; + ent->s.eventParm = eventParm; + } + ent->eventTime = level.time; +} + + +/* +============= +G_Sound +============= +*/ +void G_Sound( gentity_t *ent, int channel, int soundIndex ) { + gentity_t *te; + + te = G_TempEntity( ent->r.currentOrigin, EV_GENERAL_SOUND ); + te->s.eventParm = soundIndex; +} + + +//============================================================================== + + +/* +================ +G_SetOrigin + +Sets the pos trajectory for a fixed position +================ +*/ +void G_SetOrigin( gentity_t *ent, vec3_t origin ) { + VectorCopy( origin, ent->s.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + ent->s.pos.trTime = 0; + ent->s.pos.trDuration = 0; + VectorClear( ent->s.pos.trDelta ); + + VectorCopy( origin, ent->r.currentOrigin ); +} + +//TA: from quakestyle.telefragged.com +// (NOBODY): Code helper function +// +gentity_t *G_FindRadius( gentity_t *from, vec3_t org, float rad ) +{ + vec3_t eorg; + int j; + + if( !from ) + from = g_entities; + else + from++; + + for( ; from < &g_entities[ level.num_entities ]; from++ ) + { + if( !from->inuse ) + continue; + + for( j = 0; j < 3; j++ ) + eorg[ j ] = org[ j ] - ( from->r.currentOrigin[ j ] + ( from->r.mins[ j ] + from->r.maxs[ j ] ) * 0.5 ); + + if( VectorLength( eorg ) > rad ) + continue; + + return from; + } + + return NULL; +} + +// (NOBODY): Code helper function +// +qboolean G_Visible( gentity_t *ent1, gentity_t *ent2 ) +{ + trace_t trace; + + trap_Trace( &trace, ent1->s.pos.trBase, NULL, NULL, ent2->s.pos.trBase, ent1->s.number, MASK_SHOT ); + + if( trace.contents & CONTENTS_SOLID ) + return qfalse; + + return qtrue; +} + + +/* +================ +DebugLine + + debug polygons only work when running a local game + with r_debugSurface set to 2 +================ +*/ +int DebugLine(vec3_t start, vec3_t end, int color) { + vec3_t points[4], dir, cross, up = {0, 0, 1}; + float dot; + + VectorCopy(start, points[0]); + VectorCopy(start, points[1]); + //points[1][2] -= 2; + VectorCopy(end, points[2]); + //points[2][2] -= 2; + VectorCopy(end, points[3]); + + + VectorSubtract(end, start, dir); + VectorNormalize(dir); + dot = DotProduct(dir, up); + if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0); + else CrossProduct(dir, up, cross); + + VectorNormalize(cross); + + VectorMA(points[0], 2, cross, points[0]); + VectorMA(points[1], -2, cross, points[1]); + VectorMA(points[2], -2, cross, points[2]); + VectorMA(points[3], 2, cross, points[3]); + + return trap_DebugPolygonCreate(color, 4, points); +} + |