/* =========================================================================== Copyright (C) 1999 - 2005, Id Software, Inc. Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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, see . =========================================================================== */ // cg_ents.c -- present snapshot entities, happens every single frame #include "../game/g_local.h" #include "cg_local.h" #include "cg_media.h" #include "../game/g_functions.h" #include "../../code/ghoul2/G2.h" #include "FxScheduler.h" #include "../game/wp_saber.h" extern void CG_AddSaberBlade( centity_t *cent, centity_t *scent, refEntity_t *saber, int renderfx, int modelIndex, vec3_t origin, vec3_t angles); extern void CG_CheckSaberInWater( centity_t *cent, centity_t *scent, int modelIndex, vec3_t origin, vec3_t angles ); extern void CG_ForcePushBlur( const vec3_t org ); /* ====================== 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 cgi_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 1.0f - 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, orientation_t *tagOrient ) { int i; orientation_t lerped; vec3_t tempAxis[3]; // lerp the tag cgi_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 1.0f - parent->backlerp, tagName ); if ( tagOrient ) { VectorCopy( lerped.origin, tagOrient->origin ); for ( i = 0 ; i < 3 ; i++ ) { VectorCopy( lerped.axis[i], tagOrient->axis[i] ); } } // 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 ); } 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 ====================== */ vec3_t *CG_SetEntitySoundPosition( centity_t *cent ) { static vec3_t v3Return; if ( cent->currentState.solid == SOLID_BMODEL ) { vec3_t origin; float *v; v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; VectorAdd( cent->lerpOrigin, v, origin ); cgi_S_UpdateEntityPosition( cent->currentState.number, origin ); VectorCopy(origin, v3Return); } else { cgi_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); VectorCopy(cent->lerpOrigin, v3Return); } return &v3Return; } /* ================== CG_EntityEffects Add continuous entity effects, like local entity emission and lighting ================== */ static void CG_EntityEffects( centity_t *cent ) { // update sound origins vec3_t v3Origin; VectorCopy(*CG_SetEntitySoundPosition( cent ),v3Origin); // add loop sound if ( cent->currentState.loopSound ) { sfxHandle_t sfx = ( cent->currentState.eType == ET_MOVER ) ? cent->currentState.loopSound : cgs.sound_precache[ cent->currentState.loopSound ]; cgi_S_AddLoopingSound( cent->currentState.number, v3Origin/*cent->lerpOrigin*/, vec3_origin, sfx ); } // 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; cgi_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); } } void CG_AddRefEntWithTransportEffect ( centity_t *cent, refEntity_t *ent ) { // We are a normal thing.... cgi_R_AddRefEntityToScene (ent); if ( ent->renderfx & RF_PULSATE && cent->gent->owner && cent->gent->owner->health && !cent->gent->owner->s.number && cent->gent->owner->client && //only for player cent->gent->owner->client->ps.saberEntityState == SES_RETURNING && cent->currentState.saberActive == qfalse ) { // if we are the saber and we have been dropped, do a glow so it can be spotted easier float wv; vec3_t org; ent->customShader = cgi_R_RegisterShader( "gfx/effects/solidWhite_cull" ); ent->renderfx = RF_RGB_TINT; wv = sin( cg.time * 0.003f ) * 0.08f + 0.1f; ent->shaderRGBA[0] = wv * 255; ent->shaderRGBA[1] = wv * 255; ent->shaderRGBA[2] = wv * 0; cgi_R_AddRefEntityToScene (ent); for ( int i = -4; i < 10; i += 1 ) { VectorMA( ent->origin, -i, ent->axis[2], org ); FX_AddSprite( org, NULL, NULL, 5.5f, 5.5f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowDroppedSaberShader, 0x08000000 ); } if ( cent->gent->owner->s.weapon == WP_SABER ) {//he's still controlling me FX_AddSprite( cent->gent->owner->client->renderInfo.handRPoint, NULL, NULL, 8.0f, 8.0f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowDroppedSaberShader, 0x08000000 ); } } } /* Ghoul2 Insert Start */ // Copy the ghoul2 data into the ref ent correctly void CG_SetGhoul2Info( refEntity_t *ent, centity_t *cent) { ent->ghoul2 = ¢->gent->ghoul2; VectorCopy( cent->currentState.modelScale, ent->modelScale); ent->radius = cent->currentState.radius; VectorCopy (cent->lerpAngles, ent->angles); } // create 8 new points on screen around a model so we can see it's bounding box void CG_CreateBBRefEnts(entityState_t *s1, vec3_t origin ) { #if 0 //loadsavecrash _DEBUG refEntity_t point[8]; int i; vec3_t angles = {0,0,0}; for (i=0; i<8; i++) { memset (&point[i], 0, sizeof(refEntity_t)); point[i].reType = RT_SPRITE; point[i].radius = 1; point[i].customShader = cgi_R_RegisterShader("textures/tests/circle"); point[i].shaderRGBA[0] = 255; point[i].shaderRGBA[1] = 255; point[i].shaderRGBA[2] = 255; point[i].shaderRGBA[3] = 255; AnglesToAxis( angles, point[i].axis ); // now, we need to put the correct origins into each origin from the mins and max's switch(i) { case 0: VectorCopy(s1->mins, point[i].origin); break; case 1: VectorCopy(s1->mins, point[i].origin); point[i].origin[0] = s1->maxs[0]; break; case 2: VectorCopy(s1->mins, point[i].origin); point[i].origin[1] = s1->maxs[1]; break; case 3: VectorCopy(s1->mins, point[i].origin); point[i].origin[0] = s1->maxs[0]; point[i].origin[1] = s1->maxs[1]; break; case 4: VectorCopy(s1->maxs, point[i].origin); break; case 5: VectorCopy(s1->maxs, point[i].origin); point[i].origin[0] = s1->mins[0]; break; case 6: VectorCopy(s1->maxs, point[i].origin); point[i].origin[1] = s1->mins[1]; break; case 7: VectorCopy(s1->maxs, point[i].origin); point[i].origin[0] = s1->mins[0]; point[i].origin[1] = s1->mins[1]; break; } // add the original origin to each point and then stuff them out there VectorAdd(point[i].origin, origin, point[i].origin); cgi_R_AddRefEntityToScene (&point[i]); } #endif } // write in the axis and stuff void G2_BoltToGhoul2Model(centity_t *cent, refEntity_t *ent) { // extract the wraith ID from the bolt info int modelNum = cent->currentState.boltInfo >> MODEL_SHIFT; modelNum &= MODEL_AND; int boltNum = cent->currentState.boltInfo >> BOLT_SHIFT; boltNum &= BOLT_AND; int entNum = cent->currentState.boltInfo >> ENTITY_SHIFT; entNum &= ENTITY_AND; mdxaBone_t boltMatrix; // go away and get me the bolt position for this frame please gi.G2API_GetBoltMatrix(cent->gent->ghoul2, modelNum, boltNum, &boltMatrix, cg_entities[entNum].currentState.angles, cg_entities[entNum].currentState.origin, cg.time, cgs.model_draw, cent->currentState.modelScale); // set up the axis and origin we need for the actual effect spawning ent->origin[0] = boltMatrix.matrix[0][3]; ent->origin[1] = boltMatrix.matrix[1][3]; ent->origin[2] = boltMatrix.matrix[2][3]; ent->axis[0][0] = boltMatrix.matrix[0][0]; ent->axis[0][1] = boltMatrix.matrix[1][0]; ent->axis[0][2] = boltMatrix.matrix[2][0]; ent->axis[1][0] = boltMatrix.matrix[0][1]; ent->axis[1][1] = boltMatrix.matrix[1][1]; ent->axis[1][2] = boltMatrix.matrix[2][1]; ent->axis[2][0] = boltMatrix.matrix[0][2]; ent->axis[2][1] = boltMatrix.matrix[1][2]; ent->axis[2][2] = boltMatrix.matrix[2][2]; } void ScaleModelAxis(refEntity_t *ent) { // scale the model should we need to if (ent->modelScale[0] && ent->modelScale[0] != 1.0f) { VectorScale( ent->axis[0], ent->modelScale[0] , ent->axis[0] ); ent->nonNormalizedAxes = qtrue; } if (ent->modelScale[1] && ent->modelScale[1] != 1.0f) { VectorScale( ent->axis[1], ent->modelScale[1] , ent->axis[1] ); ent->nonNormalizedAxes = qtrue; } if (ent->modelScale[2] && ent->modelScale[2] != 1.0f) { VectorScale( ent->axis[2], ent->modelScale[2] , ent->axis[2] ); ent->nonNormalizedAxes = qtrue; } } /* Ghoul2 Insert End */ /* ================== CG_General ================== */ extern int CG_SaberHumSoundForEnt( gentity_t *gent ); static void CG_General( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; /* Ghoul2 Insert Start */ // if set to invisible, skip if (!s1->modelindex && !cent->gent->ghoul2.IsValid() ) { return; } /* Ghoul2 Insert End */ if ( s1->eFlags & EF_NODRAW ) { // If you don't like it doing NODRAW, then don't set the flag return; } memset (&ent, 0, sizeof(ent)); // set frame if ( s1->eFlags & EF_SHADER_ANIM ) { // Deliberately setting it up so that shader anim will completely override any kind of model animation frame setting. ent.renderfx|=RF_SETANIMINDEX; ent.skinNum = s1->frame; } else if ( s1->eFlags & EF_ANIM_ONCE ) { //s1->frame++; //ent.frame = s1->frame; ent.frame = cent->gent->s.frame; ent.renderfx|=RF_CAP_FRAMES; } else if ( s1->eFlags & EF_ANIM_ALLFAST ) { ent.frame = (cg.time / 100); ent.renderfx|=RF_WRAP_FRAMES; } else { ent.frame = s1->frame; } ent.oldframe = ent.frame; ent.backlerp = 0; /* Ghoul2 Insert Start */ CG_SetGhoul2Info(&ent, cent); /* Ghoul2 Insert End */ VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); ent.hModel = cgs.model_draw[s1->modelindex]; if ( s1->eFlags & EF_AUTO_SIZE && cent->gent ) { cgi_R_ModelBounds( ent.hModel, cent->gent->mins, cent->gent->maxs ); //Only do this once cent->gent->s.eFlags &= ~EF_AUTO_SIZE; } // player model if (s1->number == cg.snap->ps.clientNum) { ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors } /* Ghoul2 Insert Start */ // are we bolted to a Ghoul2 model? if (s1->boltInfo) { G2_BoltToGhoul2Model(cent, &ent); } else { //------------------------------------------------------- // Start of chair //------------------------------------------------------- if ( cent->gent->s.weapon == WP_EMPLACED_GUN || ( cent->gent->activator && cent->gent->activator->owner && cent->gent->activator->s.eFlags & EF_LOCKED_TO_WEAPON )) { vec3_t temp; if ( cent->gent->health <= 0 && cent->gent->e_ThinkFunc == thinkF_NULL ) { ent.customShader = cgi_R_RegisterShader( "models/map_objects/imp_mine/turret_chair_dmg" ); VectorSet( temp, 0, 0, 1 ); // add a big scorch mark under the gun CG_ImpactMark( cgs.media.scavMarkShader, cent->lerpOrigin, temp, 0, 1,1,1, 1.0f, qfalse, 92, qtrue ); CG_ImpactMark( cgs.media.scavMarkShader, cent->lerpOrigin, temp, 90, 1,1,1, 1.0f, qfalse, 48, qtrue ); } else { VectorSet( temp, 0, 0, 1 ); if ( !( cent->gent->svFlags & SVF_INACTIVE )) { ent.customShader = cgi_R_RegisterShader( "models/map_objects/imp_mine/turret_chair_on" ); } // shadow under the gun CG_ImpactMark( cgs.media.shadowMarkShader, cent->lerpOrigin, temp, 0, 1,1,1, 1.0f, qfalse, 32, qtrue ); } } if ( cent->gent->activator && cent->gent->activator->owner && cent->gent->activator->s.eFlags & EF_LOCKED_TO_WEAPON && cent->gent->activator->owner->s.number == cent->currentState.number ) // gun number must be same as current entities number { centity_t *cc = &cg_entities[cent->gent->activator->s.number]; const weaponData_t *wData = NULL; if ( cc->currentState.weapon ) { wData = &weaponData[cc->currentState.weapon]; } if ( !( cc->currentState.eFlags & EF_FIRING ) && !( cc->currentState.eFlags & EF_ALT_FIRING )) { // not animating..pausing was leaving the barrels in a bad state gi.G2API_PauseBoneAnim( ¢->gent->ghoul2[cent->gent->playerModel], "model_root", cg.time ); // gi.G2API_SetBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->rootBone, // 0, 0, BONE_ANIM_OVERRIDE, 1.0f, cg.time ); } // get alternating muzzle end bolts int bolt = cent->gent->handRBolt; mdxaBone_t boltMatrix; if ( !cc->gent->fxID ) { bolt = cent->gent->handLBolt; } gi.G2API_GetBoltMatrix( cent->gent->ghoul2, 0, bolt, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time, cgs.model_draw, cent->currentState.modelScale ); // store the muzzle point and direction so that we can fire in the right direction gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, cc->gent->client->renderInfo.muzzlePoint ); gi.G2API_GiveMeVectorFromMatrix( boltMatrix, POSITIVE_Y, cc->gent->client->renderInfo.muzzleDir ); cc->gent->client->renderInfo.mPCalcTime = cg.time; // HACK: adding in muzzle flashes if ( cc->muzzleFlashTime > 0 && wData ) { const char *effect = NULL; cc->muzzleFlashTime = 0; // Try and get a default muzzle so we have one to fall back on if ( wData->mMuzzleEffect[0] ) { effect = &wData->mMuzzleEffect[0]; } if ( cc->currentState.eFlags & EF_ALT_FIRING ) { // We're alt-firing, so see if we need to override with a custom alt-fire effect if ( wData->mAltMuzzleEffect[0] ) { effect = &wData->mAltMuzzleEffect[0]; } } if ( cc->currentState.eFlags & EF_FIRING || cc->currentState.eFlags & EF_ALT_FIRING ) { gi.G2API_SetBoneAnimIndex( ¢->gent->ghoul2[cent->gent->playerModel], cent->gent->rootBone, 0, 3, BONE_ANIM_OVERRIDE_FREEZE, 0.6f, cg.time, -1, -1 ); if ( effect ) { // We got an effect and we're firing, so let 'er rip. theFxScheduler.PlayEffect( effect, cc->gent->client->renderInfo.muzzlePoint, cc->gent->client->renderInfo.muzzleDir ); } } } VectorCopy( cent->gent->s.apos.trBase, cent->lerpAngles ); } //------------------------------------------------------- // End of chair //------------------------------------------------------- AnglesToAxis( cent->lerpAngles, ent.axis ); } ScaleModelAxis(&ent); if (cent->gent->ghoul2.size()) { if ( s1->weapon == WP_SABER && cent->gent && cent->gent->owner && cent->gent->owner->inuse ) {//flying lightsaber //FIXME: better way to tell what it is would be nice if ( cent->gent->classname && !Q_stricmp( "limb", cent->gent->classname ) ) {//limb, just add blade if ( cent->gent->owner->client ) { if ( cent->gent->owner->client->ps.saberLength > 0 ) { CG_AddSaberBlade( &cg_entities[cent->gent->owner->s.number], &cg_entities[cent->gent->s.number], NULL, ent.renderfx, cent->gent->weaponModel, cent->lerpOrigin, cent->lerpAngles ); } else if ( cent->gent->owner->client->ps.saberEventFlags & SEF_INWATER ) { CG_CheckSaberInWater( &cg_entities[cent->gent->owner->s.number], &cg_entities[cent->gent->s.number], cent->gent->weaponModel, cent->lerpOrigin, cent->lerpAngles ); } } } else {//thrown saber //light? sound? if ( cent->gent->owner->client && cg_entities[cent->currentState.otherEntityNum].currentState.saberActive ) {//saber is in-flight and active, play a sound on it if ( cent->gent->owner->client->ps.saberEntityState == SES_RETURNING ) { cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, CG_SaberHumSoundForEnt( cent->gent->owner ) ); } else { int spinSound; switch ( cent->gent->owner->client->ps.forcePowerLevel[FP_SABERTHROW] ) { case FORCE_LEVEL_1: spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin3.wav" ); break; case FORCE_LEVEL_2: spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin2.wav" ); break; default: case FORCE_LEVEL_3: spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin1.wav" ); break; } cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, spinSound ); /* if ( cg_weapons[WP_SABER].missileSound ) { cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cg_weapons[WP_SABER].missileSound ); } */ } } if ( cent->gent->owner->client ) { if ( cent->gent->owner->client->ps.saberLength > 0 ) {//only add the blade if it's on CG_AddSaberBlade( &cg_entities[cent->gent->owner->s.number], &cg_entities[cent->gent->s.number], NULL, ent.renderfx, 0, cent->lerpOrigin, cent->lerpAngles ); } else if ( cent->gent->owner->client->ps.saberEventFlags & SEF_INWATER ) { CG_CheckSaberInWater( &cg_entities[cent->gent->owner->s.number], &cg_entities[cent->gent->s.number], 0, cent->lerpOrigin, cent->lerpAngles ); } } if ( cent->gent->owner->health ) { //make sure we can always be seen ent.renderfx |= RF_PULSATE; } } } } else { if ( s1->weapon == WP_SABER && cent->gent && cent->gent->owner ) {//flying lightsaber //light? sound? if ( cent->gent->owner->client && cent->currentState.saberActive ) {//saber is in-flight and active, play a sound on it if ( cent->gent->owner->client->ps.saberEntityState == SES_RETURNING ) { if ( cg_weapons[WP_SABER].firingSound ) { cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cg_weapons[WP_SABER].firingSound ); } } else { int spinSound; switch ( cent->gent->owner->client->ps.forcePowerLevel[FP_SABERTHROW] ) { case FORCE_LEVEL_1: spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin3.wav" ); break; case FORCE_LEVEL_2: spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin2.wav" ); break; default: case FORCE_LEVEL_3: spinSound = cgi_S_RegisterSound( "sound/weapons/saber/saberspin1.wav" ); break; } cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, spinSound ); /* if ( cg_weapons[WP_SABER].missileSound ) { cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cg_weapons[WP_SABER].missileSound ); } */ } } CG_AddSaberBlade( &cg_entities[cent->gent->owner->s.number], NULL, &ent, ent.renderfx, 0, NULL, NULL ); if ( cent->gent->owner->health ) { //make sure we can always be seen ent.renderfx |= RF_PULSATE; } } } /* Ghoul2 Insert End */ if ( cent->gent && cent->gent->forcePushTime > cg.time ) {//FIXME: if I'm a rather large model, this will look kind of stupid... CG_ForcePushBlur( cent->lerpOrigin ); } CG_AddRefEntWithTransportEffect( cent, &ent ); if ( cent->gent->health <= 0 && cent->gent->s.weapon == WP_EMPLACED_GUN && cent->gent->e_ThinkFunc ) { // make the gun pulse red to warn about it exploding float val = (1.0f - (float)(cent->gent->nextthink - cg.time) / 3200.0f ) * 0.3f; ent.customShader = cgi_R_RegisterShader( "gfx/effects/solidWhite" ); ent.shaderRGBA[0] = (sin( cg.time * 0.04f ) * val * 0.4f + val) * 255; ent.shaderRGBA[1] = ent.shaderRGBA[2] = 0; ent.renderfx |= RF_RGB_TINT; cgi_R_AddRefEntityToScene( &ent ); } /* Ghoul2 Insert Start */ if (cg_debugBB.integer) { CG_CreateBBRefEnts(s1, cent->lerpOrigin); } /* Ghoul2 Insert End */ //-------------------------- if ( s1->eFlags & EF_FIRING && cent->gent->inuse ) { //special code for adding the beam to the attached tripwire mine vec3_t beamOrg; int handle = 0; SEffectTemplate *temp; VectorMA( ent.origin, 6.6f, ent.axis[0], beamOrg );// forward // overriding the effect, so give us a copy first temp = theFxScheduler.GetEffectCopy( "tripMine/laser", &handle ); if ( temp ) { // have a copy, so get the line element out of there CPrimitiveTemplate *prim = theFxScheduler.GetPrimitiveCopy( temp, "line1" ); if ( prim ) { // we have the primitive, so modify the endpoint prim->mOrigin2X.SetRange( cent->gent->pos4[0], cent->gent->pos4[0] ); prim->mOrigin2Y.SetRange( cent->gent->pos4[1], cent->gent->pos4[1] ); prim->mOrigin2Z.SetRange( cent->gent->pos4[2], cent->gent->pos4[2] ); // have a copy, so get the line element out of there CPrimitiveTemplate *prim = theFxScheduler.GetPrimitiveCopy( temp, "line2" ); if ( prim ) { // we have the primitive, so modify the cent->gent->pos3point prim->mOrigin2X.SetRange( cent->gent->pos4[0], cent->gent->pos4[0] ); prim->mOrigin2Y.SetRange( cent->gent->pos4[1], cent->gent->pos4[1] ); prim->mOrigin2Z.SetRange( cent->gent->pos4[2], cent->gent->pos4[2] ); // play the modified effect theFxScheduler.PlayEffect( handle, beamOrg, ent.axis[0] ); } } } theFxScheduler.PlayEffect( "tripMine/laserImpactGlow", cent->gent->pos4, ent.axis[0] ); } if ( s1->eFlags & EF_PROX_TRIP ) { //special code for adding the glow end to proximity tripmine vec3_t beamOrg; VectorMA( ent.origin, 6.6f, ent.axis[0], beamOrg );// forward theFxScheduler.PlayEffect( "tripMine/glowBit", beamOrg, ent.axis[0] ); } if ( s1->eFlags & EF_ALT_FIRING ) { // hack for the spotlight vec3_t org, axis[3], dir; AngleVectors( cent->lerpAngles, dir, NULL, NULL ); CG_GetTagWorldPosition( &ent, "tag_flash", org, axis ); theFxScheduler.PlayEffect( "env/light_cone", org, axis[0] ); VectorMA( cent->lerpOrigin, cent->gent->radius - 5, dir, org ); // stay a bit back from the impact point...this may not be enough? cgi_R_AddLightToScene( org, 225, 1.0f, 1.0f, 1.0f ); } //----------------------------------------------------------- if ( cent->gent->flags & (FL_DMG_BY_HEAVY_WEAP_ONLY | FL_SHIELDED )) { // Dumb assumption, but I guess we must be a shielded ion_cannon?? We should probably verify // if it's an ion_cannon that's Heavy Weapon only, we don't want to make it shielded do we...? if ( (!strcmp( "misc_ion_cannon", cent->gent->classname )) && (cent->gent->flags & FL_SHIELDED )) { // must be doing "pain"....er, impact if ( cent->gent->painDebounceTime > cg.time ) { float t = (float)(cent->gent->painDebounceTime - cg.time ) / 1000.0f; // Only display when we have damage if ( t >= 0.0f && t <= 1.0f ) { t *= Q_flrand(0.0f, 1.0f); ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = 255.0f * t; ent.shaderRGBA[3] = 255; ent.renderfx &= ~RF_ALPHA_FADE; ent.renderfx |= RF_RGB_TINT; ent.customShader = cgi_R_RegisterShader( "gfx/misc/ion_shield" ); cgi_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; } cgi_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.sound_precache[cent->currentState.eventParm] ); // ent->s.frame = ent->wait * 10; // ent->s.clientNum = ent->random * 10; cent->miscTime = (int)(cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * Q_flrand(-1.0f, 1.0f)); } /* ================== CG_Item ================== */ static void CG_Item( centity_t *cent ) { refEntity_t ent; entityState_t *es; gitem_t *item; // int msec; // float frac; float scale; es = ¢->currentState; if ( es->modelindex >= bg_numItems ) { CG_Error( "Bad item index %i on entity", es->modelindex ); } /* Ghoul2 Insert Start */ // if set to invisible, skip if ( (!es->modelindex && !cent->gent->ghoul2.IsValid() ) || ( es->eFlags & EF_NODRAW ) ) { return; } /* Ghoul2 Insert End */ if ( cent->gent && !cent->gent->inuse ) { // Yeah, I know....items were being freed on touch, but it could still get here and draw incorrectly... return; } item = &bg_itemlist[ es->modelindex ]; if ( cg_simpleItems.integer ) { 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; cgi_R_AddRefEntityToScene(&ent); return; } memset (&ent, 0, sizeof(ent)); // items bob up and down continuously if( item->giType == IT_HOLOCRON ) { scale = 0.005f + cent->currentState.number * 0.00001f; cent->lerpOrigin[2] += (float)(4 + cos( ( cg.time + 1000 ) * scale ) * 3)+8; // just raised them up a bit } // autorotate at one of two speeds // if ( item->giType == IT_HEALTH ) { // VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); // AxisCopy( cg.autoAxisFast, ent.axis ); // } else { if( item->giType == IT_HOLOCRON ) { VectorCopy( cg.autoAngles, cent->lerpAngles ); AxisCopy( cg.autoAxis, ent.axis ); } // 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 ) { // weaponInfo_t *wi; // // 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 // } vec3_t spinAngles; //AxisClear( ent.axis ); VectorCopy( cent->gent->s.angles, spinAngles ); ent.hModel = cg_items[es->modelindex].models; /* Ghoul2 Insert Start */ CG_SetGhoul2Info(&ent, cent); /* Ghoul2 Insert End */ VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); ent.nonNormalizedAxes = qfalse; // lovely...this is for weapons that should be oriented vertically. For weapons lockers and such. if ( cent->gent->spawnflags & 16 ) { //VectorClear( spinAngles ); spinAngles[PITCH] -= 75; } if( item->giType != IT_HOLOCRON ) { AnglesToAxis( spinAngles, ent.axis ); } // items without glow textures need to keep a minimum light value // so they are always visible /* if (( item->giType == IT_WEAPON ) || ( item->giType == IT_ARMOR )) { 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.5f, ent.axis[0] ); // VectorScale( ent.axis[1], 1.5f, ent.axis[1] ); // VectorScale( ent.axis[2], 1.5f, ent.axis[2] ); // ent.nonNormalizedAxes = qtrue; // } // add to refresh list cgi_R_AddRefEntityToScene(&ent); if ( item->giType == IT_WEAPON && item->giTag == WP_SABER ) { // saber pickup item needs to be more visible float wv; vec3_t org; ent.customShader = cgi_R_RegisterShader( "gfx/effects/solidWhite_cull" ); ent.renderfx = RF_RGB_TINT; wv = sin( cg.time * 0.002f ) * 0.08f + 0.2f; ent.shaderRGBA[0] = ent.shaderRGBA[1] = wv * 255; ent.shaderRGBA[2] = 0; cgi_R_AddRefEntityToScene(&ent); for ( int i = -4; i < 10; i += 1 ) { VectorMA( ent.origin, -i, ent.axis[2], org ); FX_AddSprite( org, NULL, NULL, 10.0f, 10.0f, wv * 0.5f, wv * 0.5f, 0.0f, 0.0f, 1.0f, cgs.media.yellowDroppedSaberShader, 0x08000000 ); } // THIS light looks crappy...maybe it should just be removed... cgi_R_AddLightToScene( ent.origin, wv * 350 + 180, 1.0f, 1.0f, 0.0f ); } } //============================================================================ /* =============== CG_Missile =============== */ static void CG_Missile( centity_t *cent ) { refEntity_t ent; entityState_t *s1; const weaponInfo_t *weapon; const weaponData_t *wData; if ( !cent->gent->inuse ) return; s1 = ¢->currentState; if ( s1->weapon >= WP_NUM_WEAPONS ) { s1->weapon = 0; } weapon = &cg_weapons[s1->weapon]; wData = &weaponData[s1->weapon]; if ( s1->pos.trType != TR_INTERPOLATE ) { // calculate the axis VectorCopy( s1->angles, cent->lerpAngles ); } if ( cent->gent->alt_fire ) { // add trails if ( weapon->alt_missileTrailFunc ) weapon->alt_missileTrailFunc( cent, weapon ); // add dynamic light if ( wData->alt_missileDlight ) cgi_R_AddLightToScene(cent->lerpOrigin, wData->alt_missileDlight, wData->alt_missileDlightColor[0], wData->alt_missileDlightColor[1], wData->alt_missileDlightColor[2] ); // add missile sound if ( weapon->alt_missileSound ) cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->alt_missileSound ); //Don't draw something without a model if ( weapon->alt_missileModel == NULL_HANDLE ) return; } else { // add trails if ( weapon->missileTrailFunc ) weapon->missileTrailFunc( cent, weapon ); // add dynamic light if ( wData->missileDlight ) cgi_R_AddLightToScene(cent->lerpOrigin, wData->missileDlight, wData->missileDlightColor[0], wData->missileDlightColor[1], wData->missileDlightColor[2] ); // add missile sound if ( weapon->missileSound ) cgi_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound ); //Don't draw something without a model if ( weapon->missileModel == NULL_HANDLE ) return; } // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); /* Ghoul2 Insert Start */ CG_SetGhoul2Info(&ent, cent); /* Ghoul2 Insert End */ // flicker between two skins ent.skinNum = cg.clientFrame & 1; ent.renderfx = /*weapon->missileRenderfx | */RF_NOSHADOW; if ( cent->gent->alt_fire ) ent.hModel = weapon->alt_missileModel; else ent.hModel = weapon->missileModel; // spin as it moves if ( s1->apos.trType != TR_INTERPOLATE ) { // convert direction of travel into axis if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { ent.axis[0][2] = 1; } if ( s1->pos.trType != TR_STATIONARY ) { if ( s1->eFlags & EF_MISSILE_STICK ) RotateAroundDirection( ent.axis, cg.time * 0.5f );//Did this so regular missiles don't get broken else RotateAroundDirection( ent.axis, cg.time * 0.25f );//JFM:FLOAT FIX } else { if ( s1->eFlags & EF_MISSILE_STICK ) RotateAroundDirection( ent.axis, (float)s1->pos.trTime * 0.5f ); else RotateAroundDirection( ent.axis, (float)s1->time ); } } else { AnglesToAxis( cent->lerpAngles, ent.axis ); } // add to refresh list, possibly with quad glow CG_AddRefEntityWithPowerups( &ent, s1->powerups, NULL ); } /* =============== CG_Mover =============== */ #define DOOR_OPENING 1 #define DOOR_CLOSING 2 #define DOOR_OPEN 3 #define DOOR_CLOSED 4 static void CG_Mover( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset (&ent, 0, sizeof(ent)); //FIXME: why are these always 0, 0, 0???! VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); AnglesToAxis( cent->lerpAngles, ent.axis ); /* Ghoul2 Insert Start */ CG_SetGhoul2Info(&ent, cent); /* Ghoul2 Insert End */ 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.model_draw[s1->modelindex]; } // If there isn't an hModel for this mover, an RGB axis model will get drawn. if ( !ent.hModel ) { return; } if ( cent->currentState.eFlags & EF_DISABLE_SHADER_ANIM ) { // by setting the shader time to the current time, we can force an animating shader to not animate ent.shaderTime = cg.time * 0.001f; } // add the secondary model if ( s1->solid == SOLID_BMODEL && s1->modelindex2 ) { // vec3_t org; if ( !(s1->eFlags & EF_NODRAW) ) { // add to refresh list CG_AddRefEntWithTransportEffect( cent, &ent ); } /* // Um, this does not interpolate nicely? Not sure why it was here.... VectorAdd(cent->gent->absmin, cent->gent->absmax, org); VectorScale(org, 0.5, org); VectorCopy( org, ent.origin); VectorCopy( org, ent.oldorigin); */ ent.hModel = cgs.model_draw[s1->modelindex2]; } // I changed it to always do it because nodraw seemed like it should actually do what it says. Be aware that if you change this, // the movers for the shooting gallery on doom_detention will break. if ( (s1->eFlags & EF_NODRAW) ) { return; } //fall through and render the hModel or... //We're a normal model being moved, animate our model ent.skinNum = 0; if ( s1->eFlags & EF_ANIM_ONCE ) {//FIXME: needs to anim at once per 100 ms ent.frame = cent->gent->s.frame; ent.renderfx|=RF_CAP_FRAMES; } else if ( s1->eFlags & EF_ANIM_ALLFAST ) { ent.frame = (cg.time / 100); ent.renderfx|=RF_WRAP_FRAMES; } else { ent.frame = s1->frame; } if ( s1->eFlags & EF_SHADER_ANIM ) { ent.renderfx|=RF_SETANIMINDEX; ent.skinNum = s1->frame; //ent.shaderTime = cg.time*0.001f - s1->frame/s1->time;//NOTE: s1->time is number of frames } // add to refresh list CG_AddRefEntWithTransportEffect( cent, &ent ); } /* =============== CG_Beam Also called as an event =============== */ void CG_Beam( centity_t *cent, int color ) { 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.skinNum = color; ent.renderfx = RF_NOSHADOW; /* Ghoul2 Insert Start */ CG_SetGhoul2Info(&ent, cent); /* Ghoul2 Insert End */ // add to refresh list cgi_R_AddRefEntityToScene(&ent); } static vec2_t st[] = { { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } }; void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ) { vec3_t point[4], rot={0,0,0}; int vec[3]; int axis, i; for ( axis = 0, vec[0] = 0, vec[1] = 1, vec[2] = 2; axis < 3; axis++, vec[0]++, vec[1]++, vec[2]++ ) { for ( i = 0; i < 3; i++ ) { if ( vec[i] > 2 ) { vec[i] = 0; } } point[0][vec[1]] = mins[vec[1]]; point[0][vec[2]] = mins[vec[2]]; point[1][vec[1]] = mins[vec[1]]; point[1][vec[2]] = maxs[vec[2]]; point[2][vec[1]] = maxs[vec[1]]; point[2][vec[2]] = maxs[vec[2]]; point[3][vec[1]] = maxs[vec[1]]; point[3][vec[2]] = mins[vec[2]]; //- face point[0][vec[0]] = point[1][vec[0]] = point[2][vec[0]] = point[3][vec[0]] = mins[vec[0]]; FX_AddPoly( point, st, 4, NULL, NULL, alpha, alpha, 0.0f, color, color, 0.0f, rot, 0.0f, 0.0f, 100, cgs.media.solidWhiteShader, 0 ); //+ face point[0][vec[0]] = point[1][vec[0]] = point[2][vec[0]] = point[3][vec[0]] = maxs[vec[0]]; FX_AddPoly( point, st, 4, NULL, NULL, alpha, alpha, 0.0f, color, color, 0.0f, rot, 0.0f, 0.0f, 100, cgs.media.solidWhiteShader, 0 ); } } void CG_CubeOutline( vec3_t mins, vec3_t maxs, int time, unsigned int color, float alpha ) { vec3_t point1, point2, point3, point4; int vec[3]; int axis, i; for ( axis = 0, vec[0] = 0, vec[1] = 1, vec[2] = 2; axis < 3; axis++, vec[0]++, vec[1]++, vec[2]++ ) { for ( i = 0; i < 3; i++ ) { if ( vec[i] > 2 ) { vec[i] = 0; } } point1[vec[1]] = mins[vec[1]]; point1[vec[2]] = mins[vec[2]]; point2[vec[1]] = mins[vec[1]]; point2[vec[2]] = maxs[vec[2]]; point3[vec[1]] = maxs[vec[1]]; point3[vec[2]] = maxs[vec[2]]; point4[vec[1]] = maxs[vec[1]]; point4[vec[2]] = mins[vec[2]]; //- face point1[vec[0]] = point2[vec[0]] = point3[vec[0]] = point4[vec[0]] = mins[vec[0]]; CG_TestLine( point1, point2, time, color, 1 ); CG_TestLine( point2, point3, time, color, 1 ); CG_TestLine( point1, point4, time, color, 1 ); CG_TestLine( point4, point3, time, color, 1 ); //+ face point1[vec[0]] = point2[vec[0]] = point3[vec[0]] = point4[vec[0]] = maxs[vec[0]]; CG_TestLine( point1, point2, time, color, 1 ); CG_TestLine( point2, point3, time, color, 1 ); CG_TestLine( point1, point4, time, color, 1 ); CG_TestLine( point4, point1, time, color, 1 ); } } void CG_Line( vec3_t start, vec3_t end, vec3_t color, float alpha ) { /*FX_AddLine( start, end, 1.0f, 1.0, 1.0f, alpha, alpha, color, color, 100.0f, cgs.media.whiteShader );*/ } /* =============== CG_Portal =============== */ static void CG_Portal( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; //FIXME: this tends to give a bad axis[1], perhaps we //should just do the VectorSubtraction here rather than //on the game side. Would also allow camera to follow //a moving target. // 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.frame = s1->frame; // rotation speed ent.skinNum = (int)(s1->clientNum/256.0 * 360); // roll offset /* Ghoul2 Insert Start */ CG_SetGhoul2Info(&ent, cent); /* Ghoul2 Insert End */ // add to refresh list cgi_R_AddRefEntityToScene(&ent); } /* ========================= CG_AdjustPositionForMover Also called by client movement prediction code ========================= */ void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int atTime, vec3_t out ) { centity_t *cent; vec3_t oldOrigin, origin, deltaOrigin; // vec3_t oldAngles, angles, deltaAngles; if ( moverNum <= 0 ) { VectorCopy( in, out ); return; } cent = &cg_entities[ moverNum ]; if ( cent->currentState.eType != ET_MOVER ) { VectorCopy( in, out ); return; } EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, oldOrigin ); // EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, oldAngles ); EvaluateTrajectory( ¢->currentState.pos, atTime, origin ); // EvaluateTrajectory( ¢->currentState.apos, atTime, angles ); VectorSubtract( origin, oldOrigin, deltaOrigin ); // VectorSubtract( angles, oldAngles, deltaAngles ); VectorAdd( in, deltaOrigin, out ); // FIXME: origin change when on a rotating object } /* =============== CG_CalcEntityLerpPositions =============== */ extern char *vtos( const vec3_t v ); #if 1 void CG_CalcEntityLerpPositions( centity_t *cent ) { if ( cent->currentState.number == cg.snap->ps.clientNum) { // if the player, take position from prediction VectorCopy( cg.predicted_player_state.origin, cent->lerpOrigin ); VectorCopy( cg.predicted_player_state.viewangles, cent->lerpAngles ); /* Ghoul2 Insert Start */ // LerpBoneAngleOverrides(cent); /* Ghoul2 Insert End */ return; } //FIXME: prediction on clients in timescale results in jerky positional translation if ( cent->interpolate ) { // if the entity has a valid next state, interpolate a value between the frames // unless it is a mover with a known start and stop 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_AddCEntity: cg.nextSnap == NULL" ); } f = cg.frameInterpolation; if ( cent->currentState.apos.trType == TR_INTERPOLATE ) { EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); 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 ); /* if(cent->gent && cent->currentState.eFlags & EF_NPC && !VectorCompare(current, next)) { Com_Printf("%s last/next/lerp apos %s/%s/%s, f = %4.2f\n", cent->gent->script_targetname, vtos(current), vtos(next), vtos(cent->lerpAngles), f); } */ /* Ghoul2 Insert Start */ // now the nasty stuff - this will interpolate all ghoul2 models bone angle overrides per model attached to this cent /* if (cent->gent->ghoul2.size()) { LerpBoneAngleOverrides(cent); } */ /* Ghoul2 Insert End */ } if ( cent->currentState.pos.trType == TR_INTERPOLATE ) { // this will linearize a sine or parabolic curve, but it is important // to not extrapolate player positions if more recent data is available EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); 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] ); /* if ( cent->gent && cent->currentState.eFlags & EF_NPC ) { Com_Printf("%s last/next/lerp pos %s/%s/%s, f = %4.2f\n", cent->gent->script_targetname, vtos(current), vtos(next), vtos(cent->lerpOrigin), f); } */ return;//FIXME: should this be outside this if? } } else { if ( cent->currentState.apos.trType == TR_INTERPOLATE ) { EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, cent->lerpAngles ); } if ( cent->currentState.pos.trType == TR_INTERPOLATE ) { EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); /* if(cent->gent && cent->currentState.eFlags & EF_NPC ) { Com_Printf("%s last/next/lerp pos %s, f = 1.0\n", cent->gent->script_targetname, vtos(cent->lerpOrigin) ); } */ return; } } // FIXME: if it's blocked, it wigs out, draws it in a predicted spot, but never // makes it there - we need to predict it in the right place if this is happens... // just use the current frame and evaluate as best we can trajectory_t *posData = ¢->currentState.pos; { gentity_t *ent = &g_entities[cent->currentState.number]; if ( ent && ent->inuse) { if ( ent->s.eFlags & EF_BLOCKED_MOVER || ent->s.pos.trType == TR_STATIONARY ) {//this mover has stopped moving and is going to wig out if we predict it //based on last frame's info- cut across the network and use the currentOrigin VectorCopy( ent->currentOrigin, cent->lerpOrigin ); posData = NULL; } else { posData = &ent->s.pos; } } } if ( posData ) { EvaluateTrajectory( posData, cg.time, cent->lerpOrigin ); } // FIXME: this will stomp an apos trType of TR_INTERPOLATE!! EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); // adjust for riding a mover CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, cg.time, cent->lerpOrigin ); /* Ghoul2 Insert Start */ // now the nasty stuff - this will interpolate all ghoul2 models bone angle overrides per model attached to this cent /* if (cent->gent->ghoul2.size()) { LerpBoneAngleOverrides(cent); } */ /* Ghoul2 Insert End */ // FIXME: perform general error decay? } #else void CG_CalcEntityLerpPositions( centity_t *cent ) { if ( cent->currentState.number == cg.snap->ps.clientNum) { // if the player, take position from prediction VectorCopy( cg.predicted_player_state.origin, cent->lerpOrigin ); VectorCopy( cg.predicted_player_state.viewangles, cent->lerpAngles ); OutputDebugString(va("b=(%6.2f,%6.2f,%6.2f)\n",cent->lerpOrigin[0],cent->lerpOrigin[1],cent->lerpOrigin[2])); return; } if (cent->currentState.number != cg.snap->ps.clientNum&¢->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE) { if (cent->interpolate) { OutputDebugString(va("[%3d] interp %4.2f t=%6d st = %6d nst = %6d b=%6.2f nb=%6.2f\n", cent->currentState.number, cg.frameInterpolation, cg.time, cg.snap->serverTime, cg.nextSnap->serverTime, cent->currentState.pos.trBase[0], cent->nextState.pos.trBase[0])); } else { OutputDebugString(va("[%3d] nonext %4.2f t=%6d st = %6d nst = %6d b=%6.2f nb=%6.2f\n", cent->currentState.number, cg.frameInterpolation, cg.time, cg.snap->serverTime, 0, cent->currentState.pos.trBase[0], 0.0f)); } } //FIXME: prediction on clients in timescale results in jerky positional translation if (cent->interpolate && (cent->currentState.number == cg.snap->ps.clientNum || cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) ) { 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_AddCEntity: cg.nextSnap == NULL" ); } f = cg.frameInterpolation; EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); 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 ); EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); 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] ); return; } // just use the current frame and evaluate as best we can trajectory_t *posData = ¢->currentState.pos; { gentity_t *ent = &g_entities[cent->currentState.number]; if ( ent && ent->inuse) { if ( ent->s.eFlags & EF_BLOCKED_MOVER || ent->s.pos.trType == TR_STATIONARY ) {//this mover has stopped moving and is going to wig out if we predict it //based on last frame's info- cut across the network and use the currentOrigin VectorCopy( ent->currentOrigin, cent->lerpOrigin ); posData = NULL; } else { posData = &ent->s.pos; EvaluateTrajectory(&ent->s.pos,cg.time, cent->lerpOrigin ); } } else { EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); } } EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); // adjust for riding a mover CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, cg.time, cent->lerpOrigin ); } #endif /* =============== CG_AddLocalSet =============== */ static void CG_AddLocalSet( centity_t *cent ) { cent->gent->setTime = cgi_S_AddLocalSet( cent->gent->soundSet, cg.refdef.vieworg, cent->lerpOrigin, cent->gent->s.number, cent->gent->setTime ); } /* ------------------------- CAS_GetBModelSound ------------------------- */ sfxHandle_t CAS_GetBModelSound( const char *name, int stage ) { return cgi_AS_GetBModelSound( name, stage ); } void CG_DLightThink ( centity_t *cent ) { if(cent->gent) { float tDelta = cg.time - cent->gent->painDebounceTime; float percentage = ( tDelta/((float)cent->gent->speed) ); vec3_t org; vec4_t currentRGBA; gentity_t *owner = NULL; int i; if ( percentage >= 1.0f ) {//We hit the end percentage = 1.0f; switch( cent->gent->pushDebounceTime ) { case 0://Fading from start to final if ( cent->gent->spawnflags & 8 ) {//PULSER if ( tDelta - cent->gent->speed - cent->gent->wait >= 0 ) {//Time to start fading down cent->gent->painDebounceTime = cg.time; cent->gent->pushDebounceTime = 1; percentage = 0.0f; } } else {//Stick on startRGBA percentage = 0.0f; } break; case 1://Fading from final to start if ( tDelta - cent->gent->speed - cent->gent->radius >= 0 ) {//Time to start fading up cent->gent->painDebounceTime = cg.time; cent->gent->pushDebounceTime = 0; percentage = 0.0f; } break; case 2://Fading from 0 intensity to start intensity //Time to start fading from start to final cent->gent->painDebounceTime = cg.time; cent->gent->pushDebounceTime = 0; percentage = 0.0f; break; case 3://Fading from current intensity to 0 intensity //Time to turn off cent->gent->misc_dlight_active = qfalse; cent->gent->e_clThinkFunc = clThinkF_NULL; cent->gent->s.eType = ET_GENERAL; cent->gent->svFlags &= ~SVF_BROADCAST; return; break; default: break; } } switch( cent->gent->pushDebounceTime ) { case 0://Fading from start to final for ( i = 0; i < 4; i++ ) { currentRGBA[i] = cent->gent->startRGBA[i] + ( (cent->gent->finalRGBA[i] - cent->gent->startRGBA[i]) * percentage ); } break; case 1://Fading from final to start for ( i = 0; i < 4; i++ ) { currentRGBA[i] = cent->gent->finalRGBA[i] + ( (cent->gent->startRGBA[i] - cent->gent->finalRGBA[i]) * percentage ); } break; case 2://Fading from 0 intensity to start for ( i = 0; i < 3; i++ ) { currentRGBA[i] = cent->gent->startRGBA[i]; } currentRGBA[3] = cent->gent->startRGBA[3] * percentage; break; case 3://Fading from current intensity to 0 for ( i = 0; i < 3; i++ ) {//FIXME: use last currentRGBA[i] = cent->gent->startRGBA[i]; } currentRGBA[3] = cent->gent->startRGBA[3] - (cent->gent->startRGBA[3] * percentage); break; default: return; break; } if ( cent->gent->owner ) { owner = cent->gent->owner; } else { owner = cent->gent; } if ( owner->s.pos.trType == TR_INTERPOLATE ) { VectorCopy( cg_entities[owner->s.number].lerpOrigin, org ); } else { VectorCopy( owner->currentOrigin, org ); } cgi_R_AddLightToScene(org, currentRGBA[3]*10, currentRGBA[0], currentRGBA[1], currentRGBA[2] ); } } void CG_Limb ( centity_t *cent ) {//first time we're drawn, remove us from the owner ent if ( cent->gent && cent->gent->owner && cent->gent->owner->ghoul2.size() ) { gentity_t *owner = cent->gent->owner; if ( cent->gent->aimDebounceTime ) {//done with dismemberment, just waiting to mark owner dismemberable again if ( cent->gent->aimDebounceTime > cg.time ) {//still waiting return; } //done! owner->client->dismembered = qfalse; //done! cent->gent->e_clThinkFunc = clThinkF_NULL; } else { extern cvar_t *g_dismemberment; extern cvar_t *g_saberRealisticCombat; //3) turn off w/descendants that surf in original model if ( cent->gent->target )//stubTagName ) {//add smoke to cap surf, spawn effect int newBolt = gi.G2API_AddBolt( &owner->ghoul2[owner->playerModel], cent->gent->target ); if ( newBolt != -1 ) { G_PlayEffect( "blaster/smoke_bolton", owner->playerModel, newBolt, owner->s.number ); } } if ( cent->gent->target2 )//limbName {//turn the limb off gi.G2API_SetSurfaceOnOff( &owner->ghoul2[owner->playerModel], cent->gent->target2, 0x00000100 );//G2SURFACEFLAG_NODESCENDANTS } if ( cent->gent->target3 )//stubCapName ) {//turn on caps gi.G2API_SetSurfaceOnOff( &owner->ghoul2[owner->playerModel], cent->gent->target3, 0 ); } if ( owner->weaponModel >= 0 ) {//the corpse hasn't dropped their weapon if ( cent->gent->count == BOTH_DISMEMBER_RARM || cent->gent->count == BOTH_DISMEMBER_TORSO1 )//&& ent->s.weapon == WP_SABER && ent->weaponModel != -1 ) {//FIXME: is this first check needed with this lower one? gi.G2API_RemoveGhoul2Model( owner->ghoul2, owner->weaponModel ); owner->weaponModel = -1; } } if ( owner->client->NPC_class == CLASS_PROTOCOL || g_dismemberment->integer >= 11381138 || g_saberRealisticCombat->integer ) { //wait 100ms before allowing owner to be dismembered again cent->gent->aimDebounceTime = cg.time + 100; return; } else { //done! cent->gent->e_clThinkFunc = clThinkF_NULL; } } } } qboolean MatrixMode = qfalse; void CG_MatrixEffect ( centity_t *cent ) { float MATRIX_EFFECT_TIME = 1000.0f; //VectorCopy( cent->lerpOrigin, cg.refdef.vieworg ); float totalElapsedTime = (float)(cg.time - cent->currentState.time); float elapsedTime = totalElapsedTime; if ( totalElapsedTime > cent->currentState.eventParm //MATRIX_EFFECT_TIME || (cent->currentState.weapon&&g_entities[cent->currentState.otherEntityNum].client&&g_entities[cent->currentState.otherEntityNum].client->ps.groundEntityNum!=ENTITYNUM_NONE) || cg.missionStatusShow ) {//time is up or this is a falling spin and they hit the ground or mission end screen is up cg.overrides.active &= ~(/*CG_OVERRIDE_3RD_PERSON_ENT|*/CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_ANG|CG_OVERRIDE_3RD_PERSON_POF); //cg.overrides.thirdPersonEntity = 0; cg.overrides.thirdPersonAngle = 0; cg.overrides.thirdPersonPitchOffset = 0; cg.overrides.thirdPersonRange = 0; cgi_Cvar_Set( "timescale", "1.0" ); MatrixMode = qfalse; cent->gent->e_clThinkFunc = clThinkF_NULL; return; } else { while ( elapsedTime > MATRIX_EFFECT_TIME ) { elapsedTime -= MATRIX_EFFECT_TIME; } } MatrixMode = qtrue; //FIXME: move the position towards them and back? //cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ENT; //cg.overrides.thirdPersonEntity = cent->currentState.otherEntityNum; //rotate cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_ANG; cg.overrides.thirdPersonAngle = 360.0f*elapsedTime/MATRIX_EFFECT_TIME; if ( !cent->currentState.weapon ) {//go ahead and do all the slowdown and vert bob stuff //slowdown float timescale = (elapsedTime/MATRIX_EFFECT_TIME); if ( timescale < 0.01f ) { timescale = 0.01f; } cgi_Cvar_Set( "timescale", va("%4.2f",timescale) ); //pitch //dip - FIXME: use pitchOffet? cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_POF; cg.overrides.thirdPersonPitchOffset = cg_thirdPersonPitchOffset.value; if ( elapsedTime < MATRIX_EFFECT_TIME*0.33f ) { cg.overrides.thirdPersonPitchOffset -= 30.0f*elapsedTime/(MATRIX_EFFECT_TIME*0.33); } else if ( elapsedTime > MATRIX_EFFECT_TIME*0.66f ) { cg.overrides.thirdPersonPitchOffset -= 30.0f*(MATRIX_EFFECT_TIME-elapsedTime)/(MATRIX_EFFECT_TIME*0.33); } else { cg.overrides.thirdPersonPitchOffset -= 30.0f; } //pull back cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG; cg.overrides.thirdPersonRange = cg_thirdPersonRange.value; if ( elapsedTime < MATRIX_EFFECT_TIME*0.33 ) { cg.overrides.thirdPersonRange += 80.0f*elapsedTime/(MATRIX_EFFECT_TIME*0.33); } else if ( elapsedTime > MATRIX_EFFECT_TIME*0.66 ) { cg.overrides.thirdPersonRange += 80.0f*(MATRIX_EFFECT_TIME-elapsedTime)/(MATRIX_EFFECT_TIME*0.33); } else { cg.overrides.thirdPersonRange += 80.0f; } } else {//FIXME: if they're on the ground, stop spinning and stop timescale //FIXME: if they go to the menu, restore timescale cgi_Cvar_Set( "timescale", "0.25f" ); } } static void CG_Think ( centity_t *cent ) { if(!cent->gent) { return; } CEntity_ThinkFunc(cent); // cent->gent->clThink(cent); } static void CG_Clouds( centity_t *cent ) { refEntity_t ent; // create the render entity memset( &ent, 0, sizeof( ent )); VectorCopy( cent->lerpOrigin, ent.origin ); ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = ent.shaderRGBA[3] = 255; ent.radius = cent->gent->radius; ent.backlerp = cent->gent->wait; ent.reType = RT_CLOUDS; if ( cent->gent->spawnflags & 1 ) // TUBE type, the one with a hole in the middle { ent.rotation = cent->gent->random; ent.renderfx = RF_GROW;// tube flag } if ( cent->gent->spawnflags & 2 ) // ALT type, uses a different shader { ent.customShader = cgi_R_RegisterShader( "gfx/world/haze2" ); } else { ent.customShader = cgi_R_RegisterShader( "gfx/world/haze" ); } cgi_R_AddRefEntityToScene( &ent ); } /* =============== CG_AddCEntity =============== */ static void CG_AddCEntity( centity_t *cent ) { // event-only entities will have been dealt with already if ( cent->currentState.eType >= ET_EVENTS ) { return; } //we must have restarted the game if (!cent->gent) { return; } // calculate the current origin CG_CalcEntityLerpPositions( cent ); // add automatic effects CG_EntityEffects( cent ); // add local sound set if any if ( cent->gent && cent->gent->soundSet && cent->gent->soundSet[0] && cent->currentState.eType != ET_MOVER ) { CG_AddLocalSet( cent ); } /* Ghoul2 Insert Start */ // do this before we copy the data to refEnts if (cent->gent->ghoul2.IsValid()) { trap_G2_SetGhoul2ModelIndexes(cent->gent->ghoul2, cgs.model_draw, cgs.skins); } /* Ghoul2 Insert End */ switch ( cent->currentState.eType ) { default: CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); break; 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; case ET_BEAM: CG_Beam( cent, 0 ); break; case ET_PORTAL: CG_Portal( cent ); break; case ET_SPEAKER: if ( cent->gent && cent->gent->soundSet && cent->gent->soundSet[0] ) { break; } CG_Speaker( cent ); break; case ET_THINKER: CG_General( cent ); CG_Think( cent ); break; case ET_CLOUD: // dumb CG_Clouds( cent ); break; } } /* =============== CG_AddPacketEntities =============== */ void CG_AddPacketEntities( void ) { int num; centity_t *cent; playerState_t *ps; // 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; } //OutputDebugString(va("interp %4.2f ct=%6d nt=%6d st=%6d\n",cg.frameInterpolation,cg.time,cg.nextSnap->serverTime,cg.snap->serverTime)); } else { cg.frameInterpolation = 0; // actually, it should never be used, because // no entities should be marked as interpolating //OutputDebugString(va("noterp %4.2f ct=%6d nt=%6d st=%6d\n",cg.frameInterpolation,cg.time,0,cg.snap->serverTime)); } // the auto-rotating items will all have the same axis cg.autoAngles[0] = 0; cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0f; 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.predicted_player_state; PlayerStateToEntityState( ps, &cg_entities[ ps->clientNum ].currentState ); // cent = &cg_entities[ ps->clientNum ]; // not needed now that player is in the snap packet // CG_AddCEntity( cent ); // // 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 ); } } //rww - This function is not currently called. Use it as the client-side ROFF //callback once that's implemented fully. void CG_ROFF_NotetrackCallback( centity_t *cent, const char *notetrack) { int i = 0, r = 0, objectID = 0, anglesGathered = 0, posoffsetGathered = 0; char type[256]; char argument[512]; char addlArg[512]; char errMsg[256]; char t[64]; int addlArgs = 0; vec3_t parsedAngles, parsedOffset, useAngles, useOrigin, forward, right, up; if (!cent || !notetrack) { return; } //notetrack = "effect effects/explosion1.efx 0+0+64 0-0-1"; while (notetrack[i] && notetrack[i] != ' ') { type[i] = notetrack[i]; i++; } type[i] = '\0'; if (notetrack[i] != ' ') { //didn't pass in a valid notetrack type, or forgot the argument for it return; } i++; while (notetrack[i] && notetrack[i] != ' ') { argument[r] = notetrack[i]; r++; i++; } argument[r] = '\0'; if (!r) { return; } if (notetrack[i] == ' ') { //additional arguments... addlArgs = 1; i++; r = 0; while (notetrack[i]) { addlArg[r] = notetrack[i]; r++; i++; } addlArg[r] = '\0'; } if (strcmp(type, "effect") == 0) { if (!addlArgs) { //sprintf(errMsg, "Offset position argument for 'effect' type is invalid."); //goto functionend; VectorClear(parsedOffset); goto defaultoffsetposition; } i = 0; while (posoffsetGathered < 3) { r = 0; while (addlArg[i] && addlArg[i] != '+' && addlArg[i] != ' ') { t[r] = addlArg[i]; r++; i++; } t[r] = '\0'; i++; if (!r) { //failure.. //sprintf(errMsg, "Offset position argument for 'effect' type is invalid."); //goto functionend; VectorClear(parsedOffset); i = 0; goto defaultoffsetposition; } parsedOffset[posoffsetGathered] = atof(t); posoffsetGathered++; } if (posoffsetGathered < 3) { Q_strncpyz(errMsg, "Offset position argument for 'effect' type is invalid.", sizeof(errMsg)); goto functionend; } i--; if (addlArg[i] != ' ') { addlArgs = 0; } defaultoffsetposition: objectID = theFxScheduler.RegisterEffect(argument); if (objectID) { if (addlArgs) { //if there is an additional argument for an effect it is expected to be XANGLE-YANGLE-ZANGLE i++; while (anglesGathered < 3) { r = 0; while (addlArg[i] && addlArg[i] != '-') { t[r] = addlArg[i]; r++; i++; } t[r] = '\0'; i++; if (!r) { //failed to get a new part of the vector anglesGathered = 0; break; } parsedAngles[anglesGathered] = atof(t); anglesGathered++; } if (anglesGathered) { VectorCopy(parsedAngles, useAngles); } else { //failed to parse angles from the extra argument provided.. VectorCopy(cent->lerpAngles, useAngles); } } else { //if no constant angles, play in direction entity is facing VectorCopy(cent->lerpAngles, useAngles); } AngleVectors(useAngles, forward, right, up); VectorCopy(cent->lerpOrigin, useOrigin); //forward useOrigin[0] += forward[0]*parsedOffset[0]; useOrigin[1] += forward[1]*parsedOffset[0]; useOrigin[2] += forward[2]*parsedOffset[0]; //right useOrigin[0] += right[0]*parsedOffset[1]; useOrigin[1] += right[1]*parsedOffset[1]; useOrigin[2] += right[2]*parsedOffset[1]; //up useOrigin[0] += up[0]*parsedOffset[2]; useOrigin[1] += up[1]*parsedOffset[2]; useOrigin[2] += up[2]*parsedOffset[2]; theFxScheduler.PlayEffect(objectID, useOrigin, useAngles); } } else if (strcmp(type, "sound") == 0) { objectID = cgi_S_RegisterSound(argument); cgi_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_BODY, objectID); } else if (strcmp(type, "loop") == 0) { //handled server-side return; } //else if ... else { if (type[0]) { Com_Printf("^3Warning: \"%s\" is an invalid ROFF notetrack function\n", type); } else { Com_Printf("^3Warning: Notetrack is missing function and/or arguments\n"); } } return; functionend: Com_Printf("^3Type-specific notetrack error: %s\n", errMsg); return; }