diff options
Diffstat (limited to 'src/cgame/cg_buildable.c')
-rw-r--r-- | src/cgame/cg_buildable.c | 2290 |
1 files changed, 2290 insertions, 0 deletions
diff --git a/src/cgame/cg_buildable.c b/src/cgame/cg_buildable.c new file mode 100644 index 0000000..7b5b85d --- /dev/null +++ b/src/cgame/cg_buildable.c @@ -0,0 +1,2290 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2009 Darklegion Development + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#include "cg_local.h" + +char *cg_buildableSoundNames[ MAX_BUILDABLE_ANIMATIONS ] = +{ + "construct1.wav", + "construct2.wav", + "idle1.wav", + "idle2.wav", + "idle3.wav", + "attack1.wav", + "attack2.wav", + "spawn1.wav", + "spawn2.wav", + "pain1.wav", + "pain2.wav", + "destroy1.wav", + "destroy2.wav", + "destroyed.wav" +}; + +static sfxHandle_t defaultAlienSounds[ MAX_BUILDABLE_ANIMATIONS ]; +static sfxHandle_t defaultHumanSounds[ MAX_BUILDABLE_ANIMATIONS ]; + + +/* +====================== +CG_DrawCuboid + +cg_drawbbox for cuboids +====================== +*/ +static void CG_DrawCuboidFace( vec3_t a, vec3_t b, vec3_t c, vec3_t d, qhandle_t shader ) +{ + polyVert_t verts[ 4 ]; + vec4_t color = { 255.0f, 255.0f, 255.0f, 255.0f }; + VectorCopy( d, verts[ 0 ].xyz ); + verts[ 0 ].st[ 0 ] = 1; + verts[ 0 ].st[ 1 ] = 1; + Vector4Copy( color, verts[ 0 ].modulate ); + VectorCopy( c, verts[ 1 ].xyz ); + verts[ 1 ].st[ 0 ] = 1; + verts[ 1 ].st[ 1 ] = 0; + Vector4Copy( color, verts[ 1 ].modulate ); + VectorCopy( b, verts[ 2 ].xyz ); + verts[ 2 ].st[ 0 ] = 0; + verts[ 2 ].st[ 1 ] = 0; + Vector4Copy( color, verts[ 2 ].modulate ); + VectorCopy( a, verts[ 3 ].xyz ); + verts[ 3 ].st[ 0 ] = 0; + verts[ 3 ].st[ 1 ] = 1; + Vector4Copy( color, verts[ 3 ].modulate ); + trap_R_AddPolyToScene( (shader ? shader : cgs.media.outlineShader ), 4, verts ); +} + +void CG_DrawCuboid( vec3_t origin, vec3_t dims, qhandle_t shader, int margin ) +{ + vec3_t mins, maxs; + vec3_t ppp, mpp, mmp, pmp; + vec3_t mmm, pmm, ppm, mpm; + int i; + BG_CuboidBBox(dims,mins,maxs); + for(i=0;i<3&&margin;i++) + { + mins[i]-=margin; + maxs[i]+=margin; + } + ppp[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + ppp[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + ppp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + mpp[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mpp[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + mpp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + mmp[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mmp[ 1 ] = origin[ 1 ] + mins[ 1 ]; + mmp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + pmp[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + pmp[ 1 ] = origin[ 1 ] + mins[ 1 ]; + pmp[ 2 ] = origin[ 2 ] + maxs[ 2 ]; + ppm[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + ppm[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + ppm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + mpm[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mpm[ 1 ] = origin[ 1 ] + maxs[ 1 ]; + mpm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + mmm[ 0 ] = origin[ 0 ] + mins[ 0 ]; + mmm[ 1 ] = origin[ 1 ] + mins[ 1 ]; + mmm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + pmm[ 0 ] = origin[ 0 ] + maxs[ 0 ]; + pmm[ 1 ] = origin[ 1 ] + mins[ 1 ]; + pmm[ 2 ] = origin[ 2 ] + mins[ 2 ]; + CG_DrawCuboidFace( ppp, mpp, mmp, pmp, shader ); + CG_DrawCuboidFace( ppp, pmp, pmm, ppm, shader ); + CG_DrawCuboidFace( mpp, ppp, ppm, mpm, shader ); + CG_DrawCuboidFace( mmp, mpp, mpm, mmm, shader ); + CG_DrawCuboidFace( pmp, mmp, mmm, pmm, shader ); + CG_DrawCuboidFace( mmm, mpm, ppm, pmm, shader ); +} + +void CG_DrawCuboidAxis(vec3_t cuboidOrigin, vec3_t size, int axis, qhandle_t shader) +{ + vec3_t origin, localZ, localX, localY, start, end, quad[2][4]; + float len, width; + polyVert_t poly[4]; + + VectorCopy(cuboidOrigin,origin); + origin[2]+=size[2]/2.0f; + VectorClear(localZ); + VectorClear(localX); + switch(axis) + { + case 0: + localZ[0]=1.0f; + localX[1]=1.0f; + len=size[0]; + break; + case 1: + localZ[1]=1.0f; + localX[2]=1.0f; + len=size[1]; + break; + default: + localZ[2]=1.0f; + localX[0]=1.0f; + len=size[2]; + break; + } + CrossProduct(localZ,localX,localY); + VectorMA(origin,-MAX(len/2.0f,32),localZ,start); + VectorMA(origin,MAX(len/2.0f,32),localZ,end); + VectorMA(start,4.0f,localX,quad[0][0]); + VectorMA(start,-4.0f,localX,quad[0][1]); + VectorMA(end,-4.0f,localX,quad[0][2]); + VectorMA(end,4.0f,localX,quad[0][3]); + VectorMA(start,4.0f,localY,quad[1][0]); + VectorMA(start,-4.0f,localY,quad[1][1]); + VectorMA(end,-4.0f,localY,quad[1][2]); + VectorMA(end,4.0f,localY,quad[1][3]); + CG_DrawCuboidFace(quad[0][0],quad[0][1],quad[0][2],quad[0][3],shader); + CG_DrawCuboidFace(quad[1][0],quad[1][1],quad[1][2],quad[1][3],shader); +} + +/* +=================== +CG_InitCuboidExplosions +CG_CuboidExplosion +CG_DrawCuboidParticles + +Simple particle system based on cg_particles.c +=================== +*/ + +#define MAX_CPARTICLES 4096 +#define MAX_CPARTICLES_PER_EXPLOSION 128 + +typedef struct +{ + qboolean use; + vec3_t origin; + vec3_t velocity; + float rot,rotspeed; + float size; + qhandle_t shader; + int lastphysics; + int spawntime; + qboolean solid; +} cuboidParticle_t; + +cuboidParticle_t cuboidParticles[MAX_CPARTICLES]; +int cuboidParticlesCount=0; +qboolean cuboidParticles_init=qfalse; + +void CG_InitCuboidExplosions(void) +{ + memset(cuboidParticles,0,sizeof(cuboidParticles)); + cuboidParticles_init=qtrue; + return; +} + +void CG_CuboidExplosion(buildable_t buildable, vec3_t origin, vec3_t dims) +{ + int i,p,m,q; + float v,s; + cuboidParticle_t *pc; + vec3_t sdims; + const cuboidInfo_t *info; + + info=&cg_cuboids[buildable-CUBOID_FIRST]; + v=dims[0]*dims[1]*dims[2]; + for(i=0;i<info->destroySoundCount;i++) + if(v*5.5306341e-5>=info->destroySoundThresholds[i]) + { + trap_S_StartSound(origin,ENTITYNUM_WORLD,CHAN_AUTO,info->destroySounds[i]); + break; + } + + if(!info->fragmentCount) + return; + + BG_CuboidSortSize(dims,sdims); + + q=cg_cuboidPSQuality.integer; + + if(q<=0) + p=2; + else if(q==1) + p=ceil(sqrt(v/pow(sdims[2],3)))*2; + else if(q==2) + p=ceil(pow(v/pow(sdims[2],3),0.75f))*3; + else if(q>=3) + p=ceil(pow(v/pow(sdims[2],3),0.85f))*6; + p=MIN(p,MAX_CPARTICLES_PER_EXPLOSION); + + s=ceil(pow(v/p,1.0f/3.0f))*1.2f; + + for(i=0;i<MAX_CPARTICLES&&p;i++) + { + pc=&cuboidParticles[i]; + if(pc->use) + continue; + pc->solid=qfalse; + pc->origin[0]=origin[0]+crandom()*dims[0]*0.5f; + pc->origin[1]=origin[1]+crandom()*dims[1]*0.5f; + pc->origin[2]=origin[2]+dims[2]/2.0f+crandom()*dims[2]*0.5f; + VectorSubtract(pc->origin,origin,pc->velocity); + pc->size=s; + pc->rot=crandom()*360.0f; + pc->rotspeed=crandom()*450.0f; + pc->shader=info->fragments[(int)(rand()%info->fragmentCount)]; + pc->lastphysics=pc->spawntime=cg.time; + pc->use=qtrue; + cuboidParticlesCount++; + p--; + } +} + +void CG_CuboidParticlePhysics(cuboidParticle_t *pc) +{ + float t; + int c; + vec3_t newOrigin; + t=(cg.time-pc->lastphysics)/1e3; + pc->lastphysics=cg.time; + if(pc->spawntime+3000<cg.time) + { + pc->use=qfalse; + cuboidParticlesCount--; + return; + } + if(pc->solid) + return; + pc->rot+=pc->rotspeed*t; + pc->velocity[2]-=t*DEFAULT_GRAVITY; + VectorMA(pc->origin,t,pc->velocity,newOrigin); + c=trap_CM_PointContents(newOrigin,0); + if(c&CONTENTS_NODROP) + { + pc->use=qfalse; + cuboidParticlesCount--; + return; + } + if(c&(CONTENTS_SOLID|CONTENTS_BODY)) + { + pc->solid=qtrue; + return; + } + VectorCopy(newOrigin,pc->origin); +} + +void CG_DrawCuboidParticles(void) +{ + int i,j,drawn=0,c; + float t; + vec3_t alight, dlight, lightdir, newOrigin; + cuboidParticle_t *pc; + refEntity_t re; + + if(!cuboidParticles_init) + CG_InitCuboidExplosions(); + + for(i=0;i<MAX_CPARTICLES;i++) + { + pc=&cuboidParticles[i]; + + if(!pc->use) + continue; + CG_CuboidParticlePhysics(pc); + if(!pc->use) //particle physics can delete a particle + continue; + memset(&re,0,sizeof(refEntity_t)); + re.reType=RT_SPRITE; + VectorCopy(pc->origin,re.origin); + //trap_R_LightForPoint(pc->origin,alight,dlight,lightdir); + //for(j=0;j<3;j++) + // re.shaderRGBA[j]=(byte)alight[j]; + for(j=0;j<3;j++) + re.shaderRGBA[j]=255; + re.customShader=pc->shader; + re.radius=pc->size; + re.rotation=pc->rot; + trap_R_AddRefEntityToScene(&re); + drawn++; + } +} + + +/* +=================== +CG_AlienBuildableExplosion + +Generated a bunch of gibs launching out from a location +=================== +*/ +void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir ) +{ + particleSystem_t *ps; + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.alienBuildableExplosion ); + + //particle system + ps = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDestroyedPS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, origin ); + CG_SetParticleSystemNormal( ps, dir ); + CG_AttachToPoint( &ps->attachment ); + } +} + +/* +================= +CG_HumanBuildableExplosion + +Called for human buildables as they are destroyed +================= +*/ +void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir ) +{ + particleSystem_t *ps; + + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.humanBuildableExplosion ); + + //particle system + ps = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDestroyedPS ); + + if( CG_IsParticleSystemValid( &ps ) ) + { + CG_SetAttachmentPoint( &ps->attachment, origin ); + CG_SetParticleSystemNormal( ps, dir ); + CG_AttachToPoint( &ps->attachment ); + } +} + + +#define CREEP_SIZE 64.0f +#define CREEP_DISTANCE 64.0f + +/* +================== +CG_Creep +================== +*/ +static void CG_Creep( centity_t *cent ) +{ + int msec; + float size, frac; + trace_t tr; + vec3_t temp, origin; + int scaleUpTime = BG_Buildable( cent->currentState.modelindex, cent->currentState.angles )->buildTime; + int time; + + if(BG_Buildable(cent->currentState.modelindex,NULL)->cuboid) + return; + + time = cent->currentState.time; + + //should the creep be growing or receding? + if( time >= 0 ) + { + msec = cg.time - time; + if( msec >= 0 && msec < scaleUpTime ) + frac = (float)msec / scaleUpTime; + else + frac = 1.0f; + } + else if( time < 0 ) + { + msec = cg.time + time; + if( msec >= 0 && msec < CREEP_SCALEDOWN_TIME ) + frac = 1.0f - ( (float)msec / CREEP_SCALEDOWN_TIME ); + else + frac = 0.0f; + } + + VectorCopy( cent->currentState.origin2, temp ); + VectorScale( temp, -CREEP_DISTANCE, temp ); + VectorAdd( temp, cent->lerpOrigin, temp ); + + CG_Trace( &tr, cent->lerpOrigin, NULL, NULL, temp, cent->currentState.number, MASK_PLAYERSOLID ); + + if(tr.entityNum!=ENTITYNUM_WORLD) + return; + + VectorCopy( tr.endpos, origin ); + + size = CREEP_SIZE * frac; + + if( size > 0.0f && tr.fraction < 1.0f ) + CG_ImpactMark( cgs.media.creepShader, origin, cent->currentState.origin2, + 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue ); +} + +/* +====================== +CG_ParseBuildableAnimationFile + +Read a configuration file containing animation counts and rates +models/buildables/hivemind/animation.cfg, etc +====================== +*/ +static qboolean CG_ParseBuildableAnimationFile( const char *filename, buildable_t buildable ) +{ + char *text_p; + int len; + int i; + char *token; + float fps; + char text[ 20000 ]; + fileHandle_t f; + animation_t *animations; + + if(BG_Buildable(buildable,NULL)->cuboid) + return qtrue; + + animations = cg_buildables[ buildable ].animations; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if( len < 0 ) + return qfalse; + + if( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read information for each frame + for( i = BANIM_NONE + 1; i < MAX_BUILDABLE_ANIMATIONS; i++ ) + { + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + animations[ i ].firstFrame = atoi( token ); + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + animations[ i ].numFrames = atoi( token ); + animations[ i ].reversed = qfalse; + animations[ i ].flipflop = qfalse; + + // if numFrames is negative the animation is reversed + if( animations[ i ].numFrames < 0 ) + { + animations[ i ].numFrames = -animations[ i ].numFrames; + animations[ i ].reversed = qtrue; + } + + token = COM_Parse( &text_p ); + if ( !*token ) + break; + + animations[i].loopFrames = atoi( token ); + + token = COM_Parse( &text_p ); + if( !*token ) + break; + + fps = atof( token ); + if( fps == 0 ) + fps = 1; + + animations[ i ].frameLerp = 1000 / fps; + animations[ i ].initialLerp = 1000 / fps; + } + + if( i != MAX_BUILDABLE_ANIMATIONS ) + { + CG_Printf( "Error parsing animation file: %s\n", filename ); + return qfalse; + } + + return qtrue; +} + +/* +====================== +CG_ParseBuildableSoundFile + +Read a configuration file containing sound properties +sound/buildables/hivemind/sound.cfg, etc +====================== +*/ +static qboolean CG_ParseBuildableSoundFile( const char *filename, buildable_t buildable ) +{ + char *text_p; + int len; + int i; + char *token; + char text[ 20000 ]; + fileHandle_t f; + sound_t *sounds; + + if(BG_Buildable(buildable,NULL)->cuboid) + return qtrue; + + sounds = cg_buildables[ buildable ].sounds; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len < 0 ) + return qfalse; + + if ( len == 0 || len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile( f ); + CG_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read information for each frame + for( i = BANIM_NONE + 1; i < MAX_BUILDABLE_ANIMATIONS; i++ ) + { + + token = COM_Parse( &text_p ); + if ( !*token ) + break; + + sounds[ i ].enabled = atoi( token ); + + token = COM_Parse( &text_p ); + if ( !*token ) + break; + + sounds[ i ].looped = atoi( token ); + + } + + if( i != MAX_BUILDABLE_ANIMATIONS ) + { + CG_Printf( "Error parsing sound file: %s\n", filename ); + return qfalse; + } + + return qtrue; +} +/* +=============== +CG_InitBuildables + +Initialises the animation db +=============== +*/ +void CG_InitBuildables( void ) +{ + char filename[ MAX_QPATH ]; + char soundfile[ MAX_QPATH ]; + char *buildableName; + const char *cuboidName; + char *modelFile; + int i; + int j; + fileHandle_t f; + pc_token_t token; + cuboidInfo_t *cuboid; + const char *s; + float n; + + memset( cg_buildables, 0, sizeof( cg_buildables ) ); + + //default sounds + for( j = BANIM_NONE + 1; j < MAX_BUILDABLE_ANIMATIONS; j++ ) + { + strcpy( soundfile, cg_buildableSoundNames[ j - 1 ] ); + + Com_sprintf( filename, sizeof( filename ), "sound/buildables/alien/%s", soundfile ); + defaultAlienSounds[ j ] = trap_S_RegisterSound( filename, qfalse ); + + Com_sprintf( filename, sizeof( filename ), "sound/buildables/human/%s", soundfile ); + defaultHumanSounds[ j ] = trap_S_RegisterSound( filename, qfalse ); + } + + cg.buildablesFraction = 0.0f; + + for( i = BA_NONE + 1; i < CUBOID_FIRST; i++ ) + { + buildableName = BG_Buildable( i, NULL )->name; + + //animation.cfg + Com_sprintf( filename, sizeof( filename ), "models/buildables/%s/animation.cfg", buildableName ); + if ( !CG_ParseBuildableAnimationFile( filename, i ) ) + Com_Printf( S_COLOR_YELLOW "WARNING: failed to load animation file %s\n", filename ); + + //sound.cfg + Com_sprintf( filename, sizeof( filename ), "sound/buildables/%s/sound.cfg", buildableName ); + if ( !CG_ParseBuildableSoundFile( filename, i ) ) + Com_Printf( S_COLOR_YELLOW "WARNING: failed to load sound file %s\n", filename ); + + //models + for( j = 0; j <= 3; j++ ) + { + modelFile = BG_BuildableConfig( i )->models[ j ]; + if( strlen( modelFile ) > 0 ) + cg_buildables[ i ].models[ j ] = trap_R_RegisterModel( modelFile ); + } + + //sounds + for( j = BANIM_NONE + 1; j < MAX_BUILDABLE_ANIMATIONS; j++ ) + { + strcpy( soundfile, cg_buildableSoundNames[ j - 1 ] ); + Com_sprintf( filename, sizeof( filename ), "sound/buildables/%s/%s", buildableName, soundfile ); + + if( cg_buildables[ i ].sounds[ j ].enabled ) + { + if( trap_FS_FOpenFile( filename, &f, FS_READ ) > 0 ) + { + //file exists so close it + trap_FS_FCloseFile( f ); + + cg_buildables[ i ].sounds[ j ].sound = trap_S_RegisterSound( filename, qfalse ); + } + else + { + //file doesn't exist - use default + if( BG_Buildable( i, NULL )->team == TEAM_ALIENS ) + cg_buildables[ i ].sounds[ j ].sound = defaultAlienSounds[ j ]; + else + cg_buildables[ i ].sounds[ j ].sound = defaultHumanSounds[ j ]; + } + } + } + + cg.buildablesFraction = (float)i / (float)( BA_NUM_BUILDABLES - 1 ); + trap_UpdateScreen( ); + } + + cgs.media.teslaZapTS = CG_RegisterTrailSystem( "models/buildables/tesla/zap" ); + + memset( cg_cuboids, 0, sizeof( cg_cuboids ) ); + + for( i = CUBOID_FIRST; i <= CUBOID_LAST; i++ ) + { + cuboid = &cg_cuboids[ i - CUBOID_FIRST ]; + cuboidName = BG_CuboidAttributes( i )->file; + + Com_sprintf( filename, sizeof( filename ), "configs/cuboids/%s.cfg", cuboidName ); + if( !( f = trap_Parse_LoadSource( filename ) ) ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: failed to load cuboid config %s\n", filename ); + continue; + } + while( 1 ) + { + if( !trap_Parse_ReadToken( f, &token ) ) + break; + else if( !Q_stricmp( token.string, "texture" ) ) + { + if( cuboid->textureCount == MAX_CUBOID_TEXTURES ) + { + Com_Printf( S_COLOR_RED "ERROR: too many cuboid textures in %s\n", filename ); + break; + } + if( trap_Parse_ReadToken( f, &token ) ) + { + cuboid->textureThresholds[ cuboid->textureCount++ ] = atof( token.string ); + if( trap_Parse_ReadToken( f, &token ) ) + cuboid->textures[ cuboid->textureCount - 1 ] = trap_R_RegisterShader( token.string ); + else + { + Com_Printf( S_COLOR_YELLOW "WARNING: incomplete cuboid texture definition in %s\n", filename ); + cuboid->textureCount--; + } + } + } + else if( !Q_stricmp( token.string, "useCracks" ) ) + cuboid->useCracks=qtrue; + else if( !Q_stricmp( token.string, "fragment") ) + { + if( cuboid->fragmentCount == MAX_CUBOID_FRAGMENTS ) + { + Com_Printf( S_COLOR_RED "ERROR: too many cuboid fragments in %s\n", filename ); + break; + } + if( trap_Parse_ReadToken( f, &token ) ) + cuboid->fragments[ cuboid->fragmentCount++ ] = trap_R_RegisterShader( token.string ); + } + else if( !Q_stricmp( token.string, "painSound" ) ) + { + if( cuboid->painSoundCount == MAX_CUBOID_SOUNDS ) + { + Com_Printf( S_COLOR_RED "ERROR: too many cuboid pain sounds in %s\n", filename ); + break; + } + if( trap_Parse_ReadToken( f, &token ) ) + cuboid->painSounds[ cuboid->painSoundCount++ ] = trap_S_RegisterSound( token.string, qfalse ); + } + else if( !Q_stricmp( token.string, "destroySound" ) ) + { + if( cuboid->destroySoundCount == MAX_CUBOID_SOUNDS ) + { + Com_Printf( S_COLOR_RED "ERROR: too many cuboid destroy sounds in %s\n", filename ); + break; + } + if( trap_Parse_ReadToken( f, &token ) ) + { + cuboid->destroySoundThresholds[ cuboid->destroySoundCount++ ] = atof( token.string ); + if( trap_Parse_ReadToken( f, &token ) ) + cuboid->destroySounds[ cuboid->destroySoundCount - 1 ] = trap_S_RegisterSound( token.string, qfalse ); + else + { + Com_Printf( S_COLOR_YELLOW "WARNING: incomplete cuboid destroy sound definition in %s\n", filename ); + cuboid->destroySoundCount--; + } + } + } + else + Com_Printf( S_COLOR_YELLOW "WARNING: invalid token %s in %s\n", token.string, filename ); + } + trap_Parse_FreeSource( f ); + } +} + +/* +=============== +CG_SetBuildableLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetBuildableLerpFrameAnimation( buildable_t buildable, lerpFrame_t *lf, int newAnimation ) +{ + animation_t *anim; + + lf->animationNumber = newAnimation; + + if( newAnimation < 0 || newAnimation >= MAX_BUILDABLE_ANIMATIONS ) + CG_Error( "Bad animation number: %i", newAnimation ); + + anim = &cg_buildables[ buildable ].animations[ newAnimation ]; + + //this item has just spawned so lf->frameTime will be zero + if( !lf->animation ) + lf->frameTime = cg.time + 1000; //1 sec delay before starting the spawn anim + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if( cg_debugAnim.integer ) + CG_Printf( "Anim: %i\n", newAnimation ); +} + +/* +=============== +CG_RunBuildableLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +static void CG_RunBuildableLerpFrame( centity_t *cent ) +{ + buildable_t buildable = cent->currentState.modelindex; + lerpFrame_t *lf = ¢->lerpFrame; + buildableAnimNumber_t newAnimation = cent->buildableAnim & ~( ANIM_TOGGLEBIT|ANIM_FORCEBIT ); + + // see if the animation sequence is switching + if( newAnimation != lf->animationNumber || !lf->animation ) + { + if( cg_debugRandom.integer ) + CG_Printf( "newAnimation: %d lf->animationNumber: %d lf->animation: %d\n", + newAnimation, lf->animationNumber, lf->animation ); + + CG_SetBuildableLerpFrameAnimation( buildable, lf, newAnimation ); + + if( !cg_buildables[ buildable ].sounds[ newAnimation ].looped && + cg_buildables[ buildable ].sounds[ newAnimation ].enabled ) + { + if( cg_debugRandom.integer ) + CG_Printf( "Sound for animation %d for a %s\n", + newAnimation, BG_Buildable( buildable, NULL )->humanName ); + + trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, + cg_buildables[ buildable ].sounds[ newAnimation ].sound ); + } + } + + if( cg_buildables[ buildable ].sounds[ lf->animationNumber ].looped && + cg_buildables[ buildable ].sounds[ lf->animationNumber ].enabled ) + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cg_buildables[ buildable ].sounds[ lf->animationNumber ].sound ); + + CG_RunLerpFrame( lf, 1.0f ); + + // animation ended + if( lf->frameTime == cg.time ) + { + cent->buildableAnim = cent->currentState.torsoAnim; + cent->buildableIdleAnim = qtrue; + } +} + +/* +=============== +CG_BuildableAnimation +=============== +*/ +static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *backLerp ) +{ + entityState_t *es = ¢->currentState; + + //if no animation is set default to idle anim + if( cent->buildableAnim == BANIM_NONE ) + { + cent->buildableAnim = es->torsoAnim; + cent->buildableIdleAnim = qtrue; + } + + //display the first frame of the construction anim if not yet spawned + if( !( es->eFlags & EF_B_SPAWNED ) ) + { + animation_t *anim = &cg_buildables[ es->modelindex ].animations[ BANIM_CONSTRUCT1 ]; + + //so that when animation starts for real it has sensible numbers + cent->lerpFrame.oldFrameTime = + cent->lerpFrame.frameTime = + cent->lerpFrame.animationTime = + cg.time; + + *old = cent->lerpFrame.oldFrame = anim->firstFrame; + *now = cent->lerpFrame.frame = anim->firstFrame; + *backLerp = cent->lerpFrame.backlerp = 0.0f; + + //ensure that an animation is triggered once the buildable has spawned + cent->oldBuildableAnim = BANIM_NONE; + } + else + { + if( ( cent->oldBuildableAnim ^ es->legsAnim ) & ANIM_TOGGLEBIT ) + { + if( cg_debugAnim.integer ) + CG_Printf( "%d->%d l:%d t:%d %s(%d)\n", + cent->oldBuildableAnim, cent->buildableAnim, + es->legsAnim, es->torsoAnim, + BG_Buildable( es->modelindex, NULL )->humanName, es->number ); + + if( cent->buildableAnim == es->torsoAnim || es->legsAnim & ANIM_FORCEBIT ) + { + cent->buildableAnim = cent->oldBuildableAnim = es->legsAnim; + cent->buildableIdleAnim = qfalse; + } + else + { + cent->buildableAnim = cent->oldBuildableAnim = es->torsoAnim; + cent->buildableIdleAnim = qtrue; + } + } + else if( cent->buildableIdleAnim == qtrue && + cent->buildableAnim != es->torsoAnim ) + { + cent->buildableAnim = es->torsoAnim; + } + + CG_RunBuildableLerpFrame( cent ); + + *old = cent->lerpFrame.oldFrame; + *now = cent->lerpFrame.frame; + *backLerp = cent->lerpFrame.backlerp; + } +} + +#define TRACE_DEPTH 64.0f + +/* +=============== +CG_PositionAndOrientateBuildable +=============== +*/ +static void CG_PositionAndOrientateBuildable( const vec3_t angles, const vec3_t inOrigin, + const vec3_t normal, const int skipNumber, + const vec3_t mins, const vec3_t maxs, + vec3_t outAxis[ 3 ], vec3_t outOrigin, + qboolean cuboid ) +{ + vec3_t forward, start, end; + trace_t tr, box_tr; + float mag, fraction; + + AngleVectors( angles, forward, NULL, NULL ); + VectorCopy( normal, outAxis[ 2 ] ); + ProjectPointOnPlane( outAxis[ 0 ], forward, outAxis[ 2 ] ); + + if( !VectorNormalize( outAxis[ 0 ] ) ) + { + if(cuboid) + forward[0]=forward[1]=0.0f,forward[2]=1.0f; + else + AngleVectors( angles, NULL, NULL, forward ); + ProjectPointOnPlane( outAxis[ 0 ], forward, outAxis[ 2 ] ); + VectorNormalize( outAxis[ 0 ] ); + } + + CrossProduct( outAxis[ 0 ], outAxis[ 2 ], outAxis[ 1 ] ); + outAxis[ 1 ][ 0 ] = -outAxis[ 1 ][ 0 ]; + outAxis[ 1 ][ 1 ] = -outAxis[ 1 ][ 1 ]; + outAxis[ 1 ][ 2 ] = -outAxis[ 1 ][ 2 ]; + + if(cuboid) + { + VectorCopy(inOrigin,outOrigin); + return; + } + VectorMA( inOrigin, -TRACE_DEPTH, normal, end ); + VectorMA( inOrigin, 1.0f, normal, start ); + + // Take both capsule and box traces. If the capsule trace does not differ + // significantly from the box trace use it. This may cause buildables to be + // positioned *inside* the surface on which it is placed. This is intentional + + CG_CapTrace( &tr, start, mins, maxs, end, skipNumber, + MASK_PLAYERSOLID ); + + if(tr.contents&(~MASK_SOLID)) //if we hit a player or another buildable then display buildable exactly at its origin + VectorCopy(inOrigin,outOrigin); + else + { + CG_Trace( &box_tr, start, mins, maxs, end, skipNumber, + MASK_PLAYERSOLID ); + + mag = Distance( tr.endpos, box_tr.endpos ); + + fraction = tr.fraction; + + // this is either too far off of the bbox to be useful for gameplay purposes + // or the model is positioned in thin air anyways. + if( mag > 15.0f || tr.fraction == 1.0f ) + fraction = box_tr.fraction; + + VectorMA( inOrigin, fraction * -TRACE_DEPTH, normal, outOrigin ); + } +} + +/* +================== +CG_GhostBuildable +================== +*/ +void CG_Cuboid_Info(void); +void CG_GhostBuildable( buildable_t buildable, vec3_t dims ) +{ + refEntity_t ent; + playerState_t *ps; + vec3_t angles, entity_origin; + vec3_t mins, maxs; + trace_t tr; + float scale; + vec3_t viewangles; + + ps = &cg.predictedPlayerState; + + memset( &ent, 0, sizeof( ent ) ); + + if(BG_Buildable(buildable,NULL)->cuboid) + { + BG_CuboidBBox(dims,mins,maxs); + CG_Cuboid_Info(); + } + else BG_BuildableBoundingBox( buildable, mins, maxs ); + + if(!BG_PositionBuildableRelativeToPlayer( ps, BG_Buildable(buildable,NULL)->cuboid, mins, maxs, CG_Trace, entity_origin, angles, &tr )) + return; + + if(BG_Buildable(buildable, NULL)->cuboid) + { + qhandle_t shader, ashader; + + CG_Cuboid_Send(); //NOTE: CG_Cuboid_Send has its own timer so we don't spam server with commands every frame + + if(cg.forbidCuboids) + shader=cgs.media.cuboidYellowBuildShader; + else if(ps->stats[STAT_BUILDABLE]&SB_VALID_TOGGLEBIT) + shader=cgs.media.cuboidGreenBuildShader; + else + shader=cgs.media.cuboidRedBuildShader; + CG_DrawCuboid(entity_origin,dims,shader,0); + CG_DrawCuboidAxis(entity_origin,dims,cg_cuboidResizeAxis.integer,cgs.media.cuboidAxis); + return; + } + + VectorCopy(ps->viewangles,viewangles); + + CG_PositionAndOrientateBuildable( viewangles, entity_origin, tr.plane.normal, ps->clientNum, + mins, maxs, ent.axis, ent.origin, qfalse ); + + //offset on the Z axis if required + + VectorMA( ent.origin, BG_BuildableConfig( buildable )->zOffset, tr.plane.normal, ent.origin ); + + VectorCopy( ent.origin, ent.lightingOrigin ); + VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all + + ent.hModel = cg_buildables[ buildable ].models[ 0 ]; + + if( ps->stats[ STAT_BUILDABLE ] & SB_VALID_TOGGLEBIT ) + ent.customShader = cgs.media.greenBuildShader; + else + ent.customShader = cgs.media.redBuildShader; + + ent.nonNormalizedAxes = qfalse; + + //rescale the model + scale = BG_BuildableConfig( buildable )->modelScale; + + if( scale != 1.0f ) + { + VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] ); + VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] ); + VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] ); + + ent.nonNormalizedAxes = qtrue; + } + + // add to refresh list + trap_R_AddRefEntityToScene( &ent ); +} + +/* +================== +CG_BuildableParticleEffects +================== +*/ +static void CG_BuildableParticleEffects( centity_t *cent ) +{ + entityState_t *es = ¢->currentState; + team_t team = BG_Buildable( es->modelindex, NULL )->team; + int health = es->generic1; + float healthFrac = (float)health / BG_Buildable( es->modelindex, es->angles )->health; + + if( !( es->eFlags & EF_B_SPAWNED ) ) + return; + + if( team == TEAM_HUMANS ) + { + if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) + { + cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDamagedPS ); + + if( CG_IsParticleSystemValid( ¢->buildablePS ) ) + { + CG_SetAttachmentCent( ¢->buildablePS->attachment, cent ); + CG_AttachToCent( ¢->buildablePS->attachment ); + } + } + else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( ¢->buildablePS ) ) + CG_DestroyParticleSystem( ¢->buildablePS ); + } + else if( team == TEAM_ALIENS ) + { + if( healthFrac < 0.33f && !CG_IsParticleSystemValid( ¢->buildablePS ) ) + { + cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDamagedPS ); + + if( CG_IsParticleSystemValid( ¢->buildablePS ) ) + { + CG_SetAttachmentCent( ¢->buildablePS->attachment, cent ); + CG_SetParticleSystemNormal( cent->buildablePS, es->origin2 ); + CG_AttachToCent( ¢->buildablePS->attachment ); + } + } + else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( ¢->buildablePS ) ) + CG_DestroyParticleSystem( ¢->buildablePS ); + } +} + +/* +================== +CG_BuildableStatusParse +================== +*/ +void CG_BuildableStatusParse( const char *filename, buildStat_t *bs ) +{ + pc_token_t token; + int handle; + const char *s; + int i; + float f; + vec4_t c; + + handle = trap_Parse_LoadSource( filename ); + if( !handle ) + return; + while( 1 ) + { + if( !trap_Parse_ReadToken( handle, &token ) ) + break; + if( !Q_stricmp( token.string, "frameShader" ) ) + { + if( PC_String_Parse( handle, &s ) ) + bs->frameShader = trap_R_RegisterShader( s ); + continue; + } + else if( !Q_stricmp( token.string, "overlayShader" ) ) + { + if( PC_String_Parse( handle, &s ) ) + bs->overlayShader = trap_R_RegisterShader( s ); + continue; + } + else if( !Q_stricmp( token.string, "noPowerShader" ) ) + { + if( PC_String_Parse( handle, &s ) ) + bs->noPowerShader = trap_R_RegisterShader( s ); + continue; + } + else if( !Q_stricmp( token.string, "markedShader" ) ) + { + if( PC_String_Parse( handle, &s ) ) + bs->markedShader = trap_R_RegisterShader( s ); + continue; + } + else if( !Q_stricmp( token.string, "healthSevereColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->healthSevereColor ); + continue; + } + else if( !Q_stricmp( token.string, "healthHighColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->healthHighColor ); + continue; + } + else if( !Q_stricmp( token.string, "healthElevatedColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->healthElevatedColor ); + continue; + } + else if( !Q_stricmp( token.string, "healthGuardedColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->healthGuardedColor ); + continue; + } + else if( !Q_stricmp( token.string, "healthLowColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->healthLowColor ); + continue; + } + else if( !Q_stricmp( token.string, "foreColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->foreColor ); + continue; + } + else if( !Q_stricmp( token.string, "backColor" ) ) + { + if( PC_Color_Parse( handle, &c ) ) + Vector4Copy( c, bs->backColor ); + continue; + } + else if( !Q_stricmp( token.string, "frameHeight" ) ) + { + if( PC_Int_Parse( handle, &i ) ) + bs->frameHeight = i; + continue; + } + else if( !Q_stricmp( token.string, "frameWidth" ) ) + { + if( PC_Int_Parse( handle, &i ) ) + bs->frameWidth = i; + continue; + } + else if( !Q_stricmp( token.string, "healthPadding" ) ) + { + if( PC_Int_Parse( handle, &i ) ) + bs->healthPadding = i; + continue; + } + else if( !Q_stricmp( token.string, "overlayHeight" ) ) + { + if( PC_Int_Parse( handle, &i ) ) + bs->overlayHeight = i; + continue; + } + else if( !Q_stricmp( token.string, "overlayWidth" ) ) + { + if( PC_Int_Parse( handle, &i ) ) + bs->overlayWidth = i; + continue; + } + else if( !Q_stricmp( token.string, "verticalMargin" ) ) + { + if( PC_Float_Parse( handle, &f ) ) + bs->verticalMargin = f; + continue; + } + else if( !Q_stricmp( token.string, "horizontalMargin" ) ) + { + if( PC_Float_Parse( handle, &f ) ) + bs->horizontalMargin = f; + continue; + } + else + { + Com_Printf("CG_BuildableStatusParse: unknown token %s in %s\n", + token.string, filename ); + bs->loaded = qfalse; + trap_Parse_FreeSource( handle ); + return; + } + } + bs->loaded = qtrue; + trap_Parse_FreeSource( handle ); +} + +#define STATUS_FADE_TIME 200 +#define STATUS_MAX_VIEW_DIST 900.0f +#define STATUS_PEEK_DIST 20 +/* +================== +CG_BuildableStatusDisplay +================== +*/ +static void CG_BuildableStatusDisplay( centity_t *cent, qboolean cuboid, vec3_t trac ) +{ + entityState_t *es = ¢->currentState; + vec3_t origin; + float healthScale; + int health; + float x, y; + vec4_t color; + qboolean powered, marked; + trace_t tr; + float d; + buildStat_t *bs; + int i, j; + int entNum; + vec3_t trOrigin; + vec3_t right; + qboolean visible = qfalse; + vec3_t mins, maxs; + entityState_t *hit; + int anim; + char buf[12]; + int bufl; + + if( BG_Buildable( es->modelindex, NULL )->team == TEAM_ALIENS ) + bs = &cgs.alienBuildStat; + else + bs = &cgs.humanBuildStat; + + if( !bs->loaded ) + return; + + d = Distance( cent->lerpOrigin, cg.refdef.vieworg ); + if( d > STATUS_MAX_VIEW_DIST ) + return; + + Vector4Copy( bs->foreColor, color ); + + // trace for center point + if(BG_Buildable(es->modelindex, NULL)->cuboid) + BG_CuboidBBox( es->angles, mins, maxs ); + else + BG_BuildableBoundingBox( es->modelindex, mins, maxs ); + + // hack for shrunken barricades + anim = es->torsoAnim & ~( ANIM_FORCEBIT | ANIM_TOGGLEBIT ); + if( es->modelindex == BA_A_BARRICADE && + ( anim == BANIM_DESTROYED || !( es->eFlags & EF_B_SPAWNED ) ) ) + maxs[ 2 ] = (int)( maxs[ 2 ] * BARRICADE_SHRINKPROP ); + + VectorCopy( cent->lerpOrigin, origin ); + + // center point + origin[ 2 ] += mins[ 2 ]; + origin[ 2 ] += ( abs( mins[ 2 ] ) + abs( maxs[ 2 ] ) ) / 2; + + entNum = cg.predictedPlayerState.clientNum; + + // if first try fails, step left, step right + for( j = 0; j < 3 && !cuboid; j++ ) + { + VectorCopy( cg.refdef.vieworg, trOrigin ); + switch( j ) + { + case 1: + // step right + AngleVectors( cg.refdefViewAngles, NULL, right, NULL ); + VectorMA( trOrigin, STATUS_PEEK_DIST, right, trOrigin ); + break; + case 2: + // step left + AngleVectors( cg.refdefViewAngles, NULL, right, NULL ); + VectorMA( trOrigin, -STATUS_PEEK_DIST, right, trOrigin ); + break; + default: + break; + } + // look through up to 3 players and/or transparent buildables + for( i = 0; i < 3; i++ ) + { + CG_Trace( &tr, trOrigin, NULL, NULL, origin, entNum, MASK_SHOT ); + if( tr.entityNum == cent->currentState.number ) + { + visible = qtrue; + break; + } + + if( tr.entityNum == ENTITYNUM_WORLD ) + break; + + hit = &cg_entities[ tr.entityNum ].currentState; + + if( tr.entityNum < MAX_CLIENTS || ( hit->eType == ET_BUILDABLE && + ( !( es->eFlags & EF_B_SPAWNED ) || + BG_Buildable( hit->modelindex, NULL )->transparentTest ) ) ) + { + entNum = tr.entityNum; + VectorCopy( tr.endpos, trOrigin ); + } + else + break; + } + } + + if(cuboid) + visible=qtrue; + + // hack to make the kit obscure view + if( cg_drawGun.integer && visible && + cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS && + CG_WorldToScreen( origin, &x, &y ) ) + { + if( x > 450 && y > 290 ) + visible = qfalse; + } + + if( !visible && cent->buildableStatus.visible ) + { + cent->buildableStatus.visible = qfalse; + cent->buildableStatus.lastTime = cg.time; + } + else if( visible && !cent->buildableStatus.visible ) + { + cent->buildableStatus.visible = qtrue; + cent->buildableStatus.lastTime = cg.time; + } + + // Fade up + if( cent->buildableStatus.visible ) + { + if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time ) + color[ 3 ] = (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME; + } + + // Fade down + if( !cent->buildableStatus.visible ) + { + if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time ) + color[ 3 ] = 1.0f - (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME; + else + return; + } + + health = es->generic1; + if(cuboid) + health = BG_CuboidUnpackHealth(es); + healthScale = (float)health / BG_Buildable( es->modelindex, es->angles )->health; + + if( health > 0 && healthScale < 0.01f ) + healthScale = 0.01f; + else if( healthScale < 0.0f ) + healthScale = 0.0f; + else if( healthScale > 1.0f ) + healthScale = 1.0f; + + if(cuboid) + { + x=320; + y=240; + d=Distance(cg.refdef.vieworg,trac); + if(d<64.0f) + d=64.0f; + } + else + if( !CG_WorldToScreen( origin, &x, &y ) ) + return; + + { + float picH = bs->frameHeight; + float picW = bs->frameWidth; + float picX = x; + float picY = y; + float scale; + float subH, subY; + float clipX, clipY, clipW, clipH; + vec4_t frameColor; + + // this is fudged to get the width/height in the cfg to be more realistic + scale = ( picH / d ) * 3; + + powered = es->eFlags & EF_B_POWERED; + marked = es->eFlags & EF_B_MARKED; + + picH *= scale; + picW *= scale; + picX -= ( picW * 0.5f ); + picY -= ( picH * 0.5f ); + + // sub-elements such as icons and number + subH = picH - ( picH * bs->verticalMargin ); + subY = picY + ( picH * 0.5f ) - ( subH * 0.5f ); + + clipW = ( 640.0f * cg_viewsize.integer ) / 100.0f; + clipH = ( 480.0f * cg_viewsize.integer ) / 100.0f; + clipX = 320.0f - ( clipW * 0.5f ); + clipY = 240.0f - ( clipH * 0.5f ); + CG_SetClipRegion( clipX, clipY, clipW, clipH ); + + if( bs->frameShader ) + { + Vector4Copy( bs->backColor, frameColor ); + frameColor[ 3 ] = color[ 3 ]; + trap_R_SetColor( frameColor ); + CG_DrawPic( picX, picY, picW, picH, bs->frameShader ); + trap_R_SetColor( NULL ); + } + + if( health > 0 ) + { + float hX, hY, hW, hH; + vec4_t healthColor; + + hX = picX + ( bs->healthPadding * scale ); + hY = picY + ( bs->healthPadding * scale ); + hH = picH - ( bs->healthPadding * 2.0f * scale ); + hW = picW * healthScale - ( bs->healthPadding * 2.0f * scale ); + + if( healthScale == 1.0f ) + Vector4Copy( bs->healthLowColor, healthColor ); + else if( healthScale >= 0.75f ) + Vector4Copy( bs->healthGuardedColor, healthColor ); + else if( healthScale >= 0.50f ) + Vector4Copy( bs->healthElevatedColor, healthColor ); + else if( healthScale >= 0.25f ) + Vector4Copy( bs->healthHighColor, healthColor ); + else + Vector4Copy( bs->healthSevereColor, healthColor ); + + healthColor[ 3 ] = color[ 3 ]; + trap_R_SetColor( healthColor ); + + CG_DrawPic( hX, hY, hW, hH, cgs.media.whiteShader ); + trap_R_SetColor( NULL ); + } + + if( bs->overlayShader ) + { + float oW = bs->overlayWidth; + float oH = bs->overlayHeight; + float oX = x; + float oY = y; + + oH *= scale; + oW *= scale; + oX -= ( oW * 0.5f ); + oY -= ( oH * 0.5f ); + + trap_R_SetColor( frameColor ); + CG_DrawPic( oX, oY, oW, oH, bs->overlayShader ); + trap_R_SetColor( NULL ); + } + + trap_R_SetColor( color ); + if( !powered ) + { + float pX; + + pX = picX + ( subH * bs->horizontalMargin ); + CG_DrawPic( pX, subY, subH, subH, bs->noPowerShader ); + } + + if( marked ) + { + float mX; + + mX = picX + picW - ( subH * bs->horizontalMargin ) - subH; + CG_DrawPic( mX, subY, subH, subH, bs->markedShader ); + } + + //NOTE: dont use CG_DrawField, too few digits + { + float nX,cW,cH; + int healthMax; + int healthPoints; + int frame; + + healthMax = BG_Buildable( es->modelindex, es->angles )->health; + healthPoints = (int)( healthScale * healthMax ); + if( health > 0 && healthPoints < 1 ) + healthPoints = 1; + + Com_sprintf(buf,sizeof(buf),"%i",healthPoints); + bufl=strlen(buf); + cW=subH*cgDC.aspectScale; + cH=subH; + nX=picX+picW*0.5f-cW*bufl*0.5f; + + for(i=0;i<bufl;i++) + { + if(buf[i]=='-') + frame=STAT_MINUS; + else + frame=buf[i]-'0'; + CG_DrawPic(nX+i*cW,y+bs->verticalMargin-subH*0.5f,cW,cH,cgs.media.numberShaders[frame]); + } + } + + trap_R_SetColor( NULL ); + CG_ClearClipRegion( ); + } +} + +/* +================== +CG_SortDistance +================== +*/ +static int CG_SortDistance( const void *a, const void *b ) +{ + centity_t *aent, *bent; + float adist, bdist; + + aent = &cg_entities[ *(int *)a ]; + bent = &cg_entities[ *(int *)b ]; + adist = Distance( cg.refdef.vieworg, aent->lerpOrigin ); + bdist = Distance( cg.refdef.vieworg, bent->lerpOrigin ); + if( adist > bdist ) + return -1; + else if( adist < bdist ) + return 1; + else + return 0; +} + +/* +================== +CG_PlayerIsBuilder +================== +*/ +static qboolean CG_PlayerIsBuilder( buildable_t buildable ) +{ + switch( cg.predictedPlayerState.weapon ) + { + case WP_ABUILD: + case WP_ABUILD2: + case WP_HBUILD: + return BG_Buildable( buildable, NULL )->team == + BG_Weapon( cg.predictedPlayerState.weapon )->team; + + default: + return qfalse; + } +} + +/* +================== +CG_BuildableRemovalPending +================== +*/ +static qboolean CG_BuildableRemovalPending( int entityNum ) +{ + int i; + playerState_t *ps = &cg.snap->ps; + + if( !( ps->stats[ STAT_BUILDABLE ] & SB_VALID_TOGGLEBIT ) ) + return qfalse; + + for( i = 0; i < MAX_MISC; i++ ) + { + if( ps->misc[ i ] == entityNum ) + return qtrue; + } + + return qfalse; +} + +/* +================== +CG_DrawBuildableStatus +================== +*/ +void CG_DrawBuildableStatus( void ) +{ + int i; + centity_t *cent; + entityState_t *es; + int buildableList[ MAX_ENTITIES_IN_SNAPSHOT ]; + int buildables = 0; + vec3_t end; + trace_t tr; + qboolean cuboid; + + if((cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT)>BA_NONE) + return; //hide buildstats if we're placing a buildable + + for( i = 0; i < cg.snap->numEntities; i++ ) + { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + es = ¢->currentState; + + if( es->eType == ET_BUILDABLE && CG_PlayerIsBuilder( es->modelindex ) ) + buildableList[ buildables++ ] = cg.snap->entities[ i ].number; + } + + VectorMA(cg.refdef.vieworg,STATUS_MAX_VIEW_DIST,cg.refdef.viewaxis[0],end); + CG_Trace(&tr,cg.refdef.vieworg,NULL,NULL,end,cg.predictedPlayerState.clientNum,MASK_SHOT); + + qsort( buildableList, buildables, sizeof( int ), CG_SortDistance ); + for( i = 0; i < buildables; i++ ) + { + cuboid = BG_Buildable(cg_entities[buildableList[i]].currentState.modelindex,NULL)->cuboid; + if(cuboid && tr.entityNum!=buildableList[i] ) + continue; + CG_BuildableStatusDisplay( &cg_entities[ buildableList[ i ] ], cuboid, tr.endpos ); + } +} + +#define BUILDABLE_SOUND_PERIOD 500 + +/* +================== +CG_Buildable +================== +*/ +void CG_Buildable( centity_t *cent ) +{ + refEntity_t ent; + entityState_t *es = ¢->currentState; + vec3_t angles; + vec3_t surfNormal, xNormal, mins, maxs; + vec3_t refNormal = { 0.0f, 0.0f, 1.0f }; + float rotAngle; + team_t team = BG_Buildable( es->modelindex, NULL )->team; + float scale; + int health; + + //must be before EF_NODRAW check + if( team == TEAM_ALIENS ) + CG_Creep( cent ); + + // if set to invisible, skip + if( es->eFlags & EF_NODRAW ) + { + if( CG_IsParticleSystemValid( ¢->buildablePS ) ) + CG_DestroyParticleSystem( ¢->buildablePS ); + + return; + } + + // cuboids use a bit different rendering code !@#CUBOID + if( BG_IsCuboid( es->modelindex ) ) + { + qhandle_t texture=0,cracks=0; + vec3_t dims; + const cuboidAttributes_t *cuboidAttr; + const cuboidInfo_t *cuboidInfo; + int i, health, sound; + float healthPct; + + cuboidAttr = BG_CuboidAttributes( es->modelindex ); + cuboidInfo = &cg_cuboids[ es->modelindex - CUBOID_FIRST ]; + VectorCopy( es->angles, dims ); + healthPct = (float)( health = BG_CuboidUnpackHealth(es) ) / (float)BG_Buildable(es->modelindex,dims)->health; + + if( healthPct > 1.0f ) + healthPct = 1.0f; + else if ( healthPct < 0.0f ) + healthPct = 0.0f; + + if( cuboidInfo->useCracks ) + { + if( cuboidInfo->textureCount ) + texture = cuboidInfo->textures[ 0 ]; + if( healthPct < 0.75f ) + cracks = cgs.media.cuboidCracks[ (int)( CUBOID_CRACK_TEXTURES - 1 - floor( CUBOID_CRACK_TEXTURES * healthPct ) ) - 1 ]; + } + else + for( i = 0; i < cuboidInfo->textureCount; i++ ) + if( healthPct >= cuboidInfo->textureThresholds[ i ] ) + { + texture = cuboidInfo->textures[ i ]; + break; + } + + if( !( es->eFlags & EF_B_SPAWNED ) ) + { + sfxHandle_t prebuildSound=cgs.media.humanBuildablePrebuild; + if( team == TEAM_HUMANS ) + { + texture = cgs.media.humanSpawningShader; + prebuildSound = cgs.media.humanBuildablePrebuild; + } + else if(team==TEAM_ALIENS) + { + texture = cgs.media.cuboidAlienPrebuild; + prebuildSound = cgs.media.alienBuildablePrebuild; + } + cracks = 0; + trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, prebuildSound ); + } + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + VectorCopy( cent->lerpOrigin, ent.lightingOrigin ); + + //NOTE: don't use CG_PositionAndOrientateBuildable, it screws up everything + ent.axis[0][0]=-dims[0]/2.0f;ent.axis[0][1]=0.0f; ent.axis[0][2]=0.0f; + ent.axis[1][0]=0.0f; ent.axis[1][1]=-dims[1]/2.0f;ent.axis[1][2]=0.0f; + ent.axis[2][0]=0.0f; ent.axis[2][1]=0.0f; ent.axis[2][2]=dims[2]/2.0f; + ent.nonNormalizedAxes = qtrue; + + ent.customShader = texture; + ent.hModel = cgs.media.cuboidModel; + trap_R_AddRefEntityToScene( &ent ); + if( cracks ) + CG_DrawCuboid( ent.origin, dims, cracks, 1 ); + + if( health < cent->lastBuildableHealth && ( es->eFlags & EF_B_SPAWNED ) ) + { + if( cent->lastBuildableDamageSoundTime + BUILDABLE_SOUND_PERIOD < cg.time ) + { + sound = (int)( cuboidInfo->painSoundCount * random() ); + trap_S_StartSound( NULL, es->number, CHAN_BODY, cuboidInfo->painSounds[sound] ); + cent->lastBuildableDamageSoundTime = cg.time; + } + cent->lastBuildableHealth = health; + } + } + else + { + + memset ( &ent, 0, sizeof( ent ) ); + + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( cent->lerpOrigin, ent.oldorigin ); + VectorCopy( cent->lerpOrigin, ent.lightingOrigin ); + + VectorCopy( es->origin2, surfNormal ); + + VectorCopy( es->angles, angles ); + BG_BuildableBoundingBox( es->modelindex, mins, maxs ); + + if( es->pos.trType == TR_STATIONARY ) + { + // Positioning a buildable involves potentially up to two traces, and + // seeing as buildables rarely move, we cache the results and recalculate + // only if the buildable moves or changes orientation + if( VectorCompare( cent->buildableCache.cachedOrigin, cent->lerpOrigin ) && + VectorCompare( cent->buildableCache.cachedNormal, surfNormal ) ) + { + VectorCopy( cent->buildableCache.axis[ 0 ], ent.axis[ 0 ] ); + VectorCopy( cent->buildableCache.axis[ 1 ], ent.axis[ 1 ] ); + VectorCopy( cent->buildableCache.axis[ 2 ], ent.axis[ 2 ] ); + VectorCopy( cent->buildableCache.origin, ent.origin ); + } + else + { + CG_PositionAndOrientateBuildable( angles, ent.origin, surfNormal, + es->number, mins, maxs, ent.axis, + ent.origin, qfalse ); + VectorCopy( ent.axis[ 0 ], cent->buildableCache.axis[ 0 ] ); + VectorCopy( ent.axis[ 1 ], cent->buildableCache.axis[ 1 ] ); + VectorCopy( ent.axis[ 2 ], cent->buildableCache.axis[ 2 ] ); + VectorCopy( ent.origin, cent->buildableCache.origin ); + VectorCopy( cent->lerpOrigin, cent->buildableCache.cachedOrigin ); + VectorCopy( surfNormal, cent->buildableCache.cachedNormal ); + } + } + + VectorMA( ent.origin, BG_BuildableConfig( es->modelindex )->zOffset, surfNormal, ent.origin ); + + + VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all + VectorCopy( ent.origin, ent.lightingOrigin ); + + ent.hModel = cg_buildables[ es->modelindex ].models[ 0 ]; + + if( !( es->eFlags & EF_B_SPAWNED ) ) + { + sfxHandle_t prebuildSound = cgs.media.humanBuildablePrebuild; + + if( team == TEAM_HUMANS ) + { + ent.customShader = cgs.media.humanSpawningShader; + prebuildSound = cgs.media.humanBuildablePrebuild; + } + else if( team == TEAM_ALIENS ) + prebuildSound = cgs.media.alienBuildablePrebuild; + + trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, prebuildSound ); + } + + CG_BuildableAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp ); + + //rescale the model + scale = BG_BuildableConfig( es->modelindex )->modelScale; + + if( scale != 1.0f ) + { + VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] ); + VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] ); + VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] ); + + ent.nonNormalizedAxes = qtrue; + } + else + ent.nonNormalizedAxes = qfalse; + + + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + ent.customShader = cgs.media.redBuildShader; + + //add to refresh list + trap_R_AddRefEntityToScene( &ent ); + + CrossProduct( surfNormal, refNormal, xNormal ); + VectorNormalize( xNormal ); + rotAngle = RAD2DEG( acos( DotProduct( surfNormal, refNormal ) ) ); + + //turret barrel bit + if( cg_buildables[ es->modelindex ].models[ 1 ] ) + { + refEntity_t turretBarrel; + vec3_t flatAxis[ 3 ]; + + memset( &turretBarrel, 0, sizeof( turretBarrel ) ); + + turretBarrel.hModel = cg_buildables[ es->modelindex ].models[ 1 ]; + + CG_PositionEntityOnTag( &turretBarrel, &ent, ent.hModel, "tag_turret" ); + VectorCopy( cent->lerpOrigin, turretBarrel.lightingOrigin ); + AnglesToAxis( es->angles2, flatAxis ); + + RotatePointAroundVector( turretBarrel.axis[ 0 ], xNormal, flatAxis[ 0 ], -rotAngle ); + RotatePointAroundVector( turretBarrel.axis[ 1 ], xNormal, flatAxis[ 1 ], -rotAngle ); + RotatePointAroundVector( turretBarrel.axis[ 2 ], xNormal, flatAxis[ 2 ], -rotAngle ); + + turretBarrel.oldframe = ent.oldframe; + turretBarrel.frame = ent.frame; + turretBarrel.backlerp = ent.backlerp; + + turretBarrel.customShader = ent.customShader; + + if( scale != 1.0f ) + { + VectorScale( turretBarrel.axis[ 0 ], scale, turretBarrel.axis[ 0 ] ); + VectorScale( turretBarrel.axis[ 1 ], scale, turretBarrel.axis[ 1 ] ); + VectorScale( turretBarrel.axis[ 2 ], scale, turretBarrel.axis[ 2 ] ); + + turretBarrel.nonNormalizedAxes = qtrue; + } + else + turretBarrel.nonNormalizedAxes = qfalse; + + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + turretBarrel.customShader = cgs.media.redBuildShader; + + trap_R_AddRefEntityToScene( &turretBarrel ); + } + + //turret barrel bit + if( cg_buildables[ es->modelindex ].models[ 2 ] ) + { + refEntity_t turretTop; + vec3_t flatAxis[ 3 ]; + vec3_t swivelAngles; + + memset( &turretTop, 0, sizeof( turretTop ) ); + + VectorCopy( es->angles2, swivelAngles ); + swivelAngles[ PITCH ] = 0.0f; + + turretTop.hModel = cg_buildables[ es->modelindex ].models[ 2 ]; + + CG_PositionRotatedEntityOnTag( &turretTop, &ent, ent.hModel, "tag_turret" ); + VectorCopy( cent->lerpOrigin, turretTop.lightingOrigin ); + AnglesToAxis( swivelAngles, flatAxis ); + + RotatePointAroundVector( turretTop.axis[ 0 ], xNormal, flatAxis[ 0 ], -rotAngle ); + RotatePointAroundVector( turretTop.axis[ 1 ], xNormal, flatAxis[ 1 ], -rotAngle ); + RotatePointAroundVector( turretTop.axis[ 2 ], xNormal, flatAxis[ 2 ], -rotAngle ); + + turretTop.oldframe = ent.oldframe; + turretTop.frame = ent.frame; + turretTop.backlerp = ent.backlerp; + + turretTop.customShader = ent.customShader; + + if( scale != 1.0f ) + { + VectorScale( turretTop.axis[ 0 ], scale, turretTop.axis[ 0 ] ); + VectorScale( turretTop.axis[ 1 ], scale, turretTop.axis[ 1 ] ); + VectorScale( turretTop.axis[ 2 ], scale, turretTop.axis[ 2 ] ); + + turretTop.nonNormalizedAxes = qtrue; + } + else + turretTop.nonNormalizedAxes = qfalse; + + if( CG_PlayerIsBuilder( es->modelindex ) && CG_BuildableRemovalPending( es->number ) ) + turretTop.customShader = cgs.media.redBuildShader; + + trap_R_AddRefEntityToScene( &turretTop ); + } + + //weapon effects for turrets + if( es->eFlags & EF_FIRING ) + { + weaponInfo_t *weapon = &cg_weapons[ es->weapon ]; + + if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME || + BG_Buildable( es->modelindex, NULL )->turretProjType == WP_TESLAGEN ) + { + if( weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ] || + weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ] || + weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 2 ] ) + { + trap_R_AddLightToScene( cent->lerpOrigin, 300 + ( rand( ) & 31 ), + weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ], + weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ], + weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 2 ] ); + } + } + + if( weapon->wim[ WPM_PRIMARY ].firingSound ) + { + trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, + weapon->wim[ WPM_PRIMARY ].firingSound ); + } + else if( weapon->readySound ) + trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, weapon->readySound ); + } + + //smoke etc for damaged buildables + CG_BuildableParticleEffects( cent ); + + + + + health = es->generic1; + + if( health < cent->lastBuildableHealth && + ( es->eFlags & EF_B_SPAWNED ) ) + { + if( cent->lastBuildableDamageSoundTime + BUILDABLE_SOUND_PERIOD < cg.time ) + { + if( team == TEAM_HUMANS ) + { + int i = rand( ) % 4; + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.humanBuildableDamage[ i ] ); + } + else if( team == TEAM_ALIENS ) + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienBuildableDamage ); + + cent->lastBuildableDamageSoundTime = cg.time; + } + } + + cent->lastBuildableHealth = health; + + } //if (is a cuboid) +} + +char cuboidInfo[128]; + +/* +====================== +CG_Cuboid_DrawInfo + +Draw the cuboid info string generated by CG_Cuboid_Info. +====================== +*/ +void CG_Cuboid_DrawInfo(void) +{ + float x,y,w,h,s=0.5f; + + if(!BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid) + return; + + w=UI_Text_Width(cuboidInfo,s); + h=UI_Text_Height(cuboidInfo,s); + x=320.0f-w/2.0f+cg_cuboidInfoX.value; + y=240.0f-h/2.0f+cg_cuboidInfoY.value; + + UI_Text_Paint(x,y,s,colorWhite,cuboidInfo,0,0,ITEM_TEXTSTYLE_SHADOWEDMORE); +} + +/* +====================== +CG_Cuboid_Info + +Update the cuboid info string with current cuboid's dimensions, +name, health, build time and build points (pretty much everything +one needs to know about his cuboid). +====================== +*/ +void CG_Cuboid_Info(void) +{ + const buildableAttributes_t *attr; + int axis=cg_cuboidResizeAxis.integer; + + attr=BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,cg.cuboidSelection); + Com_sprintf(cuboidInfo,sizeof(cuboidInfo), + "^7[^3%c^7] | ^%c%.1f^7x^%c%.1f^7x^%c%.1f ^7| ^3%i^7HP | ^3%i^7ms | ^3%i^7BP", + (axis==0?'X':(axis==1?'Y':'Z')), + (axis==0?'3':'5'), + cg.cuboidSelection[0], + (axis==1?'3':'5'), + cg.cuboidSelection[1], + (axis==2?'3':'5'), + cg.cuboidSelection[2], + attr->health, + attr->buildTime, + attr->buildPoints); +} + +/* +====================== +CG_Cuboid_Send + +Send the cuboid selection via commands. +====================== +*/ +void CG_Cuboid_Send(void) +{ + static qboolean init=qfalse; + static int lastupdate; + static vec3_t lastcuboid; + + if(!BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid) + return; + + if(!init) + { + lastupdate=cg.time; + VectorCopy(cg.cuboidSelection,lastcuboid); + init=qtrue; + } + + if(lastupdate+100>cg.time) + return; + + if(!VectorCompareEpsilon(lastcuboid,cg.cuboidSelection,1e-3)) + { + cg.latestCBNumber++; + cg.latestCBNumber%=100; + trap_SendClientCommand(va("cb %i %f %f %f\n",cg.latestCBNumber,cg.cuboidSelection[0],cg.cuboidSelection[1],cg.cuboidSelection[2])); + cg.forbidCuboids=qtrue; //wait for response + } + lastupdate=cg.time; + VectorCopy(cg.cuboidSelection,lastcuboid); +} + +/* +====================== +CG_Cuboid_Response + +Server responded to our cb with either cb2 or cb3. +====================== +*/ +void CG_Cuboid_Response(void) +{ + // cb2 <a> <b> <c> : server sets client-side cuboid + // cb3 <echo> : server agrees on player's cuboid + // cb3 <echo> <a> <b> <c> : server doesnt agree on player's cuboid and corrects it + static qboolean init=qfalse; + if(!BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid) + return; + if(!Q_stricmp(CG_Argv(0),"cb2")&&trap_Argc()==4) + { + cg.cuboidSelection[0]=atof(CG_Argv(1)); + cg.cuboidSelection[1]=atof(CG_Argv(2)); + cg.cuboidSelection[2]=atof(CG_Argv(3)); + return; + } + else if(!Q_stricmp(CG_Argv(0),"cb3")) + { + if(trap_Argc()==2) + { + if(atoi(CG_Argv(1))==cg.latestCBNumber) + cg.forbidCuboids=qfalse; + return; + } + else if(trap_Argc()==5) + { + cg.cuboidSelection[0]=atof(CG_Argv(2)); + cg.cuboidSelection[1]=atof(CG_Argv(3)); + cg.cuboidSelection[2]=atof(CG_Argv(4)); + if(atoi(CG_Argv(1))==cg.latestCBNumber) + { + cg.forbidCuboids=qfalse; + if(cg.lastCuboidError+250<cg.time) + { + trap_S_StartLocalSound(cgs.media.cuboidErrorSound,CHAN_LOCAL_SOUND); + cg.lastCuboidError=cg.time; + } + } + return; + } + } + Com_Printf("^3warning: wrong cb2/cb3 from server\n"); +} + +/* +====================== +CG_CuboidRotate_f + +Rotate the cuboid selection 90 degrees around the current axis. +Syntax: + cuboidRotate +====================== +*/ +void CG_CuboidResize(qboolean enlarge) +{ + vec3_t dims; + int rate=(enlarge?1:-1)*5; //FIXME: cvar for rate + if(!BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid) + return; + VectorCopy(cg.cuboidSelection,dims); + switch(cg_cuboidResizeAxis.integer) + { + case 0: + dims[0]+=rate; + break; + case 1: + dims[1]+=rate; + break; + default: + dims[2]+=rate; + break; + } + if(dims[0]<1||dims[1]<1||dims[2]<1|| + dims[0]*dims[1]*dims[2]<CUBOID_MINVOLUME ) + return; + if(enlarge) + trap_S_StartLocalSound(cgs.media.cuboidResizeSoundA,CHAN_LOCAL_SOUND); + else + trap_S_StartLocalSound(cgs.media.cuboidResizeSoundB,CHAN_LOCAL_SOUND); + VectorCopy(dims,cg.cuboidSelection); +} + +/* +====================== +CG_CuboidRotate_f + +Rotate the cuboid selection 90 degrees around the current axis. +Syntax: + cuboidRotate +====================== +*/ +#define SWAPFLOATS(a,b) {float __t;__t=a,a=b,b=__t;} +void CG_CuboidRotate_f(void) +{ + int axis; + + if(!BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid) + return; + axis=cg_cuboidResizeAxis.integer; + switch(axis) + { + case 0: + SWAPFLOATS(cg.cuboidSelection[1],cg.cuboidSelection[2]); + break; + case 1: + SWAPFLOATS(cg.cuboidSelection[2],cg.cuboidSelection[0]); + break; + case 2: + SWAPFLOATS(cg.cuboidSelection[0],cg.cuboidSelection[1]); + break; + } + trap_S_StartLocalSound(cgs.media.cuboidRotateSound,CHAN_LOCAL_SOUND); + CG_Cuboid_Send(); +} + +/* +====================== +CG_CuboidAxis_f + +Control the axis along which cuboids are resized and around which they are rotated. +Syntax: + cuboidAxis next - next axis (order is X->Y->Z) + cuboidAxis 0|1|2 - switch to a specific axis (respectively: X, Y and Z) +====================== +*/ +void CG_CuboidAxis_f(void) +{ + int axis; + + axis=cg_cuboidResizeAxis.integer; + if(!BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid) + return; + if(!CG_Argv(1)) + { + Com_Printf("cuboidAxis next|0|1|2 : set axis on which you want to resize your cuboid selection (0 - X, 1 - Y, 2 - Z)\n"); + return; + } + if(!Q_stricmp(CG_Argv(1),"next")) + axis++; + else + axis=atoi(CG_Argv(1)); + trap_Cvar_Set("cg_cuboidResizeAxis",va("%i",(axis+3)%3)); + trap_S_StartLocalSound(cgs.media.cuboidAxisChangeSound,CHAN_LOCAL_SOUND); +} + +/* +====================== +CG_CuboidAttack_f + +Replaces +attack/-attack. +If building a cuboid and the selection is somehow invalid, play an error sound. +Otherwise send the normal +attack / -attack; +====================== +*/ +void CG_CuboidAttack_f(void) +{ + if(BG_Buildable(cg.predictedPlayerState.stats[STAT_BUILDABLE]&~SB_VALID_TOGGLEBIT,NULL)->cuboid&&cg.forbidCuboids) + { + trap_S_StartLocalSound(cgs.media.cuboidErrorSound,CHAN_LOCAL_SOUND); + return; + } + trap_SendClientCommand(va("%s",CG_Argv(0))); +} + |