diff options
author | Tim Angus <tim@ngus.net> | 2005-11-20 03:55:39 +0000 |
---|---|---|
committer | Tim Angus <tim@ngus.net> | 2005-11-20 03:55:39 +0000 |
commit | e80ad4bf122eb05a5ed0c920e36cf656f98dc6e5 (patch) | |
tree | bdc1dcb2b08ed77abc72880e81989157c6003555 /src | |
parent | d6312bd43317816c281150e3baf7e8204a9452d8 (diff) |
* Abstract attachment system
* Scriptable trails system
* Various other stuff I'm too tired to try and remember now
Diffstat (limited to 'src')
-rw-r--r-- | src/cgame/cg_attachment.c | 344 | ||||
-rw-r--r-- | src/cgame/cg_buildable.c | 40 | ||||
-rw-r--r-- | src/cgame/cg_consolecmds.c | 4 | ||||
-rw-r--r-- | src/cgame/cg_ents.c | 103 | ||||
-rw-r--r-- | src/cgame/cg_event.c | 44 | ||||
-rw-r--r-- | src/cgame/cg_local.h | 318 | ||||
-rw-r--r-- | src/cgame/cg_main.c | 11 | ||||
-rw-r--r-- | src/cgame/cg_particles.c | 500 | ||||
-rw-r--r-- | src/cgame/cg_players.c | 7 | ||||
-rw-r--r-- | src/cgame/cg_servercmds.c | 14 | ||||
-rw-r--r-- | src/cgame/cg_trails.c | 1746 | ||||
-rw-r--r-- | src/cgame/cg_view.c | 6 | ||||
-rw-r--r-- | src/cgame/cg_weapons.c | 87 | ||||
-rw-r--r-- | src/game/bg_misc.c | 38 | ||||
-rw-r--r-- | src/game/bg_public.h | 3 | ||||
-rw-r--r-- | src/game/g_buildable.c | 83 | ||||
-rw-r--r-- | src/game/g_cmds.c | 1 | ||||
-rw-r--r-- | src/game/g_main.c | 18 | ||||
-rw-r--r-- | src/game/g_weapon.c | 6 | ||||
-rw-r--r-- | src/game/q_math.c | 11 | ||||
-rw-r--r-- | src/game/tremulous.h | 2 | ||||
-rw-r--r-- | src/ui/ui_main.c | 41 |
22 files changed, 2172 insertions, 1255 deletions
diff --git a/src/cgame/cg_attachment.c b/src/cgame/cg_attachment.c new file mode 100644 index 00000000..d8f20d90 --- /dev/null +++ b/src/cgame/cg_attachment.c @@ -0,0 +1,344 @@ +// cg_attachment.c -- an abstract attachment system + +/* + * 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 "cg_local.h" + +/* +=============== +CG_AttachmentPoint + +Return the attachment point +=============== +*/ +qboolean CG_AttachmentPoint( attachment_t *a, vec3_t v ) +{ + centity_t *cent; + + if( !a ) + return qfalse; + + switch( a->type ) + { + case AT_STATIC: + if( !a->staticValid ) + return qfalse; + + VectorCopy( a->origin, v ); + break; + + case AT_TAG: + if( !a->tagValid ) + return qfalse; + + AxisCopy( axisDefault, a->re.axis ); + CG_PositionRotatedEntityOnTag( &a->re, &a->parent, + a->model, a->tagName ); + VectorCopy( a->re.origin, v ); + break; + + case AT_CENT: + if( !a->centValid ) + return qfalse; + + if( a->centNum == cg.predictedPlayerState.clientNum ) + { + // this is smoother if it's the local client + VectorCopy( cg.predictedPlayerState.origin, v ); + } + else + { + cent = &cg_entities[ a->centNum ]; + VectorCopy( cent->lerpOrigin, v ); + } + break; + + case AT_PARTICLE: + if( !a->particleValid ) + return qfalse; + + if( !a->particle->valid ) + { + a->particleValid = qfalse; + return qfalse; + } + else + VectorCopy( a->particle->origin, v ); + break; + + default: + CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" ); + break; + } + + if( a->hasOffset ) + VectorAdd( v, a->offset, v ); + + return qtrue; +} + +/* +=============== +CG_AttachmentDir + +Return the attachment direction +=============== +*/ +qboolean CG_AttachmentDir( attachment_t *a, vec3_t v ) +{ + vec3_t forward; + centity_t *cent; + + if( !a ) + return qfalse; + + switch( a->type ) + { + case AT_STATIC: + //FIXME: hmmmmmmm + return qfalse; + break; + + case AT_TAG: + if( !a->tagValid ) + return qfalse; + + VectorCopy( a->re.axis[ 0 ], v ); + break; + + case AT_CENT: + if( !a->centValid ) + return qfalse; + + cent = &cg_entities[ a->centNum ]; + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + VectorCopy( forward, v ); + break; + + case AT_PARTICLE: + if( !a->particleValid ) + return qfalse; + + if( !a->particle->valid ) + { + a->particleValid = qfalse; + return qfalse; + } + else + VectorCopy( a->particle->velocity, v ); + break; + + default: + CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" ); + break; + } + + VectorNormalize( v ); + return qtrue; +} + +/* +=============== +CG_AttachmentVelocity + +If the attachment can have velocity, return it +=============== +*/ +qboolean CG_AttachmentVelocity( attachment_t *a, vec3_t v ) +{ + if( !a ) + return qfalse; + + if( a->particleValid && a->particle->valid ) + { + VectorCopy( a->particle->velocity, v ); + return qtrue; + } + else if( a->centValid ) + { + centity_t *cent = &cg_entities[ a->centNum ]; + + VectorCopy( cent->currentState.pos.trDelta, v ); + return qtrue; + } + + return qfalse; +} + +/* +=============== +CG_AttachmentCentNum + +If the attachment has a centNum, return it +=============== +*/ +int CG_AttachmentCentNum( attachment_t *a ) +{ + if( !a || !a->centValid ) + return -1; + + return a->centNum; +} + +/* +=============== +CG_Attached + +If the attachment is valid, return qtrue +=============== +*/ +qboolean CG_Attached( attachment_t *a ) +{ + if( !a ) + return qfalse; + + return a->attached; +} + +/* +=============== +CG_AttachToPoint + +Attach to a point in space +=============== +*/ +void CG_AttachToPoint( attachment_t *a ) +{ + if( !a || !a->staticValid ) + return; + + a->type = AT_STATIC; + a->attached = qtrue; +} + +/* +=============== +CG_AttachToCent + +Attach to a centity_t +=============== +*/ +void CG_AttachToCent( attachment_t *a ) +{ + if( !a || !a->centValid ) + return; + + a->type = AT_CENT; + a->attached = qtrue; +} + +/* +=============== +CG_AttachToTag + +Attach to a model tag +=============== +*/ +void CG_AttachToTag( attachment_t *a ) +{ + if( !a || !a->tagValid ) + return; + + a->type = AT_TAG; + a->attached = qtrue; +} + +/* +=============== +CG_AttachToParticle + +Attach to a particle +=============== +*/ +void CG_AttachToParticle( attachment_t *a ) +{ + if( !a || !a->particleValid ) + return; + + a->type = AT_PARTICLE; + a->attached = qtrue; +} + +/* +=============== +CG_SetAttachmentPoint +=============== +*/ +void CG_SetAttachmentPoint( attachment_t *a, vec3_t v ) +{ + if( !a ) + return; + + VectorCopy( v, a->origin ); + a->staticValid = qtrue; +} + +/* +=============== +CG_SetAttachmentCent +=============== +*/ +void CG_SetAttachmentCent( attachment_t *a, centity_t *cent ) +{ + if( !a || !cent ) + return; + + a->centNum = cent->currentState.number; + a->centValid = qtrue; +} + +/* +=============== +CG_SetAttachmentTag +=============== +*/ +void CG_SetAttachmentTag( attachment_t *a, refEntity_t parent, + qhandle_t model, char *tagName ) +{ + if( !a ) + return; + + a->parent = parent; + a->model = model; + strncpy( a->tagName, tagName, MAX_STRING_CHARS ); + a->tagValid = qtrue; +} + +/* +=============== +CG_SetAttachmentParticle +=============== +*/ +void CG_SetAttachmentParticle( attachment_t *a, particle_t *p ) +{ + if( !a ) + return; + + a->particle = p; + a->particleValid = qtrue; +} + +/* +=============== +CG_SetAttachmentOffset +=============== +*/ +void CG_SetAttachmentOffset( attachment_t *a, vec3_t v ) +{ + if( !a ) + return; + + VectorCopy( v, a->offset ); + a->hasOffset = qtrue; +} diff --git a/src/cgame/cg_buildable.c b/src/cgame/cg_buildable.c index 38259dc6..1daaf464 100644 --- a/src/cgame/cg_buildable.c +++ b/src/cgame/cg_buildable.c @@ -108,9 +108,13 @@ void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir ) //particle system ps = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDestroyedPS ); - CG_SetParticleSystemOrigin( ps, origin ); - CG_SetParticleSystemNormal( ps, dir ); - CG_AttachParticleSystemToOrigin( ps ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, origin ); + CG_SetParticleSystemNormal( ps, dir ); + CG_AttachToPoint( &ps->attachment ); + } } @@ -205,9 +209,13 @@ void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir ) //particle system ps = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDestroyedPS ); - CG_SetParticleSystemOrigin( ps, origin ); - CG_SetParticleSystemNormal( ps, dir ); - CG_AttachParticleSystemToOrigin( ps ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, origin ); + CG_SetParticleSystemNormal( ps, dir ); + CG_AttachToPoint( &ps->attachment ); + } } @@ -500,6 +508,8 @@ void CG_InitBuildables( ) cg.buildablesFraction = (float)i / (float)( BA_NUM_BUILDABLES - 1 ); trap_UpdateScreen( ); } + + cgs.media.teslaZapTS = CG_RegisterTrailSystem( "models/buildables/tesla/zap" ); } /* @@ -823,8 +833,12 @@ static void CG_BuildableParticleEffects( centity_t *cent ) if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) { cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDamagedPS ); - CG_SetParticleSystemCent( cent->buildablePS, cent ); - CG_AttachParticleSystemToCent( cent->buildablePS ); + + if( CG_IsParticleSystemValid( ¢->buildablePS ) ) + { + CG_SetAttachmentCent( ¢->buildablePS->attachment, cent ); + CG_AttachToCent( ¢->buildablePS->attachment ); + } } else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( ¢->buildablePS ) ) CG_DestroyParticleSystem( ¢->buildablePS ); @@ -834,9 +848,13 @@ static void CG_BuildableParticleEffects( centity_t *cent ) if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) { cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDamagedPS ); - CG_SetParticleSystemCent( cent->buildablePS, cent ); - CG_SetParticleSystemNormal( cent->buildablePS, es->origin2 ); - CG_AttachParticleSystemToCent( cent->buildablePS ); + + if( CG_IsParticleSystemValid( ¢->buildablePS ) ) + { + CG_SetAttachmentCent( ¢->buildablePS->attachment, cent ); + CG_SetParticleSystemNormal( cent->buildablePS, es->origin2 ); + CG_AttachToCent( ¢->buildablePS->attachment ); + } } else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( ¢->buildablePS ) ) CG_DestroyParticleSystem( ¢->buildablePS ); diff --git a/src/cgame/cg_consolecmds.c b/src/cgame/cg_consolecmds.c index bf15145c..c875fa48 100644 --- a/src/cgame/cg_consolecmds.c +++ b/src/cgame/cg_consolecmds.c @@ -171,6 +171,10 @@ static consoleCommand_t commands[ ] = { "tell_target", CG_TellTarget_f }, { "tell_attacker", CG_TellAttacker_f }, { "tcmd", CG_TargetCommand_f }, + { "testPS", CG_TestPS_f }, + { "destroyTestPS", CG_DestroyTestPS_f }, + { "testTS", CG_TestTS_f }, + { "destroyTestTS", CG_DestroyTestTS_f }, }; diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c index c4adf46b..8c047e27 100644 --- a/src/cgame/cg_ents.c +++ b/src/cgame/cg_ents.c @@ -240,6 +240,8 @@ static void CG_EntityEffects( centity_t *cent ) trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); } + if( cg.time > cent->muzzleTSDeathTime && CG_IsTrailSystemValid( ¢->muzzleTS ) ) + CG_DestroyTrailSystem( ¢->muzzleTS ); } @@ -320,6 +322,7 @@ static void CG_LaunchMissile( centity_t *cent ) entityState_t *es; const weaponInfo_t *wi; particleSystem_t *ps; + trailSystem_t *ts; weapon_t weapon; weaponMode_t weaponMode; @@ -335,8 +338,23 @@ static void CG_LaunchMissile( centity_t *cent ) if( wi->wim[ weaponMode ].missileParticleSystem ) { ps = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].missileParticleSystem ); - CG_SetParticleSystemCent( ps, cent ); - CG_AttachParticleSystemToCent( ps ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentCent( &ps->attachment, cent ); + CG_AttachToCent( &ps->attachment ); + } + } + + if( wi->wim[ weaponMode ].missileTrailSystem ) + { + ts = CG_SpawnNewTrailSystem( wi->wim[ weaponMode ].missileTrailSystem ); + + if( CG_IsTrailSystemValid( &ts ) ) + { + CG_SetAttachmentCent( &ts->frontAttachment, cent ); + CG_AttachToCent( &ts->frontAttachment ); + } } } @@ -763,54 +781,51 @@ CG_Lev2ZapChain */ static void CG_Lev2ZapChain( centity_t *cent ) { - int i = 0; + int i; entityState_t *es; - vec3_t start, end; centity_t *source, *target; es = ¢->currentState; - if( es->time > 0 ) + for( i = 0; i <= 2; i++ ) { - source = &cg_entities[ es->powerups ]; - target = &cg_entities[ es->time ]; - - if( es->powerups == cg.predictedPlayerState.clientNum ) - VectorCopy( cg.predictedPlayerState.origin, start ); - else - VectorCopy( source->currentState.pos.trBase, start ); - - VectorCopy( target->currentState.pos.trBase, end ); + switch( i ) + { + case 0: + if( es->time <= 0 ) + continue; - CG_DynamicLightningBolt( cgs.media.lightningShader, start, end, - 1+((cg.time%((i+2)*(i+3)))+i)%2, 7 + (float)(i%3)*5 + 6.0*random(), - qtrue, 1.0, 0, i*i*3 ); - } + source = &cg_entities[ es->powerups ]; + target = &cg_entities[ es->time ]; + break; - if( es->time2 > 0 ) - { - source = &cg_entities[ es->time ]; - target = &cg_entities[ es->time2 ]; + case 1: + if( es->time2 <= 0 ) + continue; - VectorCopy( source->currentState.pos.trBase, start ); - VectorCopy( target->currentState.pos.trBase, end ); + source = &cg_entities[ es->time ]; + target = &cg_entities[ es->time2 ]; + break; - CG_DynamicLightningBolt( cgs.media.lightningShader, start, end, - 1+((cg.time%((i+2)*(i+3)))+i)%2, 7 + (float)(i%3)*5 + 6.0*random(), - qtrue, 1.0, 0, i*i*3 ); - } + case 2: + if( es->constantLight <= 0 ) + continue; - if( es->constantLight > 0 ) - { - source = &cg_entities[ es->time2 ]; - target = &cg_entities[ es->constantLight ]; + source = &cg_entities[ es->time2 ]; + target = &cg_entities[ es->constantLight ]; + break; + } - VectorCopy( source->currentState.pos.trBase, start ); - VectorCopy( target->currentState.pos.trBase, end ); + if( !CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) + cent->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS ); - CG_DynamicLightningBolt( cgs.media.lightningShader, start, end, - 1+((cg.time%((i+2)*(i+3)))+i)%2, 7 + (float)(i%3)*5 + 6.0*random(), - qtrue, 1.0, 0, i*i*3 ); + if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) + { + CG_SetAttachmentCent( ¢->level2ZapTS[ i ]->frontAttachment, source ); + CG_SetAttachmentCent( ¢->level2ZapTS[ i ]->backAttachment, target ); + CG_AttachToCent( ¢->level2ZapTS[ i ]->frontAttachment ); + CG_AttachToCent( ¢->level2ZapTS[ i ]->backAttachment ); + } } } @@ -983,8 +998,22 @@ CG_CEntityPVSLeave */ static void CG_CEntityPVSLeave( centity_t *cent ) { + int i; + entityState_t *es = ¢->currentState; + if( cg_debugPVS.integer ) CG_Printf( "Entity %d left PVS\n", cent->currentState.number ); + + switch( es->eType ) + { + case ET_LEV2_ZAP_CHAIN: + for( i = 0; i <= 2; i++ ) + { + if( CG_IsTrailSystemValid( ¢->level2ZapTS[ i ] ) ) + CG_DestroyTrailSystem( ¢->level2ZapTS[ i ] ); + } + break; + } } diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c index 4d176096..9273922f 100644 --- a/src/cgame/cg_event.c +++ b/src/cgame/cg_event.c @@ -740,7 +740,29 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) case EV_TESLATRAIL: DEBUGNAME( "EV_TESLATRAIL" ); cent->currentState.weapon = WP_TESLAGEN; - CG_TeslaTrail( es->origin2, es->pos.trBase, es->generic1, es->clientNum ); + { + centity_t *source = &cg_entities[ es->generic1 ]; + centity_t *target = &cg_entities[ es->clientNum ]; + vec3_t sourceOffset = { 0.0f, 0.0f, 28.0f }; + vec3_t targetOffset = { 0.0f, 0.0f, -2.0f }; + + if( !CG_IsTrailSystemValid( &source->muzzleTS ) ) + { + source->muzzleTS = CG_SpawnNewTrailSystem( cgs.media.teslaZapTS ); + + if( CG_IsTrailSystemValid( &source->muzzleTS ) ) + { + CG_SetAttachmentCent( &source->muzzleTS->frontAttachment, source ); + CG_SetAttachmentCent( &source->muzzleTS->backAttachment, target ); + CG_AttachToCent( &source->muzzleTS->frontAttachment ); + CG_AttachToCent( &source->muzzleTS->backAttachment ); + CG_SetAttachmentOffset( &source->muzzleTS->frontAttachment, sourceOffset ); + CG_SetAttachmentOffset( &source->muzzleTS->backAttachment, targetOffset ); + + source->muzzleTSDeathTime = cg.time + cg_teslaTrailTime.integer; + } + } + } break; case EV_BULLET_HIT_WALL: @@ -880,8 +902,12 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienEvolveSound ); { particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienEvolvePS ); - CG_SetParticleSystemCent( ps, cent ); - CG_AttachParticleSystemToCent( ps ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentCent( &ps->attachment, cent ); + CG_AttachToCent( &ps->attachment ); + } } if( es->number == cg.clientNum ) @@ -902,10 +928,14 @@ void CG_EntityEvent( centity_t *cent, vec3_t position ) DEBUGNAME( "EV_ALIEN_ACIDTUBE" ); { particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienAcidTubePS ); - CG_SetParticleSystemCent( ps, cent ); - ByteToDir( es->eventParm, dir ); - CG_SetParticleSystemNormal( ps, dir ); - CG_AttachParticleSystemToCent( ps ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentCent( &ps->attachment, cent ); + ByteToDir( es->eventParm, dir ); + CG_SetParticleSystemNormal( ps, dir ); + CG_AttachToCent( &ps->attachment ); + } } break; diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h index d6b07714..069dd604 100644 --- a/src/cgame/cg_local.h +++ b/src/cgame/cg_local.h @@ -71,19 +71,6 @@ #define NUM_CROSSHAIRS 10 -//TA: ripped from wolf source -// Ridah, trails -#define STYPE_STRETCH 0 -#define STYPE_REPEAT 1 - -#define TJFL_FADEIN (1<<0) -#define TJFL_CROSSOVER (1<<1) -#define TJFL_NOCULL (1<<2) -#define TJFL_FIXDISTORT (1<<3) -#define TJFL_SPARKHEADFLARE (1<<4) -#define TJFL_NOPOLYMERGE (1<<5) -// done. - #define TEAM_OVERLAY_MAXNAME_WIDTH 12 #define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 @@ -124,6 +111,51 @@ typedef enum JPS_ASCENDING } jetPackState_t; +//====================================================================== + +//attachment system +typedef enum +{ + AT_STATIC, + AT_TAG, + AT_CENT, + AT_PARTICLE +} attachmentType_t; + +//forward declaration for particle_t +struct particle_s; + +typedef struct attachment_s +{ + attachmentType_t type; + qboolean attached; + + qboolean staticValid; + qboolean tagValid; + qboolean centValid; + qboolean particleValid; + + qboolean hasOffset; + vec3_t offset; + + //AT_STATIC + vec3_t origin; + + //AT_TAG + refEntity_t re; //FIXME: should be pointers? + refEntity_t parent; // + qhandle_t model; + char tagName[ MAX_STRING_CHARS ]; + + //AT_CENT + int centNum; + + //AT_PARTICLE + struct particle_s *particle; +} attachment_t; + +//====================================================================== + //particle system stuff #define MAX_SHADER_FRAMES 32 #define MAX_EJECTORS_PER_SYSTEM 4 @@ -269,51 +301,18 @@ RUN TIME STRUCTURES =============== */ -typedef enum -{ - PSA_STATIC, - PSA_TAG, - PSA_CENT_ORIGIN, - PSA_PARTICLE -} psAttachmentType_t; - - -typedef struct psAttachment_s -{ - qboolean staticValid; - qboolean tagValid; - qboolean centValid; - qboolean normalValid; - qboolean particleValid; - - //PMT_STATIC - vec3_t origin; - - //PMT_TAG - refEntity_t re; //FIXME: should be pointers? - refEntity_t parent; // - qhandle_t model; - char tagName[ MAX_STRING_CHARS ]; - - //PMT_CENT_ANGLES - int centNum; - - //PMT_NORMAL - vec3_t normal; -} psAttachment_t; - - typedef struct particleSystem_s { baseParticleSystem_t *class; - psAttachmentType_t attachType; - psAttachment_t attachment; - qboolean attached; //is the particle system attached to anything + attachment_t attachment; qboolean valid; qboolean lazyRemove; //mark this system for later removal + //for PMT_NORMAL + qboolean normalValid; + vec3_t normal; } particleSystem_t; @@ -357,14 +356,133 @@ typedef struct particle_s pLerpValues_t rotation; qboolean valid; + int frameWhenInvalidated; int sortKey; - - particleSystem_t *childSystem; } particle_t; +//====================================================================== -//================================================= +//trail system stuff +#define MAX_BEAMS_PER_SYSTEM 4 + +#define MAX_BASETRAIL_SYSTEMS 64 +#define MAX_BASETRAIL_BEAMS MAX_BASETRAIL_SYSTEMS*MAX_BEAMS_PER_SYSTEM + +#define MAX_TRAIL_SYSTEMS 32 +#define MAX_TRAIL_BEAMS MAX_TRAIL_SYSTEMS*MAX_BEAMS_PER_SYSTEM +#define MAX_TRAIL_BEAM_NODES 128 + +#define MAX_TRAIL_BEAM_JITTERS 4 + +typedef enum +{ + TBTT_STRETCH, + TBTT_REPEAT +} trailBeamTextureType_t; + +typedef struct baseTrailJitter_s +{ + float magnitude; + int period; +} baseTrailJitter_t; + +//beam template +typedef struct baseTrailBeam_s +{ + int numSegments; + float frontWidth; + float backWidth; + float frontAlpha; + float backAlpha; + byte frontColor[ 3 ]; + byte backColor[ 3 ]; + + // the time it takes for a segment to vanish (single attached only) + int segmentTime; + + // the time it takes for a beam to fade out (double attached only) + int fadeOutTime; + + char shaderName[ MAX_QPATH ]; + qhandle_t shader; + + trailBeamTextureType_t textureType; + + //TBTT_STRETCH + float frontTextureCoord; + float backTextureCoord; + + //TBTT_REPEAT + float repeatLength; + qboolean clampToBack; + + qboolean realLight; + + int numJitters; + baseTrailJitter_t jitters[ MAX_TRAIL_BEAM_JITTERS ]; + qboolean jitterAttachments; +} baseTrailBeam_t; + + +//trail system template +typedef struct baseTrailSystem_s +{ + char name[ MAX_QPATH ]; + baseTrailBeam_t *beams[ MAX_BEAMS_PER_SYSTEM ]; + int numBeams; + + qboolean registered; //whether or not the assets for this trail have been loaded +} baseTrailSystem_t; + +typedef struct trailSystem_s +{ + baseTrailSystem_t *class; + + attachment_t frontAttachment; + attachment_t backAttachment; + + int destroyTime; + qboolean valid; +} trailSystem_t; + +typedef struct trailBeamNode_s +{ + vec3_t refPosition; + vec3_t position; + + int timeLeft; + + float textureCoord; + float halfWidth; + byte alpha; + byte color[ 3 ]; + + float jitters[ MAX_TRAIL_BEAM_JITTERS ]; + float jitter; + + struct trailBeamNode_s *prev; + struct trailBeamNode_s *next; + + qboolean used; +} trailBeamNode_t; + +typedef struct trailBeam_s +{ + baseTrailBeam_t *class; + trailSystem_t *parent; + + trailBeamNode_t nodePool[ MAX_TRAIL_BEAM_NODES ]; + trailBeamNode_t *nodes; + + int lastEvalTime; + + qboolean valid; + + int nextJitterTimes[ MAX_TRAIL_BEAM_JITTERS ]; +} trailBeam_t; + +//====================================================================== // player entities need to track more information // than any other type of entity. @@ -435,8 +553,6 @@ typedef struct lightFlareStatus_s //================================================= -#define MAX_CENTITY_PARTICLE_SYSTEMS 8 - // centity_t have a direct corespondence with gentity_t in the game, but // only the entityState_t is directly communicated to the cgame typedef struct centity_s @@ -495,6 +611,11 @@ typedef struct centity_s particleSystem_t *entityPS; qboolean entityPSMissing; + trailSystem_t *level2ZapTS[ 3 ]; + + trailSystem_t *muzzleTS; //used for the tesla and reactor + int muzzleTSDeathTime; + qboolean valid; qboolean oldValid; } centity_t; @@ -521,7 +642,6 @@ typedef enum { LE_MARK, LE_EXPLOSION, - LE_LIGHTNING_BOLT, //wolf trail LE_SPRITE_EXPLOSION, LE_FRAGMENT, LE_MOVE_SCALE_FADE, @@ -700,6 +820,7 @@ typedef struct weaponInfoMode_s qhandle_t missileSprite; int missileSpriteSize; qhandle_t missileParticleSystem; + qhandle_t missileTrailSystem; qboolean missileRotates; qboolean missileAnimates; int missileAnimStartFrame; @@ -1086,12 +1207,10 @@ typedef struct qhandle_t gibSpark1; qhandle_t gibSpark2; - qhandle_t smoke2; - qhandle_t machinegunBrassModel; qhandle_t shotgunBrassModel; - qhandle_t lightningShader; + qhandle_t level2ZapTS; qhandle_t friendShader; @@ -1148,7 +1267,6 @@ typedef struct qhandle_t bulletFlashModel; qhandle_t ringFlashModel; qhandle_t dishFlashModel; - qhandle_t lightningExplosionModel; // weapon effect shaders qhandle_t bloodExplosionShader; @@ -1216,9 +1334,6 @@ typedef struct qhandle_t selectCursor; qhandle_t sizeCursor; - //TA: for wolf trail effects - qhandle_t sparkFlareShader; - //light armour qhandle_t larmourHeadSkin; qhandle_t larmourLegsSkin; @@ -1243,6 +1358,7 @@ typedef struct qhandle_t humanBuildableDestroyedPS; qhandle_t alienBuildableDamagedPS; qhandle_t alienBuildableDestroyedPS; + qhandle_t teslaZapTS; sfxHandle_t lCannonWarningSound; @@ -1463,6 +1579,7 @@ extern vmCvar_t cg_depthSortParticles; extern vmCvar_t cg_consoleLatency; extern vmCvar_t cg_lightFlare; extern vmCvar_t cg_debugParticles; +extern vmCvar_t cg_debugTrails; extern vmCvar_t cg_debugPVS; extern vmCvar_t cg_disableWarningDialogs; extern vmCvar_t cg_disableScannerPlane; @@ -1659,7 +1776,6 @@ void CG_MissileHitPlayer( weapon_t weapon, weaponMode_t weaponMode, vec3_ void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); void CG_ShotgunFire( entityState_t *es ); -void CG_TeslaTrail( vec3_t start, vec3_t end, int srcENum, int destENum ); void CG_AddViewWeapon (playerState_t *ps); void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent ); void CG_DrawItemSelect( rectDef_t *rect, vec4_t color ); @@ -1707,7 +1823,6 @@ localEntity_t *CG_SmokePuff( const vec3_t p, void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ); void CG_SpawnEffect( vec3_t org ); void CG_GibPlayer( vec3_t playerOrigin ); -void CG_BigExplode( vec3_t playerOrigin ); void CG_Bleed( vec3_t origin, int entityNum ); @@ -1715,30 +1830,6 @@ localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, qhandle_t hModel, qhandle_t shader, int msec, qboolean isSprite ); -//TA: wolf tesla effect -void CG_DynamicLightningBolt( qhandle_t shader, vec3_t start, vec3_t pend, - int numBolts, float maxWidth, qboolean fade, - float startAlpha, int recursion, int randseed ); - -// Ridah, trails -// -// cg_trails.c -// -int CG_AddTrailJunc( int headJuncIndex, qhandle_t shader, int spawnTime, - int sType, vec3_t pos, int trailLife, float alphaStart, - float alphaEnd, float startWidth, float endWidth, int flags, - vec3_t colorStart, vec3_t colorEnd, float sRatio, float animSpeed ); -int CG_AddSparkJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, - float alphaStart, float alphaEnd, float startWidth, float endWidth ); -int CG_AddSmokeJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, - float alpha, float startWidth, float endWidth ); -int CG_AddFireJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, - float alpha, float startWidth, float endWidth ); -void CG_AddTrails( void ); -void CG_ClearTrails( void ); -// done. - - // // cg_snapshot.c // @@ -1774,6 +1865,28 @@ void CG_Free( void *ptr ); void CG_DefragmentMemory( void ); // +// cg_attachment.c +// +qboolean CG_AttachmentPoint( attachment_t *a, vec3_t v ); +qboolean CG_AttachmentDir( attachment_t *a, vec3_t v ); +qboolean CG_AttachmentVelocity( attachment_t *a, vec3_t v ); +int CG_AttachmentCentNum( attachment_t *a ); + +qboolean CG_Attached( attachment_t *a ); + +void CG_AttachToPoint( attachment_t *a ); +void CG_AttachToCent( attachment_t *a ); +void CG_AttachToTag( attachment_t *a ); +void CG_AttachToParticle( attachment_t *a ); +void CG_SetAttachmentPoint( attachment_t *a, vec3_t v ); +void CG_SetAttachmentCent( attachment_t *a, centity_t *cent ); +void CG_SetAttachmentTag( attachment_t *a, refEntity_t parent, + qhandle_t model, char *tagName ); +void CG_SetAttachmentParticle( attachment_t *a, particle_t *p ); + +void CG_SetAttachmentOffset( attachment_t *a, vec3_t v ); + +// // cg_particles.c // void CG_LoadParticleSystems( void ); @@ -1785,20 +1898,31 @@ void CG_DestroyParticleSystem( particleSystem_t **ps ); qboolean CG_IsParticleSystemInfinite( particleSystem_t *ps ); qboolean CG_IsParticleSystemValid( particleSystem_t **ps ); -void CG_SetParticleSystemCent( particleSystem_t *ps, centity_t *cent ); -void CG_AttachParticleSystemToCent( particleSystem_t *ps ); -void CG_SetParticleSystemTag( particleSystem_t *ps, refEntity_t parent, qhandle_t model, char *tagName ); -void CG_AttachParticleSystemToTag( particleSystem_t *ps ); -void CG_SetParticleSystemOrigin( particleSystem_t *ps, vec3_t origin ); -void CG_AttachParticleSystemToOrigin( particleSystem_t *ps ); void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ); -void CG_AttachParticleSystemToParticle( particleSystem_t *ps ); -void CG_SetParticleSystemParentParticle( particleSystem_t *ps, particle_t *p ); void CG_AddParticles( void ); void CG_ParticleSystemEntity( centity_t *cent ); +void CG_TestPS_f( void ); +void CG_DestroyTestPS_f( void ); + +// +// cg_trails.c +// +void CG_LoadTrailSystems( void ); +qhandle_t CG_RegisterTrailSystem( char *name ); + +trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle ); +void CG_DestroyTrailSystem( trailSystem_t **ts ); + +qboolean CG_IsTrailSystemValid( trailSystem_t **ts ); + +void CG_AddTrails( void ); + +void CG_TestTS_f( void ); +void CG_DestroyTestTS_f( void ); + // // cg_ptr.c // diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c index a5db719c..663ace40 100644 --- a/src/cgame/cg_main.c +++ b/src/cgame/cg_main.c @@ -199,6 +199,7 @@ vmCvar_t cg_depthSortParticles; vmCvar_t cg_consoleLatency; vmCvar_t cg_lightFlare; vmCvar_t cg_debugParticles; +vmCvar_t cg_debugTrails; vmCvar_t cg_debugPVS; vmCvar_t cg_disableWarningDialogs; vmCvar_t cg_disableScannerPlane; @@ -304,6 +305,7 @@ static cvarTable_t cvarTable[ ] = { &cg_consoleLatency, "cg_consoleLatency", "3000", CVAR_ARCHIVE }, { &cg_lightFlare, "cg_lightFlare", "3", CVAR_ARCHIVE }, { &cg_debugParticles, "cg_debugParticles", "0", CVAR_CHEAT }, + { &cg_debugTrails, "cg_debugTrails", "0", CVAR_CHEAT }, { &cg_debugPVS, "cg_debugPVS", "0", CVAR_CHEAT }, { &cg_disableWarningDialogs, "cg_disableWarningDialogs", "0", CVAR_ARCHIVE }, { &cg_disableScannerPlane, "cg_disableScannerPlane", "0", CVAR_ARCHIVE }, @@ -794,8 +796,8 @@ static void CG_RegisterGraphics( void ) cgs.media.upgradeClassIconShader = trap_R_RegisterShader( "icons/icona_upgrade.tga" ); - cgs.media.machinegunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/m_shell.md3" ); - cgs.media.shotgunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); + cgs.media.machinegunBrassModel = trap_R_RegisterModel( "models/weapons/shells/rifle_shell.md3" ); + cgs.media.shotgunBrassModel = trap_R_RegisterModel( "models/weapons/shells/shotgun_shell.md3" ); cgs.media.gibAbdomen = trap_R_RegisterModel( "models/gibs/abdomen.md3" ); cgs.media.gibArm = trap_R_RegisterModel( "models/gibs/arm.md3" ); @@ -825,8 +827,6 @@ static void CG_RegisterGraphics( void ) cgs.media.alienGib3 = trap_R_RegisterModel( "models/fx/alien_gibs/a_gib3.md3" ); cgs.media.alienGib4 = trap_R_RegisterModel( "models/fx/alien_gibs/a_gib4.md3" ); - cgs.media.smoke2 = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); - cgs.media.balloonShader = trap_R_RegisterShader( "sprites/balloon3" ); cgs.media.bloodExplosionShader = trap_R_RegisterShader( "bloodExplosion" ); @@ -1803,6 +1803,9 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) CG_LoadParticleSystems( ); CG_UpdateMediaFraction( 0.05f ); + CG_LoadTrailSystems( ); + CG_UpdateMediaFraction( 0.05f ); + CG_RegisterSounds( ); CG_UpdateMediaFraction( 0.60f ); diff --git a/src/cgame/cg_particles.c b/src/cgame/cg_particles.c index b6a52d90..a6ef99e5 100644 --- a/src/cgame/cg_particles.c +++ b/src/cgame/cg_particles.c @@ -25,7 +25,8 @@ static int numBaseParticles = 0; static particleSystem_t particleSystems[ MAX_PARTICLE_SYSTEMS ]; static particleEjector_t particleEjectors[ MAX_PARTICLE_EJECTORS ]; static particle_t particles[ MAX_PARTICLES ]; -static particle_t sortParticles[ MAX_PARTICLES ]; +static particle_t *sortedParticles[ MAX_PARTICLES ]; +static particle_t *radixBuffer[ MAX_PARTICLES ]; /* =============== @@ -85,7 +86,7 @@ CG_DestroyParticle Destroy an individual particle =============== */ -static void CG_DestroyParticle( particle_t *p ) +static void CG_DestroyParticle( particle_t *p, vec3_t impactNormal ) { //this particle has an onDeath particle system attached if( p->class->onDeathSystemName[ 0 ] != '\0' ) @@ -96,13 +97,19 @@ static void CG_DestroyParticle( particle_t *p ) if( CG_IsParticleSystemValid( &ps ) ) { - CG_SetParticleSystemOrigin( ps, p->origin ); - CG_SetParticleSystemNormal( ps, p->velocity ); - CG_AttachParticleSystemToOrigin( ps ); + if( impactNormal ) + CG_SetParticleSystemNormal( ps, impactNormal ); + + CG_SetAttachmentPoint( &ps->attachment, p->origin ); + CG_AttachToPoint( &ps->attachment ); } } p->valid = qfalse; + + //this gives other systems a couple of + //frames to realise the particle is gone + p->frameWhenInvalidated = cg.clientFrame; } /* @@ -118,14 +125,14 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p particle_t *p = NULL; particleEjector_t *pe = parent; particleSystem_t *ps = parent->parent; - vec3_t forward; - centity_t *cent = &cg_entities[ ps->attachment.centNum ]; + vec3_t attachmentPoint, attachmentVelocity; for( i = 0; i < MAX_PARTICLES; i++ ) { p = &particles[ i ]; - if( !p->valid ) + //FIXME: the + 1 may be unnecessary + if( !p->valid && cg.clientFrame > p->frameWhenInvalidated + 1 ) { memset( p, 0, sizeof( particle_t ) ); @@ -148,59 +155,10 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p p->rotation.initial = CG_RandomiseValue( bp->rotation.initial, bp->rotation.initialRandFrac ); p->rotation.final = CG_RandomiseValue( bp->rotation.final, bp->rotation.finalRandFrac ); - switch( ps->attachType ) - { - case PSA_STATIC: - if( !ps->attachment.staticValid ) - return NULL; - - VectorCopy( ps->attachment.origin, p->origin ); - break; - - case PSA_TAG: - if( !ps->attachment.tagValid ) - return NULL; - - AxisCopy( axisDefault, ps->attachment.re.axis ); - CG_PositionRotatedEntityOnTag( &ps->attachment.re, &ps->attachment.parent, - ps->attachment.model, ps->attachment.tagName ); - VectorCopy( ps->attachment.re.origin, p->origin ); - break; - - case PSA_CENT_ORIGIN: - if( !ps->attachment.centValid ) - return NULL; - - VectorCopy( cent->lerpOrigin, p->origin ); - break; - - case PSA_PARTICLE: - if( !ps->attachment.particleValid ) - return NULL; - - //find a particle which has ps as a child - for( j = 0; j < MAX_PARTICLES; j++ ) - { - particle_t *parentParticle = &particles[ j ]; - - if( parentParticle->valid && parentParticle->childSystem == ps ) - { - VectorCopy( parentParticle->origin, p->origin ); - break; - } - } - - if( j == MAX_PARTICLES ) - { - //didn't find the parent, so it's probably died already - - //prevent further (expensive) attempts at particle creation - ps->attachment.particleValid = qfalse; - return NULL; - } - break; - } + if( !CG_AttachmentPoint( &ps->attachment, attachmentPoint ) ) + return NULL; + VectorCopy( attachmentPoint, p->origin ); VectorAdd( p->origin, bp->displacement, p->origin ); for( j = 0; j <= 2; j++ ) @@ -209,52 +167,32 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p switch( bp->velMoveType ) { case PMT_STATIC: - if( bp->velMoveValues.dirType == PMD_POINT ) VectorSubtract( bp->velMoveValues.point, p->origin, p->velocity ); else if( bp->velMoveValues.dirType == PMD_LINEAR ) VectorCopy( bp->velMoveValues.dir, p->velocity ); - break; case PMT_TAG: - - if( !ps->attachment.tagValid ) - return NULL; - - if( bp->velMoveValues.dirType == PMD_POINT ) - VectorSubtract( ps->attachment.re.origin, p->origin, p->velocity ); - else if( bp->velMoveValues.dirType == PMD_LINEAR ) - VectorCopy( ps->attachment.re.axis[ 0 ], p->velocity ); - - break; - case PMT_CENT_ANGLES: - - if( !ps->attachment.centValid ) - return NULL; - if( bp->velMoveValues.dirType == PMD_POINT ) - VectorSubtract( cent->lerpOrigin, p->origin, p->velocity ); + VectorSubtract( attachmentPoint, p->origin, p->velocity ); else if( bp->velMoveValues.dirType == PMD_LINEAR ) { - AngleVectors( cent->lerpAngles, forward, NULL, NULL ); - VectorCopy( forward, p->velocity ); + if( !CG_AttachmentDir( &ps->attachment, p->velocity ) ) + return NULL; } - break; case PMT_NORMAL: - - if( !ps->attachment.normalValid ) + if( !ps->normalValid ) return NULL; - VectorCopy( ps->attachment.normal, p->velocity ); + VectorCopy( ps->normal, p->velocity ); //normal displacement VectorNormalize( p->velocity ); VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin ); - break; } @@ -264,11 +202,11 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p CG_RandomiseValue( bp->velMoveValues.mag, bp->velMoveValues.magRandFrac ), p->velocity ); - if( ps->attachment.centValid ) + if( CG_AttachmentVelocity( &ps->attachment, attachmentVelocity ) ) { VectorMA( p->velocity, - CG_RandomiseValue( bp->velMoveValues.parentVelFrac, bp->velMoveValues.parentVelFracRandFrac ), - cent->currentState.pos.trDelta, p->velocity ); + CG_RandomiseValue( bp->velMoveValues.parentVelFrac, + bp->velMoveValues.parentVelFracRandFrac ), attachmentVelocity, p->velocity ); } p->lastEvalTime = cg.time; @@ -278,15 +216,12 @@ static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *p //this particle has a child particle system attached if( bp->childSystemName[ 0 ] != '\0' ) { - particleSystem_t *ps; - - ps = CG_SpawnNewParticleSystem( bp->childSystemHandle ); + particleSystem_t *ps = CG_SpawnNewParticleSystem( bp->childSystemHandle ); if( CG_IsParticleSystemValid( &ps ) ) { - CG_SetParticleSystemParentParticle( ps, p ); - CG_SetParticleSystemNormal( ps, p->velocity ); - CG_AttachParticleSystemToParticle( ps ); + CG_SetAttachmentParticle( &ps->attachment, p ); + CG_AttachToParticle( &ps->attachment ); } } @@ -324,7 +259,7 @@ static void CG_SpawnNewParticles( void ) if( pe->valid ) { //a non attached particle system can't make particles - if( !ps->attached ) + if( !CG_Attached( &ps->attachment ) ) continue; bpe = particleEjectors[ i ].class; @@ -528,60 +463,13 @@ qhandle_t CG_RegisterParticleSystem( char *name ) } } - CG_Printf( S_COLOR_RED "ERROR: failed to load particle system %s\n", name ); + CG_Printf( S_COLOR_RED "ERROR: failed to register particle system %s\n", name ); return 0; } /* =============== -atof_neg - -atof with an allowance for negative values -=============== -*/ -static float atof_neg( char *token, qboolean allowNegative ) -{ - float value; - - value = atof( token ); - - if( !allowNegative && value < 0.0f ) - { - CG_Printf( S_COLOR_YELLOW "WARNING: negative value %f is now allowed here, " - "replaced with 1.0f\n", value ); - value = 1.0f; - } - - return value; -} - -/* -=============== -atoi_neg - -atoi with an allowance for negative values -=============== -*/ -static int atoi_neg( char *token, qboolean allowNegative ) -{ - int value; - - value = atoi( token ); - - if( !allowNegative && value < 0 ) - { - CG_Printf( S_COLOR_YELLOW "WARNING: negative value %d is now allowed here, " - "replaced with 1\n", value ); - value = 1; - } - - return value; -} - - -/* -=============== CG_ParseValueAndVariance Parse a value and its random variance @@ -1275,7 +1163,8 @@ static qboolean CG_ParseParticleSystem( baseParticleSystem_t *bps, char **text_p } else if( numBaseParticleEjectors == MAX_BASEPARTICLE_EJECTORS ) { - CG_Printf( S_COLOR_RED "ERROR: maximum number of particle ejectors (%d) reached\n", MAX_BASEPARTICLE_EJECTORS ); + CG_Printf( S_COLOR_RED "ERROR: maximum number of particle ejectors (%d) reached\n", + MAX_BASEPARTICLE_EJECTORS ); return qfalse; } else @@ -1347,11 +1236,8 @@ static qboolean CG_ParseParticleFile( const char *fileName ) { token = COM_Parse( &text_p ); - if( !token ) - break; - if( !Q_stricmp( token, "" ) ) - return qfalse; + break; if( !Q_stricmp( token, "{" ) ) { @@ -1380,7 +1266,8 @@ static qboolean CG_ParseParticleFile( const char *fileName ) if( numBaseParticleSystems == MAX_BASEPARTICLE_SYSTEMS ) { - CG_Printf( S_COLOR_RED "ERROR: maximum number of particle systems (%d) reached\n", MAX_BASEPARTICLE_EJECTORS ); + CG_Printf( S_COLOR_RED "ERROR: maximum number of particle systems (%d) reached\n", + MAX_BASEPARTICLE_SYSTEMS ); return qfalse; } else @@ -1518,167 +1405,9 @@ void CG_LoadParticleSystems( void ) } } - -/* -=============== -CG_AttachParticleSystemToCent - -Attach a particle system to a centity_t -=============== -*/ -void CG_AttachParticleSystemToCent( particleSystem_t *ps ) -{ - if( ps == NULL || !ps->valid ) - { - CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); - return; - } - - ps->attachType = PSA_CENT_ORIGIN; - ps->attached = qtrue; -} - -/* -=============== -CG_SetParticleSystemCent - -Set a particle system attachment means -=============== -*/ -void CG_SetParticleSystemCent( particleSystem_t *ps, centity_t *cent ) -{ - if( ps == NULL || !ps->valid ) - { - CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); - return; - } - - ps->attachment.centValid = qtrue; - ps->attachment.centNum = cent->currentState.number; -} - -/* -=============== -CG_AttachParticleSystemToTag - -Attach a particle system to a model tag -=============== -*/ -void CG_AttachParticleSystemToTag( particleSystem_t *ps ) -{ - if( ps == NULL || !ps->valid ) - { - CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); - return; - } - - ps->attachType = PSA_TAG; - ps->attached = qtrue; -} - -/* -=============== -CG_SetParticleSystemToTag - -Set a particle system attachment means -=============== -*/ -void CG_SetParticleSystemTag( particleSystem_t *ps, refEntity_t parent, - qhandle_t model, char *tagName ) -{ - if( ps == NULL || !ps->valid ) - { - CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); - return; - } - - ps->attachment.tagValid = qtrue; - ps->attachment.parent = parent; - ps->attachment.model = model; - strncpy( ps->attachment.tagName, tagName, MAX_STRING_CHARS ); -} - -/* -=============== -CG_AttachParticleSystemToOrigin - -Attach a particle system to a point in space -=============== -*/ -void CG_AttachParticleSystemToOrigin( particleSystem_t *ps ) -{ - if( ps == NULL || !ps->valid ) - { - CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); - return; - } - - ps->attachType = PSA_STATIC; - ps->attached = qtrue; -} - -/* -=============== -CG_SetParticleSystemOrigin - -Set a particle system attachment means -=============== -*/ -void CG_SetParticleSystemOrigin( particleSystem_t *ps, vec3_t origin ) -{ - if( ps == NULL || !ps->valid ) - { - CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); - return; - } - - ps->attachment.staticValid = qtrue; - VectorCopy( origin, ps->attachment.origin ); -} - -/* -=============== -CG_AttachParticleSystemToParticle - -Attach a particle system to a particle -=============== -*/ -void CG_AttachParticleSystemToParticle( particleSystem_t *ps ) -{ - if( ps == NULL || !ps->valid ) - { - CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); - return; - } - - ps->attachType = PSA_PARTICLE; - ps->attached = qtrue; -} - -/* -=============== -CG_SetParticleSystemParentParticle - -Set a particle system attachment means -=============== -*/ -void CG_SetParticleSystemParentParticle( particleSystem_t *ps, particle_t *p ) -{ - if( ps == NULL || !ps->valid ) - { - CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" ); - return; - } - - ps->attachment.particleValid = qtrue; - p->childSystem = ps; -} - /* =============== CG_SetParticleSystemNormal - -Set a particle system attachment means =============== */ void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ) @@ -1689,9 +1418,9 @@ void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal ) return; } - ps->attachment.normalValid = qtrue; - VectorCopy( normal, ps->attachment.normal ); - VectorNormalize( ps->attachment.normal ); + ps->normalValid = qtrue; + VectorCopy( normal, ps->normal ); + VectorNormalize( ps->normal ); } @@ -1807,6 +1536,7 @@ static void CG_GarbageCollectParticleSystems( void ) int i, j, count; particleSystem_t *ps; particleEjector_t *pe; + int centNum; for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ ) { @@ -1830,9 +1560,10 @@ static void CG_GarbageCollectParticleSystems( void ) //check systems where the parent cent has left the PVS //( local player entity is always valid ) - if( ps->attachment.centValid && ps->attachment.centNum != cg.snap->ps.clientNum ) + if( ( centNum = CG_AttachmentCentNum( &ps->attachment ) ) >= 0 && + centNum != cg.snap->ps.clientNum ) { - if( !cg_entities[ ps->attachment.centNum ].valid ) + if( !cg_entities[ centNum ].valid ) ps->lazyRemove = qtrue; } @@ -1874,16 +1605,14 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) { particleSystem_t *ps = p->parent->parent; baseParticle_t *bp = p->class; - vec3_t acceleration, forward, newOrigin; + vec3_t acceleration, newOrigin; vec3_t mins, maxs; float deltaTime, bounce, radius, dot; trace_t trace; - centity_t *cent; switch( bp->accMoveType ) { case PMT_STATIC: - if( bp->accMoveValues.dirType == PMD_POINT ) VectorSubtract( bp->accMoveValues.point, p->origin, acceleration ); else if( bp->accMoveValues.dirType == PMD_LINEAR ) @@ -1892,40 +1621,28 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) break; case PMT_TAG: - - if( !ps->attachment.tagValid ) - return; - - if( bp->accMoveValues.dirType == PMD_POINT ) - VectorSubtract( ps->attachment.re.origin, p->origin, acceleration ); - else if( bp->accMoveValues.dirType == PMD_LINEAR ) - VectorCopy( ps->attachment.re.axis[ 0 ], acceleration ); - - break; - case PMT_CENT_ANGLES: + if( bp->accMoveValues.dirType == PMD_POINT ) + { + vec3_t point; - if( !ps->attachment.centValid ) - return; - - cent = &cg_entities[ ps->attachment.centNum ]; + if( !CG_AttachmentPoint( &ps->attachment, point ) ) + return; - if( bp->accMoveValues.dirType == PMD_POINT ) - VectorSubtract( cent->lerpOrigin, p->origin, acceleration ); + VectorSubtract( point, p->origin, acceleration ); + } else if( bp->accMoveValues.dirType == PMD_LINEAR ) { - AngleVectors( cent->lerpAngles, forward, NULL, NULL ); - VectorCopy( forward, acceleration ); + if( !CG_AttachmentDir( &ps->attachment, acceleration ) ) + return; } - break; case PMT_NORMAL: - - if( !ps->attachment.normalValid ) + if( !ps->normalValid ) return; - VectorCopy( ps->attachment.normal, acceleration ); + VectorCopy( ps->normal, acceleration ); break; } @@ -1974,10 +1691,8 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) VectorMA( p->origin, deltaTime, p->velocity, newOrigin ); p->lastEvalTime = cg.time; - if( !ps->attachment.centValid ) - CG_Trace( &trace, p->origin, mins, maxs, newOrigin, -1, CONTENTS_SOLID ); - else - CG_Trace( &trace, p->origin, mins, maxs, newOrigin, ps->attachment.centNum, CONTENTS_SOLID ); + CG_Trace( &trace, p->origin, mins, maxs, newOrigin, + CG_AttachmentCentNum( &ps->attachment ), CONTENTS_SOLID ); //not hit anything or not a collider if( trace.fraction == 1.0f || bounce == 0.0f ) @@ -1988,9 +1703,14 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) //remove particles that get into a CONTENTS_NODROP brush if( ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) || - ( bp->cullOnStartSolid && trace.startsolid ) || bp->bounceCull ) + ( bp->cullOnStartSolid && trace.startsolid ) ) { - CG_DestroyParticle( p ); + CG_DestroyParticle( p, NULL ); + return; + } + else if( bp->bounceCull ) + { + CG_DestroyParticle( p, trace.plane.normal ); return; } @@ -2011,7 +1731,7 @@ static void CG_EvaluateParticlePhysics( particle_t *p ) CG_Radix =============== */ -static void CG_Radix( int bits, int size, particle_t *source, particle_t *dest ) +static void CG_Radix( int bits, int size, particle_t **source, particle_t **dest ) { int count[ 256 ]; int index[ 256 ]; @@ -2020,7 +1740,7 @@ static void CG_Radix( int bits, int size, particle_t *source, particle_t *dest ) memset( count, 0, sizeof( count ) ); for( i = 0; i < size; i++ ) - count[ GETKEY( source[ i ].sortKey, bits ) ]++; + count[ GETKEY( source[ i ]->sortKey, bits ) ]++; index[ 0 ] = 0; @@ -2028,7 +1748,7 @@ static void CG_Radix( int bits, int size, particle_t *source, particle_t *dest ) index[ i ] = index[ i - 1 ] + count[ i - 1 ]; for( i = 0; i < size; i++ ) - dest[ index[ GETKEY( source[ i ].sortKey, bits ) ]++ ] = source[ i ]; + dest[ index[ GETKEY( source[ i ]->sortKey, bits ) ]++ ] = source[ i ]; } /* @@ -2038,7 +1758,7 @@ CG_RadixSort Radix sort with 4 byte size buckets =============== */ -static void CG_RadixSort( particle_t *source, particle_t *temp, int size ) +static void CG_RadixSort( particle_t **source, particle_t **temp, int size ) { CG_Radix( 0, size, source, temp ); CG_Radix( 8, size, temp, source ); @@ -2059,19 +1779,22 @@ static void CG_CompactAndSortParticles( void ) int numParticles; vec3_t delta; + for( i = 0; i < MAX_PARTICLES; i++ ) + sortedParticles[ i ] = &particles[ i ]; + for( i = MAX_PARTICLES - 1; i >= 0; i-- ) { - if( particles[ i ].valid ) + if( sortedParticles[ i ]->valid ) { - while( particles[ j ].valid ) + //find the first hole + while( sortedParticles[ j ]->valid ) j++; //no more holes if( j >= i ) break; - particles[ j ] = particles[ i ]; - memset( &particles[ i ], 0, sizeof( particles[ 0 ] ) ); + sortedParticles[ j ] = sortedParticles[ i ]; } } @@ -2080,18 +1803,19 @@ static void CG_CompactAndSortParticles( void ) //set sort keys for( i = 0; i < numParticles; i++ ) { - VectorSubtract( particles[ i ].origin, cg.refdef.vieworg, delta ); - particles[ i ].sortKey = (int)DotProduct( delta, delta ); + VectorSubtract( sortedParticles[ i ]->origin, cg.refdef.vieworg, delta ); + sortedParticles[ i ]->sortKey = (int)DotProduct( delta, delta ); } - CG_RadixSort( particles, sortParticles, numParticles ); + CG_RadixSort( sortedParticles, radixBuffer, numParticles ); + //FIXME: wtf? //reverse order of particles array for( i = 0; i < numParticles; i++ ) - sortParticles[ i ] = particles[ numParticles - i - 1 ]; + radixBuffer[ i ] = sortedParticles[ numParticles - i - 1 ]; for( i = 0; i < numParticles; i++ ) - particles[ i ] = sortParticles[ i ]; + sortedParticles[ i ] = radixBuffer[ i ]; } /* @@ -2198,7 +1922,7 @@ void CG_AddParticles( void ) for( i = 0; i < MAX_PARTICLES; i++ ) { - p = &particles[ i ]; + p = sortedParticles[ i ]; if( p->valid ) { @@ -2209,7 +1933,7 @@ void CG_AddParticles( void ) CG_RenderParticle( p ); } else - CG_DestroyParticle( p ); + CG_DestroyParticle( p, NULL ); } } @@ -2258,11 +1982,63 @@ void CG_ParticleSystemEntity( centity_t *cent ) if( CG_IsParticleSystemValid( ¢->entityPS ) ) { - CG_SetParticleSystemOrigin( cent->entityPS, cent->lerpOrigin ); - CG_SetParticleSystemCent( cent->entityPS, cent ); - CG_AttachParticleSystemToOrigin( cent->entityPS ); + CG_SetAttachmentPoint( ¢->entityPS->attachment, cent->lerpOrigin ); + CG_SetAttachmentCent( ¢->entityPS->attachment, cent ); + CG_AttachToPoint( ¢->entityPS->attachment ); } else cent->entityPSMissing = qtrue; } } + +static particleSystem_t *testPS; +static qhandle_t testPSHandle; + +/* +=============== +CG_DestroyTestPS_f + +Destroy the test a particle system +=============== +*/ +void CG_DestroyTestPS_f( void ) +{ + if( CG_IsParticleSystemValid( &testPS ) ) + CG_DestroyParticleSystem( &testPS ); +} + +/* +=============== +CG_TestPS_f + +Test a particle system +=============== +*/ +void CG_TestPS_f( void ) +{ + vec3_t origin; + vec3_t up = { 0.0f, 0.0f, 1.0f }; + char psName[ MAX_QPATH ]; + + if( trap_Argc( ) < 2 ) + return; + + Q_strncpyz( psName, CG_Argv( 1 ), MAX_QPATH ); + testPSHandle = CG_RegisterParticleSystem( psName ); + + if( testPSHandle ) + { + CG_DestroyTestPS_f( ); + + testPS = CG_SpawnNewParticleSystem( testPSHandle ); + + VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[ 0 ], origin ); + + if( CG_IsParticleSystemValid( &testPS ) ) + { + CG_SetAttachmentPoint( &testPS->attachment, origin ); + CG_SetParticleSystemNormal( testPS, up ); + CG_AttachToPoint( &testPS->attachment ); + } + } +} diff --git a/src/cgame/cg_players.c b/src/cgame/cg_players.c index 18a32efc..1ef18f8b 100644 --- a/src/cgame/cg_players.c +++ b/src/cgame/cg_players.c @@ -1631,9 +1631,10 @@ static void CG_PlayerUpgrades( centity_t *cent, refEntity_t *torso ) if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) { - CG_SetParticleSystemTag( cent->jetPackPS, jetpack, jetpack.hModel, "tag_flash" ); - CG_SetParticleSystemCent( cent->jetPackPS, cent ); - CG_AttachParticleSystemToTag( cent->jetPackPS ); + CG_SetAttachmentTag( ¢->jetPackPS->attachment, + jetpack, jetpack.hModel, "tag_flash" ); + CG_SetAttachmentCent( ¢->jetPackPS->attachment, cent ); + CG_AttachToTag( ¢->jetPackPS->attachment ); } } else if( CG_IsParticleSystemValid( ¢->jetPackPS ) ) diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c index e2391127..a92fa37e 100644 --- a/src/cgame/cg_servercmds.c +++ b/src/cgame/cg_servercmds.c @@ -484,10 +484,6 @@ static void CG_MapRestart( void ) CG_InitLocalEntities( ); CG_InitMarkPolys( ); - // Ridah, trails - CG_ClearTrails( ); - // done. - // make sure the "3 frags left" warnings play again cg.fraglimitWarnings = 0; @@ -1048,9 +1044,13 @@ static void CG_ServerCommand( void ) if( !strcmp( cmd, "poisoncloud" ) ) { cg.poisonedTime = cg.time; - cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS ); - CG_SetParticleSystemCent( cg.poisonCloudPS, &cg.predictedPlayerEntity ); - CG_AttachParticleSystemToCent( cg.poisonCloudPS ); + + if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) ) + { + cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS ); + CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity ); + CG_AttachToCent( &cg.poisonCloudPS->attachment ); + } return; } diff --git a/src/cgame/cg_trails.c b/src/cgame/cg_trails.c index 30347e61..e80225e4 100644 --- a/src/cgame/cg_trails.c +++ b/src/cgame/cg_trails.c @@ -1,816 +1,1430 @@ -// Ridah, cg_trails.c - draws a trail using multiple junction points +// cg_trails.c -- the trail system -#include "cg_local.h" - -typedef struct trailJunc_s -{ - struct trailJunc_s *nextGlobal, *prevGlobal; // next junction in the global list it is in (free or used) - struct trailJunc_s *nextJunc; // next junction in the trail - struct trailJunc_s *nextHead, *prevHead; // next head junc in the world - - qboolean inuse, freed; - int ownerIndex; - qhandle_t shader; - - int sType; - int flags; - float sTex; - vec3_t pos; - int spawnTime, endTime; - float alphaStart, alphaEnd; - vec3_t colorStart, colorEnd; - float widthStart, widthEnd; - - // current settings - float alpha; - float width; - vec3_t color; - -} trailJunc_t; - -#define MAX_TRAILJUNCS 4096 +/* + * 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. + */ -trailJunc_t trailJuncs[ MAX_TRAILJUNCS ]; -trailJunc_t *freeTrails, *activeTrails; -trailJunc_t *headTrails; +#include "cg_local.h" -qboolean initTrails = qfalse; +static baseTrailSystem_t baseTrailSystems[ MAX_BASETRAIL_SYSTEMS ]; +static baseTrailBeam_t baseTrailBeams[ MAX_BASETRAIL_BEAMS ]; +static int numBaseTrailSystems = 0; +static int numBaseTrailBeams = 0; -int numTrailsInuse; +static trailSystem_t trailSystems[ MAX_TRAIL_SYSTEMS ]; +static trailBeam_t trailBeams[ MAX_TRAIL_BEAMS ]; /* =============== -CG_ClearTrails +CG_CalculateBeamTextureCoordinates + +Fills in trailBeamNode_t.textureCoord =============== */ -void CG_ClearTrails( void ) +static void CG_CalculateBeamTextureCoordinates( trailBeam_t *tb ) { - int i; - - memset( trailJuncs, 0, sizeof( trailJunc_t ) * MAX_TRAILJUNCS ); + trailBeamNode_t *i = NULL; + trailSystem_t *ts; + baseTrailBeam_t *btb; + float nodeDistances[ MAX_TRAIL_BEAM_NODES ]; + float totalDistance = 0.0f, position = 0.0f; + int j, numNodes = 0; + float TCRange, widthRange, alphaRange; + vec3_t colorRange; + float fadeAlpha = 1.0f; + + if( !tb || !tb->nodes ) + return; - freeTrails = trailJuncs; - activeTrails = NULL; - headTrails = NULL; + ts = tb->parent; + btb = tb->class; - for( i = 0; i < MAX_TRAILJUNCS; i++ ) + if( ts->destroyTime > 0 && btb->fadeOutTime ) { - trailJuncs[ i ].nextGlobal = &trailJuncs[ i + 1 ]; + fadeAlpha -= ( cg.time - ts->destroyTime ) / btb->fadeOutTime; - if( i > 0 ) - trailJuncs[ i ].prevGlobal = &trailJuncs[ i - 1 ]; - else - trailJuncs[ i ].prevGlobal = NULL; + if( fadeAlpha < 0.0f ) + fadeAlpha = 0.0f; + } - trailJuncs[ i ].inuse = qfalse; + TCRange = tb->class->backTextureCoord - + tb->class->frontTextureCoord; + widthRange = tb->class->backWidth - + tb->class->frontWidth; + alphaRange = tb->class->backAlpha - + tb->class->frontAlpha; + VectorSubtract( tb->class->backColor, + tb->class->frontColor, colorRange ); + + for( i = tb->nodes; i && i->next; i = i->next ) + { + nodeDistances[ numNodes++ ] = + Distance( i->position, i->next->position ); } - trailJuncs[ MAX_TRAILJUNCS - 1 ].nextGlobal = NULL; + for( j = 0; j < numNodes; j++ ) + totalDistance += nodeDistances[ j ]; - initTrails = qtrue; - numTrailsInuse = 0; + for( j = 0, i = tb->nodes; i; i = i->next, j++ ) + { + if( tb->class->textureType == TBTT_STRETCH ) + { + i->textureCoord = tb->class->frontTextureCoord + + ( ( position / totalDistance ) * TCRange ); + } + else if( tb->class->textureType == TBTT_REPEAT ) + { + if( tb->class->clampToBack ) + i->textureCoord = ( totalDistance - position ) / + tb->class->repeatLength; + else + i->textureCoord = position / tb->class->repeatLength; + } + + i->halfWidth = ( tb->class->frontWidth + + ( ( position / totalDistance ) * widthRange ) ) / 2.0f; + i->alpha = (byte)( (float)0xFF * ( tb->class->frontAlpha + + ( ( position / totalDistance ) * alphaRange ) ) * fadeAlpha ); + VectorMA( tb->class->frontColor, ( position / totalDistance ), + colorRange, i->color ); + + position += nodeDistances[ j ]; + } } /* =============== -CG_SpawnTrailJunc +CG_LightVertex + +Lights a particular vertex =============== */ -trailJunc_t *CG_SpawnTrailJunc( trailJunc_t *headJunc ) +static void CG_LightVertex( vec3_t point, byte alpha, byte *rgba ) { - trailJunc_t *j; + int i; + vec3_t alight, dlight, lightdir; - if( !freeTrails ) - return NULL; + trap_R_LightForPoint( point, alight, dlight, lightdir ); + for( i = 0; i <= 2; i++ ) + rgba[ i ] = (int)alight[ i ]; - if( cg_paused.integer ) - return NULL; + rgba[ 3 ] = alpha; +} - // select the first free trail, and remove it from the list - j = freeTrails; - freeTrails = j->nextGlobal; +/* +=============== +CG_RenderBeam - if( freeTrails ) - freeTrails->prevGlobal = NULL; +Renders a beam +=============== +*/ +static void CG_RenderBeam( trailBeam_t *tb ) +{ + trailBeamNode_t *i = NULL; + trailBeamNode_t *prev = NULL; + trailBeamNode_t *next = NULL; + vec3_t up; + polyVert_t verts[ ( MAX_TRAIL_BEAM_NODES - 1 ) * 4 ]; + int numVerts = 0; + baseTrailBeam_t *btb; + + if( !tb || !tb->nodes ) + return; - j->nextGlobal = activeTrails; + btb = tb->class; - if( activeTrails ) - activeTrails->prevGlobal = j; + CG_CalculateBeamTextureCoordinates( tb ); - activeTrails = j; - j->prevGlobal = NULL; - j->inuse = qtrue; - j->freed = qfalse; + i = tb->nodes; - // if this owner has a headJunc, add us to the start - if( headJunc ) + do { - // remove the headJunc from the list of heads - if( headJunc == headTrails ) - { - headTrails = headJunc->nextHead; + prev = i->prev; + next = i->next; - if( headTrails ) - headTrails->prevHead = NULL; + if( prev && next ) + { + //this node has two neighbours + GetPerpendicularViewVector( cg.refdef.vieworg, next->position, prev->position, up ); + } + else if( !prev && next ) + { + //this is the front + GetPerpendicularViewVector( cg.refdef.vieworg, next->position, i->position, up ); + } + else if( prev && !next ) + { + //this is the back + GetPerpendicularViewVector( cg.refdef.vieworg, i->position, prev->position, up ); } else + break; + + if( prev ) { - if( headJunc->nextHead ) - headJunc->nextHead->prevHead = headJunc->prevHead; + VectorMA( i->position, i->halfWidth + i->jitter, up, verts[ numVerts ].xyz ); + verts[ numVerts ].st[ 0 ] = i->textureCoord; + verts[ numVerts ].st[ 1 ] = 1.0f; + + if( btb->realLight ) + CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); + else + { + VectorCopy( i->color, verts[ numVerts ].modulate ); + verts[ numVerts ].modulate[ 3 ] = i->alpha; + } + + numVerts++; - if( headJunc->prevHead ) - headJunc->prevHead->nextHead = headJunc->nextHead; + VectorMA( i->position, -i->halfWidth + i->jitter, up, verts[ numVerts ].xyz ); + verts[ numVerts ].st[ 0 ] = i->textureCoord; + verts[ numVerts ].st[ 1 ] = 0.0f; + + if( btb->realLight ) + CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); + else + { + VectorCopy( i->color, verts[ numVerts ].modulate ); + verts[ numVerts ].modulate[ 3 ] = i->alpha; + } + + numVerts++; } - headJunc->prevHead = NULL; - headJunc->nextHead = NULL; - } - // make us the headTrail - if( headTrails ) - headTrails->prevHead = j; + if( next ) + { + VectorMA( i->position, -i->halfWidth + i->jitter, up, verts[ numVerts ].xyz ); + verts[ numVerts ].st[ 0 ] = i->textureCoord; + verts[ numVerts ].st[ 1 ] = 0.0f; - j->nextHead = headTrails; - j->prevHead = NULL; - headTrails = j; + if( btb->realLight ) + CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); + else + { + VectorCopy( i->color, verts[ numVerts ].modulate ); + verts[ numVerts ].modulate[ 3 ] = i->alpha; + } - j->nextJunc = headJunc; // if headJunc is NULL, then we'll just be the end of the list + numVerts++; - numTrailsInuse++; + VectorMA( i->position, i->halfWidth + i->jitter, up, verts[ numVerts ].xyz ); + verts[ numVerts ].st[ 0 ] = i->textureCoord; + verts[ numVerts ].st[ 1 ] = 1.0f; - // debugging -// CG_Printf( "NumTrails: %i\n", numTrailsInuse ); + if( btb->realLight ) + CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate ); + else + { + VectorCopy( i->color, verts[ numVerts ].modulate ); + verts[ numVerts ].modulate[ 3 ] = i->alpha; + } - return j; -} + numVerts++; + } + + i = i->next; + } while( i ); + trap_R_AddPolysToScene( tb->class->shader, 4, &verts[ 0 ], numVerts / 4 ); +} /* =============== -CG_AddTrailJunc - - returns the index of the trail junction created +CG_AllocateBeamNode - Used for generic trails +Allocates a trailBeamNode_t from a trailBeam_t's nodePool =============== */ -int CG_AddTrailJunc( int headJuncIndex, qhandle_t shader, int spawnTime, int sType, vec3_t pos, - int trailLife, float alphaStart, float alphaEnd, float startWidth, - float endWidth, int flags, vec3_t colorStart, vec3_t colorEnd, - float sRatio, float animSpeed ) +static trailBeamNode_t *CG_AllocateBeamNode( trailBeam_t *tb ) { - trailJunc_t *j, *headJunc; + baseTrailBeam_t *btb = tb->class; + int i; + trailBeamNode_t *tbn; - if( headJuncIndex > 0 ) + for( i = 0; i < MAX_TRAIL_BEAM_NODES; i++ ) { - headJunc = &trailJuncs[ headJuncIndex - 1 ]; - - if( !headJunc->inuse ) - headJunc = NULL; + tbn = &tb->nodePool[ i ]; + if( !tbn->used ) + { + tbn->timeLeft = btb->segmentTime; + tbn->prev = NULL; + tbn->next = NULL; + tbn->used = qtrue; + return tbn; + } } - else - headJunc = NULL; - j = CG_SpawnTrailJunc( headJunc ); + // no space left + return NULL; +} - if( !j ) +/* +=============== +CG_DestroyBeamNode + +Removes a node from a beam +Returns the new head +=============== +*/ +static trailBeamNode_t *CG_DestroyBeamNode( trailBeamNode_t *tbn ) +{ + trailBeamNode_t *newHead = NULL; + + if( tbn->prev ) { -// CG_Printf("couldnt spawn trail junc\n"); - return 0; - } + if( tbn->next ) + { + // node is in the middle + tbn->prev->next = tbn->next; + tbn->next->prev = tbn->prev; + } + else // node is at the back + tbn->prev->next = NULL; - if( alphaStart > 1.0 ) - alphaStart = 1.0; + // find the new head (shouldn't have changed) + newHead = tbn->prev; - if( alphaStart < 0.0 ) - alphaStart = 0.0; + while( newHead->prev ) + newHead = newHead->prev; + } + else if( tbn->next ) + { + //node is at the front + tbn->next->prev = NULL; + newHead = tbn->next; + } - if( alphaEnd > 1.0 ) - alphaEnd = 1.0; + tbn->prev = NULL; + tbn->next = NULL; + tbn->used = qfalse; - if( alphaEnd < 0.0 ) - alphaEnd = 0.0; + return newHead; +} - // setup the trail junction - j->shader = shader; - j->sType = sType; - VectorCopy( pos, j->pos ); - j->flags = flags; +/* +=============== +CG_FindLastBeamNode - j->spawnTime = spawnTime; - j->endTime = spawnTime + trailLife; +Returns the last beam node in a beam +=============== +*/ +static trailBeamNode_t *CG_FindLastBeamNode( trailBeam_t *tb ) +{ + trailBeamNode_t *i = tb->nodes; - VectorCopy( colorStart, j->colorStart ); - VectorCopy( colorEnd, j->colorEnd ); + while( i && i->next ) + i = i->next; - j->alphaStart = alphaStart; - j->alphaEnd = alphaEnd; + return i; +} - j->widthStart = startWidth; - j->widthEnd = endWidth; +/* +=============== +CG_CountBeamNodes + +Returns the number of nodes in a beam +=============== +*/ +static int CG_CountBeamNodes( trailBeam_t *tb ) +{ + trailBeamNode_t *i = tb->nodes; + int numNodes = 0; - if( sType == STYPE_REPEAT ) + while( i ) { - if( headJunc ) - j->sTex = headJunc->sTex + ( ( Distance( headJunc->pos, pos ) / sRatio) / j->widthEnd ); - else - { - // FIXME: need a way to specify offset timing - j->sTex = ( animSpeed * ( 1.0 - ( (float)( cg.time % 1000 ) / 1000.0 ) ) ) / ( sRatio ); -// j->sTex = 0; - } + numNodes++; + i = i->next; } - return ( (int)( j - trailJuncs ) + 1 ); + return numNodes; } /* =============== -CG_AddSparkJunc +CG_PrependBeamNode - returns the index of the trail junction created +Prepend a new beam node to the front of a beam +Returns the new node =============== */ -int CG_AddSparkJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, - float alphaStart, float alphaEnd, float startWidth, float endWidth ) +static trailBeamNode_t *CG_PrependBeamNode( trailBeam_t *tb ) { - trailJunc_t *j, *headJunc; + trailBeamNode_t *i; - if( headJuncIndex > 0 ) + if( tb->nodes ) { - headJunc = &trailJuncs[ headJuncIndex - 1 ]; + // prepend another node + i = CG_AllocateBeamNode( tb ); - if( !headJunc->inuse ) - headJunc = NULL; + if( i ) + { + i->next = tb->nodes; + tb->nodes->prev = i; + tb->nodes = i; + } } - else - headJunc = NULL; + else //add first node + { + i = CG_AllocateBeamNode( tb ); - j = CG_SpawnTrailJunc( headJunc ); + if( i ) + tb->nodes = i; + } - if( !j ) - return 0; + return i; +} - // setup the trail junction - j->shader = shader; - j->sType = STYPE_STRETCH; - VectorCopy( pos, j->pos ); - j->flags = TJFL_NOCULL; // don't worry about fading up close +/* +=============== +CG_AppendBeamNode - j->spawnTime = cg.time; - j->endTime = cg.time + trailLife; +Append a new beam node to the back of a beam +Returns the new node +=============== +*/ +static trailBeamNode_t *CG_AppendBeamNode( trailBeam_t *tb ) +{ + trailBeamNode_t *last, *i; - VectorSet( j->colorStart, 1.0, 0.8 + 0.2 * alphaStart, 0.4 + 0.4 * alphaStart ); - VectorSet( j->colorEnd, 1.0, 0.8 + 0.2 * alphaEnd, 0.4 + 0.4 * alphaEnd ); -// VectorScale( j->colorStart, alphaStart, j->colorStart ); -// VectorScale( j->colorEnd, alphaEnd, j->colorEnd ); + if( tb->nodes ) + { + // append another node + last = CG_FindLastBeamNode( tb ); + i = CG_AllocateBeamNode( tb ); - j->alphaStart = alphaStart*2; - j->alphaEnd = alphaEnd*2; -// j->alphaStart = 1.0; -// j->alphaEnd = 1.0; + if( i ) + { + last->next = i; + i->prev = last; + i->next = NULL; + } + } + else //add first node + { + i = CG_AllocateBeamNode( tb ); - j->widthStart = startWidth; - j->widthEnd = endWidth; + if( i ) + tb->nodes = i; + } - return ( (int)( j - trailJuncs ) + 1 ); + return i; } /* =============== -CG_AddSmokeJunc - - returns the index of the trail junction created +CG_ApplyJitters =============== */ -int CG_AddSmokeJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, - float alpha, float startWidth, float endWidth ) +static void CG_ApplyJitters( trailBeam_t *tb ) { -#define ST_RATIO 4.0 // sprite image: width / height - trailJunc_t *j, *headJunc; + trailBeamNode_t *i = NULL; + int j; + baseTrailBeam_t *btb; + trailSystem_t *ts; + trailBeamNode_t *start; + trailBeamNode_t *end; + + if( !tb || !tb->nodes ) + return; - if( headJuncIndex > 0 ) + btb = tb->class; + ts = tb->parent; + + for( j = 0; j < btb->numJitters; j++ ) { - headJunc = &trailJuncs[ headJuncIndex - 1 ]; + if( tb->nextJitterTimes[ j ] <= cg.time ) + { + for( i = tb->nodes; i; i = i->next ) + i->jitters[ j ] = ( crandom( ) * btb->jitters[ j ].magnitude ); - if( !headJunc->inuse ) - headJunc = NULL; + tb->nextJitterTimes[ j ] = cg.time + btb->jitters[ j ].period; + } } - else - headJunc = NULL; - j = CG_SpawnTrailJunc( headJunc ); + start = tb->nodes; + end = CG_FindLastBeamNode( tb ); - if( !j ) - return 0; + if( !btb->jitterAttachments ) + { + if( CG_Attached( &ts->frontAttachment ) && start->next ) + start = start->next; - // setup the trail junction - j->shader = shader; - j->sType = STYPE_REPEAT; - VectorCopy( pos, j->pos ); - j->flags = TJFL_FADEIN; + if( CG_Attached( &ts->backAttachment ) && end->prev ) + end = end->prev; + } - j->spawnTime = cg.time; - j->endTime = cg.time + trailLife; + for( i = start; i; i = i->next ) + { + i->jitter = 0.0f; - // VectorSet(j->colorStart, 0.2, 0.2, 0.2); - VectorSet(j->colorStart, 0.0, 0.0, 0.0); - // VectorSet(j->colorEnd, 0.1, 0.1, 0.1); - VectorSet(j->colorEnd, 0.0, 0.0, 0.0); + for( j = 0; j < btb->numJitters; j++ ) + i->jitter += i->jitters[ j ]; - j->alphaStart = alpha; - j->alphaEnd = 0.0; + //mmmm... nice + if( i == end ) + break; + } +} - j->widthStart = startWidth; - j->widthEnd = endWidth; +/* +=============== +CG_UpdateBeam - if( headJunc ) - j->sTex = headJunc->sTex + ( ( Distance( headJunc->pos, pos ) / ST_RATIO ) / j->widthEnd ); - else +Updates a beam +=============== +*/ +static void CG_UpdateBeam( trailBeam_t *tb ) +{ + baseTrailBeam_t *btb; + trailSystem_t *ts; + trailBeamNode_t *i; + int deltaTime; + int nodesToAdd; + int j; + int numNodes; + + if( !tb ) + return; + + btb = tb->class; + ts = tb->parent; + + deltaTime = cg.time - tb->lastEvalTime; + tb->lastEvalTime = cg.time; + + // first make sure this beam has enough nodes + if( ts->destroyTime <= 0 ) { - // first junction, so this will become the "tail" very soon, make it fade out - j->sTex = 0; - j->alphaStart = 0.0; - j->alphaEnd = 0.0; + nodesToAdd = btb->numSegments - CG_CountBeamNodes( tb ) + 1; + + while( nodesToAdd-- ) + { + i = CG_AppendBeamNode( tb ); + + if( !tb->nodes->next && CG_Attached( &ts->frontAttachment ) ) + { + // this is the first node to be added + CG_AttachmentPoint( &ts->frontAttachment, i->refPosition ); + } + else + VectorCopy( i->prev->refPosition, i->refPosition ); + } } - return ( (int)( j - trailJuncs ) + 1 ); -} + numNodes = CG_CountBeamNodes( tb ); -void CG_KillTrail( trailJunc_t *t ); + for( i = tb->nodes; i; i = i->next ) + VectorCopy( i->refPosition, i->position ); -/* -=========== -CG_FreeTrailJunc -=========== -*/ -void CG_FreeTrailJunc( trailJunc_t *junc ) -{ - // kill any juncs after us, so they aren't left hanging - if( junc->nextJunc ) - CG_KillTrail( junc ); + if( CG_Attached( &ts->frontAttachment ) && CG_Attached( &ts->backAttachment ) ) + { + // beam between two attachments + vec3_t dir, front, back; + + if( ts->destroyTime > 0 && ( cg.time - ts->destroyTime ) >= btb->fadeOutTime ) + { + tb->valid = qfalse; + return; + } - // make it non-active - junc->inuse = qfalse; - junc->freed = qtrue; + CG_AttachmentPoint( &ts->frontAttachment, front ); + CG_AttachmentPoint( &ts->backAttachment, back ); + VectorSubtract( back, front, dir ); - if( junc->nextGlobal ) - junc->nextGlobal->prevGlobal = junc->prevGlobal; + for( j = 0, i = tb->nodes; i; i = i->next, j++ ) + { + float scale = (float)j / (float)( numNodes - 1 ); - if( junc->prevGlobal ) - junc->prevGlobal->nextGlobal = junc->nextGlobal; + VectorMA( front, scale, dir, i->position ); + } + } + else if( CG_Attached( &ts->frontAttachment ) ) + { + // beam from one attachment - if( junc == activeTrails ) - activeTrails = junc->nextGlobal; + // cull the trail tail + i = CG_FindLastBeamNode( tb ); - // if it's a head, remove it - if( junc == headTrails ) - headTrails = junc->nextHead; + if( i && i->timeLeft >= 0 ) + { + i->timeLeft -= deltaTime; - if( junc->nextHead ) - junc->nextHead->prevHead = junc->prevHead; + if( i->timeLeft < 0 ) + { + tb->nodes = CG_DestroyBeamNode( i ); - if( junc->prevHead ) - junc->prevHead->nextHead = junc->nextHead; + if( !tb->nodes ) + { + tb->valid = qfalse; + return; + } - junc->nextHead = NULL; - junc->prevHead = NULL; + // if the ts has been destroyed, stop creating new nodes + if( ts->destroyTime <= 0 ) + CG_PrependBeamNode( tb ); + } + else if( i->timeLeft >= 0 && i->prev ) + { + vec3_t dir; + float length; - // stick it in the free list - junc->prevGlobal = NULL; - junc->nextGlobal = freeTrails; + VectorSubtract( i->refPosition, i->prev->refPosition, dir ); + length = VectorNormalize( dir ) * + ( (float)i->timeLeft / (float)tb->class->segmentTime ); - if( freeTrails ) - freeTrails->prevGlobal = junc; + VectorMA( i->prev->refPosition, length, dir, i->position ); + } + } - freeTrails = junc; + CG_AttachmentPoint( &ts->frontAttachment, tb->nodes->refPosition ); + VectorCopy( tb->nodes->refPosition, tb->nodes->position ); + } - numTrailsInuse--; + CG_ApplyJitters( tb ); } /* -=========== -CG_KillTrail -=========== +=============== +CG_ParseTrailBeamColor +=============== */ -void CG_KillTrail( trailJunc_t *t ) +static qboolean CG_ParseTrailBeamColor( byte *c, char **text_p ) { - trailJunc_t *next; + char *token; + int i; - next = t->nextJunc; + for( i = 0; i <= 2; i++ ) + { + token = COM_Parse( text_p ); + + if( !Q_stricmp( token, "" ) ) + return qfalse; - // kill the trail here - t->nextJunc = NULL; + c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) ); + } - if( next ) - CG_FreeTrailJunc( next ); + return qtrue; } /* -============== -CG_AddTrailToScene +=============== +CG_ParseTrailBeam - TODO: this can do with some major optimization -============== +Parse a trail beam +=============== */ -static vec3_t vforward, vright, vup; +static qboolean CG_ParseTrailBeam( baseTrailBeam_t *btb, char **text_p ) +{ + char *token; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); -//TA: staticised to please QVM - #define MAX_TRAIL_VERTS 2048 -static polyVert_t verts[ MAX_TRAIL_VERTS ]; -static polyVert_t outVerts[ MAX_TRAIL_VERTS * 3 ]; + if( !Q_stricmp( token, "" ) ) + return qfalse; -void CG_AddTrailToScene( trailJunc_t *trail, int iteration, int numJuncs ) -{ - int k, i, n, l, numOutVerts; - polyVert_t mid; - float mod[ 4 ]; - float sInc = 0.0f, s = 0.0f; // TTimo: init - trailJunc_t *j, *jNext; - vec3_t fwd, up, p, v; - - // clipping vars - #define TRAIL_FADE_CLOSE_DIST 64.0 - #define TRAIL_FADE_FAR_SCALE 4.0 - vec3_t viewProj; - float viewDist, fadeAlpha; - - // add spark shader at head position - if( trail->flags & TJFL_SPARKHEADFLARE ) - { - j = trail; - VectorCopy( j->pos, p ); - VectorMA( p, -j->width * 2, vup, p ); - VectorMA( p, -j->width * 2, vright, p ); - VectorCopy( p, verts[ 0 ].xyz ); - verts[ 0 ].st[ 0 ] = 0; - verts[ 0 ].st[ 1 ] = 0; - verts[ 0 ].modulate[ 0 ] = 255; - verts[ 0 ].modulate[ 1 ] = 255; - verts[ 0 ].modulate[ 2 ] = 255; - verts[ 0 ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 ); - - VectorCopy( j->pos, p ); - VectorMA( p, -j->width * 2, vup, p ); - VectorMA( p, j->width * 2, vright, p ); - VectorCopy( p, verts[ 1 ].xyz ); - verts[ 1 ].st[ 0 ] = 0; - verts[ 1 ].st[ 1 ] = 1; - verts[ 1 ].modulate[ 0 ] = 255; - verts[ 1 ].modulate[ 1 ] = 255; - verts[ 1 ].modulate[ 2 ] = 255; - verts[ 1 ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 ); - - VectorCopy( j->pos, p ); - VectorMA( p, j->width * 2, vup, p ); - VectorMA( p, j->width * 2, vright, p ); - VectorCopy( p, verts[ 2 ].xyz ); - verts[ 2 ].st[ 0 ] = 1; - verts[ 2 ].st[ 1 ] = 1; - verts[ 2 ].modulate[ 0 ] = 255; - verts[ 2 ].modulate[ 1 ] = 255; - verts[ 2 ].modulate[ 2 ] = 255; - verts[ 2 ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 ); - - VectorCopy( j->pos, p ); - VectorMA( p, j->width * 2, vup, p ); - VectorMA( p, -j->width * 2, vright, p ); - VectorCopy( p, verts[ 3 ].xyz ); - verts[ 3 ].st[ 0 ] = 1; - verts[ 3 ].st[ 1 ] = 0; - verts[ 3 ].modulate[ 0 ] = 255; - verts[ 3 ].modulate[ 1 ] = 255; - verts[ 3 ].modulate[ 2 ] = 255; - verts[ 3 ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 ); - - trap_R_AddPolyToScene( cgs.media.sparkFlareShader, 4, verts ); - } - -// if (trail->flags & TJFL_CROSSOVER && iteration < 1) { -// iteration = 1; -// } - - if( !numJuncs ) - { - // first count the number of juncs in the trail - j = trail; - numJuncs = 0; - sInc = 0; - - while( j ) - { - numJuncs++; - - // check for a dead next junc - if( !j->inuse && j->nextJunc && !j->nextJunc->inuse ) - CG_KillTrail( j ); - else if( j->nextJunc && j->nextJunc->freed ) + if( !Q_stricmp( token, "segments" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->numSegments = atoi_neg( token, qfalse ); + + if( btb->numSegments >= MAX_TRAIL_BEAM_NODES ) { - // not sure how this can happen, but it does, and causes infinite loops - j->nextJunc = NULL; + btb->numSegments = MAX_TRAIL_BEAM_NODES - 1; + CG_Printf( S_COLOR_YELLOW "WARNING: too many segments in trail beam\n" ); } + continue; + } + else if( !Q_stricmp( token, "width" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; - if( j->nextJunc ) - sInc += VectorDistance( j->nextJunc->pos, j->pos ); + btb->frontWidth = atof_neg( token, qfalse ); - j = j->nextJunc; + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "-" ) ) + btb->backWidth = btb->frontWidth; + else + btb->backWidth = atof_neg( token, qfalse ); + continue; } - } + else if( !Q_stricmp( token, "alpha" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; - if( numJuncs < 2 ) - return; + btb->frontAlpha = atof_neg( token, qfalse ); - if( trail->sType == STYPE_STRETCH ) - { - //sInc = ((1.0 - 0.1) / (float)(numJuncs)); // hack, the end of funnel shows a bit of the start (looping) - s = 0.05; - //s = 0.05; - } - else if( trail->sType == STYPE_REPEAT ) - s = trail->sTex; + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; - // now traverse the list - j = trail; - jNext = j->nextJunc; - i = 0; - while( jNext ) - { - // first get the directional vectors to the next junc - VectorSubtract( jNext->pos, j->pos, fwd ); - GetPerpendicularViewVector( cg.refdef.vieworg, j->pos, jNext->pos, up ); - - // if it's a crossover, draw it twice - if( j->flags & TJFL_CROSSOVER ) + if( !Q_stricmp( token, "-" ) ) + btb->backAlpha = btb->frontAlpha; + else + btb->backAlpha = atof_neg( token, qfalse ); + continue; + } + else if( !Q_stricmp( token, "color" ) ) { - if( iteration > 0 ) - { - ProjectPointOntoVector( cg.refdef.vieworg, j->pos, jNext->pos, viewProj ); - VectorSubtract( cg.refdef.vieworg, viewProj, v ); - VectorNormalize( v ); + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; - if( iteration == 1 ) - VectorMA( up, 0.3, v, up ); + if( !Q_stricmp( token, "{" ) ) + { + if( !CG_ParseTrailBeamColor( btb->frontColor, text_p ) ) + break; + + token = COM_Parse( text_p ); + if( Q_stricmp( token, "}" ) ) + { + CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); + break; + } + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "-" ) ) + { + btb->backColor[ 0 ] = btb->frontColor[ 0 ]; + btb->backColor[ 1 ] = btb->frontColor[ 1 ]; + btb->backColor[ 2 ] = btb->frontColor[ 2 ]; + } + else if( !Q_stricmp( token, "{" ) ) + { + if( !CG_ParseTrailBeamColor( btb->backColor, text_p ) ) + break; + + token = COM_Parse( text_p ); + if( Q_stricmp( token, "}" ) ) + { + CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" ); + break; + } + } else - VectorMA( up, -0.3, v, up ); - - VectorNormalize( up ); + { + CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); + break; + } } + else + { + CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" ); + break; + } + + continue; } - // do fading when moving towards the projection point onto the trail segment vector - else if( !( j->flags & TJFL_NOCULL ) && ( j->widthEnd > 4 || jNext->widthEnd > 4 ) ) + else if( !Q_stricmp( token, "segmentTime" ) ) { - ProjectPointOntoVector( cg.refdef.vieworg, j->pos, jNext->pos, viewProj ); - viewDist = Distance( viewProj, cg.refdef.vieworg ); + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->segmentTime = atoi_neg( token, qfalse ); + continue; + } + else if( !Q_stricmp( token, "fadeOutTime" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->fadeOutTime = atoi_neg( token, qfalse ); + continue; + } + else if( !Q_stricmp( token, "shader" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + Q_strncpyz( btb->shaderName, token, MAX_QPATH ); + + continue; + } + else if( !Q_stricmp( token, "textureType" ) ) + { + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "stretch" ) ) + { + btb->textureType = TBTT_STRETCH; + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->frontTextureCoord = atof_neg( token, qfalse ); + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; - if( viewDist < ( TRAIL_FADE_CLOSE_DIST * TRAIL_FADE_FAR_SCALE ) ) + btb->backTextureCoord = atof_neg( token, qfalse ); + } + else if( !Q_stricmp( token, "repeat" ) ) { - if( viewDist < TRAIL_FADE_CLOSE_DIST ) - fadeAlpha = 0.0; + btb->textureType = TBTT_REPEAT; + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + if( !Q_stricmp( token, "front" ) ) + btb->clampToBack = qfalse; + else if( !Q_stricmp( token, "back" ) ) + btb->clampToBack = qtrue; else - fadeAlpha = ( viewDist - TRAIL_FADE_CLOSE_DIST ) / ( TRAIL_FADE_CLOSE_DIST * TRAIL_FADE_FAR_SCALE ); + { + CG_Printf( S_COLOR_RED "ERROR: unknown textureType clamp \"%s\"\n", token ); + break; + } - if( fadeAlpha < j->alpha ) - j->alpha = fadeAlpha; + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; - if( fadeAlpha < jNext->alpha ) - jNext->alpha = fadeAlpha; + btb->repeatLength = atof_neg( token, qfalse ); } + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown textureType \"%s\"\n", token ); + break; + } + + continue; } + else if( !Q_stricmp( token, "realLight" ) ) + { + btb->realLight = qtrue; - // now output the QUAD for this segment + continue; + } + else if( !Q_stricmp( token, "jitter" ) ) + { + if( btb->numJitters == MAX_TRAIL_BEAM_JITTERS ) + { + CG_Printf( S_COLOR_RED "ERROR: too many jitters\n", token ); + break; + } + + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; + + btb->jitters[ btb->numJitters ].magnitude = atof_neg( token, qfalse ); - // 1 ---- - VectorMA( j->pos, 0.5 * j->width, up, p ); - VectorCopy( p, verts[ i ].xyz ); - verts[ i ].st[ 0 ] = s; - verts[ i ].st[ 1 ] = 1.0; + token = COM_Parse( text_p ); + if( !Q_stricmp( token, "" ) ) + break; - for( k = 0; k < 3; k++ ) - verts[ i ].modulate[ k ] = (unsigned char)( j->color[ k ] * 255.0 ); + btb->jitters[ btb->numJitters ].period = atoi_neg( token, qfalse ); - verts[ i ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 ); + btb->numJitters++; + + continue; + } + else if( !Q_stricmp( token, "jitterAttachments" ) ) + { + btb->jitterAttachments = qtrue; - // blend this with the previous junc - if( j != trail ) + continue; + } + else if( !Q_stricmp( token, "}" ) ) + return qtrue; //reached the end of this trail beam + else { - VectorAdd( verts[ i ].xyz, verts[ i - 1 ].xyz, verts[ i ].xyz ); - VectorScale( verts[ i ].xyz, 0.5, verts[ i ].xyz ); - VectorCopy( verts[ i ].xyz, verts[ i - 1 ].xyz ); + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in trail beam\n", token ); + return qfalse; } - else if( j->flags & TJFL_FADEIN ) - verts[ i ].modulate[ 3 ] = 0; // fade in + } - i++; + return qfalse; +} - // 2 ---- - VectorMA( p, -1 * j->width, up, p ); - VectorCopy( p, verts[ i ].xyz ); - verts[ i ].st[ 0 ] = s; - verts[ i ].st[ 1 ] = 0.0; +/* +=============== +CG_InitialiseTrailBeam +=============== +*/ +static void CG_InitialiseTrailBeam( baseTrailBeam_t *btb ) +{ + memset( btb, 0, sizeof( baseTrailBeam_t ) ); - for( k = 0; k < 3; k++ ) - verts[ i ].modulate[ k ] = (unsigned char)( j->color[ k ] * 255.0 ); + btb->numSegments = 1; + btb->frontWidth = btb->backWidth = 8.0f; + btb->frontAlpha = btb->backAlpha = 1.0f; + memset( btb->frontColor, 0xFF, sizeof( btb->frontColor ) ); + memset( btb->backColor, 0xFF, sizeof( btb->backColor ) ); - verts[ i ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 ); + btb->segmentTime = 250; + + btb->textureType = TBTT_STRETCH; + btb->frontTextureCoord = 0.0f; + btb->backTextureCoord = 1.0f; +} + +/* +=============== +CG_ParseTrailSystem + +Parse a trail system section +=============== +*/ +static qboolean CG_ParseTrailSystem( baseTrailSystem_t *bts, char **text_p, const char *name ) +{ + char *token; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); - // blend this with the previous junc - if( j != trail ) + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "{" ) ) { - VectorAdd( verts[ i ].xyz, verts[ i - 3 ].xyz, verts[ i ].xyz ); - VectorScale( verts[ i ].xyz, 0.5, verts[ i ].xyz ); - VectorCopy( verts[ i ].xyz, verts[ i - 3 ].xyz ); - } - else if( j->flags & TJFL_FADEIN ) - verts[ i ].modulate[ 3 ] = 0; // fade in + CG_InitialiseTrailBeam( &baseTrailBeams[ numBaseTrailBeams ] ); - i++; + if( !CG_ParseTrailBeam( &baseTrailBeams[ numBaseTrailBeams ], text_p ) ) + { + CG_Printf( S_COLOR_RED "ERROR: failed to parse trail beam\n" ); + return qfalse; + } + + if( bts->numBeams == MAX_BEAMS_PER_SYSTEM ) + { + CG_Printf( S_COLOR_RED "ERROR: trail system has > %d beams\n", MAX_BEAMS_PER_SYSTEM ); + return qfalse; + } + else if( numBaseTrailBeams == MAX_BASETRAIL_BEAMS ) + { + CG_Printf( S_COLOR_RED "ERROR: maximum number of trail beams (%d) reached\n", + MAX_BASETRAIL_BEAMS ); + return qfalse; + } + else + { + //start parsing beams again + bts->beams[ bts->numBeams ] = &baseTrailBeams[ numBaseTrailBeams ]; + bts->numBeams++; + numBaseTrailBeams++; + } + continue; + } + else if( !Q_stricmp( token, "beam" ) ) //acceptable text + continue; + else if( !Q_stricmp( token, "}" ) ) + { + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "Parsed trail system %s\n", name ); - if( trail->sType == STYPE_REPEAT ) - s = jNext->sTex; + return qtrue; //reached the end of this trail system + } else { - //s += sInc; - s += VectorDistance( j->pos, jNext->pos ) / sInc; - if( s > 1.0 ) - s = 1.0; + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in trail system %s\n", token, bts->name ); + return qfalse; } + } - // 3 ---- - VectorMA( jNext->pos, -0.5 * jNext->width, up, p ); - VectorCopy( p, verts[ i ].xyz ); - verts[ i ].st[ 0 ] = s; - verts[ i ].st[ 1 ] = 0.0; + return qfalse; +} - for( k = 0; k < 3; k++ ) - verts[ i ].modulate[ k ] = (unsigned char)( jNext->color[ k ] * 255.0 ); +/* +=============== +CG_ParseTrailFile - verts[ i ].modulate[ 3 ] = (unsigned char)( jNext->alpha * 255.0 ); - i++; +Load the trail systems from a trail file +=============== +*/ +static qboolean CG_ParseTrailFile( const char *fileName ) +{ + char *text_p; + int i; + int len; + char *token; + char text[ 32000 ]; + char tsName[ MAX_QPATH ]; + qboolean tsNameSet = qfalse; + fileHandle_t f; + + // load the file + len = trap_FS_FOpenFile( fileName, &f, FS_READ ); + if( len <= 0 ) + return qfalse; + + if( len >= sizeof( text ) - 1 ) + { + CG_Printf( S_COLOR_RED "ERROR: trail file %s too long\n", fileName ); + return qfalse; + } - // 4 ---- - VectorMA( p, jNext->width, up, p ); - VectorCopy( p, verts[ i ].xyz ); - verts[ i ].st[ 0 ] = s; - verts[ i ].st[ 1 ] = 1.0; + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); - for( k = 0; k < 3; k++ ) - verts[ i ].modulate[ k ] = (unsigned char)( jNext->color[ k ] * 255.0 ); + // parse the text + text_p = text; - verts[ i ].modulate[ 3 ] = (unsigned char)( jNext->alpha * 255.0 ); - i++; + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); - if( i + 4 > MAX_TRAIL_VERTS ) + if( !Q_stricmp( token, "" ) ) break; - j = jNext; - jNext = j->nextJunc; + if( !Q_stricmp( token, "{" ) ) + { + if( tsNameSet ) + { + //check for name space clashes + for( i = 0; i < numBaseTrailSystems; i++ ) + { + if( !Q_stricmp( baseTrailSystems[ i ].name, tsName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: a trail system is already named %s\n", tsName ); + return qfalse; + } + } + + Q_strncpyz( baseTrailSystems[ numBaseTrailSystems ].name, tsName, MAX_QPATH ); + + if( !CG_ParseTrailSystem( &baseTrailSystems[ numBaseTrailSystems ], &text_p, tsName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: %s: failed to parse trail system %s\n", fileName, tsName ); + return qfalse; + } + + //start parsing trail systems again + tsNameSet = qfalse; + + if( numBaseTrailSystems == MAX_BASETRAIL_SYSTEMS ) + { + CG_Printf( S_COLOR_RED "ERROR: maximum number of trail systems (%d) reached\n", + MAX_BASETRAIL_SYSTEMS ); + return qfalse; + } + else + numBaseTrailSystems++; + + continue; + } + else + { + CG_Printf( S_COLOR_RED "ERROR: unamed trail system\n" ); + return qfalse; + } + } + + if( !tsNameSet ) + { + Q_strncpyz( tsName, token, sizeof( tsName ) ); + tsNameSet = qtrue; + } + else + { + CG_Printf( S_COLOR_RED "ERROR: trail system already named\n" ); + return qfalse; + } } - if( trail->flags & TJFL_FIXDISTORT ) + return qtrue; +} + +/* +=============== +CG_LoadTrailSystems + +Load trail system templates +=============== +*/ +void CG_LoadTrailSystems( void ) +{ + int i; + /*const char *s[ MAX_TRAIL_FILES ];*/ + + //clear out the old + numBaseTrailSystems = 0; + numBaseTrailBeams = 0; + + for( i = 0; i < MAX_BASETRAIL_SYSTEMS; i++ ) { - // build the list of outVerts, by dividing up the QUAD's into 4 Tri's each, so as to allow - // any shaped (convex) Quad without bilinear distortion - for( k = 0, numOutVerts = 0; k < i; k += 4 ) - { - VectorCopy( verts[ k ].xyz, mid.xyz ); - mid.st[ 0 ] = verts[ k ].st[ 0 ]; - mid.st[ 1 ] = verts[ k ].st[ 1 ]; + baseTrailSystem_t *bts = &baseTrailSystems[ i ]; + memset( bts, 0, sizeof( baseTrailSystem_t ) ); + } - for( l = 0; l < 4; l++ ) - mod[ l ] = (float)verts[ k ].modulate[ l ]; + for( i = 0; i < MAX_BASETRAIL_BEAMS; i++ ) + { + baseTrailBeam_t *btb = &baseTrailBeams[ i ]; + memset( btb, 0, sizeof( baseTrailBeam_t ) ); + } - for( n = 1; n < 4; n++ ) - { - VectorAdd( verts[ k + n ].xyz, mid.xyz, mid.xyz ); - mid.st[ 0 ] += verts[ k + n ].st[ 0 ]; - mid.st[ 1 ] += verts[ k + n ].st[ 1 ]; + //and bring in the new +/* for( i = 0; i < MAX_TRAIL_FILES; i++ ) + { + s[ i ] = CG_ConfigString( CS_TRAIL_FILES + i ); - for( l = 0; l < 4; l++ ) - mod[ l ] += (float)verts[ k + n ].modulate[ l ]; - } + if( strlen( s[ i ] ) > 0 ) + { + CG_Printf( "...loading '%s'\n", s[ i ] ); + CG_ParseTrailFile( s[ i ] ); + } + else + break; + }*/ + CG_Printf( "trail.trail: %d\n", CG_ParseTrailFile( "scripts/trail.trail" ) ); +} + +/* +=============== +CG_RegisterTrailSystem + +Load the media that a trail system needs +=============== +*/ +qhandle_t CG_RegisterTrailSystem( char *name ) +{ + int i, j; + baseTrailSystem_t *bts; + baseTrailBeam_t *btb; - VectorScale( mid.xyz, 0.25, mid.xyz ); - mid.st[ 0 ] *= 0.25; - mid.st[ 1 ] *= 0.25; + for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ ) + { + bts = &baseTrailSystems[ i ]; - for( l = 0; l < 4; l++ ) - mid.modulate[ l ] = (unsigned char)( mod[ l ] / 4.0 ); + if( !Q_stricmp( bts->name, name ) ) + { + //already registered + if( bts->registered ) + return i + 1; - // now output the tri's - for( n = 0; n < 4; n++ ) + for( j = 0; j < bts->numBeams; j++ ) { - outVerts[ numOutVerts++ ] = verts[ k + n ]; - outVerts[ numOutVerts++ ] = mid; + btb = bts->beams[ j ]; - if( n < 3 ) - outVerts[ numOutVerts++ ] = verts[ k + n + 1 ]; - else - outVerts[ numOutVerts++ ] = verts[ k ]; + btb->shader = trap_R_RegisterShader( btb->shaderName ); } + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "Registered trail system %s\n", name ); + + bts->registered = qtrue; + + //avoid returning 0 + return i + 1; } + } - if( !( trail->flags & TJFL_NOPOLYMERGE ) ) - trap_R_AddPolysToScene( trail->shader, 3, &outVerts[ 0 ], numOutVerts / 3 ); - else + CG_Printf( S_COLOR_RED "ERROR: failed to register trail system %s\n", name ); + return 0; +} + + +/* +=============== +CG_SpawnNewTrailBeam + +Allocate a new trail beam +=============== +*/ +static trailBeam_t *CG_SpawnNewTrailBeam( baseTrailBeam_t *btb, + trailSystem_t *parent ) +{ + int i; + trailBeam_t *tb = NULL; + trailSystem_t *ts = parent; + + for( i = 0; i < MAX_TRAIL_BEAMS; i++ ) + { + tb = &trailBeams[ i ]; + + if( !tb->valid ) { - int k; + memset( tb, 0, sizeof( trailBeam_t ) ); - for( k = 0; k < numOutVerts / 3; k++ ) - trap_R_AddPolyToScene( trail->shader, 3, &outVerts[ k * 3 ] ); + //found a free slot + tb->class = btb; + tb->parent = ts; + + tb->valid = qtrue; + + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "TB %s created\n", ts->class->name ); + + break; } } - else + + return tb; +} + + +/* +=============== +CG_SpawnNewTrailSystem + +Spawns a new trail system +=============== +*/ +trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle ) +{ + int i, j; + trailSystem_t *ts = NULL; + baseTrailSystem_t *bts = &baseTrailSystems[ psHandle - 1 ]; + + if( !bts->registered ) { - // send the polygons - // FIXME: is it possible to send a GL_STRIP here? We are actually sending 2x the verts we really need to - if( !( trail->flags & TJFL_NOPOLYMERGE ) ) - trap_R_AddPolysToScene( trail->shader, 4, &verts[ 0 ], i / 4 ); - else + CG_Printf( S_COLOR_RED "ERROR: a trail system has not been registered yet\n" ); + return NULL; + } + + for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ ) + { + ts = &trailSystems[ i ]; + + if( !ts->valid ) { - int k; + memset( ts, 0, sizeof( trailSystem_t ) ); + + //found a free slot + ts->class = bts; + + ts->valid = qtrue; + ts->destroyTime = -1; + + for( j = 0; j < bts->numBeams; j++ ) + CG_SpawnNewTrailBeam( bts->beams[ j ], ts ); - for( k = 0; k < i / 4; k++ ) - trap_R_AddPolyToScene( trail->shader, 4, &verts[ k * 4 ] ); + if( cg_debugTrails.integer >= 1 ) + CG_Printf( "TS %s created\n", bts->name ); + + break; } } - // do we need to make another pass? - if( trail->flags & TJFL_CROSSOVER ) + return ts; +} + +/* +=============== +CG_DestroyTrailSystem + +Destroy a trail system +=============== +*/ +void CG_DestroyTrailSystem( trailSystem_t **ts ) +{ + (*ts)->destroyTime = cg.time; + + if( CG_Attached( &(*ts)->frontAttachment ) && + !CG_Attached( &(*ts)->backAttachment ) ) { - if( iteration < 2 ) - CG_AddTrailToScene( trail, iteration + 1, numJuncs ); + vec3_t v; + + // attach the trail head to a static point + CG_AttachmentPoint( &(*ts)->frontAttachment, v ); + CG_SetAttachmentPoint( &(*ts)->frontAttachment, v ); + CG_AttachToPoint( &(*ts)->frontAttachment ); + + (*ts)->frontAttachment.centValid = qfalse; // a bit naughty } + ts = NULL; } /* =============== -CG_AddTrails +CG_IsTrailSystemValid + +Test a trail system for validity =============== */ -void CG_AddTrails( void ) +qboolean CG_IsTrailSystemValid( trailSystem_t **ts ) { - float lifeFrac; - trailJunc_t *j, *jNext; + if( *ts == NULL || ( *ts && !(*ts)->valid ) ) + { + if( *ts && !(*ts)->valid ) + *ts = NULL; + + return qfalse; + } - if( !initTrails ) - CG_ClearTrails( ); + return qtrue; +} - //AngleVectors( cg.snap->ps.viewangles, vforward, vright, vup ); - VectorCopy( cg.refdef.viewaxis[ 0 ], vforward ); - VectorCopy( cg.refdef.viewaxis[ 1 ], vright ); - VectorCopy( cg.refdef.viewaxis[ 2 ], vup ); +/* +=============== +CG_GarbageCollectTrailSystems - // update the settings for each junc - j = activeTrails; +Destroy inactive trail systems +=============== +*/ +static void CG_GarbageCollectTrailSystems( void ) +{ + int i, j, count; + trailSystem_t *ts; + trailBeam_t *tb; + int centNum; - while( j ) + for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ ) { - lifeFrac = (float)( cg.time - j->spawnTime ) / (float)( j->endTime - j->spawnTime ); + ts = &trailSystems[ i ]; + count = 0; + + //don't bother checking already invalid systems + if( !ts->valid ) + continue; - if( lifeFrac >= 1.0 ) + for( j = 0; j < MAX_TRAIL_BEAMS; j++ ) { - j->inuse = qfalse; // flag it as dead - j->width = j->widthEnd; - j->alpha = j->alphaEnd; + tb = &trailBeams[ j ]; + + if( tb->valid && tb->parent == ts ) + count++; + } + + if( !count ) + ts->valid = qfalse; - if( j->alpha > 1.0 ) - j->alpha = 1.0; - else if( j->alpha < 0.0 ) - j->alpha = 0.0; + //check systems where the parent cent has left the PVS + //( local player entity is always valid ) + if( ( centNum = CG_AttachmentCentNum( &ts->frontAttachment ) ) >= 0 && + centNum != cg.snap->ps.clientNum ) + { + trailSystem_t *tempTS = ts; - VectorCopy( j->colorEnd, j->color ); + if( !cg_entities[ centNum ].valid ) + CG_DestroyTrailSystem( &tempTS ); } - else + + if( ( centNum = CG_AttachmentCentNum( &ts->backAttachment ) ) >= 0 && + centNum != cg.snap->ps.clientNum ) { - j->width = j->widthStart + ( j->widthEnd - j->widthStart ) * lifeFrac; - j->alpha = j->alphaStart + ( j->alphaEnd - j->alphaStart ) * lifeFrac; + trailSystem_t *tempTS = ts; - if( j->alpha > 1.0 ) - j->alpha = 1.0; - else if( j->alpha < 0.0 ) - j->alpha = 0.0; + if( !cg_entities[ centNum ].valid ) + CG_DestroyTrailSystem( &tempTS ); + } + + if( cg_debugTrails.integer >= 1 && !ts->valid ) + CG_Printf( "TS %s garbage collected\n", ts->class->name ); + } +} - VectorSubtract( j->colorEnd, j->colorStart, j->color ); - VectorMA( j->colorStart, lifeFrac, j->color, j->color ); +/* +=============== +CG_AddTrails + +Add trails to the scene +=============== +*/ +void CG_AddTrails( void ) +{ + int i; + trailBeam_t *tb; + int numTS = 0, numTB = 0; + + //remove expired trail systems + CG_GarbageCollectTrailSystems( ); + + for( i = 0; i < MAX_TRAIL_BEAMS; i++ ) + { + tb = &trailBeams[ i ]; + + if( tb->valid ) + { + CG_UpdateBeam( tb ); + CG_RenderBeam( tb ); } + } + + if( cg_debugTrails.integer >= 2 ) + { + for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ ) + if( trailSystems[ i ].valid ) + numTS++; + + for( i = 0; i < MAX_TRAIL_BEAMS; i++ ) + if( trailBeams[ i ].valid ) + numTB++; - j = j->nextGlobal; + CG_Printf( "TS: %d TB: %d\n", numTS, numTB ); } +} + +static trailSystem_t *testTS; +static qhandle_t testTSHandle; + +/* +=============== +CG_DestroyTestTS_f + +Destroy the test a trail system +=============== +*/ +void CG_DestroyTestTS_f( void ) +{ + if( CG_IsTrailSystemValid( &testTS ) ) + CG_DestroyTrailSystem( &testTS ); +} + +/* +=============== +CG_TestTS_f - // draw the trailHeads - j = headTrails; +Test a trail system +=============== +*/ +void CG_TestTS_f( void ) +{ + char tsName[ MAX_QPATH ]; - while( j ) + if( trap_Argc( ) < 2 ) + return; + + Q_strncpyz( tsName, CG_Argv( 1 ), MAX_QPATH ); + testTSHandle = CG_RegisterTrailSystem( tsName ); + + if( testTSHandle ) { - jNext = j->nextHead; // in case it gets removed + CG_DestroyTestTS_f( ); - if( !j->inuse ) - CG_FreeTrailJunc( j ); - else - CG_AddTrailToScene( j, 0, 0 ); + testTS = CG_SpawnNewTrailSystem( testTSHandle ); - j = jNext; + if( CG_IsTrailSystemValid( &testTS ) ) + { + CG_SetAttachmentCent( &testTS->frontAttachment, &cg_entities[ 0 ] ); + CG_AttachToCent( &testTS->frontAttachment ); + } } } diff --git a/src/cgame/cg_view.c b/src/cgame/cg_view.c index 0e98036d..59016dc4 100644 --- a/src/cgame/cg_view.c +++ b/src/cgame/cg_view.c @@ -1311,7 +1311,7 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo // build the render lists if( !cg.hyperspace ) { - CG_AddPacketEntities( ); // adter calcViewValues, so predicted player state is correct + CG_AddPacketEntities( ); // after calcViewValues, so predicted player state is correct CG_AddMarks( ); CG_AddLocalEntities( ); } @@ -1322,9 +1322,7 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo if( !cg.hyperspace ) { CG_AddParticles( ); - - //TA: wolf trails stuff - CG_AddTrails( ); // this must come last, so the trails dropped this frame get drawn + CG_AddTrails( ); } // add buffered sounds diff --git a/src/cgame/cg_weapons.c b/src/cgame/cg_weapons.c index dcef9f82..935342ac 100644 --- a/src/cgame/cg_weapons.c +++ b/src/cgame/cg_weapons.c @@ -157,35 +157,6 @@ static void CG_ShotgunEjectBrass( centity_t *cent ) le->leMarkType = LEMT_NONE; } -/* -========================== -CG_TeslaTrail -========================== -*/ -void CG_TeslaTrail( vec3_t start, vec3_t end, int srcENum, int destENum ) -{ - localEntity_t *le; - refEntity_t *re; - - //add a bunch of bolt segments - le = CG_AllocLocalEntity( ); - re = &le->refEntity; - - le->leType = LE_LIGHTNING_BOLT; - le->startTime = cg.time; - le->endTime = cg.time + cg_teslaTrailTime.value; - le->lifeRate = 1.0 / ( le->endTime - le->startTime ); - re->customShader = cgs.media.lightningShader; - - le->srcENum = srcENum; - le->destENum = destENum; - le->vOffset = 28; - le->maxRange = BG_FindRangeForBuildable( BA_H_TESLAGEN ) * M_ROOT3; - - VectorCopy( start, re->origin ); - VectorCopy( end, re->oldorigin ); -} - /* ================= @@ -351,6 +322,19 @@ static qboolean CG_ParseWeaponModeSection( weaponInfoMode_t *wim, char **text_p continue; } + else if( !Q_stricmp( token, "missileTrailSystem" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + wim->missileTrailSystem = CG_RegisterTrailSystem( token ); + + if( !wim->missileTrailSystem ) + CG_Printf( S_COLOR_RED "ERROR: missile trail system not found %s\n", token ); + + continue; + } else if( !Q_stricmp( token, "muzzleParticleSystem" ) ) { token = COM_Parse( text_p ); @@ -856,8 +840,7 @@ void CG_InitWeapons( void ) for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) CG_RegisterWeapon( i ); - cgs.media.lightningShader = trap_R_RegisterShader( "models/ammo/tesla/tesla_bolt"); - cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/crackle.md3" ); + cgs.media.level2ZapTS = CG_RegisterTrailSystem( "models/weapons/lev2zap/lightning" ); } @@ -1102,9 +1085,9 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent cent->currentState.number != cg.predictedPlayerState.clientNum ) { if( noGunModel ) - CG_SetParticleSystemTag( cent->muzzlePS, *parent, parent->hModel, "tag_weapon" ); + CG_SetAttachmentTag( ¢->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" ); else - CG_SetParticleSystemTag( cent->muzzlePS, gun, weapon->weaponModel, "tag_flash" ); + CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" ); } //if the PS is infinite disable it when not firing @@ -1148,13 +1131,17 @@ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent { cent->muzzlePS = CG_SpawnNewParticleSystem( weapon->wim[ weaponMode ].muzzleParticleSystem ); - if( noGunModel ) - CG_SetParticleSystemTag( cent->muzzlePS, *parent, parent->hModel, "tag_weapon" ); - else - CG_SetParticleSystemTag( cent->muzzlePS, gun, weapon->weaponModel, "tag_flash" ); + if( CG_IsParticleSystemValid( ¢->muzzlePS ) ) + { + if( noGunModel ) + CG_SetAttachmentTag( ¢->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" ); + else + CG_SetAttachmentTag( ¢->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" ); + + CG_SetAttachmentCent( ¢->muzzlePS->attachment, cent ); + CG_AttachToTag( ¢->muzzlePS->attachment ); + } - CG_SetParticleSystemCent( cent->muzzlePS, cent ); - CG_AttachParticleSystemToTag( cent->muzzlePS ); cent->muzzlePsTrigger = qfalse; } @@ -1228,15 +1215,19 @@ void CG_AddViewWeapon( playerState_t *ps ) VectorMA( origin, -8, cg.refdef.viewaxis[ 2 ], origin ); if( cent->muzzlePS ) - CG_SetParticleSystemOrigin( cent->muzzlePS, origin ); + CG_SetAttachmentPoint( ¢->muzzlePS->attachment, origin ); //check for particle systems if( wi->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger ) { cent->muzzlePS = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].muzzleParticleSystem ); - CG_SetParticleSystemOrigin( cent->muzzlePS, origin ); - CG_SetParticleSystemCent( cent->muzzlePS, cent ); - CG_AttachParticleSystemToOrigin( cent->muzzlePS ); + + if( CG_IsParticleSystemValid( ¢->muzzlePS ) ) + { + CG_SetAttachmentPoint( ¢->muzzlePS->attachment, origin ); + CG_SetAttachmentCent( ¢->muzzlePS->attachment, cent ); + CG_AttachToPoint( ¢->muzzlePS->attachment ); + } cent->muzzlePsTrigger = qfalse; } @@ -1765,9 +1756,13 @@ void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientN if( ps ) { particleSystem_t *partSystem = CG_SpawnNewParticleSystem( ps ); - CG_SetParticleSystemOrigin( partSystem, origin ); - CG_SetParticleSystemNormal( partSystem, dir ); - CG_AttachParticleSystemToOrigin( partSystem ); + + if( CG_IsParticleSystemValid( &partSystem ) ) + { + CG_SetAttachmentPoint( &partSystem->attachment, origin ); + CG_SetParticleSystemNormal( partSystem, dir ); + CG_AttachToPoint( &partSystem->attachment ); + } } // diff --git a/src/game/bg_misc.c b/src/game/bg_misc.c index 062ffdaf..10bb419b 100644 --- a/src/game/bg_misc.c +++ b/src/game/bg_misc.c @@ -5037,3 +5037,41 @@ int BG_GetValueOfHuman( playerState_t *ps ) 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; +} diff --git a/src/game/bg_public.h b/src/game/bg_public.h index ea3b7eef..5cc157e4 100644 --- a/src/game/bg_public.h +++ b/src/game/bg_public.h @@ -1288,3 +1288,6 @@ float VectorDistance( vec3_t v1, vec3_t v2 ); 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 ); diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c index fddbaf13..d83e76b7 100644 --- a/src/game/g_buildable.c +++ b/src/game/g_buildable.c @@ -1258,7 +1258,6 @@ Called when an alien touches a booster */ void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) { - int maxAmmo, maxClips; gclient_t *client = other->client; if( !self->spawned ) @@ -1289,52 +1288,50 @@ void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) //================================================================================== +#define TRAPPER_ACCURACY 10 // lower is better /* ================ -ADef_fireonemeny +ATrapper_FireOnEnemy -Used by ADef2_Think to fire at enemy +Used by ATrapper_Think to fire at enemy ================ */ -void ADef_FireOnEnemy( gentity_t *self, int firespeed, float range ) +void ATrapper_FireOnEnemy( gentity_t *self, int firespeed, float range ) { - vec3_t dirToTarget; - vec3_t halfAcceleration, thirdJerk; - float distanceToTarget = BG_FindRangeForBuildable( self->s.modelindex ); - int i; - - VectorScale( self->enemy->acceleration, 1.0f / 2.0f, halfAcceleration ); - VectorScale( self->enemy->jerk, 1.0f / 3.0f, thirdJerk ); - - //O( time ) - worst case O( time ) = ( range * 1000 ) / speed - for( i = 0; (float)( i * LOCKBLOB_SPEED ) / 1000.0f < distanceToTarget; i++ ) + 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 ) { - float time = (float)i / 1000.0f; - - if( i > ( ( (float)range * 1000.0f ) / LOCKBLOB_SPEED ) ) - { - VectorSubtract( self->enemy->s.pos.trBase, self->s.pos.trBase, dirToTarget ); - distanceToTarget = VectorLength( dirToTarget ); - - G_LogPrintf( "ADef_FireOnEnemy failed.\n" - " %dth iteration\n enemy location: %v\n" - " enemy accleration: %v\n enemy jerk: %v\n" - " base location: %v\n distanceToTarget: %f\n", - i, self->enemy->s.pos.trBase, self->enemy->acceleration, - self->enemy->jerk, self->s.pos.trBase, distanceToTarget ); - - return; - } + int partitionMsec = ( highMsec + lowMsec ) / 2; + float time = (float)partitionMsec / 1000.0f; + float projectileDistance = LOCKBLOB_SPEED * time; - VectorMA( self->enemy->s.pos.trBase, time, self->enemy->s.pos.trDelta, - dirToTarget ); + 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 ); - distanceToTarget -= self->enemy->r.maxs[ 0 ]; + if( projectileDistance < distanceToTarget ) + lowMsec = partitionMsec; + else if( projectileDistance > distanceToTarget ) + highMsec = partitionMsec; + else if( projectileDistance == distanceToTarget ) + break; // unlikely to happen } VectorNormalize( dirToTarget ); @@ -1348,12 +1345,12 @@ void ADef_FireOnEnemy( gentity_t *self, int firespeed, float range ) /* ================ -ADef_checktarget +ATrapper_CheckTarget -Used by ADef2_Think to check enemies for validity +Used by ATrapper_Think to check enemies for validity ================ */ -qboolean ADef_CheckTarget( gentity_t *self, gentity_t *target, int range ) +qboolean ATrapper_CheckTarget( gentity_t *self, gentity_t *target, int range ) { vec3_t distance; trace_t trace; @@ -1393,12 +1390,12 @@ qboolean ADef_CheckTarget( gentity_t *self, gentity_t *target, int range ) /* ================ -adef_findenemy +ATrapper_FindEnemy -Used by DDef2_Think to locate enemy gentities +Used by ATrapper_Think to locate enemy gentities ================ */ -void ADef_FindEnemy( gentity_t *ent, int range ) +void ATrapper_FindEnemy( gentity_t *ent, int range ) { gentity_t *target; @@ -1406,7 +1403,7 @@ void ADef_FindEnemy( gentity_t *ent, int range ) for( target = g_entities; target < &g_entities[ level.num_entities ]; target++ ) { //if target is not valid keep searching - if( !ADef_CheckTarget( ent, target, range ) ) + if( !ATrapper_CheckTarget( ent, target, range ) ) continue; //we found a target @@ -1444,8 +1441,8 @@ void ATrapper_Think( gentity_t *self ) if( self->spawned && findOvermind( self ) ) { //if the current target is not valid find a new one - if( !ADef_CheckTarget( self, self->enemy, range ) ) - ADef_FindEnemy( self, range ); + 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 ) @@ -1453,7 +1450,7 @@ void ATrapper_Think( gentity_t *self ) //if we are pointing at our target and we can fire shoot it if( self->count < level.time ) - ADef_FireOnEnemy( self, firespeed, range ); + ATrapper_FireOnEnemy( self, firespeed, range ); } } diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index 606d1875..840fc3bb 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -460,6 +460,7 @@ void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam ) 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 ); } diff --git a/src/game/g_main.c b/src/game/g_main.c index 64715b20..8ae93622 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -59,8 +59,8 @@ vmCvar_t g_synchronousClients; vmCvar_t g_warmup; vmCvar_t g_doWarmup; vmCvar_t g_restarted; -vmCvar_t g_log; -vmCvar_t g_logSync; +vmCvar_t g_logFile; +vmCvar_t g_logFileSync; vmCvar_t g_blood; vmCvar_t g_podiumDist; vmCvar_t g_podiumDrop; @@ -124,8 +124,8 @@ static cvarTable_t gameCvarTable[ ] = { &g_warmup, "g_warmup", "20", CVAR_ARCHIVE, 0, qtrue }, { &g_doWarmup, "g_doWarmup", "0", 0, 0, qtrue }, - { &g_log, "g_log", "games.log", CVAR_ARCHIVE, 0, qfalse }, - { &g_logSync, "g_logSync", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse }, + { &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse }, @@ -477,15 +477,15 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) level.snd_fry = G_SoundIndex( "sound/player/fry.wav" ); // FIXME standing in lava / slime - if( g_log.string[ 0 ] ) + if( g_logFile.string[ 0 ] ) { - if( g_logSync.integer ) - trap_FS_FOpenFile( g_log.string, &level.logFile, FS_APPEND_SYNC ); + if( g_logFileSync.integer ) + trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND_SYNC ); else - trap_FS_FOpenFile( g_log.string, &level.logFile, FS_APPEND ); + trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND ); if( !level.logFile ) - G_Printf( "WARNING: Couldn't open logfile: %s\n", g_log.string ); + G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logFile.string ); else { char serverinfo[ MAX_INFO_STRING ]; diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c index 94a1aabe..a28da78b 100644 --- a/src/game/g_weapon.c +++ b/src/game/g_weapon.c @@ -631,12 +631,6 @@ void teslaFire( gentity_t *ent ) // move origin a bit to come closer to the drawn gun muzzle VectorMA( tent->s.origin2, 28, up, tent->s.origin2 ); - - // no explosion at end if SURF_NOIMPACT, but still make the trail - if( tr.surfaceFlags & SURF_NOIMPACT ) - tent->s.eventParm = 255; // don't make the explosion at the end - else - tent->s.eventParm = DirToByte( tr.plane.normal ); } diff --git a/src/game/q_math.c b/src/game/q_math.c index 62c5a5a8..78209fd1 100644 --- a/src/game/q_math.c +++ b/src/game/q_math.c @@ -1378,8 +1378,6 @@ float pointToLineDistance( const vec3_t p0, const vec3_t p1, const vec3_t p2 ) return c1 / c2; } -//TA: wolf trail stuff -// Ridah /* ================= GetPerpendicularViewVector @@ -1417,15 +1415,6 @@ void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vP VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); } -float VectorDistance(vec3_t v1, vec3_t v2) -{ - vec3_t dir; - - VectorSubtract(v2, v1, dir); - return VectorLength(dir); -} -// done. - /* ================ VectorMaxComponent diff --git a/src/game/tremulous.h b/src/game/tremulous.h index 085fabbf..ab9db16c 100644 --- a/src/game/tremulous.h +++ b/src/game/tremulous.h @@ -261,7 +261,7 @@ #define TRAPPER_CREEPSIZE 30 #define TRAPPER_RANGE 400 #define TRAPPER_REPEAT 1000 -#define LOCKBLOB_SPEED 500.0f +#define LOCKBLOB_SPEED 650.0f #define LOCKBLOB_DOT 0.85f // max angle = acos( LOCKBLOB_DOT ) #define OVERMIND_BP 0 diff --git a/src/ui/ui_main.c b/src/ui/ui_main.c index 60e801cf..85b3fdb9 100644 --- a/src/ui/ui_main.c +++ b/src/ui/ui_main.c @@ -115,7 +115,6 @@ static char* netnames[] = { }; static int gamecodetoui[] = {4,2,3,0,5,1,6}; -static int uitogamecode[] = {4,6,2,3,1,5,7}; static void UI_StartServerRefresh(qboolean full); @@ -2663,27 +2662,6 @@ static qboolean UI_Handicap_HandleKey(int flags, float *special, int key) { return qfalse; } -static qboolean UI_Effects_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - - if (key == K_MOUSE2) { - uiInfo.effectsColor--; - } else { - uiInfo.effectsColor++; - } - - if( uiInfo.effectsColor > 6 ) { - uiInfo.effectsColor = 0; - } else if (uiInfo.effectsColor < 0) { - uiInfo.effectsColor = 6; - } - - trap_Cvar_SetValue( "color1", uitogamecode[uiInfo.effectsColor] ); - return qtrue; - } - return qfalse; -} - static qboolean UI_ClanName_HandleKey(int flags, float *special, int key) { if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { int i; @@ -2974,25 +2952,6 @@ static qboolean UI_RedBlue_HandleKey(int flags, float *special, int key) { return qfalse; } -static qboolean UI_Crosshair_HandleKey(int flags, float *special, int key) { - if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) { - if (key == K_MOUSE2) { - uiInfo.currentCrosshair--; - } else { - uiInfo.currentCrosshair++; - } - - if (uiInfo.currentCrosshair >= NUM_CROSSHAIRS) { - uiInfo.currentCrosshair = 0; - } else if (uiInfo.currentCrosshair < 0) { - uiInfo.currentCrosshair = NUM_CROSSHAIRS - 1; - } - trap_Cvar_Set("cg_drawCrosshair", va("%d", uiInfo.currentCrosshair)); - return qtrue; - } - return qfalse; -} - static qboolean UI_SelectedPlayer_HandleKey(int flags, float *special, int key) { |