// cg_effects.c -- these functions generate localentities #include "cg_local.h" #include "cg_media.h" #include "fx_public.h" /* ==================== CG_MakeExplosion ==================== */ localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, qhandle_t hModel, int numFrames, qhandle_t shader, int msec, qboolean isSprite, float scale, int flags ) { float ang = 0; localEntity_t *ex; int offset; vec3_t tmpVec, newOrigin; if ( msec <= 0 ) { CG_Error( "CG_MakeExplosion: msec = %i", msec ); } // skew the time a bit so they aren't all in sync offset = rand() & 63; ex = CG_AllocLocalEntity(); if ( isSprite ) { ex->leType = LE_SPRITE_EXPLOSION; ex->refEntity.rotation = rand() % 360; ex->radius = scale; VectorScale( dir, 16, tmpVec ); VectorAdd( tmpVec, origin, newOrigin ); } else { ex->leType = LE_EXPLOSION; VectorCopy( origin, newOrigin ); // set axis with random rotate when necessary if ( !dir ) { AxisClear( ex->refEntity.axis ); } else { if ( !(flags & LEF_NO_RANDOM_ROTATE) ) ang = rand() % 360; VectorCopy( dir, ex->refEntity.axis[0] ); RotateAroundDirection( ex->refEntity.axis, ang ); } } ex->startTime = cg.time - offset; ex->endTime = ex->startTime + msec; // bias the time so all shader effects start correctly ex->refEntity.shaderTime = ex->startTime / 1000.0f; ex->refEntity.hModel = hModel; ex->refEntity.customShader = shader; ex->lifeRate = (float)numFrames / msec; ex->leFlags = flags; //Scale the explosion if (scale != 1) { ex->refEntity.nonNormalizedAxes = qtrue; VectorScale( ex->refEntity.axis[0], scale, ex->refEntity.axis[0] ); VectorScale( ex->refEntity.axis[1], scale, ex->refEntity.axis[1] ); VectorScale( ex->refEntity.axis[2], scale, ex->refEntity.axis[2] ); } // set origin VectorCopy ( newOrigin, ex->refEntity.origin); VectorCopy ( newOrigin, ex->refEntity.oldorigin ); ex->color[0] = ex->color[1] = ex->color[2] = 1.0; return ex; } // When calling this version, just pass in a zero for the flags localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, qhandle_t hModel, int numFrames, qhandle_t shader, int msec, qboolean isSprite, float scale ) { return CG_MakeExplosion( origin, dir, hModel, numFrames, shader, msec, isSprite, scale, 0 ); } /* ==================== CG_AddTempLight ==================== */ localEntity_t *CG_AddTempLight( vec3_t origin, float scale, vec3_t color, int msec ) { localEntity_t *ex; if ( msec <= 0 ) { CG_Error( "CG_AddTempLight: msec = %i", msec ); } ex = CG_AllocLocalEntity(); ex->leType = LE_LIGHT; ex->startTime = cg.time; ex->endTime = ex->startTime + msec; // set origin VectorCopy ( origin, ex->refEntity.origin); VectorCopy ( origin, ex->refEntity.oldorigin ); VectorCopy( color, ex->lightColor ); ex->light = scale; return ex; } /* ------------------------- CG_ExplosionEffects Used to find the player and shake the camera if close enough intensity ranges from 1 (minor tremble) to 16 (major quake) ------------------------- */ void CG_ExplosionEffects( vec3_t origin, int intensity, int radius) { //FIXME: When exactly is the vieworg calculated in relation to the rest of the frame?s vec3_t dir; float dist, intensityScale; float realIntensity; VectorSubtract( cg.refdef.vieworg, origin, dir ); dist = VectorNormalize( dir ); //Use the dir to add kick to the explosion if ( dist > radius ) return; intensityScale = 1 - ( dist / (float) radius ); realIntensity = intensity * intensityScale; CGCam_Shake( realIntensity, 750 ); // 500 seemed a bit too quick } /* ------------------------- CG_SmokeSpawn ------------------------- */ void CG_SmokeSpawn( vec3_t origin, vec3_t normal, vec3_t vel, vec3_t user ) { // This function will create directable smoke. CG_Smoke( origin, normal, 24.0f, 24.0f, cgs.media.smokeShader, FXF_USE_ALPHA_CHAN ); } /* ------------------------- CG_SurfaceExplosion Adds an explosion to a surface ------------------------- */ #define NUM_SPARKS 12 #define NUM_PUFFS 1 #define NUM_EXPLOSIONS 4 void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke ) { localEntity_t *le; FXTrail *particle; vec3_t direction, new_org; vec3_t velocity = { 0, 0, 0 }; vec3_t temp_org, temp_vel; float scale, dscale; int i, numSparks; //Sparks numSparks = 16 + (random() * 16.0f); for ( i = 0; i < numSparks; i++ ) { scale = 0.25f + (random() * 2.0f); dscale = -scale*0.5; particle = FX_AddTrail( origin, NULL, NULL, 32.0f, -64.0f, scale, -scale, 1.0f, 0.0f, 0.25f, 4000.0f, cgs.media.sparkShader, rand() & FXF_BOUNCE); if ( particle == NULL ) return; FXE_Spray( normal, 500, 150, 1.0f, 768 + (rand() & 255), (FXPrimitive *) particle ); } //Smoke //Move this out a little from the impact surface VectorMA( origin, 4, normal, new_org ); VectorSet( velocity, 0.0f, 0.0f, 16.0f ); for ( i = 0; i < 4; i++ ) { VectorSet( temp_org, new_org[0] + (crandom() * 16.0f), new_org[1] + (crandom() * 16.0f), new_org[2] + (random() * 4.0f) ); VectorSet( temp_vel, velocity[0] + (crandom() * 8.0f), velocity[1] + (crandom() * 8.0f), velocity[2] + (crandom() * 8.0f) ); FX_AddSprite( temp_org, temp_vel, NULL, 64.0f + (random() * 32.0f), 16.0f, 1.0f, 0.0f, 20.0f + (crandom() * 90.0f), 0.5f, 1500.0f, cgs.media.smokeShader, FXF_USE_ALPHA_CHAN ); } //Core of the explosion //Orient the explosions to face the camera VectorSubtract( cg.refdef.vieworg, origin, direction ); VectorNormalize( direction ); //Tag the last one with a light le = CG_MakeExplosion( origin, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 500, qfalse, radius * 0.02f + (random() * 0.3f) ); le->light = 150; VectorSet( le->lightColor, 0.9f, 0.8f, 0.5f ); for ( i = 0; i < NUM_EXPLOSIONS-1; i ++) { VectorSet( new_org, (origin[0] + (16 + (crandom() * 8))*crandom()), (origin[1] + (16 + (crandom() * 8))*crandom()), (origin[2] + (16 + (crandom() * 8))*crandom()) ); le = CG_MakeExplosion( new_org, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 300 + (rand() & 99), qfalse, radius * 0.05f + (crandom() *0.3f) ); } //Shake the camera CG_ExplosionEffects( origin, shake_speed, 350 ); // The level designers wanted to be able to turn the smoke spawners off. The rationale is that they // want to blow up catwalks and such that fall down...when that happens, it shouldn't really leave a mark // and a smoke spewer at the explosion point... if ( smoke ) { VectorMA( origin, -8, normal, temp_org ); FX_AddSpawner( temp_org, normal, NULL, NULL, 100, random()*25.0f, 5000.0f, (void *) CG_SmokeSpawn ); //Impact mark //FIXME: Replace mark //CG_ImpactMark( cgs.media.burnMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 8, qfalse ); } } /* ------------------------- CG_MiscModelExplosion Adds an explosion to a misc model breakables ------------------------- */ void CG_MiscModelExplosion( vec3_t origin, vec3_t normal ) { localEntity_t *le; FXTrail *particle; vec3_t direction, new_org; vec3_t velocity = { 0, 0, 0 }; vec3_t temp_org, temp_vel; float scale, dscale; int i, numSparks; //Sparks numSparks = 8 + (random() * 8.0f); for ( i = 0; i < numSparks; i++ ) { scale = 0.25f + (random() * 2.0f); dscale = -scale*0.5; particle = FX_AddTrail( origin, NULL, NULL, 32.0f, -64.0f, scale, -scale, 1.0f, 0.0f, 0.25f, 4000.0f, cgs.media.sparkShader, rand() & FXF_BOUNCE); if ( particle == NULL ) return; FXE_Spray( normal, 500, 150, 1.0f, 768 + (rand() & 255), (FXPrimitive *) particle ); } //Smoke //Move this out a little from the impact surface VectorMA( origin, 4, normal, new_org ); VectorScale( normal, -16, velocity ); for ( i = 0; i < 3; i++ ) { VectorSet( temp_org, new_org[0] + (crandom() * 8.0f), new_org[1] + (crandom() * 8.0f), new_org[2] + (random() * 8.0f) ); VectorSet( temp_vel, velocity[0] + (crandom() * 16.0f), velocity[1] + (crandom() * 16.0f), velocity[2] + (crandom() * 16.0f) ); FX_AddSprite( temp_org, temp_vel, NULL, 16.0f + (random() * 16.0f), 8.0f, 1.0f, 0.0f, 20.0f + (crandom() * 90.0f), 0.5f, 1000.0f + random() * 1000.0f, cgs.media.smokeShader, FXF_USE_ALPHA_CHAN ); } //Orient the explosions to face the camera VectorSubtract( cg.refdef.vieworg, origin, direction ); VectorNormalize( direction ); //Tag the last one with a light le = CG_MakeExplosion( origin, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 500, qfalse, 0.7 + crandom() * 0.3f ); le->light = 150; VectorSet( le->lightColor, 0.9f, 0.8f, 0.5f ); // for ( i = 0; i < 2; i ++) { VectorSet( new_org, origin[0] + crandom() * 16, origin[1] + crandom() * 16, origin[2] + crandom() * 16 ); le = CG_MakeExplosion( new_org, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 300 + (rand() & 99), qfalse, 0.7 + crandom() *0.3f ); } } /* ------------------------- CG_Chunks Fun chunk spewer ------------------------- */ void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, float speed, int numChunks, material_t chunkType, int customChunk, float baseScale ) { localEntity_t *le; refEntity_t *re; vec3_t dir; int i, j, k; int chunkModel=0, chunkSound; qboolean chunk = qfalse; switch(chunkType) { case MAT_METAL: case MAT_BORG: // FIXME: Precache sounds? chunkSound = cgs.media.borgChunkSound; break; case MAT_GLASS: chunkSound = cgs.media.glassChunkSound; break; case MAT_GLASS_METAL: chunkSound = cgs.media.chunkSound; break; case MAT_ELECTRICAL:// (sparks) cgi_S_StartSound (NULL, owner, CHAN_BODY, cgi_S_RegisterSound (va("sound/ambience/spark%d.wav", Q_irand(1, 6))) ); vectoangles(normal, dir); CG_Spark( origin, dir ); return; break; case MAT_ORGANIC:// (not implemented) case MAT_STASIS: chunkSound = cgs.media.stasisChunkSound; break; default: //Invalid material? Assume MAT_METAL? CG_Error( "CG_Chunks: Invalid chunk type" ); return; break; } if(baseScale <= 0) { baseScale = 1; } //FIXME: LOD //Chunks for ( i = 0; i < numChunks; i++ ) { if ( customChunk > 0 ) { // Try to use a custom chunk. if ( cgs.model_draw[customChunk] ) { chunk = qtrue; chunkModel = cgs.model_draw[customChunk]; } } if ( !chunk ) { // No custom chunk. Pick a random chunk type at run-time so we don't get the same chunks switch( chunkType ) { case MAT_GLASS: chunkModel = cgs.media.glassChunkModels[0][Q_irand(0, 5)]; break; case MAT_ORGANIC: // FIXME: These are actually metal chunks. chunkModel = cgs.media.chunkModels[0][Q_irand(0, 5)]; break; case MAT_BORG: chunkModel = cgs.media.borgChunkModels[0][Q_irand(0, 2)]; break; case MAT_STASIS: chunkModel = cgs.media.stasisChunkModels[0][Q_irand(0, 3)]; break; case MAT_GLASS_METAL: // Do some random chunk types if ( rand() & 1 ) { chunkModel = cgs.media.glassChunkModels[0][Q_irand(0, 5)]; } else { chunkModel = cgs.media.chunkModels[0][Q_irand(0, 5)]; } break; default: // Metal chunks, etc.. chunkModel = cgs.media.chunkModels[0][Q_irand(0, 5)]; break; } } le = CG_AllocLocalEntity(); re = &le->refEntity; le->leType = LE_FRAGMENT; le->endTime = cg.time + 2000; VectorCopy( origin, re->origin ); for ( j = 0; j < 3; j++ ) { re->origin[j] += crandom() * 12; } VectorCopy( re->origin, le->pos.trBase ); //Velocity for ( j = 0; j < 3; j ++ ) { dir[j] = normal[j] + Q_flrand(-0.8f, 0.8f); } // VectorNormalize(dir); VectorScale( dir, Q_flrand( speed * 0.25f, speed * 1.75f ), le->pos.trDelta ); //Angular Velocity VectorSet( le->angles.trBase, Q_flrand(0, 360), Q_flrand(0, 360), Q_flrand(0, 360) ); for ( j = 0; j < 3; j ++ ) { le->angles.trDelta[j] = Q_flrand(-24.0f, 24.0f); } VectorNormalize(le->angles.trDelta); VectorScale( le->angles.trDelta, Q_flrand(200.0f, 800.0f), le->angles.trDelta ); AxisCopy( axisDefault, re->axis ); le->radius = Q_flrand(baseScale * 0.7f, baseScale * 1.3f ); re->nonNormalizedAxes = qtrue; re->hModel = chunkModel; le->pos.trType = TR_GRAVITY; le->pos.trTime = cg.time; le->angles.trType = TR_INTERPOLATE; le->angles.trTime = cg.time; le->bounceFactor = 0.2f + random() * 0.2f; le->leFlags |= LEF_TUMBLE; le->ownerGentNum = owner; // Make sure that we have the desired start size set for( k = 0; k < 3; k++) { VectorScale(le->refEntity.axis[k], le->radius, le->refEntity.axis[k]); } } //Sound cgi_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, chunkSound ); } void CG_EnergyGibs( int owner, vec3_t origin ) { localEntity_t *le; refEntity_t *re; vec3_t dir; int i, j, k; int chunkModel=0; float baseScale = 0.7f; int numChunks = Q_irand( 13, 20 );//FIXME: LOD for ( i = 0; i < numChunks; i++ ) { if ( rand() & 1 ) { chunkModel = cgs.media.glassChunkModels[0][Q_irand(0, 5)]; } else { chunkModel = cgs.media.chunkModels[0][Q_irand(0, 5)]; } le = CG_AllocLocalEntity(); re = &le->refEntity; le->leType = LE_FRAGMENT; le->endTime = cg.time + 2000; VectorCopy( origin, re->origin ); for ( j = 0; j < 3; j++ ) { re->origin[j] += crandom() * 12; } VectorCopy( re->origin, le->pos.trBase ); //Velocity VectorSet( dir, crandom(), crandom(), crandom() ); VectorScale( dir, Q_flrand( 300, 500 ), le->pos.trDelta ); //Angular Velocity VectorSet( le->angles.trBase, crandom() * 360, crandom() * 360, crandom() * 360 ); VectorSet( le->angles.trDelta, crandom() * 90, crandom() * 90, crandom() * 90 ); AxisCopy( axisDefault, re->axis ); le->radius = Q_flrand(baseScale * 0.7f, baseScale * 1.3f ); re->nonNormalizedAxes = qtrue; re->hModel = chunkModel; re->customShader = cgs.media.quantumDisruptorShader; re->shaderTime = cg.time/1000.0f; le->pos.trType = TR_GRAVITY; le->pos.trTime = cg.time; le->angles.trType = TR_INTERPOLATE; le->angles.trTime = cg.time; le->bounceFactor = 0.3f + random() * 0.3f; le->leFlags |= LEF_TUMBLE; le->ownerGentNum = owner; re->shaderRGBA[0] = re->shaderRGBA[1] = re->shaderRGBA[2] = re->shaderRGBA[3] = 255; // Make sure that we have the desired start size set for( k = 0; k < 3; k++) { VectorScale(le->refEntity.axis[k], le->radius, le->refEntity.axis[k]); } } }