/* Copyright (C) 2002-2003 Victor Luchits This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "cg_local.h" void CG_UpdateEntities( void ); /* ================== CG_FixVolumeCvars Don't let the user go too far away with volumes ================== */ void CG_FixVolumeCvars( void ) { if( developer->integer ) return; if( cg_volume_players->value < 0.0f ) trap_Cvar_SetValue( "cg_volume_players", 0.0f ); else if( cg_volume_players->value > 2.0f ) trap_Cvar_SetValue( "cg_volume_players", 2.0f ); if( cg_volume_effects->value < 0.0f ) trap_Cvar_SetValue( "cg_volume_effects", 0.0f ); else if( cg_volume_effects->value > 2.0f ) trap_Cvar_SetValue( "cg_volume_effects", 2.0f ); if( cg_volume_announcer->value < 0.0f ) trap_Cvar_SetValue( "cg_volume_announcer", 0.0f ); else if( cg_volume_announcer->value > 2.0f ) trap_Cvar_SetValue( "cg_volume_announcer", 2.0f ); #ifdef VSAYS if( cg_volume_voicechats->value < 0.0f ) trap_Cvar_SetValue( "cg_volume_voicechats", 0.0f ); else if( cg_volume_voicechats->value > 2.0f ) trap_Cvar_SetValue( "cg_volume_voicechats", 2.0f ); #endif if( cg_volume_hitsound->value < 0.0f ) trap_Cvar_SetValue( "cg_volume_hitsound", 0.0f ); else if( cg_volume_hitsound->value > 10.0f ) trap_Cvar_SetValue( "cg_volume_hitsound", 10.0f ); } /* ================== CG_FireEvents ================== */ void CG_FireEvents( void ) { int pnum; entity_state_t *state; for( pnum = 0; pnum < cg.frame.numEntities; pnum++ ) { state = &cg.frame.parsedEntities[pnum&(MAX_PARSE_ENTITIES-1)]; if( state->events[0] ) CG_EntityEvent( state ); } } //================== //CG_NewPacketEntityState //================== void CG_NewPacketEntityState( entity_state_t *state ) { centity_t *cent; cent = &cg_entities[state->number]; // some data changes will force no lerping if( state->modelindex != cent->current.modelindex || state->modelindex2 != cent->current.modelindex2 || abs(state->origin[0] - cent->current.origin[0]) > 512 || abs(state->origin[1] - cent->current.origin[1]) > 512 || abs(state->origin[2] - cent->current.origin[2]) > 512 || state->events[0] == EV_TELEPORT || state->events[1] == EV_TELEPORT ) { cent->serverFrame = -99; } if( cent->serverFrame != cg.frame.serverFrame - 1 ) { // wasn't in last update, so initialize some things // duplicate the current state so lerping doesn't hurt anything cent->prev = *state; if ( state->events[0] == EV_TELEPORT || state->events[1] == EV_TELEPORT ) { VectorCopy( state->origin, cent->prev.origin ); VectorCopy( state->origin, cent->trailOrigin ); } else { VectorCopy( state->old_origin, cent->prev.origin ); VectorCopy( state->old_origin, cent->trailOrigin ); } //splitmodels:(jalPVSfix) Init the animation when new into PVS if ( cg.frame.valid && state->type == ET_PLAYER ) { CG_ClearEventAnimations( state->number ); CG_AddPModelAnimation( state->number, (state->frame)&0x3F, (state->frame>>6)&0x3F, (state->frame>>12)&0xF, BASIC_CHANNEL); } } else { // shuffle the last state to previous cent->prev = cent->current; } cent->serverFrame = cg.frame.serverFrame; cent->current = *state; } //================== //CG_NewFrameSnap // a new frame snap has been received from the server //================== void CG_NewFrameSnap( frame_t *frame, frame_t *deltaframe ) { int i; if( deltaframe ) { cg.oldFrame = *deltaframe; } else { cg.oldFrame = *frame; } cg.frame = *frame; cg.time = cg.frame.serverTime; for( i = 0; i < frame->numEntities; i++ ) { CG_NewPacketEntityState( &frame->parsedEntities[i & (MAX_PARSE_ENTITIES-1)] ); } // a new server frame begins now CG_FixVolumeCvars(); // wsw : jal CG_BuildSolidList(); CG_UpdateEntities(); //wsw : jal CG_vWeapUpdateState(); //splitmodels CG_FireEvents(); CG_CheckWeaponState(); // wsw : mdr CG_FirePlayerStateEvents(); // wsw : jal CG_CheckPredictionError(); } //============================================================= /* ========================================================================== ADD INTERPOLATED ENTITIES TO RENDERING LIST ========================================================================== */ //=============== //CG_EntAddBobEffect //=============== void CG_EntAddBobEffect( centity_t *cent ) { static float scale; static float bob; // bobbing items //if( !(cent->effects & EF_ROTATE_AND_BOB) ) // return; scale = 0.005f + cent->current.number * 0.00001f; bob = 4 + cos( (cg.time + 1000) * scale ) * 4; cent->ent.oldorigin[2] += bob; cent->ent.origin[2] += bob; cent->ent.lightingOrigin[2] += bob; } //========================================================================== // ET_GENERIC //========================================================================== //=============== //CG_UpdateGenericEnt //=============== void CG_UpdateGenericEnt( centity_t *cent ) { // start from clean memset( ¢->ent, 0, sizeof( cent->ent ) ); cent->ent.scale = 1.0f; cent->ent.flags = cent->renderfx; // make all of white color by default Vector4Set( cent->ent.color, 255, 255, 255, 255 ); if( cent->effects & EF_OUTLINE ) Vector4Set( cent->outlineColor, 0, 0, 0, 255 ); // set frame cent->ent.frame = cent->current.frame; cent->ent.oldframe = cent->prev.frame; // set up the model cent->ent.rtype = RT_MODEL; if( cent->current.solid == SOLID_BMODEL ) { cent->ent.model = cgs.inlineModelDraw[cent->current.modelindex]; } else { cent->ent.skinnum = cent->current.skinnum; cent->ent.model = cgs.modelDraw[cent->current.modelindex]; } // copy, not interpolated, starting positions (oldframe ones) cent->ent.backlerp = 1.0f; VectorCopy( cent->prev.origin, cent->ent.origin ); VectorCopy( cent->prev.origin, cent->ent.oldorigin ); VectorCopy( cent->prev.origin, cent->ent.lightingOrigin ); if( cent->prev.angles[0] || cent->prev.angles[1] || cent->prev.angles[2] ) AnglesToAxis( cent->prev.angles, cent->ent.axis ); else Matrix_Copy( axis_identity, cent->ent.axis ); //relink entity boneposes to cg_entity ones CG_RegisterBoneposesForCGEntity( cent, cent->ent.model ); cent->ent.boneposes = cent->ent.oldboneposes = centBoneposes[cent->current.number].lerpboneposes; } //=============== //CG_LerpGenericEnt //=============== void CG_LerpGenericEnt( centity_t *cent ) { int i; vec3_t ent_angles = { 0, 0, 0 }; cent->ent.backlerp = 1.0f - cg.lerpfrac; if( cent->renderfx & RF_FRAMELERP ) { // let the renderer do the origin interpolation VectorCopy( cent->current.origin, cent->ent.origin ); VectorCopy( cent->current.old_origin, cent->ent.oldorigin ); } else { // interpolate origin from old state for( i = 0; i < 3; i++ ) { cent->ent.origin[i] = cent->ent.oldorigin[i] = cent->prev.origin[i] + cg.lerpfrac * (cent->current.origin[i] - cent->prev.origin[i]); } } VectorCopy( cent->ent.origin, cent->ent.lightingOrigin ); // interpolate angles for( i = 0; i < 3; i++ ) ent_angles[i] = LerpAngle( cent->prev.angles[i], cent->current.angles[i], cg.lerpfrac ); if( ent_angles[0] || ent_angles[1] || ent_angles[2] ) AnglesToAxis( ent_angles, cent->ent.axis ); else Matrix_Copy( axis_identity, cent->ent.axis ); } //=============== //CG_AddGenericEnt //=============== void CG_AddGenericEnt( centity_t *cent ) { if( !cent->ent.scale ) return; // if set to invisible, skip if( !cent->current.modelindex ) return; // bobbing & auto-rotation if( cent->effects & EF_ROTATE_AND_BOB ) { CG_EntAddBobEffect( cent ); Matrix_Copy( cg.autorotateAxis, cent->ent.axis ); } // render effects if( cent->renderfx & (RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE) ) { cent->ent.flags = cent->renderfx & RF_MINLIGHT; // renderfx go on color shell entity } else { cent->ent.flags = cent->renderfx; } if( cent->item ) { cent->ent.flags |= cent->item->renderfx; if( cent->effects & EF_AMMOBOX ) { // find out the ammo box color if( cent->item->color && strlen(cent->item->color) > 1 ) { vec4_t scolor; Vector4Copy( color_table[ColorIndex(cent->item->color[1])], scolor ); cent->ent.color[0] = ( qbyte )( 255 * scolor[0] ); cent->ent.color[1] = ( qbyte )( 255 * scolor[1] ); cent->ent.color[2] = ( qbyte )( 255 * scolor[2] ); cent->ent.color[3] = ( qbyte )( 255 * scolor[3] ); } else // set white Vector4Set( cent->ent.color, 255, 255, 255, 255 ); } #ifdef CGAMEGETLIGHTORIGIN // add shadows for items (do it before offseting for weapons) if( !(cent->ent.flags & RF_NOSHADOW) ) CG_AllocShadeBox( cent->current.number, cent->ent.origin, item_box_mins, item_box_maxs, NULL ); #endif // offset weapon items by their special tag if( cent->item->type & IT_WEAPON ) { CG_PlaceModelOnTag( ¢->ent, ¢->ent, &cgs.weaponItemTag ); } } // add to refresh list CG_SetBoneposesForCGEntity( ¢->ent, cent ); // skelmod CG_AddEntityToScene( ¢->ent ); // skelmod // shells generate a separate entity for the main model CG_AddCentityOutLineEffect( cent ); CG_AddColorShell( ¢->ent, cent->renderfx ); cent->ent.customSkin = NULL; cent->ent.customShader = NULL; // never use a custom skin on others Vector4Set( cent->ent.color, 255, 255, 255, 255 ); // if it's a rocket, add a second model with the flare if( cent->type == ET_ROCKET ) { struct model_s *model1 = cent->ent.model; cent->ent.model = CG_MediaModel( cgs.media.modRocketFlare ); CG_SetBoneposesForCGEntity( ¢->ent, cent ); // skelmod CG_AddEntityToScene( ¢->ent ); cent->ent.model = model1; } // duplicate for linked models if( cent->current.modelindex2 ) { struct model_s *model1 = cent->ent.model; if( cent->item ) { if( cent->item->type & IT_WEAPON ) { // at this point ent still has the first modelindex model orientation_t tag; if( CG_GrabTag( &tag, ¢->ent, "tag_barrel" ) ) CG_PlaceModelOnTag( ¢->ent, ¢->ent, &tag ); } if( cent->effects & EF_AMMOBOX ) { // special ammobox icon cent->ent.customShader = trap_R_RegisterPic( cent->item->icon ); } } cent->ent.model = cgs.modelDraw[cent->current.modelindex2]; CG_AddEntityToScene( ¢->ent ); // skelmod cent->ent.customShader = NULL; //recover the model cent->ent.model = model1; } } //========================================================================== // ET_FLAG_BASE //========================================================================== //=============== //CG_AddFlagModelOnTag //=============== void CG_AddFlagModelOnTag( centity_t *cent, int flag_team, char *tagname ) { static entity_t flag; static vec4_t teamcolor; orientation_t tag; if( !(cent->effects & EF_ENEMY_FLAG) ) return; // fixme?: only 2 teams, red and blue, are considered if( cent->current.team != TEAM_BLUE && cent->current.team != TEAM_RED ) return; GS_TeamColor( flag_team, teamcolor ); memset( &flag, 0, sizeof(entity_t) ); flag.model = trap_R_RegisterModel( PATH_FLAG_MODEL ); if( !flag.model ) { return; } flag.rtype = RT_MODEL; flag.scale = 1.0f; flag.flags = cent->ent.flags; flag.customShader = NULL; flag.customSkin = NULL; if( cent->ent.flags & RF_VIEWERMODEL ) { VectorCopy( cg.refdef.vieworg, flag.origin ); VectorCopy( cg.refdef.vieworg, flag.oldorigin ); VectorCopy( cg.refdef.vieworg, flag.lightingOrigin ); Matrix_Copy( axis_identity, flag.axis ); VectorMA( flag.origin, -24, flag.axis[0], flag.origin ); // move it some backwards VectorMA( flag.origin, 64, flag.axis[2], flag.origin ); // move it some upwards } else { VectorCopy( cent->ent.origin, flag.origin ); VectorCopy( cent->ent.origin, flag.oldorigin ); VectorCopy( cent->ent.origin, flag.lightingOrigin ); Matrix_Copy( cent->ent.axis, flag.axis ); // place the flag on the tag if available if( tagname && CG_GrabTag( &tag, ¢->ent, tagname ) ) { CG_PlaceModelOnTag( &flag, ¢->ent, &tag ); } CG_AddEntityToScene( &flag ); CG_AddColoredOutLineEffect( &flag, EF_OUTLINE, (qbyte)( 255*(teamcolor[0]*0.3f) ), (qbyte)( 255*(teamcolor[1]*0.3f) ), (qbyte)( 255*(teamcolor[2]*0.3f) ), 255 ); // add the light & energy effects if( CG_GrabTag( &tag, &flag, "tag_color" ) ) { CG_PlaceModelOnTag( &flag, &flag, &tag ); } if( !(cent->ent.flags & RF_VIEWERMODEL) ) { flag.rtype = RT_SPRITE; flag.model = NULL; flag.flags = RF_NOSHADOW|RF_FULLBRIGHT; flag.frame = flag.oldframe = 0; flag.radius = 32.0f; flag.customShader = CG_MediaShader( cgs.media.shaderFlagFlare ); flag.color[0] = ( qbyte )( 255 * teamcolor[0] ); flag.color[1] = ( qbyte )( 255 * teamcolor[1] ); flag.color[2] = ( qbyte )( 255 * teamcolor[2] ); flag.color[3] = ( qbyte )( 255 * teamcolor[3] ); CG_AddEntityToScene( &flag ); } } // if on a player, flag drops colored particles and lights up if( cent->current.type == ET_PLAYER ) { CG_AddLightToScene( flag.origin, 350, teamcolor[0], teamcolor[1], teamcolor[2], NULL ); if( cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] + FLAG_TRAIL_DROP_DELAY < cg.time ) { cent->localEffects[LOCALEFFECT_TRAIL_LAST_DROP] = cg.time; CG_FlagTrail( flag.origin, cent->trailOrigin, cent->ent.origin, teamcolor[0], teamcolor[1], teamcolor[2] ); } } } //=============== //CG_UpdateFlagBaseEnt //=============== void CG_UpdateFlagBaseEnt( centity_t *cent ) { // start from clean memset( ¢->ent, 0, sizeof( cent->ent ) ); Vector4Set( cent->ent.color, 255, 255, 255, 255 ); cent->ent.scale = 1.0f; cent->ent.flags = cent->renderfx; cent->item = GS_FindItemByTag( cent->current.skinnum ); if( cent->item ) { cent->effects |= cent->item->effects; } cent->ent.rtype = RT_MODEL; cent->ent.frame = cent->current.frame; cent->ent.oldframe = cent->prev.frame; // set up the model if( cent->current.solid == SOLID_BMODEL ) { cent->ent.model = cgs.inlineModelDraw[cent->current.modelindex]; } else { cent->ent.model = cgs.modelDraw[cent->current.modelindex]; } if( cent->effects & EF_OUTLINE ) { vec4_t teamcolor; GS_TeamColor( cent->current.team, teamcolor ); CG_SetOutlineColor( cent->outlineColor, teamcolor ); } // copy, not interpolated, starting positions (oldframe ones) cent->ent.backlerp = 1.0f; VectorCopy( cent->prev.origin, cent->ent.origin ); VectorCopy( cent->prev.origin, cent->ent.oldorigin ); VectorCopy( cent->prev.origin, cent->ent.lightingOrigin ); if( cent->prev.angles[0] || cent->prev.angles[1] || cent->prev.angles[2] ) AnglesToAxis( cent->prev.angles, cent->ent.axis ); else Matrix_Copy( axis_identity, cent->ent.axis ); //relink entity boneposes to cg_entity ones CG_RegisterBoneposesForCGEntity( cent, cent->ent.model ); cent->ent.boneposes = cent->ent.oldboneposes = centBoneposes[cent->current.number].lerpboneposes; } //=============== //CG_AddFlagBaseEnt //=============== static void CG_AddFlagBaseEnt( centity_t *cent ) { if( !cent->ent.scale ) return; // if set to invisible, skip if( !cent->current.modelindex ) return; // bobbing & auto-rotation if( cent->effects & EF_ROTATE_AND_BOB ) { CG_EntAddBobEffect( cent ); Matrix_Copy( cg.autorotateAxis, cent->ent.axis ); } // render effects if( cent->renderfx & (RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE) ) { cent->ent.flags = cent->renderfx & RF_MINLIGHT; // renderfx go on color shell entity } else { cent->ent.flags = cent->renderfx; } if( cent->item ) cent->ent.flags |= cent->item->renderfx; // let's see: We add first the modelindex 1 (the base) // add to refresh list CG_SetBoneposesForCGEntity( ¢->ent, cent ); // skelmod CG_AddEntityToScene( ¢->ent ); // skelmod //CG_DrawTestBox( cent->ent.origin, item_box_mins, item_box_maxs ); // shells generate a separate entity for the main model CG_AddCentityOutLineEffect( cent ); CG_AddColorShell( ¢->ent, cent->renderfx ); #ifdef CGAMEGETLIGHTORIGIN // no light for flag bases //if( !(cent->ent.flags & RF_NOSHADOW) ) // CG_AllocShadeBox( cent->current.number, cent->ent.origin, item_box_mins, item_box_maxs, NULL ); #endif cent->ent.customSkin = NULL; cent->ent.customShader = NULL; // never use a custom skin on others Vector4Set( cent->ent.color, 255, 255, 255, 255 ); // see if we have to add a flag if( cent->effects & EF_ENEMY_FLAG ) CG_AddFlagModelOnTag( cent, cent->current.team, "tag_flag1" ); else { // add countdown value as a sprite int charcount = 1 + ( cent->current.modelindex2 > 9 ); // can never be > 99 if( charcount == 1 ) { // we only draw from 9 down static entity_t number; number = cent->ent; number.rtype = RT_SPRITE; number.origin[2] += 24; number.oldorigin[2] += 24; number.model = NULL; number.radius = 12; number.customShader = CG_MediaShader(cgs.media.sbNums[cent->current.modelindex2]); CG_AddEntityToScene( &number ); } } } //========================================================================== // ET_PLAYER //========================================================================== //=============== //CG_UpdatePlayerModelEnt //=============== void CG_UpdatePlayerModelEnt( centity_t *cent ); //=============== //CG_AddPlayerEnt //ET_PLAYER entities can only draw as player models //=============== void CG_AddPlayerEnt( centity_t *cent ) { // render effects if( cent->renderfx & (RF_SHELL_GREEN | RF_SHELL_RED | RF_SHELL_BLUE) ) cent->ent.flags = RF_MINLIGHT; // renderfx go on color shell entity else cent->ent.flags = cent->renderfx | RF_MINLIGHT; if( cent->current.number == cg.chasedNum + 1 ) { cg.effects = cent->effects; if( !cg.thirdPerson && cent->current.modelindex ) cent->ent.flags |= RF_VIEWERMODEL; // only draw from mirrors } // if set to invisible, skip if( !cent->current.modelindex ) return; CG_AddPModel( cent ); cent->ent.customSkin = NULL; cent->ent.customShader = NULL; // never use a custom skin on others cent->ent.skinnum = 0; cent->ent.flags = cent->ent.flags & RF_VIEWERMODEL; // only draw from mirrors Vector4Set( cent->ent.color, 255, 255, 255, 255 ); // corpses can never have a model in modelindex2 if( cent->effects & EF_CORPSE ) return; // duplicate for linked models if( cent->current.modelindex2 ) { cent->ent.model = cgs.modelDraw[cent->current.modelindex2]; CG_AddEntityToScene( ¢->ent ); // skelmod CG_AddShellEffects( ¢->ent, cent->effects ); CG_AddColorShell( ¢->ent, cent->renderfx ); } } //========================================================================== // ET_ITEM //========================================================================== //=============== //CG_UpdateItemEnt //=============== void CG_UpdateItemEnt( centity_t *cent ) { memset( ¢->ent, 0, sizeof( cent->ent ) ); Vector4Set( cent->ent.color, 255, 255, 255, 255 ); cent->item = GS_FindItemByTag( cent->current.skinnum ); if( !cent->item ) return; cent->effects |= cent->item->effects; if( cg_simpleItems->integer && cent->item->tag != FLAG_RED && cent->item->tag != FLAG_BLUE && cent->item->tag != FLAG_YELLOW && cent->item->tag != FLAG_GREEN ) { cent->ent.rtype = RT_SPRITE; cent->ent.model = NULL; cent->ent.flags = RF_NOSHADOW|RF_FULLBRIGHT; cent->ent.frame = cent->ent.oldframe = 0; cent->ent.radius = cg_simpleItemsSize->value <= 32 ? cg_simpleItemsSize->value : 32; if( cent->ent.radius < 1.0f ) cent->ent.radius = 1.0f; if( cg_simpleItems->integer == 2 ) cent->effects &= ~EF_ROTATE_AND_BOB; cent->ent.customShader = NULL; cent->ent.customShader = trap_R_RegisterPic( cent->item->icon ); } else { cent->ent.rtype = RT_MODEL; cent->ent.frame = cent->current.frame; cent->ent.oldframe = cent->prev.frame; if( cent->effects & EF_OUTLINE ) { if( !cent->item->color || strlen(cent->item->color) < 2 ) { Vector4Set( cent->outlineColor, 0, 0, 0, 255 ); // black } else if( !cg_outlineItemsBlack->integer ) { CG_SetOutlineColor( cent->outlineColor, color_table[ColorIndex(cent->item->color[1])] ); } else // black Vector4Set( cent->outlineColor, 0, 0, 0, 255 ); } // set up the model if( cent->current.solid == SOLID_BMODEL ) { cent->ent.model = cgs.inlineModelDraw[cent->current.modelindex]; } else { cent->ent.model = cgs.modelDraw[cent->current.modelindex]; } } // copy, not interpolated, starting positions (oldframe ones) cent->ent.backlerp = 1.0f; VectorCopy( cent->prev.origin, cent->ent.origin ); VectorCopy( cent->prev.origin, cent->ent.oldorigin ); VectorCopy( cent->prev.origin, cent->ent.lightingOrigin ); if( cent->prev.angles[0] || cent->prev.angles[1] || cent->prev.angles[2] ) AnglesToAxis( cent->prev.angles, cent->ent.axis ); else Matrix_Copy( axis_identity, cent->ent.axis ); //relink entity boneposes to cg_entity ones CG_RegisterBoneposesForCGEntity( cent, cent->ent.model ); cent->ent.boneposes = cent->ent.oldboneposes = centBoneposes[cent->current.number].lerpboneposes; } //=============== //CG_AddItemEnt //=============== void CG_AddItemEnt( centity_t *cent ) { int msec; if( !cent->item ) return; // respawning items if( cent->respawnTime ) msec = cg.time - cent->respawnTime; else msec = ITEM_RESPAWN_TIME; if( msec >= 0 && msec < ITEM_RESPAWN_TIME ) cent->ent.scale = (float)msec / ITEM_RESPAWN_TIME; else cent->ent.scale = 1.0f; if( cent->ent.rtype != RT_SPRITE ) { // weapons are special if( cent->item && cent->item->type & IT_WEAPON ) cent->ent.scale *= 1.5f; //flags are special if( cent->effects & EF_ENEMY_FLAG ) { CG_AddFlagModelOnTag( cent, cent->current.team, NULL ); return; } CG_AddGenericEnt( cent ); return; } //offset the item origin up cent->ent.origin[2] += cent->ent.radius + 2; cent->ent.oldorigin[2] += cent->ent.radius + 2; if( cent->effects & EF_ROTATE_AND_BOB ) CG_EntAddBobEffect( cent ); Matrix_Identity( cent->ent.axis ); CG_AddEntityToScene( ¢->ent ); // skelmod } //========================================================================== // ET_BEAM //========================================================================== //=============== //CG_AddBeamEnt //=============== void CG_AddBeamEnt( centity_t *cent ) { CG_AddLaser( cent->current.origin, cent->current.origin2, cent->current.frame * 0.5f, cent->current.colorRGBA, CG_MediaShader( cgs.media.shaderLaser ) ); } //========================================================================== // ET_LASERBEAM //========================================================================== //=============== //CG_UpdateLaserbeamEnt - lasegun's continuous beam //=============== void CG_UpdateLaserbeamEnt( centity_t *cent ) { memset( ¢->ent, 0, sizeof( cent->ent ) ); Vector4Set( cent->ent.color, 255, 255, 255, 255 ); //cent->ent.model = cgs.modelDraw[cent->current.modelindex]; cent->ent.model = NULL; } //=============== //CG_LerpLaserbeamEnt - lasegun's continuous beam //=============== void CG_LerpLaserbeamEnt( centity_t *cent ) { } //=============== //CG_AddLaserbeamEnt - lasegun's continuous beam //=============== void CG_AddLaserbeamEnt( centity_t *cent ) { centity_t *owner; orientation_t projectsource; vec3_t dir; int i; int range = cent->current.skinnum; // was player updated this frame? owner = &cg_entities[ cent->current.ownerNum ]; if( (cent->current.ownerNum == cg.chasedNum+1) && (owner->serverFrame == cg.frame.serverFrame) ) { vec3_t fireorg, end, front; trace_t trace; if( !cg.thirdPerson && cg_predict->integer && cg_predictLaserBeam->value ) { vec3_t front_refdef; if( cg_predictLaserBeam->value < 0 || cg_predictLaserBeam->value > 1 ) trap_Cvar_Set( "cg_predictLaserBeam", "1" ); for( i = 0; i < 3; i++ ) { fireorg[i] = cg_predictLaserBeam->value * cg.refdef.vieworg[i] + (1 - cg_predictLaserBeam->value) * (cg.player.origin[i] + cg.player.viewoffset[i]); } AngleVectors( cg.refdef.viewangles, front_refdef, NULL, NULL ); AngleVectors( cg.player.viewangles, front, NULL, NULL ); for( i = 0; i < 3; i++ ) { front[i] = cg_predictLaserBeam->value * front_refdef[i] + (1 - cg_predictLaserBeam->value) * front[i]; } } else { // use only playerstate values for( i = 0; i < 3; i++ ) { fireorg[i] = cg.player.origin[i] + cg.player.viewoffset[i]; } AngleVectors( cg.player.viewangles, front, NULL, NULL ); } VectorNormalizeFast( front ); VectorMA( fireorg, range, front, end ); CG_Trace( &trace, fireorg, vec3_origin, vec3_origin, end, cg.chasedNum+1, MASK_SHOT ); VectorCopy( trace.endpos, cent->ent.origin ); // for the actual drawing, we use weapon projection source if available if( !CG_PModel_GetProjectionSource( cent->current.ownerNum, &projectsource ) ) VectorCopy( fireorg, projectsource.origin ); } else { // interpolate both origins from old states for( i = 0; i < 3; i++ ) { cent->ent.origin[i] = cent->prev.origin[i] + cg.lerpfrac * (cent->current.origin[i] - cent->prev.origin[i]); } // for the actual drawing, we use weapon projection source if available if( !CG_PModel_GetProjectionSource( cent->current.ownerNum, &projectsource ) ) { for( i = 0; i < 3; i++ ) { projectsource.origin[i] = cent->prev.origin2[i] + cg.lerpfrac * (cent->current.origin2[i] - cent->prev.origin2[i]); } } } // find dir from final beam for particle effects VectorSubtract( projectsource.origin, cent->ent.origin, dir ); VectorNormalizeFast( dir ); if( DistanceFast(cent->current.origin, cent->current.origin2) < (range-1) ) { CG_NewElectroBeamPuff( cent, cent->ent.origin, dir ); //CG_ImpactPufParticles( cent->ent.origin, dir, 1, 1.5f, 0.9f, 0.9f, 0.5f, 1.0f, NULL ); //light on impact CG_AddLightToScene( cent->ent.origin, 100, 1.0f, 1.0f, 0.5f, NULL ); } if( !(CG_PointContents(projectsource.origin) & MASK_SOLID) ) CG_LaserGunPolyBeam( projectsource.origin, cent->ent.origin ); //enable continuous flash on the weapon if( cg_weaponFlashes->integer ) { pmodel_t *pmodel = &cg_entPModels[cent->current.ownerNum]; pmodel->pweapon.flashtime = cg.time + (cg.frame.serverTime - cg.oldFrame.serverTime); if( cent->current.ownerNum == cg.chasedNum + 1 ) vweap.pweapon.flashtime = cg.time + cg.frameTime; } //light on weapon CG_AddLightToScene( projectsource.origin, 150, 1.0f, 1.0f, 0.5f, NULL ); if( cent->current.sound ) { trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], owner->current.origin, cg_volume_effects->value, (owner->current.number != cg.chasedNum + 1) ); } } //=============== //CG_AddCurveLaserbeamEnt - lasegun's continuous curved beam //=============== void CG_AddCurveLaserbeamEnt( centity_t *cent ) { centity_t *owner; orientation_t projectsource; int i; int range = cent->current.skinnum; vec3_t viewangles; vec3_t fireorg, front, dir; vec3_t aim_end; // was player updated this frame? owner = &cg_entities[ cent->current.ownerNum ]; if( (cent->current.ownerNum == cg.chasedNum+1) && (owner->serverFrame == cg.frame.serverFrame) ) { if( !cg.thirdPerson && cg_predict->integer && cg_predictLaserBeam->value ) { vec3_t front_refdef; if( cg_predictLaserBeam->value < 0 || cg_predictLaserBeam->value > 1 ) trap_Cvar_Set( "cg_predictLaserBeam", "1" ); for( i = 0; i < 3; i++ ) { fireorg[i] = cg_predictLaserBeam->value * cg.refdef.vieworg[i] + (1 - cg_predictLaserBeam->value) * (cg.player.origin[i] + cg.player.viewoffset[i]); } AngleVectors( cg.refdef.viewangles, front_refdef, NULL, NULL ); AngleVectors( cg.player.viewangles, front, NULL, NULL ); for( i = 0; i < 3; i++ ) { //viewangles[i] = LerpAngle( cg.player.viewangles[i], cg.refdef.viewangles[i], cg_predictLaserBeam->value ); //viewangles[i] = LerpAngle( cg.refdef.viewangles[i], cg.player.viewangles[i], cg_predictLaserBeam->value ); viewangles[i] = cg.refdef.viewangles[i]; //front[i] = cg_predictLaserBeam->value * front_refdef[i] + (1 - cg_predictLaserBeam->value) * front[i]; } } else { // use only playerstate values for( i = 0; i < 3; i++ ) { fireorg[i] = cg.player.origin[i] + cg.player.viewoffset[i]; } //AngleVectors( cg.player.viewangles, front, NULL, NULL ); VectorCopy( cg.player.viewangles, viewangles ); } //VectorMA( fireorg, range, front, end ); //CG_Trace( &trace, fireorg, vec3_origin, vec3_origin, end, cg.chasedNum+1, MASK_SHOT ); //VectorCopy( trace.endpos, cent->ent.origin ); // for the actual drawing, we use weapon projection source if available if( !CG_PModel_GetProjectionSource( cent->current.ownerNum, &projectsource ) ) VectorCopy( fireorg, projectsource.origin ); } else { // for the actual drawing, we use weapon projection source if available if( !CG_PModel_GetProjectionSource( cent->current.ownerNum, &projectsource ) ) { for( i = 0; i < 3; i++ ) { projectsource.origin[i] = cent->prev.origin2[i] + cg.lerpfrac * (cent->current.origin2[i] - cent->prev.origin2[i]); } } // use the angles value in the entity for( i = 0; i < 3; i++ ) viewangles[i] = LerpAngle( cent->prev.angles[i], cent->current.angles[i], cg.lerpfrac ); } AngleVectors( viewangles, front, NULL, NULL ); VectorNormalizeFast( front ); VectorMA( projectsource.origin, range, front, aim_end ); // interpolate both origins from old states for( i = 0; i < 3; i++ ) { cent->ent.origin[i] = cent->prev.origin[i] + cg.lerpfrac * (cent->current.origin[i] - cent->prev.origin[i]); } // find dir from final beam for particle effects VectorSubtract( cent->ent.origin, projectsource.origin, dir ); VectorNormalizeFast( dir ); // ok, we have a final start and end points, find out the curve // let's draw it server side { int j; float frac; vec3_t impactangles, tmpangles; vec3_t segmentStart, segmentEnd; //VectorSubtract( end, start, dir ); VecToAngles( dir, impactangles ); if( cg_laserBeamSubdivisions->integer < 3 ) trap_Cvar_SetValue( "cg_laserBeamSubdivisions", 3 ); VectorCopy( projectsource.origin, segmentStart ); for( i = 1; i <= cg_laserBeamSubdivisions->integer; i++ ) { frac = ( ((float)range/cg_laserBeamSubdivisions->value)*(float)i ) / (float)range; for( j = 0; j < 3; j++ )tmpangles[j] = LerpAngle( viewangles[j], impactangles[j], frac ); AngleVectors( tmpangles, dir, NULL, NULL ); VectorMA( projectsource.origin, range*frac, dir, segmentEnd ); //segment is ready here CG_LaserGunPolyBeam( segmentStart, segmentEnd ); VectorCopy( segmentEnd, segmentStart ); } } //enable continuous flash on the weapon if( cg_weaponFlashes->integer ) { pmodel_t *pmodel = &cg_entPModels[cent->current.ownerNum]; pmodel->pweapon.flashtime = cg.time + (cg.frame.serverTime - cg.oldFrame.serverTime); if( cent->current.ownerNum == cg.chasedNum + 1 ) vweap.pweapon.flashtime = cg.time + cg.frameTime; } if( cent->current.sound ) { trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], owner->current.origin, cg_volume_effects->value, (owner->current.number != cg.chasedNum + 1) ); } } //========================================================================== // ET_PORTALSURFACE //========================================================================== //=============== //CG_UpdatePortalSurfaceEnt //=============== void CG_UpdatePortalSurfaceEnt( centity_t *cent ) { // start from clean memset( ¢->ent, 0, sizeof( cent->ent ) ); VectorCopy( cent->current.origin, cent->ent.origin ); VectorCopy( cent->current.origin2, cent->ent.oldorigin ); cent->ent.rtype = RT_PORTALSURFACE; cent->ent.scale = cent->current.frame / 256.0f; if( cent->current.effects & EF_ROTATE_AND_BOB ) cent->ent.frame = cent->current.modelindex2 ? cent->current.modelindex2 : 50; cent->ent.skinnum = cent->current.skinnum; } //=============== //CG_AddPortalSurfaceEnt //=============== void CG_AddPortalSurfaceEnt( centity_t *cent ) { CG_AddEntityToScene( ¢->ent ); } //=============== //CG_AddPacketEntities // Add the entities to the rendering list //=============== void CG_AddPacketEntities( void ) { entity_state_t *state; vec3_t autorotate; vec3_t sound_origin; int pnum; centity_t *cent; // bonus items rotate at a fixed rate VectorSet( autorotate, 0, anglemod( cg.time * 0.1 ), 0 ); AnglesToAxis( autorotate, cg.autorotateAxis ); for( pnum = 0; pnum < cg.frame.numEntities; pnum++ ) { state = &cg.frame.parsedEntities[pnum & (MAX_PARSE_ENTITIES-1)]; cent = &cg_entities[state->number]; switch( cent->type ) { case ET_GENERIC: CG_AddGenericEnt( cent ); break; case ET_GIB: if( cg_gibs->integer ) { CG_AddGenericEnt( cent ); CG_NewBloodTrail( cent ); } break; case ET_BLASTER: CG_AddGenericEnt( cent ); CG_BlasterTrail( cent->trailOrigin, cent->ent.origin ); CG_AddLightToScene( cent->ent.origin, 300, 1, 1, 0, NULL ); break; case ET_SHOCKWAVE: cent->ent.scale = 5.0; // temporary hax CG_AddGenericEnt( cent ); break; case ET_ELECTRO_WEAK: CG_AddGenericEnt( cent ); CG_AddLightToScene( cent->ent.origin, 300, 1, 1, 1, NULL ); CG_ElectroWeakTrail( cent->trailOrigin, cent->ent.origin ); break; case ET_ROCKET: CG_AddGenericEnt( cent ); CG_NewRocketTrail( cent ); CG_AddLightToScene( cent->ent.origin, 300, 1, 1, 0, NULL ); break; case ET_GRENADE: CG_AddGenericEnt( cent ); CG_NewGrenadeTrail( cent ); break; case ET_PLASMA: CG_AddGenericEnt( cent ); //CG_AddLightToScene( cent->ent.origin, 300, 0, 1, 0, NULL ); break; case ET_ITEM: CG_AddItemEnt( cent ); break; case ET_PLAYER: CG_AddPlayerEnt( cent ); break; case ET_BEAM: CG_AddBeamEnt( cent ); break; case ET_LASERBEAM: CG_AddLaserbeamEnt( cent ); break; case ET_CURVELASERBEAM: CG_AddCurveLaserbeamEnt( cent ); break; case ET_PORTALSURFACE: CG_AddPortalSurfaceEnt( cent ); break; case ET_FLAG_BASE: CG_AddFlagBaseEnt( cent ); break; case ET_EVENT: break; case ET_PUSH_TRIGGER: break; default: CG_Error( "CG_AddPacketEntities: unknown entity type" ); break; } // add loop sound, laserbeam does it by itself if( cent->current.sound && cent->type != ET_LASERBEAM && cent->type != ET_CURVELASERBEAM ) { CG_GetEntitySoundOrigin( state->number, sound_origin ); trap_S_AddLoopSound( cgs.soundPrecache[cent->current.sound], sound_origin, 1.0, qtrue ); } // glow if light is set if( state->light ) { CG_AddLightToScene( cent->ent.origin, COLOR_A( state->light ) * 4.0, COLOR_R( state->light ) * (1.0/255.0), COLOR_G( state->light ) * (1.0/255.0), COLOR_B( state->light ) * (1.0/255.0), NULL ); } VectorCopy( cent->ent.origin, cent->trailOrigin ); } } //============== //CG_LerpEntities // Interpolate the entity states positions into the entity_t structs //============== void CG_LerpEntities( void ) { entity_state_t *state; int pnum; centity_t *cent; for( pnum = 0; pnum < cg.frame.numEntities; pnum++ ) { state = &cg.frame.parsedEntities[pnum & (MAX_PARSE_ENTITIES-1)]; cent = &cg_entities[state->number]; switch( cent->type ) { case ET_GENERIC: case ET_GIB: case ET_BLASTER: case ET_SHOCKWAVE: case ET_ELECTRO_WEAK: case ET_ROCKET: case ET_GRENADE: case ET_PLASMA: case ET_ITEM: case ET_PLAYER: case ET_FLAG_BASE: CG_LerpGenericEnt( cent ); break; case ET_BEAM: // beams aren't interpolated break; case ET_LASERBEAM: case ET_CURVELASERBEAM: CG_LerpLaserbeamEnt( cent ); break; case ET_PORTALSURFACE: //portals aren't interpolated break; case ET_EVENT: break; case ET_PUSH_TRIGGER: break; default: CG_Error( "CG_LerpEntities: unknown entity type" ); break; } } } //============== //CG_UpdateEntities // Called at receiving a new serverframe. Sets up the model, type, etc to be drawn later on //============== void CG_UpdateEntities( void ) { entity_state_t *state; int pnum; centity_t *cent; for( pnum = 0; pnum < cg.frame.numEntities; pnum++ ) { state = &cg.frame.parsedEntities[pnum & (MAX_PARSE_ENTITIES-1)]; cent = &cg_entities[state->number]; cent->renderfx = state->renderfx & ~RF_CULLHACK; // cullhack is never set from the server cent->type = state->type; cent->effects = state->effects; cent->item = NULL; switch( cent->type ) { case ET_GENERIC: case ET_GIB: case ET_BLASTER: case ET_SHOCKWAVE: case ET_ELECTRO_WEAK: case ET_ROCKET: case ET_GRENADE: case ET_PLASMA: CG_UpdateGenericEnt( cent ); break; case ET_ITEM: CG_UpdateItemEnt( cent ); break; case ET_PLAYER: CG_UpdatePlayerModelEnt( cent ); break; case ET_BEAM: break; case ET_LASERBEAM: case ET_CURVELASERBEAM: CG_UpdateLaserbeamEnt( cent ); break; case ET_FLAG_BASE: CG_UpdateFlagBaseEnt( cent ); break; case ET_PORTALSURFACE: CG_UpdatePortalSurfaceEnt( cent ); break; case ET_EVENT: break; case ET_PUSH_TRIGGER: break; default: CG_Error( "CG_UpdateEntities: unknown entity type" ); break; } } } //============================================================= /* ============== CG_AddViewWeapon - SPLITMODELS ============== */ void CG_AddViewWeapon( void ); void CG_CalcViewWeapon( void ); //============== //CG_StartKickAnglesEffect - wsw //============== void CG_StartKickAnglesEffect( vec3_t source, float knockback, float radius, int time ) { float kick; float side; float dist; float delta; float ftime; vec3_t forward, right, v; int i, kicknum = -1; vec3_t playerorigin; player_state_t *ps; ps = &cg.frame.playerState; if( knockback <= 0 || time <= 0 || radius <= 0.0f ) return; // if spectator but not in chasecam, don't get any kick if( ps->pmove.pm_type == PM_SPECTATOR ) return; // not if dead if( cg_entities[cg.chasedNum+1].current.effects & EF_CORPSE ) return; // unpredicted if it's in chasecam (fixme: they aren't interpolated and should be) if( cgs.playerNum != cg.chasedNum ) VectorScale( cg.frame.playerState.pmove.origin, (1.0/16.0), playerorigin ); else VectorCopy( cg.predictedOrigin, playerorigin ); VectorSubtract( source, playerorigin, v ); dist = VectorNormalize(v); if( dist > radius ) return; delta = 1.0f - (dist / radius); if( delta > 1.0f ) delta = 1.0f; if( delta <= 0.0f ) return; kick = abs(knockback) * delta; if( kick ) // kick of 0 means no view adjust at all { //find first free kick spot, or the one closer to be finished for( i = 0; i < MAX_ANGLES_KICKS; i++ ) { if( cg.time > cg.kickangles[i].timestamp + cg.kickangles[i].kicktime ) { kicknum = i; break; } } // all in use. Choose the closer to be finished if( kicknum == -1 ) { int remaintime; int best = (cg.kickangles[0].timestamp + cg.kickangles[0].kicktime) - cg.time; kicknum = 0; for( i = 1; i < MAX_ANGLES_KICKS; i++ ) { remaintime = (cg.kickangles[i].timestamp + cg.kickangles[i].kicktime) - cg.time; if( remaintime < best ) { best = remaintime; kicknum = i; } } } AngleVectors( ps->viewangles, forward, right, NULL ); if( kick < 1.0f ) kick = 1.0f; side = DotProduct( v, right ); cg.kickangles[kicknum].v_roll = kick*side*0.3; side = -DotProduct( v, forward ); cg.kickangles[kicknum].v_pitch = kick*side*0.3; cg.kickangles[kicknum].timestamp = cg.time; ftime = (float)time * delta; if( ftime < 100 ) ftime = 100; cg.kickangles[kicknum].kicktime = ftime; } } //============== //CG_StartColorBlendEffect - wsw //============== void CG_StartColorBlendEffect( float r, float g, float b, float a, int time ) { int i, bnum = -1; if( a <= 0.0f || time <= 0 ) return; //find first free colorblend spot, or the one closer to be finished for( i = 0; i < MAX_COLORBLENDS; i++ ) { if( cg.time > cg.colorblends[i].timestamp + cg.colorblends[i].blendtime ) { bnum = i; break; } } // all in use. Choose the closer to be finished if( bnum == -1 ) { int remaintime; int best = (cg.colorblends[0].timestamp + cg.colorblends[0].blendtime) - cg.time; bnum = 0; for( i = 1; i < MAX_COLORBLENDS; i++ ) { remaintime = (cg.colorblends[i].timestamp + cg.colorblends[i].blendtime) - cg.time; if( remaintime < best ) { best = remaintime; bnum = i; } } } // assign the color blend cg.colorblends[bnum].blend[0] = r; cg.colorblends[bnum].blend[1] = g; cg.colorblends[bnum].blend[2] = b; cg.colorblends[bnum].blend[3] = a; cg.colorblends[bnum].timestamp = cg.time; cg.colorblends[bnum].blendtime = time; //CG_Printf("New Blend: spot:%i, alpha:%f, cgtime:%i blendtime:%i\n", bnum, a, cg.time, cg.colorblends[bnum].blendtime ); } /* =============== CG_AddEntities Emits all entities, particles, and lights to the refresh =============== */ void CG_AddEntities( void ) { extern int cg_numBeamEnts; cg_numBeamEnts = 0; CG_CalcViewWeapon(); CG_AddPacketEntities(); CG_AddViewWeapon(); CG_AddBeams(); CG_AddLocalEntities(); CG_AddParticles(); CG_AddDlights(); #ifdef CGAMEGETLIGHTORIGIN CG_AddShadeBoxes(); #endif CG_AddDecals(); CG_AddPolys(); } /* =============== CG_GlobalSound =============== */ void CG_GlobalSound( vec3_t origin, int entNum, int entChannel, int soundNum, float fvol, float attenuation ) { if( entNum < 0 || entNum >= MAX_EDICTS ) CG_Error( "CG_GlobalSound: bad entnum" ); if( cgs.soundPrecache[soundNum] ) { if( entNum == cg.chasedNum + 1 ) trap_S_StartSound( NULL, entNum, entChannel, cgs.soundPrecache[soundNum], fvol, attenuation, 0.0 ); else trap_S_StartSound( origin, 0, entChannel, cgs.soundPrecache[soundNum], fvol, attenuation, 0.0 ); } else if( cgs.configStrings[CS_SOUNDS + soundNum][0] == '*' ) { CG_SexedSound( entNum, entChannel, cgs.configStrings[CS_SOUNDS + soundNum], fvol ); } } /* =============== CG_GetEntitySoundOrigin Called to get the sound spatialization origin =============== */ void CG_GetEntitySoundOrigin( int entNum, vec3_t org ) { centity_t *cent; struct cmodel_s *cmodel; vec3_t mins, maxs; if( entNum < 0 || entNum >= MAX_EDICTS ) CG_Error( "CG_GetEntitySoundOrigin: bad entnum" ); cent = &cg_entities[entNum]; if( cent->current.solid != SOLID_BMODEL ) { VectorCopy( cent->ent.origin, org ); return; } cmodel = trap_CM_InlineModel( cent->current.modelindex ); trap_CM_InlineModelBounds( cmodel, mins, maxs ); VectorAdd( maxs, mins, org ); VectorMA( cent->ent.origin, 0.5f, org, org ); }