/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2002-2021 Q3Rally Team (Per Thormann - q3rally@gmail.com) This file is part of q3rally source code. q3rally source code 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. q3rally source code 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 q3rally; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_ents.c -- present snapshot entities, happens every single frame #include "cg_local.h" // Q3Rally Code Start // used to know when to update physics variables from server //qboolean updateEnts; // Q3Rally Code END /* ====================== CG_PositionEntityOnTag Modifies the entities position and axis by the given tag location ====================== */ void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName ) { int i; orientation_t lerped; // lerp the tag trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName ); // FIXME: allow origin offsets along tag? VectorCopy( parent->origin, entity->origin ); for ( i = 0 ; i < 3 ; i++ ) { VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); } // had to cast away the const to avoid compiler problems... MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis ); entity->backlerp = parent->backlerp; } /* ====================== CG_PositionRotatedEntityOnTag Modifies the entities position and axis by the given tag location ====================== */ void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName ) { int i; orientation_t lerped; vec3_t tempAxis[3]; //AxisClear( entity->axis ); // lerp the tag trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName ); // FIXME: allow origin offsets along tag? VectorCopy( parent->origin, entity->origin ); for ( i = 0 ; i < 3 ; i++ ) { VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); } // had to cast away the const to avoid compiler problems... MatrixMultiply( entity->axis, lerped.axis, tempAxis ); MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis ); } /* ========================================================================== FUNCTIONS CALLED EACH FRAME ========================================================================== */ /* ====================== CG_SetEntitySoundPosition Also called by event processing code ====================== */ void CG_SetEntitySoundPosition( centity_t *cent ) { if ( cent->currentState.solid == SOLID_BMODEL ) { vec3_t origin; float *v; v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; VectorAdd( cent->lerpOrigin, v, origin ); trap_S_UpdateEntityPosition( cent->currentState.number, origin ); } else { trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); } } /* ================== CG_EntityEffects Add continuous entity effects, like local entity emission and lighting ================== */ static void CG_EntityEffects( centity_t *cent ) { // update sound origins CG_SetEntitySoundPosition( cent ); // add loop sound if ( cent->currentState.loopSound ) { if (cent->currentState.eType != ET_SPEAKER) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ] ); } else { trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ] ); } } else { trap_S_StopLoopingSound(cent->currentState.number); } // constant light glow if(cent->currentState.constantLight) { int cl; float i, r, g, b; cl = cent->currentState.constantLight; r = (float) (cl & 0xFF) / 255.0; g = (float) ((cl >> 8) & 0xFF) / 255.0; b = (float) ((cl >> 16) & 0xFF) / 255.0; i = (float) ((cl >> 24) & 0xFF) * 4.0; trap_R_AddLightToScene(cent->lerpOrigin, i, r, g, b); } } /* ================== CG_General ================== */ static void CG_General( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // if set to invisible, skip if (!s1->modelindex) { return; } memset (&ent, 0, sizeof(ent)); // set frame ent.frame = s1->frame; ent.oldframe = ent.frame; ent.backlerp = 0; VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); ent.hModel = cgs.gameModels[s1->modelindex]; // player model if (s1->number == cg.snap->ps.clientNum) { ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors } // convert angles to axis AnglesToAxis( cent->lerpAngles, ent.axis ); // add to refresh list trap_R_AddRefEntityToScene (&ent); } /* ================== CG_Speaker Speaker entities can automatically play sounds ================== */ static void CG_Speaker( centity_t *cent ) { if ( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum... return; // not auto triggering } if ( cg.time < cent->miscTime ) { return; } trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); // ent->s.frame = ent->wait * 10; // ent->s.clientNum = ent->random * 10; cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); } /* ================== CG_Item ================== */ static void CG_Item( centity_t *cent ) { refEntity_t ent; entityState_t *es; gitem_t *item; int msec; float frac; float scale; weaponInfo_t *wi; es = ¢->currentState; if ( es->modelindex >= bg_numItems ) { CG_Error( "Bad item index %i on entity", es->modelindex ); } // if set to invisible, skip if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) { return; } item = &bg_itemlist[ es->modelindex ]; if ( cg_simpleItems.integer && item->giType != IT_TEAM ) { memset( &ent, 0, sizeof( ent ) ); ent.reType = RT_SPRITE; VectorCopy( cent->lerpOrigin, ent.origin ); ent.radius = 14; ent.customShader = cg_items[es->modelindex].icon; ent.shaderRGBA[0] = 255; ent.shaderRGBA[1] = 255; ent.shaderRGBA[2] = 255; ent.shaderRGBA[3] = 255; trap_R_AddRefEntityToScene(&ent); return; } // items bob up and down continuously scale = 0.005 + cent->currentState.number * 0.00001; cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; memset (&ent, 0, sizeof(ent)); // autorotate at one of two speeds if ( item->giType == IT_HEALTH ) { VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); AxisCopy( cg.autoAxisFast, ent.axis ); } else { VectorCopy( cg.autoAngles, cent->lerpAngles ); AxisCopy( cg.autoAxis, ent.axis ); } wi = NULL; // the weapons have their origin where they attatch to player // models, so we need to offset them or they will rotate // eccentricly if ( item->giType == IT_WEAPON ) { wi = &cg_weapons[item->giTag]; cent->lerpOrigin[0] -= wi->weaponMidpoint[0] * ent.axis[0][0] + wi->weaponMidpoint[1] * ent.axis[1][0] + wi->weaponMidpoint[2] * ent.axis[2][0]; cent->lerpOrigin[1] -= wi->weaponMidpoint[0] * ent.axis[0][1] + wi->weaponMidpoint[1] * ent.axis[1][1] + wi->weaponMidpoint[2] * ent.axis[2][1]; cent->lerpOrigin[2] -= wi->weaponMidpoint[0] * ent.axis[0][2] + wi->weaponMidpoint[1] * ent.axis[1][2] + wi->weaponMidpoint[2] * ent.axis[2][2]; cent->lerpOrigin[2] += 8; // an extra height boost } if( item->giType == IT_WEAPON && item->giTag == WP_RAILGUN ) { clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; Byte4Copy( ci->c1RGBA, ent.shaderRGBA ); } ent.hModel = cg_items[es->modelindex].models[0]; VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); ent.nonNormalizedAxes = qfalse; // if just respawned, slowly scale up msec = cg.time - cent->miscTime; if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) { frac = (float)msec / ITEM_SCALEUP_TIME; VectorScale( ent.axis[0], frac, ent.axis[0] ); VectorScale( ent.axis[1], frac, ent.axis[1] ); VectorScale( ent.axis[2], frac, ent.axis[2] ); ent.nonNormalizedAxes = qtrue; } else { frac = 1.0; } // items without glow textures need to keep a minimum light value // so they are always visible // Q3Rally Code Start // if ( ( item->giType == IT_WEAPON ) || ( item->giType == IT_ARMOR ) ) { if ( ( item->giType == IT_WEAPON ) || ( item->giType == IT_RFWEAPON ) || ( item->giType == IT_ARMOR ) ) { // Q3Rally Code END ent.renderfx |= RF_MINLIGHT; } // increase the size of the weapons when they are presented as items if ( item->giType == IT_WEAPON ) { VectorScale( ent.axis[0], 1.5, ent.axis[0] ); VectorScale( ent.axis[1], 1.5, ent.axis[1] ); VectorScale( ent.axis[2], 1.5, ent.axis[2] ); ent.nonNormalizedAxes = qtrue; #ifdef MISSIONPACK trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); #endif } #ifdef MISSIONPACK if ( item->giType == IT_HOLDABLE && item->giTag == HI_KAMIKAZE ) { VectorScale( ent.axis[0], 2, ent.axis[0] ); VectorScale( ent.axis[1], 2, ent.axis[1] ); VectorScale( ent.axis[2], 2, ent.axis[2] ); ent.nonNormalizedAxes = qtrue; } #endif // Q3Rally Code Start ent.origin[2] += 6; // Q3Rally Code END // add to refresh list trap_R_AddRefEntityToScene(&ent); if ( item->giType == IT_WEAPON && wi && wi->barrelModel ) { refEntity_t barrel; vec3_t angles; memset( &barrel, 0, sizeof( barrel ) ); barrel.hModel = wi->barrelModel; VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); barrel.shadowPlane = ent.shadowPlane; barrel.renderfx = ent.renderfx; angles[YAW] = 0; angles[PITCH] = 0; angles[ROLL] = 0; AnglesToAxis( angles, barrel.axis ); CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); barrel.nonNormalizedAxes = ent.nonNormalizedAxes; trap_R_AddRefEntityToScene( &barrel ); } // accompanying rings / spheres for powerups if ( !cg_simpleItems.integer ) { vec3_t spinAngles; VectorClear( spinAngles ); // Q3Rally Code Start // if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP ) if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP || item->giTag == HI_TURBO) // Q3Rally Code END { if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 ) { // Q3Rally Code Start /* if ( item->giType == IT_POWERUP ) { ent.origin[2] += 12; spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f; } */ if ( item->giType == IT_POWERUP || item->giTag == HI_TURBO) { spinAngles[ROLL] = ( (cg.time/2) & 1023 ) * 360 / -1024.0f; spinAngles[1] = ( (cg.time/2) & 1023 ) * 360 / -1024.0f; } // Q3Rally Code END AnglesToAxis( spinAngles, ent.axis ); // scale up if respawning if ( frac != 1.0 ) { VectorScale( ent.axis[0], frac, ent.axis[0] ); VectorScale( ent.axis[1], frac, ent.axis[1] ); VectorScale( ent.axis[2], frac, ent.axis[2] ); ent.nonNormalizedAxes = qtrue; } trap_R_AddRefEntityToScene( &ent ); } } } // Q3Rally Code Start if ( !cg_simpleItems.integer ) { vec3_t spinAngles; VectorClear ( spinAngles ); if (item->giTag == PW_HASTE){ if ( ( ent.hModel = cg_items[es->modelindex].models[2] ) != 0 ) { AxisCopy( cg.autoAxis, ent.axis ); // scale up if respawning if ( frac != 1.0 ) { VectorScale( ent.axis[0], frac, ent.axis[0] ); VectorScale( ent.axis[1], frac, ent.axis[1] ); VectorScale( ent.axis[2], frac, ent.axis[2] ); ent.nonNormalizedAxes = qtrue; } trap_R_AddRefEntityToScene( &ent ); } } } // Q3Rally Code END } //============================================================================ /* =============== CG_Missile =============== */ static void CG_Missile( centity_t *cent ) { refEntity_t ent; entityState_t *s1; const weaponInfo_t *weapon; // int col; s1 = ¢->currentState; if ( s1->weapon >= WP_NUM_WEAPONS ) { s1->weapon = 0; } weapon = &cg_weapons[s1->weapon]; // calculate the axis VectorCopy( s1->angles, cent->lerpAngles); // add trails if ( weapon->missileTrailFunc ) { weapon->missileTrailFunc( cent, weapon ); } /* if ( cent->currentState.modelindex == TEAM_RED ) { col = 1; } else if ( cent->currentState.modelindex == TEAM_BLUE ) { col = 2; } else { col = 0; } // add dynamic light if ( weapon->missileDlight ) { trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, weapon->missileDlightColor[col][0], weapon->missileDlightColor[col][1], weapon->missileDlightColor[col][2] ); } */ // add dynamic light if ( weapon->missileDlight ) { trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); } // add missile sound if ( weapon->missileSound ) { vec3_t velocity; BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); } // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); if ( cent->currentState.weapon == WP_PLASMAGUN ) { ent.reType = RT_SPRITE; ent.radius = 16; ent.rotation = 0; ent.customShader = cgs.media.plasmaBallShader; trap_R_AddRefEntityToScene( &ent ); return; } if ( cent->currentState.weapon == WP_FLAME_THROWER ) { ent.reType = RT_SPRITE; ent.radius = 32; ent.rotation = 0; ent.customShader = cgs.media.flameBallShader; trap_R_AddRefEntityToScene( &ent ); return; } // Q3Rally Code Start if (cent->currentState.weapon == RWP_MINE){ if (cgs.gametype >= GT_TEAM){ switch(cgs.clientinfo[cent->currentState.otherEntityNum].team){ default: case TEAM_RED: ent.customSkin = trap_R_RegisterSkin( "models/rearfire/red.skin" ); break; case TEAM_BLUE: ent.customSkin = trap_R_RegisterSkin( "models/rearfire/blue.skin" ); break; case TEAM_GREEN: ent.customSkin = trap_R_RegisterSkin( "models/rearfire/green.skin" ); break; case TEAM_YELLOW: ent.customSkin = trap_R_RegisterSkin( "models/rearfire/yellow.skin" ); break; } } else { ent.skinNum = 0; } } else{ // flicker between two skins ent.skinNum = cg.clientFrame & 1; } //ent.skinNum = cg.clientFrame & 1; // Q3Rally Code END ent.hModel = weapon->missileModel; ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; #ifdef MISSIONPACK if ( cent->currentState.weapon == WP_PROX_LAUNCHER ) { if (s1->generic1 == TEAM_BLUE) { ent.hModel = cgs.media.blueProxMine; } } #endif // convert direction of travel into axis if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { // Q3Rally Code Start if (cent->currentState.weapon == RWP_MINE) AxisClear(ent.axis); else // Q3Rally Code END ent.axis[0][2] = 1; } // spin as it moves if ( s1->pos.trType != TR_STATIONARY ) { RotateAroundDirection( ent.axis, cg.time / 4 ); } else { #ifdef MISSIONPACK if ( s1->weapon == WP_PROX_LAUNCHER ) { AnglesToAxis( cent->lerpAngles, ent.axis ); } else #endif { RotateAroundDirection( ent.axis, s1->time ); } } // add to refresh list, possibly with quad glow CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); } /* =============== CG_Grapple This is called when the grapple is sitting up against the wall =============== */ // Q3Rally Code Start - removed /* static void CG_Grapple( centity_t *cent ) { refEntity_t ent; entityState_t *s1; const weaponInfo_t *weapon; s1 = ¢->currentState; if ( s1->weapon >= WP_NUM_WEAPONS ) { s1->weapon = 0; } weapon = &cg_weapons[s1->weapon]; // calculate the axis VectorCopy( s1->angles, cent->lerpAngles); #if 0 // FIXME add grapple pull sound here..? // add missile sound if ( weapon->missileSound ) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound ); } #endif // Will draw cable if needed CG_GrappleTrail ( cent, weapon ); // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); // flicker between two skins ent.skinNum = cg.clientFrame & 1; ent.hModel = weapon->missileModel; ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; // convert direction of travel into axis if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { ent.axis[0][2] = 1; } trap_R_AddRefEntityToScene( &ent ); } */ /* Q3Rally Code Start ================== CG_Auxent car wheel entities ================== */ static void CG_Auxent( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; if ( s1->otherEntityNum == cg.snap->ps.clientNum && !cg.newSnap ) return; cg_entities[s1->otherEntityNum].wheelSpeeds[s1->otherEntityNum2] = s1->apos.trDelta[0]; cg_entities[s1->otherEntityNum].wheelSkidding[s1->otherEntityNum2] = s1->frame; cg_entities[s1->otherEntityNum].steeringAngle = s1->apos.trDelta[1]; if (s1->otherEntityNum == cg.snap->ps.clientNum){ // Com_Printf("updating wheels\n"); cg.car.sPoints[s1->otherEntityNum2].w = s1->apos.trDelta[0]; cg.car.sPoints[s1->otherEntityNum2].slipping = s1->frame; cg.car.wheelAngle = s1->apos.trDelta[1]; VectorCopy(s1->pos.trBase, cg.car.sPoints[s1->otherEntityNum2].r); VectorCopy(s1->pos.trDelta, cg.car.sPoints[s1->otherEntityNum2].v); VectorCopy(s1->origin2, cg.car.sPoints[s1->otherEntityNum2].normals[0]); cg.car.sPoints[s1->otherEntityNum2].onGround = s1->groundEntityNum; } // if set to invisible, skip if (!s1->modelindex) { return; } memset (&ent, 0, sizeof(ent)); // set frame ent.frame = s1->frame; ent.oldframe = ent.frame; ent.backlerp = 0; VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); ent.hModel = cgs.gameModels[s1->modelindex]; // player model if (s1->number == cg.snap->ps.clientNum) { ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors } // convert angles to axis AnglesToAxis( cent->lerpAngles, ent.axis ); // add to refresh list trap_R_AddRefEntityToScene (&ent); } /* ================== CG_Weather ================== */ static void CG_Weather( centity_t *cent ) { entityState_t *s1; s1 = ¢->currentState; // CG_EffectParse( "T=RAIN,B=5 10,C=0.5,G=0.5 2,BV=0,GV=0 100,W=1 2,D=300" ); CG_Atmospheric_SetParticles( s1->weapon, s1->powerups, s1->legsAnim ); } // Q3Rally Code END /* =============== CG_Mover =============== */ static void CG_Mover( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); AnglesToAxis( cent->lerpAngles, ent.axis ); ent.renderfx = RF_NOSHADOW; // flicker between two skins (FIXME?) ent.skinNum = ( cg.time >> 6 ) & 1; // get the model, either as a bmodel or a modelindex if ( s1->solid == SOLID_BMODEL ) { ent.hModel = cgs.inlineDrawModel[s1->modelindex]; } else { ent.hModel = cgs.gameModels[s1->modelindex]; } // add to refresh list trap_R_AddRefEntityToScene(&ent); // add the secondary model if ( s1->modelindex2 ) { ent.skinNum = 0; ent.hModel = cgs.gameModels[s1->modelindex2]; trap_R_AddRefEntityToScene(&ent); } } /* =============== CG_Beam Also called as an event =============== */ void CG_Beam( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( s1->pos.trBase, ent.origin ); VectorCopy( s1->origin2, ent.oldorigin ); AxisClear( ent.axis ); ent.reType = RT_BEAM; ent.renderfx = RF_NOSHADOW; // add to refresh list trap_R_AddRefEntityToScene(&ent); } /* =============== CG_Portal =============== */ static void CG_Portal( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( s1->origin2, ent.oldorigin ); ByteToDir( s1->eventParm, ent.axis[0] ); PerpendicularVector( ent.axis[1], ent.axis[0] ); // negating this tends to get the directions like they want // we really should have a camera roll value VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); ent.reType = RT_PORTALSURFACE; ent.oldframe = s1->powerups; ent.frame = s1->frame; // rotation speed ent.skinNum = s1->clientNum/256.0 * 360; // roll offset // add to refresh list trap_R_AddRefEntityToScene(&ent); } /* ================ CG_CreateRotationMatrix ================ */ void CG_CreateRotationMatrix(vec3_t angles, vec3_t matrix[3]) { AngleVectors(angles, matrix[0], matrix[1], matrix[2]); VectorInverse(matrix[1]); } /* ================ CG_TransposeMatrix ================ */ void CG_TransposeMatrix(vec3_t matrix[3], vec3_t transpose[3]) { int i, j; for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { transpose[i][j] = matrix[j][i]; } } } /* ================ CG_RotatePoint ================ */ void CG_RotatePoint(vec3_t point, vec3_t matrix[3]) { vec3_t tvec; VectorCopy(point, tvec); point[0] = DotProduct(matrix[0], tvec); point[1] = DotProduct(matrix[1], tvec); point[2] = DotProduct(matrix[2], tvec); } /* ========================= CG_AdjustPositionForMover Also called by client movement prediction code ========================= */ void CG_AdjustPositionForMover(const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out, vec3_t angles_in, vec3_t angles_out) { centity_t *cent; vec3_t oldOrigin, origin, deltaOrigin; vec3_t oldAngles, angles, deltaAngles; vec3_t matrix[3], transpose[3]; vec3_t org, org2, move2; if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { VectorCopy( in, out ); VectorCopy(angles_in, angles_out); return; } cent = &cg_entities[ moverNum ]; if ( cent->currentState.eType != ET_MOVER ) { VectorCopy( in, out ); VectorCopy(angles_in, angles_out); return; } BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); VectorSubtract( origin, oldOrigin, deltaOrigin ); VectorSubtract( angles, oldAngles, deltaAngles ); // origin change when on a rotating object CG_CreateRotationMatrix( deltaAngles, transpose ); CG_TransposeMatrix( transpose, matrix ); VectorSubtract( in, oldOrigin, org ); VectorCopy( org, org2 ); CG_RotatePoint( org2, matrix ); VectorSubtract( org2, org, move2 ); VectorAdd( deltaOrigin, move2, deltaOrigin ); VectorAdd( in, deltaOrigin, out ); VectorAdd( angles_in, deltaAngles, angles_out ); } /* ============================= CG_InterpolateEntityPosition ============================= */ static void CG_InterpolateEntityPosition( centity_t *cent ) { vec3_t current, next; float f; // it would be an internal error to find an entity that interpolates without // a snapshot ahead of the current one if ( cg.nextSnap == NULL ) { CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); } f = cg.frameInterpolation; // this will linearize a sine or parabolic curve, but it is important // to not extrapolate player positions if more recent data is available BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); } /* =============== CG_CalcEntityLerpPositions =============== */ static void CG_CalcEntityLerpPositions( centity_t *cent ) { // if this player does not want to see extrapolated players if ( !cg_smoothClients.integer ) { // make sure the clients use TR_INTERPOLATE if ( cent->currentState.number < MAX_CLIENTS ) { cent->currentState.pos.trType = TR_INTERPOLATE; cent->nextState.pos.trType = TR_INTERPOLATE; // Q3Rally Code Start cent->currentState.apos.trType = TR_INTERPOLATE; cent->nextState.apos.trType = TR_INTERPOLATE; // Q3Rally Code END } } if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { CG_InterpolateEntityPosition( cent ); return; } // first see if we can interpolate between two snaps for // linear extrapolated clients if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && cent->currentState.number < MAX_CLIENTS) { CG_InterpolateEntityPosition( cent ); return; } // just use the current frame and evaluate as best we can BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); // Q3Rally Code Start /* if( !cg_paused.integer && cent->currentState.number < MAX_CLIENTS && cent->currentState.number != cg.snap->ps.clientNum ) { Com_Printf( "angles %f %f %f\n", cent->lerpAngles[0], cent->lerpAngles[1], cent->lerpAngles[2] ); } */ // Q3Rally Code END // adjust for riding a mover if it wasn't rolled into the predicted // player state if ( cent != &cg.predictedPlayerEntity ) { CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, cg.snap->serverTime, cg.time, cent->lerpOrigin, cent->lerpAngles, cent->lerpAngles); } } /* =============== CG_TeamBase =============== */ static void CG_TeamBase( centity_t *cent ) { refEntity_t model; #ifdef MISSIONPACK vec3_t angles; int t, h; float c; if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) { #else if ( cgs.gametype == GT_CTF) { #endif // show the flag base memset(&model, 0, sizeof(model)); model.reType = RT_MODEL; VectorCopy( cent->lerpOrigin, model.lightingOrigin ); VectorCopy( cent->lerpOrigin, model.origin ); AnglesToAxis( cent->currentState.angles, model.axis ); if ( cent->currentState.modelindex == TEAM_RED ) { model.hModel = cgs.media.redFlagBaseModel; } else if ( cent->currentState.modelindex == TEAM_BLUE ) { model.hModel = cgs.media.blueFlagBaseModel; } else { model.hModel = cgs.media.neutralFlagBaseModel; } trap_R_AddRefEntityToScene( &model ); } #ifdef MISSIONPACK else if ( cgs.gametype == GT_OBELISK ) { // show the obelisk memset(&model, 0, sizeof(model)); model.reType = RT_MODEL; VectorCopy( cent->lerpOrigin, model.lightingOrigin ); VectorCopy( cent->lerpOrigin, model.origin ); AnglesToAxis( cent->currentState.angles, model.axis ); model.hModel = cgs.media.overloadBaseModel; trap_R_AddRefEntityToScene( &model ); // if hit if ( cent->currentState.frame == 1) { // show hit model // modelindex2 is the health value of the obelisk c = cent->currentState.modelindex2; model.shaderRGBA[0] = 0xff; model.shaderRGBA[1] = c; model.shaderRGBA[2] = c; model.shaderRGBA[3] = 0xff; // model.hModel = cgs.media.overloadEnergyModel; trap_R_AddRefEntityToScene( &model ); } // if respawning if ( cent->currentState.frame == 2) { if ( !cent->miscTime ) { cent->miscTime = cg.time; } t = cg.time - cent->miscTime; h = (cg_obeliskRespawnDelay.integer - 5) * 1000; // if (t > h) { c = (float) (t - h) / h; if (c > 1) c = 1; } else { c = 0; } // show the lights AnglesToAxis( cent->currentState.angles, model.axis ); // model.shaderRGBA[0] = c * 0xff; model.shaderRGBA[1] = c * 0xff; model.shaderRGBA[2] = c * 0xff; model.shaderRGBA[3] = c * 0xff; model.hModel = cgs.media.overloadLightsModel; trap_R_AddRefEntityToScene( &model ); // show the target if (t > h) { if ( !cent->muzzleFlashTime ) { trap_S_StartSound (cent->lerpOrigin, ENTITYNUM_NONE, CHAN_BODY, cgs.media.obeliskRespawnSound); cent->muzzleFlashTime = 1; } VectorCopy(cent->currentState.angles, angles); angles[YAW] += (float) 16 * Q_acos(1-c) * 180 / M_PI; AnglesToAxis( angles, model.axis ); VectorScale( model.axis[0], c, model.axis[0]); VectorScale( model.axis[1], c, model.axis[1]); VectorScale( model.axis[2], c, model.axis[2]); model.shaderRGBA[0] = 0xff; model.shaderRGBA[1] = 0xff; model.shaderRGBA[2] = 0xff; model.shaderRGBA[3] = 0xff; // model.origin[2] += 56; model.hModel = cgs.media.overloadTargetModel; trap_R_AddRefEntityToScene( &model ); } else { //FIXME: show animated smoke } } else { cent->miscTime = 0; cent->muzzleFlashTime = 0; // modelindex2 is the health value of the obelisk c = cent->currentState.modelindex2; model.shaderRGBA[0] = 0xff; model.shaderRGBA[1] = c; model.shaderRGBA[2] = c; model.shaderRGBA[3] = 0xff; // show the lights model.hModel = cgs.media.overloadLightsModel; trap_R_AddRefEntityToScene( &model ); // show the target model.origin[2] += 56; model.hModel = cgs.media.overloadTargetModel; trap_R_AddRefEntityToScene( &model ); } } else if ( cgs.gametype == GT_HARVESTER ) { // show harvester model memset(&model, 0, sizeof(model)); model.reType = RT_MODEL; VectorCopy( cent->lerpOrigin, model.lightingOrigin ); VectorCopy( cent->lerpOrigin, model.origin ); AnglesToAxis( cent->currentState.angles, model.axis ); if ( cent->currentState.modelindex == TEAM_RED ) { model.hModel = cgs.media.harvesterModel; model.customSkin = cgs.media.harvesterRedSkin; } else if ( cent->currentState.modelindex == TEAM_BLUE ) { model.hModel = cgs.media.harvesterModel; model.customSkin = cgs.media.harvesterBlueSkin; } else { model.hModel = cgs.media.harvesterNeutralModel; model.customSkin = 0; } trap_R_AddRefEntityToScene( &model ); } #endif } /* =============== CG_AddCEntity =============== */ // Q3Rally Code Start // static void CG_AddCEntity( centity_t *cent ) { void CG_AddCEntity( centity_t *cent ) { // Q3Rally Code END // event-only entities will have been dealt with already if ( cent->currentState.eType >= ET_EVENTS ) { return; } // calculate the current origin CG_CalcEntityLerpPositions( cent ); // add automatic effects CG_EntityEffects( cent ); switch ( cent->currentState.eType ) { default: CG_Error( "Bad entity type: %i", cent->currentState.eType ); break; // Q3Rally Code Start case ET_CHECKPOINT: // Q3Rally Code END case ET_INVISIBLE: case ET_PUSH_TRIGGER: case ET_TELEPORT_TRIGGER: break; case ET_GENERAL: CG_General( cent ); break; case ET_PLAYER: CG_Player( cent ); break; case ET_ITEM: CG_Item( cent ); break; case ET_MISSILE: CG_Missile( cent ); break; case ET_MOVER: CG_Mover( cent ); break; // Q3Rally Code Start case ET_BREAKGLASS: CG_Mover( cent ); break; case ET_BREAKWOOD: CG_Mover( cent ); break; case ET_BREAKMETAL: CG_Mover( cent ); break; // Q3Rally Code END case ET_BEAM: CG_Beam( cent ); break; case ET_PORTAL: CG_Portal( cent ); break; case ET_SPEAKER: CG_Speaker( cent ); break; // Q3Rally Code Start /* case ET_GRAPPLE: CG_Grapple( cent ); break; */ case ET_AUXENT: CG_Auxent( cent ); break; case ET_WEATHER: CG_Weather( cent ); break; case ET_SCRIPTED: CG_Scripted_Object( cent ); break; // Q3Rally Code END case ET_TEAM: CG_TeamBase( cent ); break; } } /* =============== CG_AddPacketEntities =============== */ // Q3Rally Code Start //static vec3_t lastVel; //static vec3_t lastAngM; //static int lastPMType; // Q3Rally Code END void CG_AddPacketEntities( void ) { int num; centity_t *cent; playerState_t *ps; // Q3Rally Code Start // char value[16]; // int i; // Q3Rally Code END // set cg.frameInterpolation if ( cg.nextSnap ) { int delta; delta = (cg.nextSnap->serverTime - cg.snap->serverTime); if ( delta == 0 ) { cg.frameInterpolation = 0; } else { cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; } } else { cg.frameInterpolation = 0; // actually, it should never be used, because // no entities should be marked as interpolating } // the auto-rotating items will all have the same axis cg.autoAngles[0] = 0; cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0; cg.autoAngles[2] = 0; cg.autoAnglesFast[0] = 0; cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f; cg.autoAnglesFast[2] = 0; AnglesToAxis( cg.autoAngles, cg.autoAxis ); AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); // generate and add the entity from the playerstate ps = &cg.predictedPlayerState; BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); CG_AddCEntity( &cg.predictedPlayerEntity ); // lerp the non-predicted value for lightning gun origins CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); // add each entity sent over by the server for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { cent = &cg_entities[ cg.snap->entities[ num ].number ]; CG_AddCEntity( cent ); } // Q3Rally Code Start if( cg_drawBotPaths.integer ) CG_DrawCheckpointLinks(); if ( cg.newSnap ) { // int weaponTime; /* float m[3][3]; float m2[3][3]; vec3_t angles, delta_angles; float time = 0.5f; */ // Com_Printf("updating car body variables\n"); if ( !cg_paused.integer && cg_debugpredict.integer ) Com_Printf( "updateEnts\n" ); /* if ( !cg_paused.integer ) { int frontTime; int rearTime; int newFrontTime; int newRearTime; rearTime = ( cg.predictedPlayerState.weaponTime & REAR_WEAPON_TIME_MASK ) >> 16; newRearTime = ( cg.snap->ps.weaponTime & REAR_WEAPON_TIME_MASK ) >> 16; Com_Printf( "cg forward: cur weapon time %i new weap time %i\n", cg.predictedPlayerState.weaponTime & NORMAL_WEAPON_TIME_MASK, cg.snap->ps.weaponTime & NORMAL_WEAPON_TIME_MASK ); Com_Printf( "cg rear: cur weapon time %i new weap time %i\n", rearTime, newRearTime ); Com_Printf( "cg: cur weapon time %i new weap time %i\n", cg.predictedPlayerState.weaponTime, cg.snap->ps.weaponTime ); } */ // cg.predictedPlayerState = cg.snap->ps; // HACK: save the rear weapon time because for some stupid reason q3 // engine fucks that part up when it sends the weapon time. { int rearTime = cg.predictedPlayerState.weaponTime & REAR_WEAPON_TIME_MASK; cg.predictedPlayerState = cg.snap->ps; cg.predictedPlayerState.weaponTime &= ~REAR_WEAPON_TIME_MASK; cg.predictedPlayerState.weaponTime |= rearTime; } CG_UpdateCarFromPS ( &cg.snap->ps ); /* m[0][0] = 0; m[0][1] = time * -cg.car.sBody.w[2]; m[0][2] = time * cg.car.sBody.w[1]; m[1][0] = time * cg.car.sBody.w[2]; m[1][1] = 0; m[1][2] = time * -cg.car.sBody.w[0]; m[2][0] = time * -cg.car.sBody.w[1]; m[2][1] = time * cg.car.sBody.w[0]; m[2][2] = 0; MatrixMultiply(m, cg.car.sBody.t, m2); MatrixAdd(cg.car.sBody.t, m2, cg.car.tBody.t); OrthonormalizeOrientation(cg.car.tBody.t); OrientationToAngles( cg.car.tBody.t, angles ); OrientationToDeltaAngles( cg.car.sBody.t, cg.car.sBody.w, delta_angles ); Com_Printf( "view angles1 (%f %f %f)\n", cg.snap->ps.viewangles[0], cg.snap->ps.viewangles[1], cg.snap->ps.viewangles[2] ); Com_Printf( "delta angles (%f %f %f)\n", delta_angles[0], delta_angles[1], delta_angles[2] ); Com_Printf( "view angles2 (%f %f %f)\n", angles[0], angles[1], angles[2] ); */ if (!cg_paused.integer){ // Com_Printf("client time %d\n", ps->commandTime); /* Car Com_Printf("springStrength %f\n", cg.car.springStrength); Com_Printf("springMaxLength %f\n", cg.car.springMaxLength); Com_Printf("springMinLength %f\n", cg.car.springMinLength); Com_Printf("shockStrength %f\n", cg.car.shockStrength); Com_Printf("wheelAngle %f\n", cg.car.wheelAngle); Com_Printf("throttle %f\n", cg.car.throttle); Com_Printf("gear %d\n", cg.car.gear); Com_Printf("rpm %f\n", cg.car.rpm); Com_Printf("aCOF %f\n", cg.car.aCOF); Com_Printf("sCOF %f\n", cg.car.sCOF); Com_Printf("kCOF %f\n", cg.car.kCOF); Com_Printf("dfCOF %f\n", cg.car.dfCOF); Com_Printf("ewCOF %f\n", cg.car.ewCOF); Com_Printf("inverseBodyInertiaTensor:\n"); Com_Printf("%f, %f, %f\n", cg.car.inverseBodyInertiaTensor[0][0], cg.car.inverseBodyInertiaTensor[0][1], cg.car.inverseBodyInertiaTensor[0][2]); Com_Printf("%f, %f, %f\n", cg.car.inverseBodyInertiaTensor[1][0], cg.car.inverseBodyInertiaTensor[1][1], cg.car.inverseBodyInertiaTensor[1][2]); Com_Printf("%f, %f, %f\n", cg.car.inverseBodyInertiaTensor[2][0], cg.car.inverseBodyInertiaTensor[2][1], cg.car.inverseBodyInertiaTensor[2][2]); */ /* Body Com_Printf("r %f, %f, %f\n", cg.car.sBody.r[0], cg.car.sBody.r[1], cg.car.sBody.r[2]); Com_Printf("v %f, %f, %f\n", cg.car.sBody.v[0], cg.car.sBody.v[1], cg.car.sBody.v[2]); Com_Printf("w %f, %f, %f\n", cg.car.sBody.w[0], cg.car.sBody.w[1], cg.car.sBody.w[2]); Com_Printf("L %f, %f, %f\n", cg.car.sBody.L[0], cg.car.sBody.L[1], cg.car.sBody.L[2]); Com_Printf("CoM %f, %f, %f\n", cg.car.sBody.CoM[0], cg.car.sBody.CoM[1], cg.car.sBody.CoM[2]); Com_Printf("t:\n"); Com_Printf("%f, %f, %f\n", cg.car.sBody.t[0][0], cg.car.sBody.t[0][1], cg.car.sBody.t[0][2]); Com_Printf("%f, %f, %f\n", cg.car.sBody.t[1][0], cg.car.sBody.t[1][1], cg.car.sBody.t[1][2]); Com_Printf("%f, %f, %f\n", cg.car.sBody.t[2][0], cg.car.sBody.t[2][1], cg.car.sBody.t[2][2]); */ /* Point i = 4; Com_Printf("r %f, %f, %f\n", cg.car.tPoints[i].r[0], cg.car.tPoints[i].r[1], cg.car.tPoints[i].r[2]); Com_Printf("v %f, %f, %f\n", cg.car.tPoints[i].v[0], cg.car.tPoints[i].v[1], cg.car.tPoints[i].v[2]); Com_Printf("w %f\n", cg.car.tPoints[i].w); Com_Printf("netForce %f, %f, %f\n", cg.car.sPoints[i].netForce[0], cg.car.sPoints[i].netForce[1], cg.car.sPoints[i].netForce[2]); Com_Printf("netMoment %f\n", cg.car.sPoints[i].netMoment); Com_Printf("normals %f, %f, %f\n", cg.car.sPoints[i].normals[0][0], cg.car.sPoints[i].normals[0][1], cg.car.sPoints[i].normals[0][2]); Com_Printf("mass %f\n", cg.car.sPoints[i].mass); Com_Printf("elasticity %f\n", cg.car.sPoints[i].elasticity); Com_Printf("kcof %f\n", cg.car.sPoints[i].kcof); Com_Printf("scof %f\n", cg.car.sPoints[i].scof); Com_Printf("fluidDensity %f\n", cg.car.sPoints[i].fluidDensity); Com_Printf("onGround %d\n", cg.car.sPoints[i].onGround); Com_Printf("slipping %d\n", cg.car.sPoints[i].slipping); */ } } // VectorCopy(cg.snap->ps.velocity, lastVel); // VectorCopy(cg.snap->ps.origin, lastAngM); // lastPMType = cg.snap->ps.pm_type; cg.newSnap = qfalse; // Q3Rally Code END }