// RAVEN BEGIN // bdube: note that this file is no longer merged with Doom3 updates // // MERGE_DATE 09/30/2004 #include "../idlib/precompiled.h" #pragma hdrstop #include "Game_local.h" #include "ai/AI_Manager.h" #include "Projectile.h" #include "spawner.h" /* =============================================================================== idProjectile =============================================================================== */ static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f; static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; const idEventDef EV_Explode( "", NULL ); const idEventDef EV_Fizzle( "", NULL ); const idEventDef EV_RadiusDamage( "", "E" ); const idEventDef EV_ResidualDamage ( "", "E" ); const idEventDef EV_EmitDamage ( "", "E" ); CLASS_DECLARATION( idEntity, idProjectile ) EVENT( EV_Explode, idProjectile::Event_Explode ) EVENT( EV_Fizzle, idProjectile::Event_Fizzle ) EVENT( EV_Touch, idProjectile::Event_Touch ) EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage ) EVENT( EV_ResidualDamage, idProjectile::Event_ResidualDamage ) EVENT( EV_EmitDamage, idProjectile::Event_EmitDamage ) END_CLASS /* ================ idProjectile::idProjectile ================ */ idProjectile::idProjectile( void ) { methodOfDeath = -1; owner = NULL; memset( &projectileFlags, 0, sizeof( projectileFlags ) ); damagePower = 1.0f; memset( &renderLight, 0, sizeof( renderLight ) ); lightDefHandle = -1; lightOffset.Zero(); lightStartTime = 0; lightEndTime = 0; lightColor.Zero(); visualAngles.Zero(); angularVelocity.Zero(); speed.Init( 0.0f, 0.0f, 0.0f, 0.0f ); updateVelocity = false; rotation.Init( 0, 0.0f, mat3_identity.ToQuat(), mat3_identity.ToQuat() ); flyEffect = NULL; flyEffectAttenuateSpeed = 0.0f; bounceCount = 0; hitCount = 0; state = SPAWNED; fl.networkSync = true; prePredictTime = 0; syncPhysics = false; launchTime = 0; launchOrig = vec3_origin; launchDir = vec3_origin; launchSpeed = 0.0f; playedDamageEffect = false; predictedProjectiles = false; } /* ================ idProjectile::Spawn ================ */ void idProjectile::Spawn( void ) { physicsObj.SetSelf( this ); // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation RV_PUSH_HEAP_MEM(this); // RAVEN END physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); // RAVEN BEGIN // mwhitlock: Dynamic memory consolidation RV_POP_HEAP(); // RAVEN END physicsObj.SetContents( 0 ); physicsObj.SetClipMask( 0 ); physicsObj.PutToRest(); SetPhysics( &physicsObj ); prePredictTime = spawnArgs.GetInt( "predictTime", "0" ); syncPhysics = spawnArgs.GetBool( "net_syncPhysics", "0" ); if ( gameLocal.isClient ) { Hide(); } predictedProjectiles = g_predictedProjectiles.GetBool(); } /* ================ idProjectile::Save ================ */ void idProjectile::Save( idSaveGame *savefile ) const { savefile->WriteInt( methodOfDeath ); // cnicholson: Added unsaved var owner.Save( savefile ); savefile->Write( &projectileFlags, sizeof( projectileFlags ) ); savefile->WriteFloat( damagePower ); savefile->WriteRenderLight( renderLight ); savefile->WriteInt( ( int )lightDefHandle ); savefile->WriteVec3( lightOffset ); savefile->WriteInt( lightStartTime ); savefile->WriteInt( lightEndTime ); savefile->WriteVec3( lightColor ); savefile->WriteStaticObject( physicsObj ); savefile->WriteAngles( visualAngles ); // cnicholson: added unsaved var savefile->WriteAngles( angularVelocity ); // cnicholson: moved var // Save speed savefile->WriteBool ( updateVelocity ); savefile->WriteFloat( speed.GetStartTime() ); savefile->WriteFloat( speed.GetDuration() ); savefile->WriteFloat( speed.GetStartValue() ); savefile->WriteFloat( speed.GetEndValue() ); // rotation; this is a class, so it doesnt get saved here flyEffect.Save( savefile ); // cnicholson: added unsaved var savefile->WriteFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var savefile->WriteInt ( bounceCount ); savefile->WriteInt ( hitCount ); savefile->WriteInt( (int)state ); } /* ================ idProjectile::Restore ================ */ void idProjectile::Restore( idRestoreGame *savefile ) { float fset; idVec3 temp; savefile->ReadInt( methodOfDeath ); // cnicholson: Added unrestored var owner.Restore( savefile ); savefile->Read( &projectileFlags, sizeof( projectileFlags ) ); savefile->ReadFloat( damagePower ); savefile->ReadRenderLight( renderLight ); savefile->ReadInt( (int &)lightDefHandle ); if ( lightDefHandle != -1 ) { //get the handle again as it's out of date after a restore! lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); } savefile->ReadVec3( lightOffset ); savefile->ReadInt( lightStartTime ); savefile->ReadInt( lightEndTime ); savefile->ReadVec3( lightColor ); // Restore the physics savefile->ReadStaticObject( physicsObj ); RestorePhysics ( &physicsObj ); savefile->ReadAngles( visualAngles ); // cnicholson: added unrestored var savefile->ReadAngles( angularVelocity ); // cnicholson: moved var // Restore speed savefile->ReadBool ( updateVelocity ); savefile->ReadFloat( fset ); speed.SetStartTime( fset ); savefile->ReadFloat( fset ); speed.SetDuration( fset ); savefile->ReadFloat( fset ); speed.SetStartValue( fset ); savefile->ReadFloat( fset ); speed.SetEndValue( fset ); // rotation? flyEffect.Restore( savefile ); // cnicholson: added unrestored var savefile->ReadFloat( flyEffectAttenuateSpeed ); // cnicholson: added unsaved var savefile->ReadInt ( bounceCount ); savefile->ReadInt ( hitCount ); savefile->ReadInt( (int &)state ); } /* ================ idProjectile::GetOwner ================ */ idEntity *idProjectile::GetOwner( void ) const { return owner.GetEntity(); } /* ================ idProjectile::SetSpeed ================ */ void idProjectile::SetSpeed( float s, int accelTime ) { idVec3 vel; vel = physicsObj.GetLinearVelocity(); vel.Normalize(); speed.Init( gameLocal.time, accelTime, speed.GetCurrentValue(gameLocal.time), s ); if ( accelTime > 0 ) { updateVelocity = true; } else { updateVelocity = false; } // Update the velocity to match the direction we are facing and include any accelerations physicsObj.SetLinearVelocity( speed.GetCurrentValue( gameLocal.time ) * vel ); } /* ================ idProjectile::Create ================ */ void idProjectile::Create( idEntity* _owner, const idVec3 &start, const idVec3 &dir, idEntity* ignore, idEntity* extraPassEntity ) { idDict args; idStr shaderName; idVec3 light_color; idVec3 light_offset; idVec3 tmp; idMat3 axis; Unbind(); axis = dir.ToMat3(); physicsObj.SetOrigin( start ); physicsObj.SetAxis( axis ); physicsObj.GetClipModel()->SetOwner( ignore ? ignore : _owner ); physicsObj.extraPassEntity = extraPassEntity; owner = _owner; memset( &renderLight, 0, sizeof( renderLight ) ); shaderName = spawnArgs.GetString( "mtr_light_shader" ); if ( *shaderName ) { renderLight.shader = declManager->FindMaterial( shaderName, false ); renderLight.pointLight = true; renderLight.lightRadius[0] = renderLight.lightRadius[1] = renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" ); spawnArgs.GetVector( "light_color", "1 1 1", light_color ); renderLight.shaderParms[0] = light_color[0]; renderLight.shaderParms[1] = light_color[1]; renderLight.shaderParms[2] = light_color[2]; renderLight.shaderParms[3] = 1.0f; // RAVEN BEGIN // dluetscher: added detail levels to render lights renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; // dluetscher: set the projectile lights to be no shadows renderLight.noShadows = cvarSystem->GetCVarInteger("com_machineSpec") < 3; // RAVEN END } spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset ); lightStartTime = 0; lightEndTime = 0; damagePower = 1.0f; UpdateVisuals(); state = CREATED; } /* ================= idProjectile::~idProjectile ================= */ idProjectile::~idProjectile() { StopSound( SND_CHANNEL_ANY, false ); FreeLightDef(); SetPhysics( NULL ); } /* ================= idProjectile::FreeLightDef ================= */ void idProjectile::FreeLightDef( void ) { if ( lightDefHandle != -1 ) { gameRenderWorld->FreeLightDef( lightDefHandle ); lightDefHandle = -1; } } /* ================= idProjectile::Launch ================= */ void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float dmgPower ) { float fuse; idVec3 velocity; float linear_friction; float angular_friction; float contact_friction; float bounce; float mass; float gravity; float temp, temp2; idVec3 gravVec; idVec3 tmp; int contents; int clipMask; // allow characters to throw projectiles during cinematics, but not the player if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) { cinematic = owner.GetEntity()->cinematic; } else { cinematic = false; } // Set the damage damagePower = dmgPower; if ( !spawnArgs.GetFloat( "speed", "0", temp ) ) { spawnArgs.GetVector( "velocity", "0 0 0", tmp ); temp = tmp[0]; } else { float speedRandom; if ( !spawnArgs.GetFloat( "speedRandom", "0", speedRandom ) ) { temp += gameLocal.random.CRandomFloat()*speedRandom; } } if ( !spawnArgs.GetFloat( "speed_end", "0", temp2 ) ) { temp2 = temp; } float speedDuration; speedDuration = SEC2MS( spawnArgs.GetFloat( "speed_duration", "0" ) ); speed.Init( gameLocal.time, speedDuration, temp, temp2 ); if ( speedDuration > 0 && temp != temp2 ) { // only support constant velocity projectiles in MP // ( we also assume that no MP projectiles use speedRandom ) assert( !gameLocal.isServer ); updateVelocity = true; } launchSpeed = temp; spawnArgs.GetAngles( "angular_velocity", "0 0 0", angularVelocity ); linear_friction = spawnArgs.GetFloat( "linear_friction" ); angular_friction = spawnArgs.GetFloat( "angular_friction" ); contact_friction = spawnArgs.GetFloat( "contact_friction" ); bounce = spawnArgs.GetFloat( "bounce" ); mass = spawnArgs.GetFloat( "mass" ); gravity = spawnArgs.GetFloat( "gravity" ); fuse = spawnArgs.GetFloat( "fuse" ) + ( spawnArgs.GetFloat( "fuse_random", "0" ) * gameLocal.random.RandomFloat() ); bounceCount = spawnArgs.GetInt( "bounce_count", "-1" ); //spawn impact entity information impactEntity = spawnArgs.GetString("def_impactEntity",""); numImpactEntities = spawnArgs.GetInt("numImpactEntities","0"); ieMinPitch = spawnArgs.GetInt("ieMinPitch","0"); ieMaxPitch = spawnArgs.GetInt("ieMaxPitch","0"); ieSlicePercentage = spawnArgs.GetFloat("ieSlicePercentage","0.0"); projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" ); projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" ); projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" ); projectileFlags.detonate_on_bounce = spawnArgs.GetBool( "detonate_on_bounce" ); lightStartTime = 0; lightEndTime = 0; impactedEntity = 0; if ( health ) { fl.takedamage = true; } // RAVEN BEGIN // abahr: gravVec = ( idMath::Fabs(gravity) > VECTOR_EPSILON ) ? gameLocal.GetCurrentGravity(this) * gravity : vec3_zero; // RAVEN END Unbind(); contents = 0; clipMask = spawnArgs.GetBool( "clipmask_rendermodel", "1" ) ? MASK_SHOT_RENDERMODEL : MASK_SHOT_BOUNDINGBOX; // all projectiles are projectileclip clipMask |= CONTENTS_PROJECTILECLIP; if ( spawnArgs.GetBool( "clipmask_largeshot", "1" ) ) { clipMask |= CONTENTS_LARGESHOTCLIP; } if ( spawnArgs.GetBool( "clipmask_moveable", "0" ) ) { clipMask |= CONTENTS_MOVEABLECLIP; } if ( spawnArgs.GetBool( "clipmask_monsterclip", "0" ) ) { clipMask |= CONTENTS_MONSTERCLIP; } if ( spawnArgs.GetBool( "detonate_on_trigger" ) ) { contents |= CONTENTS_TRIGGER; } if ( !spawnArgs.GetBool( "no_contents", "1" ) ) { contents |= CONTENTS_PROJECTILE; } clipMask |= CONTENTS_PROJECTILE; // don't do tracers on client, we don't know origin and direction if ( spawnArgs.GetBool( "tracers" ) && gameLocal.random.RandomFloat() > 0.5f ) { SetModel( spawnArgs.GetString( "model_tracer" ) ); projectileFlags.isTracer = true; } physicsObj.SetMass( mass ); physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); physicsObj.SetBouncyness( bounce, !projectileFlags.detonate_on_bounce ); physicsObj.SetGravity( gravVec ); physicsObj.SetContents( contents ); physicsObj.SetClipMask( clipMask | CONTENTS_WATER ); physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity ); physicsObj.SetOrigin( start ); physicsObj.SetAxis( dir.ToMat3() ); if ( !gameLocal.isClient ) { if ( fuse <= 0 ) { // run physics for 1 second RunPhysics(); PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); } else if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { fuse -= timeSinceFire; if ( fuse < 0.0f ) { fuse = 0.0f; } PostEventSec( &EV_Explode, fuse ); } else { fuse -= timeSinceFire; if ( fuse < 0.0f ) { fuse = 0.0f; } PostEventSec( &EV_Fizzle, fuse ); } } idQuat q( dir.ToMat3().ToQuat() ); rotation.Init( gameLocal.GetTime(), 0.0f, q, q ); if ( projectileFlags.isTracer ) { StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL ); } else { StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); } // used for the plasma bolts but may have other uses as well if ( projectileFlags.randomShaderSpin ) { float f = gameLocal.random.RandomFloat(); f *= 0.5f; renderEntity.shaderParms[SHADERPARM_DIVERSITY] = f; } UpdateVisuals(); // Make sure these come after update visuals so the origin and axis are correct PlayEffect( "fx_launch", renderEntity.origin, renderEntity.axis ); flyEffect = PlayEffect( "fx_fly", renderEntity.origin, renderEntity.axis, true ); flyEffectAttenuateSpeed = spawnArgs.GetFloat( "flyEffectAttenuateSpeed", "0" ); state = LAUNCHED; hitCount = 0; predictTime = prePredictTime; if ( spawnArgs.GetFloat( "delay_emit_damage" ) > 0.0f ) { PostEventSec( &EV_EmitDamage, spawnArgs.GetFloat( "wait_emit_damage", "0" ), this ); } if ( gameLocal.isServer ) { // store launch information for networking launchTime = gameLocal.time; launchOrig = physicsObj.GetOrigin(); launchDir = dir; } else { if ( predictedProjectiles ) { physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time ); } } if ( g_perfTest_noProjectiles.GetBool() ) { PostEventMS( &EV_Remove, 0 ); } } /* ================ idProjectile::Think ================ */ void idProjectile::Think( void ) { // run physics if ( thinkFlags & TH_PHYSICS ) { // Update the velocity to match the changing speed if ( updateVelocity ) { idVec3 vel; vel = physicsObj.GetLinearVelocity ( ); vel.Normalize ( ); physicsObj.SetLinearVelocity ( speed.GetCurrentValue ( gameLocal.time ) * vel ); if ( speed.IsDone ( gameLocal.time ) ) { updateVelocity = false; } } RunPhysics(); // If we werent at rest and are now then start the atrest fuse if ( physicsObj.IsAtRest( ) ) { float fuse = spawnArgs.GetFloat( "fuse_atrest" ); if ( fuse > 0.0f ) { if ( spawnArgs.GetBool( "detonate_on_fuse" ) ) { CancelEvents( &EV_Explode ); PostEventSec( &EV_Explode, fuse ); } else { CancelEvents( &EV_Fizzle ); PostEventSec( &EV_Fizzle, fuse ); } } } // Stop the trail effect if the physics flag was removed if ( flyEffect && flyEffectAttenuateSpeed > 0.0f ) { if ( physicsObj.IsAtRest( ) ) { flyEffect->Stop( ); flyEffect = NULL; } else { float speed; speed = idMath::ClampFloat( 0, flyEffectAttenuateSpeed, physicsObj.GetLinearVelocity ( ).LengthFast ( ) ); flyEffect->Attenuate( speed / flyEffectAttenuateSpeed ); } } UpdateVisualAngles(); } Present(); // add the light if ( renderLight.lightRadius.x > 0.0f && g_projectileLights.GetBool() ) { renderLight.origin = GetPhysics()->GetOrigin() + GetPhysics()->GetAxis() * lightOffset; renderLight.axis = GetPhysics()->GetAxis(); if ( ( lightDefHandle != -1 ) ) { if ( lightEndTime > 0 && gameLocal.time <= lightEndTime + gameLocal.GetMSec() ) { idVec3 color( 0, 0, 0 ); if ( gameLocal.time < lightEndTime ) { float frac = ( float )( gameLocal.time - lightStartTime ) / ( float )( lightEndTime - lightStartTime ); color.Lerp( lightColor, color, frac ); } renderLight.shaderParms[SHADERPARM_RED] = color.x; renderLight.shaderParms[SHADERPARM_GREEN] = color.y; renderLight.shaderParms[SHADERPARM_BLUE] = color.z; } gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); } else { lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); } } } /* ================= idProjectile::UpdateVisualAngles ================= */ void idProjectile::UpdateVisualAngles() { idVec3 linearVelocity( GetPhysics()->GetLinearVelocity() ); if( angularVelocity.Compare(ang_zero, VECTOR_EPSILON) ) { rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), linearVelocity.ToNormal().ToMat3().ToQuat() ); return; } if( physicsObj.GetNumContacts() ) { return; } if( !rotation.IsDone(gameLocal.GetTime()) ) { return; } if( linearVelocity.Length() <= BOUNCE_SOUND_MIN_VELOCITY ) { return; } visualAngles += angularVelocity; idQuat q = visualAngles.ToQuat() * linearVelocity.ToNormal().ToMat3().ToQuat(); rotation.Init( gameLocal.GetTime(), gameLocal.GetMSec(), rotation.GetCurrentValue(gameLocal.GetTime()), q ); } /* ================= idProjectile::Collide ================= */ bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) { bool dummy = false; return Collide( collision, velocity, dummy ); } bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity, bool &hitTeleporter ) { idEntity* ent; idEntity* actualHitEnt = NULL; idEntity* ignore; const char* damageDefName; idVec3 dir; bool canDamage; hitTeleporter = false; if ( state == EXPLODED || state == FIZZLED || ( state == IMPACTED && predictedProjectiles ) ) { return true; } if ( IsHidden() && predictedProjectiles ) { // teleported, ignore impacts return false; } // allow projectiles to hit triggers (teleports) // predict this on a client if ( collision.c.contents & CONTENTS_TRIGGER ) { idEntity* trigger = gameLocal.entities[ collision.c.entityNum ]; if ( trigger ) { if ( trigger->RespondsTo( EV_Touch ) || trigger->HasSignal( SIG_TOUCH ) ) { hitTeleporter = true; trace_t trace; trace.endpos = physicsObj.GetOrigin(); trace.endAxis = physicsObj.GetAxis(); trace.c.contents = collision.c.contents; trace.c.entityNum = collision.c.entityNum; if( trigger->GetPhysics()->GetClipModel() ) { trace.c.id = trigger->GetPhysics()->GetClipModel()->GetId(); } else { trace.c.id = 0; } // hack to play the effect on clients when mispredicted (Hide() ensures it can't play twice for the same teleport) int wasNewFrame = gameLocal.isNewFrame; if ( gameLocal.isClient && predictedProjectiles ) { gameLocal.isNewFrame = true; } trigger->Signal( SIG_TOUCH ); trigger->ProcessEvent( &EV_Touch, this, &trace ); if ( gameLocal.isClient && predictedProjectiles ) { Hide(); gameLocal.isNewFrame = wasNewFrame; } } } // when we hit a trigger, align our velocity to the trigger's coordinate plane if( gameLocal.isServer ) { idVec3 up( 0.0f, 0.0f, 1.0f ); idVec3 right = collision.c.normal.Cross( up ); idMat3 mat( collision.c.normal, right, up ); physicsObj.SetLinearVelocity( -1.0f * (physicsObj.GetLinearVelocity() * mat.Transpose()) ); physicsObj.SetLinearVelocity( idVec3( physicsObj.GetLinearVelocity()[ 0 ], -1.0 *physicsObj.GetLinearVelocity()[ 1 ], -1.0 * physicsObj.GetLinearVelocity()[ 2 ] ) ); // update the projectile's launchdir and launch origin // this will propagate the change to the clients for prediction // re-launch the projectile idVec3 newDir = physicsObj.GetLinearVelocity(); newDir.Normalize(); launchTime = gameLocal.time; launchDir = newDir; physicsObj.SetOrigin( physicsObj.GetOrigin() + idVec3( 0.0f, 0.0f, 32.0f ) ); physicsObj.SetAxis( newDir.ToMat3() ); launchOrig = physicsObj.GetOrigin(); } if( !(collision.c.contents & CONTENTS_SOLID) || hitTeleporter ) { return false; } } // network clients: heuristic to skip predicting collisions if ( gameLocal.isClient ) { if ( predictedProjectiles ) { if ( syncPhysics ) { return false; } } else { if ( spawnArgs.GetBool( "no_impact_prediction" ) || syncPhysics ) { return false; } } } // remove projectile when a 'noimpact' surface is hit if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { PostEventMS( &EV_Remove, 0 ); StopEffect( "fx_fly" ); if( flyEffect) { //flyEffect->Event_Remove(); } return true; } // get the entity the projectile collided with ent = gameLocal.entities[ collision.c.entityNum ]; if ( ent == owner.GetEntity() ) { return true; } // just get rid of the projectile when it hits a player in noclip if ( ent->IsType( idPlayer::GetClassType() ) && static_cast( ent )->noclip ) { PostEventMS( &EV_Remove, 0 ); common->DPrintf( "Projectile collision no impact\n" ); return true; } // If the hit entity is bound to an actor use the actor instead if ( ent->GetTeamMaster() && ent->GetTeamMaster()->IsType ( idActor::GetClassType() ) ) { actualHitEnt = ent; ent = ent->GetTeamMaster(); } // Can the projectile damage? canDamage = ent->fl.takedamage && !(( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NODAMAGE )); // direction of projectile dir = velocity; dir.Normalize(); // projectiles can apply an additional impulse next to the rigid body physics impulse // RAVEN BEGIN // abahr: added call to SkipDamageImpulse changed where push comes from damageDefName = NULL; if ( collision.c.materialType ) { damageDefName = spawnArgs.GetString( va("def_damage_%s", collision.c.materialType->GetName()) ); } if ( !damageDefName || !*damageDefName ) { damageDefName = spawnArgs.GetString ( "def_damage" ); } if( damageDefName && damageDefName[0] ) { const idDict* dict = gameLocal.FindEntityDefDict( damageDefName, false ); if ( dict ) { ent->ApplyImpulse( this, collision.c.id, collision.endpos, dir, dict ); } } // RAVEN END //Spawn any impact entities if necessary. SpawnImpactEntities(collision, velocity); //Apply any impact force if the necessary //ApplyImpactForce(ent, collision, dir); // MP: projectiles open doors if ( gameLocal.isMultiplayer && ent->IsType( idDoor::GetClassType() ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) { ent->ProcessEvent( &EV_Activate , this ); } // If the projectile hits water then we need to let the projectile keep going if ( ent->GetPhysics()->GetContents() & CONTENTS_WATER ) { if ( !physicsObj.IsInWater( ) ) { StopEffect( "fx_fly" ); if( flyEffect) { //flyEffect->Event_Remove(); } } // Pass through water return false; } else if ( canDamage && ent->IsType( idActor::GetClassType() ) ) { if ( !projectileFlags.detonate_on_actor ) { return false; } } else { bool bounce = false; // Determine if the projectile should bounce bounce = !physicsObj.IsInWater() && !projectileFlags.detonate_on_world && !canDamage; bounce = bounce && (bounceCount == -1 || bounceCount > 0); //assert(collision.c.material); if ( !bounce && collision.c.material && (collision.c.material->GetSurfaceFlags() & SURF_BOUNCE) ) { bounce = !projectileFlags.detonate_on_bounce; } if ( bounce ) { if ( bounceCount != -1 ) { bounceCount--; } StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ); float len = velocity.Length(); if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { if ( ent->IsType ( idMover::GetClassType ( ) ) ) { ent->PlayEffect( gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), collision.c.point, collision.c.normal.ToMat3(), false, vec3_origin, true ); } else { gameLocal.PlayEffect( gameLocal.GetEffect(spawnArgs,"fx_bounce",collision.c.materialType), collision.c.point, collision.c.normal.ToMat3(), false, vec3_origin, true ); } } else { // FIXME: clean up idMat3 axis( rotation.GetCurrentValue(gameLocal.GetTime()).ToMat3() ); axis[0].ProjectOntoPlane( collision.c.normal ); axis[0].Normalize(); axis[2] = collision.c.normal; axis[1] = axis[2].Cross( axis[0] ).ToNormal(); rotation.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("settle_duration")), rotation.GetCurrentValue(gameLocal.GetTime()), axis.ToQuat() ); } if ( actualHitEnt && actualHitEnt != ent && actualHitEnt->spawnArgs.GetBool( "takeBounceDamage" ) ) {//bleh... if ( damageDefName[0] != '\0' ) { idVec3 dir = velocity; dir.Normalize(); actualHitEnt->Damage( this, owner, dir, damageDefName, damagePower, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); } } return false; } } SetOrigin( collision.endpos ); // SetAxis( collision.endAxis ); // unlink the clip model because we no longer need it GetPhysics()->UnlinkClip(); ignore = NULL; // RAVEN BEGIN // jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects. bool willPlayDamageEffect = true; if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::GetClassType() ) ) { // if the projectile hit an ai if ( ent->IsType( idAI::GetClassType() ) ) { idPlayer *player = static_cast( owner.GetEntity() ); player->AddProjectileHits( 1 ); // jshepard: Single Player- if the the player is the attacker and the victim is teammate, don't play any blood effects. idAI * ai_ent = static_cast(ent); if( ai_ent->team == player->team) { willPlayDamageEffect = false; } } } // RAVEN END // if the hit entity takes damage if ( canDamage ) { if ( damageDefName[0] != '\0' ) { idVec3 dir = velocity; dir.Normalize(); // RAVEN BEGIN // jdischler: code from the 'other' project..to ensure that if an attached head is hit, the body will use the head joint // otherwise damage zones for head attachments no-worky int hitJoint = CLIPMODEL_ID_TO_JOINT_HANDLE(collision.c.id); if ( ent->IsType(idActor::GetClassType()) ) { idActor* entActor = static_cast(ent); if ( entActor && entActor->GetHead() && entActor->GetHead()->IsType(idAFAttachment::GetClassType()) ) { idAFAttachment* headEnt = static_cast(entActor->GetHead()); if ( headEnt && headEnt->entityNumber == collision.c.entityNum ) {//hit ent's head, get the proper joint for the head hitJoint = entActor->GetAnimator()->GetJointHandle("head"); } } } // RAVEN END ent->Damage( this, owner, dir, damageDefName, damagePower, hitJoint ); if( owner && owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) ) { statManager->WeaponHit( (const idActor*)(owner.GetEntity()), ent, methodOfDeath, hitCount == 0 ); hitCount++; } } } ignore = ent; if ( predictedProjectiles ) { if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect ) { ent->AddDamageEffect( collision, velocity, damageDefName, owner ); } // hack to play the effect on clients when mispredicted (state/playedDamageEffect ensure it can't happen twice) int wasNewFrame = gameLocal.isNewFrame; if ( gameLocal.isClient ) { gameLocal.isNewFrame = true; } // if the the player is the attacker and the victim is teammate, don't play any effects. if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect && ( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") ) ) { DefaultDamageEffect( collision, velocity, damageDefName ); playedDamageEffect = true; } Explode( &collision, false, ignore ); gameLocal.isNewFrame = wasNewFrame; } else { ent->AddDamageEffect ( collision, velocity, damageDefName, owner ); // if the the player is the attacker and the victim is teammate, don't play any effects. if ( willPlayDamageEffect || spawnArgs.GetBool( "friendly_impact") ) { DefaultDamageEffect( collision, velocity, damageDefName ); } // don't predict explosions on clients if ( gameLocal.isClient ) { return true; } Explode( &collision, false, ignore ); } return true; } void idProjectile::SpawnImpactEntities(const trace_t& collision, const idVec3 velocity) { if( impactEntity.Length() == 0 || numImpactEntities == 0 ) return; const idDict* impactEntityDict = gameLocal.FindEntityDefDict(impactEntity); if(impactEntityDict == NULL) return; idVec3 tempDirection; idVec3 direction; direction.Zero(); idVec3 up = collision.c.normal; //Calculate the axes for that are oriented to the impact point. idMat3 impactAxes; idVec3 right = velocity.Cross(up); idVec3 forward = up.Cross(right); right.Normalize(); forward.Normalize(); impactAxes[0] = forward; impactAxes[1] = right; impactAxes[2] = up; //Calculate the reflection vector by calculating the forward component and up component of the projectile direction //idVec3 reflectionVelocity = (forward * (velocity*0.33f * forward));// - (up * (velocity * up)); idVec3 reflectionVelocity = up * 0.01f; //The algorithm below will launch entities at a random pitch and somewhat random yaw. //The yaw is calculated by dividing 360 by the number of entities to spawn. This creates //a distribution slice. Then using the slice percentage, this will determine how much of the //slice to use. //This creates a random,but somewhat even coverage of the circle. //Calculate the slice size and pick a random start position. int sliceSize = 360 / numImpactEntities; int startPosition = rvRandom::irand(0, 360); //Move the origin away from the collision point. This prevents the projectiles //from colliding with the surface. idVec3 origin = collision.endpos; origin += 10.0f * collision.c.normal; for(int i = 0;i < numImpactEntities; i++) { idProjectile* spawnProjectile = NULL; gameLocal.SpawnEntityDef(*impactEntityDict,(idEntity**)&spawnProjectile); if(spawnProjectile != NULL) { int pitch = rvRandom::irand(ieMinPitch, ieMaxPitch); int sliceMiddle = (i * sliceSize) + startPosition; int sliceSloppiness = (sliceSize * ieSlicePercentage) / 2; int yaw = rvRandom::irand(sliceMiddle - sliceSloppiness, sliceMiddle + sliceSloppiness); yaw = yaw % 360; float cosPitch = idMath::Cos(DEG2RAD(pitch)); tempDirection.x = cosPitch * idMath::Cos(DEG2RAD(yaw)); tempDirection.y = cosPitch * idMath::Sin(DEG2RAD(yaw)); tempDirection.z = idMath::Sin(DEG2RAD(pitch)); spawnProjectile->SetOwner(owner); //Now orient the direction to the surface world orientation. direction = impactAxes * tempDirection; spawnProjectile->Launch(origin, direction, reflectionVelocity); } } } /* ================= idProjectile::DefaultDamageEffect ================= */ void idProjectile::DefaultDamageEffect( const trace_t &tr, const idVec3 &velocity, const char *damageDefName ) { idEntity* ent; idMat3 axis; ent = gameLocal.entities[ tr.c.entityNum ]; // Make sure we want to play effects if ( (!*spawnArgs.GetString( "def_splash_damage" ) || spawnArgs.GetBool( "bloodyImpactEffect" )) && owner.GetEntity( ) && !ent->CanPlayImpactEffect( owner, ent ) ) { return; } // Effect axis when hitting actors is along the direction of impact because actor models are // very detailed. if ( ent->IsType( idActor::GetClassType() ) || ent->IsType( idAFAttachment::GetClassType() ) ) { idVec3 dir; dir = velocity; dir.Normalize ( ); axis = ((-dir + tr.c.normal) * 0.5f).ToMat3(); // Play an actor specific impact effect? const idDecl *actorImpactEffect = gameLocal.GetEffect( spawnArgs, "fx_impact_actor", tr.c.materialType ); if ( actorImpactEffect ) { gameLocal.PlayEffect( actorImpactEffect, tr.c.point, axis, false, vec3_origin, true, true ); return; } } else { axis = tr.c.normal.ToMat3(); } // Play an impact effect on the entity that got hit if ( ent->IsType( idMover::GetClassType ( ) ) ) { ent->PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, spawnArgs.GetBool( "no_impact_prediction" ), true ); } else { gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), tr.c.point, axis, false, vec3_origin, spawnArgs.GetBool( "no_impact_prediction" ), true ); } } /* ================ idProjectile::Killed ================ */ void idProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { if ( spawnArgs.GetBool( "detonate_on_death" ) ) { Explode( NULL, true ); physicsObj.ClearContacts(); physicsObj.PutToRest(); } else { Fizzle(); } } /* ================ idProjectile::Fizzle ================ */ void idProjectile::Fizzle( void ) { if ( state == EXPLODED || state == FIZZLED ) { return; } if ( predictedProjectiles ) { if ( state == IMPACTED ) { return; } } else { if ( gameLocal.isClient ) { return; } } StopSound( SND_CHANNEL_BODY, false ); StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); gameLocal.PlayEffect( spawnArgs, "fx_fuse", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.GetClipModel()->Unlink(); physicsObj.PutToRest(); // No more fly effects StopEffect( "fx_fly" ); if( flyEffect) { //flyEffect->Event_Remove(); } Hide(); FreeLightDef(); state = FIZZLED; int removeTime = spawnArgs.GetInt( "remove_time", "1500" ); if ( predictedProjectiles ) { // ensure the projectile makes it in to at least one snapshot int snapshotTime = gameLocal.GetMSec() + cvarSystem->GetCVarInteger( "net_serverSnapshotDelay" ); if ( removeTime < snapshotTime ) if ( removeTime < snapshotTime ) { removeTime = snapshotTime; } } CancelEvents( &EV_Fizzle ); PostEventMS( &EV_Remove, removeTime ); } /* ================ idProjectile::Event_RadiusDamage ================ */ void idProjectile::Event_RadiusDamage( idEntity *ignore ) { const char *splash_damage = spawnArgs.GetString( "def_splash_damage" ); if ( splash_damage[0] != '\0' ) { gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, splash_damage, damagePower, &hitCount ); } } /* ================ idProjectile::Event_ResidualDamage ================ */ void idProjectile::Event_ResidualDamage ( idEntity* ignore ) { const char *residual_damage = spawnArgs.GetString( "def_residual_damage" ); if ( residual_damage[0] != '\0' ) { gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, residual_damage, damagePower, &hitCount ); } // Keep the loop going PostEventSec ( &EV_ResidualDamage, spawnArgs.GetFloat ( "delay_residual" ), ignore ); } void idProjectile::Event_EmitDamage ( idEntity* ignore ) { const char *emit_damage = spawnArgs.GetString( "def_emit_damage" ); if ( emit_damage[0] != '\0' ) { gameLocal.RadiusDamage( physicsObj.GetOrigin(), this, owner, ignore, this, emit_damage, damagePower, &hitCount ); } // Keep the loop going PostEventSec ( &EV_EmitDamage, spawnArgs.GetFloat ( "delay_emit_damage" ), ignore ); } /* ================ idProjectile::Explode ================ */ void idProjectile::Explode( const trace_t *collision, const bool showExplodeFX, idEntity *ignore, const char *sndExplode ) { idVec3 normal, endpos; int removeTime; if ( state == EXPLODED || state == FIZZLED ) { return; } if ( predictedProjectiles && state == IMPACTED ) { return; } CancelEvents( &EV_EmitDamage ); if ( spawnArgs.GetVector( "detonation_axis", "", normal ) ) { GetPhysics()->SetAxis( normal.ToMat3() ); } else { normal = collision ? collision->c.normal : idVec3( 0, 0, 1 ); } endpos = ( collision ) ? collision->endpos : GetPhysics()->GetOrigin(); removeTime = spawnArgs.GetInt( "remove_time", "1500" ); // play sound StopSound( SND_CHANNEL_BODY, false ); StartSound( sndExplode, SND_CHANNEL_BODY, 0, false, NULL ); idVec3 fxDir; if ( physicsObj.GetGravityNormal( ) != vec3_zero ) { fxDir = -physicsObj.GetGravityNormal( ); } else { fxDir = -physicsObj.GetLinearVelocity( ); fxDir.Normalize( ); } if ( predictedProjectiles ) { if ( ( gameLocal.isClient || gameLocal.isListenServer ) && !playedDamageEffect ) { PlayDetonateEffect( endpos, fxDir.ToMat3() ); } } else if ( showExplodeFX ) { PlayDetonateEffect( endpos, fxDir.ToMat3() ); } // Stop the fly effect without destroying particles to ensure the trail within can persist. StopEffect( "fx_fly" ); // Stop the remaining particles StopAllEffects( ); Hide(); FreeLightDef(); GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() + 8.0f * normal ); fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.PutToRest(); if ( predictedProjectiles ) { state = showExplodeFX ? EXPLODED : IMPACTED; } else { state = EXPLODED; } if ( gameLocal.isClient ) { return; } // alert the ai gameLocal.AlertAI( owner.GetEntity() ); // bind the projectile to the impact entity if necesary if ( collision && gameLocal.entities[collision->c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) { Bind( gameLocal.entities[collision->c.entityNum], true ); } if ( predictedProjectiles ) { removeTime = spawnArgs.GetInt( "detonate_remove_time", "0" ); // ensure the projectile makes it in to at least one snapshot int snapshotTime = gameLocal.GetMSec() + cvarSystem->GetCVarInteger( "net_serverSnapshotDelay" ); if ( removeTime < snapshotTime ) { removeTime = snapshotTime; } } else { removeTime = 0; } // splash damage float delay = spawnArgs.GetFloat( "delay_splash" ); if ( delay ) { if ( removeTime < delay * 1000 ) { removeTime = ( delay + 0.10 ) * 1000; } PostEventSec( &EV_RadiusDamage, delay, ignore ); } else { Event_RadiusDamage( ignore ); } // Residual damage (damage over time) delay = SEC2MS ( spawnArgs.GetFloat ( "delay_residual" ) ); if ( delay > 0.0f ) { PostEventMS ( &EV_ResidualDamage, delay, ignore ); // Keep the projectile around until the residual damage is done delay = SEC2MS ( spawnArgs.GetFloat ( "residual_time" ) ); if ( removeTime < delay ) { removeTime = delay; } } CancelEvents( &EV_Explode ); PostEventMS( &EV_Remove, removeTime ); } /* ================ idProjectile::GetVelocity ================ */ idVec3 idProjectile::GetVelocity( const idDict *projectile ) { idVec3 velocity; velocity.Zero ( ); if ( projectile && !projectile->GetFloat ( "speed", "0", velocity.x ) ) { projectile->GetVector( "velocity", "0 0 0", velocity ); } return velocity; } /* ================ idProjectile::GetGravity ================ */ idVec3 idProjectile::GetGravity( const idDict *projectile ) { if ( projectile ) { return gameLocal.GetGravity ( ) * projectile->GetFloat( "gravity" ); } return vec3_origin; } /* ================ idProjectile::PlayPainEffect ================ */ void idProjectile::PlayPainEffect ( idEntity* ent, int damage, const rvDeclMatType* materialType, const idVec3& origin, const idVec3& dir ) { static int damageTable[] = { 100, 50, 25, 10, 0 }; int index; // Normalize the damage value to the damage table for ( index = 0; damage < damageTable[index] && damageTable[index]; index ++ ); // loop until we find a pain effect, trying lower damage numbers if needed for ( ; damageTable[index]; index ++ ) { // Try the pain effect for the current damage value and if it plays then // we are done if ( ent->PlayEffect( gameLocal.GetEffect( spawnArgs, va( "fx_pain%d", damageTable[index] ), materialType ), origin, dir.ToMat3() ) ) { return; } } // Play the default pain effect ent->PlayEffect( gameLocal.GetEffect ( spawnArgs, "fx_pain", materialType ), origin, dir.ToMat3() ); } /* ================ idProjectile::PlayDetonateEffect ================ */ void idProjectile::PlayDetonateEffect( const idVec3& origin, const idMat3& axis, bool forceImpact ) { if( physicsObj.HasGroundContacts() || ( forceImpact && predictedProjectiles ) ) { if ( spawnArgs.GetBool( "detonateTestGroundMaterial" ) ) { trace_t tr; idVec3 down; down = GetPhysics()->GetOrigin() + GetPhysics()->GetGravityNormal()*8.0f; gameLocal.Translation( this, tr, GetPhysics()->GetOrigin(), down, GetPhysics()->GetClipModel(), GetPhysics()->GetClipModel()->GetAxis(), GetPhysics()->GetClipMask(), this ); gameLocal.PlayEffect( gameLocal.GetEffect( spawnArgs, "fx_impact", tr.c.materialType ), origin, axis, false, vec3_origin, true, true ); } else { gameLocal.PlayEffect( spawnArgs, "fx_impact", origin, axis, false, vec3_origin, true, true ); } return; } gameLocal.PlayEffect( spawnArgs, "fx_detonate", origin, axis, false, vec3_origin, true, true ); } /* ================ idProjectile::Event_Explode ================ */ void idProjectile::Event_Explode( void ) { // events are processed outside of the think loop, so set the current thinking ent appropriately idEntity* think = gameLocal.currentThinkingEntity; gameLocal.currentThinkingEntity = this; Explode( NULL, true ); gameLocal.currentThinkingEntity = think; } /* ================ idProjectile::Event_Fizzle ================ */ void idProjectile::Event_Fizzle( void ) { idEntity* think = gameLocal.currentThinkingEntity; gameLocal.currentThinkingEntity = this; Fizzle(); gameLocal.currentThinkingEntity = think; } /* ================ idProjectile::Event_Touch ================ */ void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) { if ( IsHidden() ) { return; } if ( other != owner.GetEntity() ) { idEntity* think = gameLocal.currentThinkingEntity; gameLocal.currentThinkingEntity = this; trace_t collision; memset( &collision, 0, sizeof( collision ) ); collision.c.point = GetPhysics()->GetOrigin(); collision.c.normal.Set( 0, 0, 1 ); bool playDefaultDamageEffect = predictedProjectiles ? !playedDamageEffect : true; if ( playDefaultDamageEffect ) { DefaultDamageEffect( collision, collision.c.normal, NULL ); playedDamageEffect = true; } Explode( NULL, !predictedProjectiles ); gameLocal.currentThinkingEntity = think; } } /* ================ idProjectile::ClientPredictionThink ================ */ void idProjectile::ClientPredictionThink( void ) { if ( !renderEntity.hModel && clientEntities.IsListEmpty() ) { return; } if ( !syncPhysics && state == LAUNCHED ) { idMat3 axis = launchDir.ToMat3(); idVec3 origin( launchOrig ); origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir; physicsObj.SetAxis( axis ); physicsObj.SetOrigin( origin ); physicsObj.SetLinearVelocity( launchSpeed * launchDir ); } Think(); } /* ================ idProjectile::WriteToSnapshot ================ */ void idProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const { if ( syncPhysics ) { physicsObj.WriteToSnapshot( msg ); } msg.WriteBits( state, 3 ); if ( state >= LAUNCHED ) { // feed the client with start position, direction and time. let the client do everything else // this won't change during projectile life and be completely deltified away msg.WriteLong( launchTime ); msg.WriteFloat( launchOrig[ 0 ] ); msg.WriteFloat( launchOrig[ 1 ] ); msg.WriteFloat( launchOrig[ 2 ] ); msg.WriteDir( launchDir, 24 ); int ownerNum = (GetOwner() && GetOwner()->entityNumber < MAX_CLIENTS) ? GetOwner()->entityNumber : MAX_CLIENTS; msg.WriteBits( ownerNum, idMath::BitsForInteger(MAX_CLIENTS) ); } } /* ================ idProjectile::ReadFromSnapshot ================ */ void idProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) { projectileState_t newState; idEntity *ownerEnt = NULL; int newLaunchTime; if ( syncPhysics ) { physicsObj.ReadFromSnapshot( msg ); } newState = (projectileState_t) msg.ReadBits( 3 ); if ( newState >= LAUNCHED ) { newLaunchTime = msg.ReadLong(); launchOrig[ 0 ] = msg.ReadFloat(); launchOrig[ 1 ] = msg.ReadFloat(); launchOrig[ 2 ] = msg.ReadFloat(); launchDir = msg.ReadDir( 24 ); int ownerNum = msg.ReadBits( idMath::BitsForInteger(MAX_CLIENTS) ); if ( ownerNum < MAX_CLIENTS && gameLocal.entities[ ownerNum ] && gameLocal.entities[ ownerNum ]->IsType( idPlayer::GetClassType() ) ) { ownerEnt = gameLocal.entities[ ownerNum ]; } if ( predictedProjectiles && launchTime != newLaunchTime ) { launchTime = newLaunchTime; if ( !syncPhysics && state == LAUNCHED && newState == LAUNCHED ) { idMat3 axis = launchDir.ToMat3(); idVec3 origin( launchOrig ); physicsObj.SetAxis( axis ); physicsObj.SetOrigin( origin ); physicsObj.SetLinearVelocity( launchSpeed * launchDir ); physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time ); } if ( state == LAUNCHED && newState == LAUNCHED ) { Show(); } UpdateVisuals(); } if ( !predictedProjectiles ) { launchTime = newLaunchTime; } } // we always create and launch at the same time // state on a client can be SPAWNED, LAUNCHED, EXPLODED // expect never to get a CREATED projectile, they should launch right away if ( predictedProjectiles ) { if ( msg.HasChanged() ) { if ( !syncPhysics && state == LAUNCHED && newState == LAUNCHED ) { idMat3 axis = launchDir.ToMat3(); idVec3 origin( launchOrig ); physicsObj.SetAxis( axis ); physicsObj.SetOrigin( origin ); physicsObj.SetLinearVelocity( launchSpeed * launchDir ); physicsObj.Evaluate( gameLocal.time - launchTime, gameLocal.time ); } } UpdateVisuals(); assert( state != CREATED && newState != CREATED ); if ( newState != state ) { if ( state < LAUNCHED && newState >= LAUNCHED ) { Create( ownerEnt, launchOrig, launchDir ); Launch( launchOrig, launchDir, vec3_origin ); Show(); } switch ( newState ) { case FIZZLED: Fizzle(); break; case EXPLODED: case IMPACTED: Explode( NULL, state == EXPLODED ); break; } } } else { assert( state != CREATED && state != FIZZLED && newState != CREATED ); if ( newState != state ) { switch ( newState ) { case LAUNCHED: Create( NULL, launchOrig, launchDir ); Launch( launchOrig, launchDir, vec3_origin ); Show(); break; case FIZZLED: case EXPLODED: if ( state != EXPLODED ) { StopSound( SND_CHANNEL_BODY, false ); StopAllEffects(); Hide(); FreeLightDef(); state = EXPLODED; } break; } } if ( !syncPhysics && state == LAUNCHED ) { idMat3 axis = launchDir.ToMat3(); idVec3 origin( launchOrig ); origin += ( ( gameLocal.time - launchTime ) / 1000.0f ) * launchSpeed * launchDir; physicsObj.SetAxis( axis ); physicsObj.SetOrigin( origin ); physicsObj.SetLinearVelocity( launchSpeed * launchDir ); } UpdateVisuals(); } } /* =============== idProjectile::ClientStale =============== */ bool idProjectile::ClientStale( void ) { // delete stale projectile ents. if they pop back in pvs, they will be re-spawned ( rare case anyway ) StopAllEffects(); return true; } /* ================ idProjectile::GetPhysicsToVisualTransform ================ */ bool idProjectile::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) { axis = rotation.GetCurrentValue( gameLocal.GetTime() ).ToMat3() * GetPhysics()->GetAxis().Transpose(); origin.Zero(); return true; } /* =============================================================================== idGuidedProjectile =============================================================================== */ CLASS_DECLARATION( idProjectile, idGuidedProjectile ) END_CLASS /* ================ idGuidedProjectile::idGuidedProjectile( void ) ================ */ idGuidedProjectile::idGuidedProjectile( void ) { guideType = GUIDE_NONE; turn_max.Init ( gameLocal.time, 0, 0.0f, 0.0f ); } /* ================= idGuidedProjectile::~idGuidedProjectile ================= */ idGuidedProjectile::~idGuidedProjectile() { } /* ================ idGuidedProjectile::Save ================ */ void idGuidedProjectile::Save( idSaveGame *savefile ) const { savefile->WriteInt ( guideType ); guideEnt.Save( savefile ); savefile->WriteVec3 ( guideDir ); savefile->WriteVec3 ( guidePos ); savefile->WriteJoint ( guideJoint ); savefile->WriteFloat( guideMinDist ); // cnicholson: Added unsaved var savefile->WriteInt ( driftTime ); savefile->WriteInt ( driftRate ); savefile->WriteFloat ( driftRange ); savefile->WriteFloat ( driftRadius ); savefile->WriteFloat ( driftDiversity ); // cnicholson: Added unsaved var savefile->WriteFloat ( driftAngle ); savefile->WriteFloat ( driftAngleStep ); savefile->WriteFloat ( driftProjectRange ); savefile->WriteFloat( turn_max.GetStartTime() ); savefile->WriteFloat( turn_max.GetDuration() ); savefile->WriteFloat( turn_max.GetStartValue() ); savefile->WriteFloat( turn_max.GetEndValue() ); savefile->WriteInt ( launchTime ); savefile->WriteInt ( guideDelay ); savefile->WriteInt ( driftDelay ); } /* ================ idGuidedProjectile::Restore ================ */ void idGuidedProjectile::Restore( idRestoreGame *savefile ) { float set; savefile->ReadInt ( guideType ); guideEnt.Restore( savefile ); savefile->ReadVec3 ( guideDir ); savefile->ReadVec3 ( guidePos ); savefile->ReadJoint ( guideJoint ); savefile->ReadFloat( guideMinDist ); // cnicholson: Added unrestored var savefile->ReadInt ( driftTime ); savefile->ReadInt ( driftRate ); savefile->ReadFloat ( driftRange ); savefile->ReadFloat ( driftRadius ); savefile->ReadFloat ( driftDiversity ); // cnicholson: Added unrestored var savefile->ReadFloat ( driftAngle ); savefile->ReadFloat ( driftAngleStep ); savefile->ReadFloat ( driftProjectRange ); savefile->ReadFloat( set ); turn_max.SetStartTime( set ); savefile->ReadFloat( set ); turn_max.SetDuration( set ); savefile->ReadFloat( set ); turn_max.SetStartValue( set ); savefile->ReadFloat( set ); turn_max.SetEndValue( set ); savefile->ReadInt ( launchTime ); savefile->ReadInt ( guideDelay ); savefile->ReadInt ( driftDelay ); } /* ================ idGuidedProjectile::GetGuideDir ================ */ bool idGuidedProjectile::GetGuideDir ( idVec3 &outDir, float& outDist ) { // Dont start guiding immeidately? if ( gameLocal.GetTime() - launchTime < guideDelay ) { return false; } switch ( guideType ) { case GUIDE_ENTITY: // If the guide entity is gone or dead then cancel the guide if ( !guideEnt.GetEntity ( ) || (guideEnt->fl.takedamage && guideEnt->health <= 0 ) ) { CancelGuide ( ); return false; } // Use eye position for actors and center of bounds for everything else if ( guideJoint != INVALID_JOINT ) { idMat3 jointAxis; guideEnt->GetAnimator()->GetJointTransform( guideJoint, gameLocal.GetTime(), outDir, jointAxis ); outDir = guideEnt->GetRenderEntity()->origin + (outDir*guideEnt->GetRenderEntity()->axis); if ( !guidePos.Compare( vec3_origin ) ) { jointAxis = jointAxis * guideEnt->GetRenderEntity()->axis; outDir += jointAxis[0]*guidePos[0]; outDir += jointAxis[1]*guidePos[1]; outDir += jointAxis[2]*guidePos[2]; } } else { outDir = guideEnt->GetPhysics()->GetAbsBounds().GetCenter(); if ( guideEnt->IsType( idActor::GetClassType() ) ) { outDir += static_cast(guideEnt.GetEntity())->GetEyePosition(); outDir *= 0.5f; } } outDir -= physicsObj.GetOrigin(); break; case GUIDE_POS: outDir = guidePos - physicsObj.GetOrigin(); break; case GUIDE_DIR: // Project our current position on to the desired direction outDir = guidePos + guideDir * ((physicsObj.GetOrigin() - guidePos) * guideDir); // Seek towards a point forward along our desired direction outDir = (outDir + guideDir * (guideMinDist * 1.10f)) - physicsObj.GetOrigin(); break; default: return false; } // Add drifting if ( driftRate && gameLocal.GetTime() - launchTime > driftDelay ) { idMat3 axis; outDist = outDir.NormalizeFast(); axis = outDir.ToMat3(); if ( gameLocal.time > driftTime ) { driftRadius = driftRange + gameLocal.random.RandomFloat ( ) * driftRange * driftDiversity; driftTime = gameLocal.time + driftRate; } else { driftAngle += driftAngleStep * MS2SEC ( gameLocal.msec ); idMath::AngleNormalize360 ( driftAngle ); } float angle; angle = DEG2RAD ( driftAngle ); outDir = physicsObj.GetOrigin ( ) + outDir * Min( outDist, driftProjectRange ); outDir += axis[2] * (driftRadius * idMath::Sin ( angle )); outDir += axis[1] * (driftRadius * idMath::Cos ( angle )); outDir -= physicsObj.GetOrigin(); } outDist = outDir.Normalize ( ); return true; } /* ================ idGuidedProjectile::Think ================ */ void idGuidedProjectile::Think( void ) { if ( state == LAUNCHED ) { idVec3 dir; idVec3 vel; float angle; float maxangle; idMat3 axis; float dist; // crank up to normal speed ? if ( guideDelay && gameLocal.GetTime() - launchTime >= guideDelay ) { float newSpeed = spawnArgs.GetFloat( "speed" ); float newSpeed2; if ( !spawnArgs.GetFloat ( "speed_end", "0", newSpeed2 ) ) { newSpeed2 = newSpeed; } float newSpeedDuration; newSpeedDuration = SEC2MS( spawnArgs.GetFloat ( "speed_duration", "0" ) ); speed.Init ( gameLocal.time, newSpeedDuration, newSpeed, newSpeed2 ); guideDelay = 0; } if ( !GetGuideDir( dir, dist ) ) { idProjectile::Think(); return; } // Direction of travel vel = physicsObj.GetLinearVelocity(); vel.Normalize(); // Calculate the angle between the current projectile direction and where we want to go angle = RAD2DEG( idMath::ACos( dir * vel ) ); // Make sure the angle doesnt cross our max turn radius maxangle = turn_max.GetCurrentValue( gameLocal.time ); if ( angle < -maxangle ) { angle = -maxangle; } else if ( angle > maxangle ) { angle = maxangle; } // Debug information if ( g_debugWeapon.GetBool ( ) ) { gameRenderWorld->DebugArrow( colorCyan, physicsObj.GetOrigin(), physicsObj.GetOrigin() + vel * 50.0f, 10.0f ); gameRenderWorld->DebugArrow( colorMagenta, physicsObj.GetOrigin(), physicsObj.GetOrigin() + dir * 50.0f, 10.0f ); } // Calculate the new axis by rotating the current forward vector around the cross of the forward // vector and the direction vector. vel = vel * idRotation( vec3_origin, dir.Cross ( vel ), angle ); physicsObj.SetLinearVelocity( vel * GetSpeed ( ) ); // If within the minium distance to the target anything over a 45 degree change will cancel the guide if ( guideMinDist != 0.0f && dist < guideMinDist ) { // Stop guiding if we have passed our target vel = physicsObj.GetLinearVelocity ( ); vel.Normalize( ); if ( vel * dir < 0.7f ) { guideType = GUIDE_NONE; } } idProjectile::Think(); } else { idProjectile::Think(); } } /* ================= idGuidedProjectile::Launch ================= */ void idGuidedProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower ) { idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, dmgPower ); launchTime = gameLocal.GetTime(); if ( owner.GetEntity() ) { if ( owner.GetEntity()->IsType( idAI::GetClassType() ) ) { GuideTo ( static_cast( owner.GetEntity() )->GetEnemy() ); } } guideMinDist = spawnArgs.GetFloat ( "min_dist", "128" ); guideDelay = SEC2MS(spawnArgs.GetFloat ( "delayGuide" ) + ( gameLocal.random.RandomFloat ( ) * spawnArgs.GetFloat ( "delayGuide_random")) ); if ( guideDelay ) { float delaySpeed; if ( spawnArgs.GetFloat( "delaySpeed", "0", delaySpeed ) ) { float delaySpeed2; if ( !spawnArgs.GetFloat ( "delaySpeed_end", "0", delaySpeed2 ) ) { delaySpeed2 = delaySpeed; } float delaySpeedDuration; delaySpeedDuration = SEC2MS( spawnArgs.GetFloat ( "delaySpeed_duration", "0" ) ); speed.Init( gameLocal.time, delaySpeedDuration, delaySpeed, delaySpeed2 ); physicsObj.SetLinearVelocity( dir * speed.GetCurrentValue(gameLocal.time) + pushVelocity ); } } driftTime = 0; driftRate = SEC2MS ( spawnArgs.GetFloat ( "driftRate", "0" ) ); driftRange = spawnArgs.GetFloat ( "driftRange" ); driftDiversity = spawnArgs.GetFloat ( "driftDiversity", ".5" ); driftAngle = gameLocal.random.RandomFloat ( ) * 360.0f; driftAngleStep = spawnArgs.GetFloat ( "driftRotate" ); driftAngleStep += gameLocal.random.CRandomFloat ( ) * (driftAngleStep * driftDiversity) * (gameLocal.random.RandomFloat()<0.5f?-1.0f:1.0f); driftDelay = SEC2MS(spawnArgs.GetFloat ( "driftDelay" )); driftProjectRange = spawnArgs.GetFloat ( "driftProjectRange", "128" ); // Turn rate can be ramped up over time turn_max.Init ( gameLocal.time, SEC2MS(spawnArgs.GetFloat ( "turn_accel", "0" )), 0, spawnArgs.GetFloat( "turn_max", "180" ) / ( float )gameLocal.GetMHz() ); UpdateVisuals(); } /* ================= idGuidedProjectile::Launch ================= */ void idGuidedProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { if ( guideEnt.IsValid() ) { guideEnt->GuidedProjectileIncoming( NULL ); } idProjectile::Killed( inflictor, attacker, damage, dir, location ); } /* =============================================================================== rvDriftingProjectile =============================================================================== */ CLASS_DECLARATION( idProjectile, rvDriftingProjectile ) END_CLASS /* ================ rvDriftingProjectile::rvDriftingProjectile( void ) ================ */ rvDriftingProjectile::rvDriftingProjectile( void ) { } /* ================= rvDriftingProjectile::~rvDriftingProjectile ================= */ rvDriftingProjectile::~rvDriftingProjectile ( void ) { } /* ================ rvDriftingProjectile::Save ================ */ void rvDriftingProjectile::Save( idSaveGame *savefile ) const { savefile->WriteVec3 ( startDir ); savefile->WriteVec3 ( startOrigin ); savefile->WriteMat3 ( startAxis ); savefile->WriteFloat ( startSpeed ); savefile->WriteFloat ( driftOffsetMax ); savefile->WriteFloat ( driftSpeedMax ); savefile->WriteFloat ( driftTime ); savefile->WriteInterpolate( driftSpeed ); for( int ix = 0; ix < 2; ++ix ) { savefile->WriteInterpolate( driftOffset[ix] ); } } /* ================ rvDriftingProjectile::Restore ================ */ void rvDriftingProjectile::Restore( idRestoreGame *savefile ) { savefile->ReadVec3 ( startDir ); savefile->ReadVec3 ( startOrigin ); savefile->ReadMat3 ( startAxis ); savefile->ReadFloat ( startSpeed ); savefile->ReadFloat ( driftOffsetMax ); savefile->ReadFloat ( driftSpeedMax ); savefile->ReadFloat ( driftTime ); savefile->ReadInterpolate( driftSpeed ); for( int ix = 0; ix < 2; ++ix ) { savefile->ReadInterpolate( driftOffset[ix] ); } } /* ================ rvDriftingProjectile::Think ================ */ void rvDriftingProjectile::Think( void ) { idVec3 diff; idVec3 origin; idVec3 oldOrigin; idVec3 dir; float dist; oldOrigin = GetPhysics()->GetOrigin ( ); diff = oldOrigin - startOrigin; dist = diff.Length ( ); origin = startOrigin + startDir * dist; if ( driftSpeed.IsDone ( gameLocal.time ) ) { driftSpeed.Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, driftSpeed.GetCurrentValue ( gameLocal.time ), gameLocal.random.RandomFloat() * driftSpeedMax * (driftSpeed.GetCurrentValue ( gameLocal.time )<0?1:-1) ); } if ( driftOffset[0].IsDone ( gameLocal.time ) ) { driftOffset[0].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat() * driftTime, driftOffset[0].GetCurrentValue ( gameLocal.time ), gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[0].GetCurrentValue ( gameLocal.time )<0?1:-1) ); } if ( driftOffset[1].IsDone ( gameLocal.time ) ) { driftOffset[1].Init ( gameLocal.time, driftTime / 4.0f, driftTime / 4.0f, driftTime + gameLocal.random.RandomFloat()*driftTime, driftOffset[1].GetCurrentValue ( gameLocal.time ), gameLocal.random.RandomFloat() * driftOffsetMax * (driftOffset[1].GetCurrentValue ( gameLocal.time )<0?1:-1) ); } origin += startAxis[1] * driftOffset[0].GetCurrentValue ( gameLocal.time ); origin += startAxis[2] * driftOffset[1].GetCurrentValue ( gameLocal.time ); GetPhysics ( )->SetOrigin ( origin ); GetPhysics ( )->SetLinearVelocity ( startDir * (startSpeed + driftSpeed.GetCurrentValue ( gameLocal.time )) ); idProjectile::Think(); // Now orient the projectile using the old origin dir = GetPhysics()->GetOrigin() - oldOrigin; dir.Normalize ( ); GetPhysics ( )->SetAxis ( dir.ToMat3 ( ) ); } /* ================= rvDriftingProjectile::Launch ================= */ void rvDriftingProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, float dmgPower) { startDir = dir; startOrigin = start; startAxis = dir.ToMat3(); driftOffsetMax = spawnArgs.GetFloat ( "driftOffset", "50" ); driftSpeedMax = spawnArgs.GetFloat ( "driftSpeed", "50" ); driftTime = SEC2MS ( spawnArgs.GetFloat ( "driftTime", ".5" ) ); idProjectile::Launch ( start, dir, pushVelocity, timeSinceFire, dmgPower ); startSpeed = GetPhysics()->GetLinearVelocity().Length ( ); } /* ================= rvDriftingProjectile::Launch ================= */ void rvDriftingProjectile::UpdateVisualAngles ( void ) { rotation.Init( gameLocal.GetTime(), 0.0f, rotation.GetCurrentValue(gameLocal.GetTime()), GetPhysics()->GetAxis().ToQuat() ); } /* =============================================================================== rvSpawnerProjectile =============================================================================== */ CLASS_DECLARATION( idProjectile, rvSpawnerProjectile ) EVENT( EV_PostSpawn, rvSpawnerProjectile::Event_PostSpawn ) END_CLASS /* ================ rvSpawnerProjectile::rvSpawnerProjectile( void ) ================ */ rvSpawnerProjectile::rvSpawnerProjectile( void ) { spawnState = STATE_NONE; } /* ================= rvSpawnerProjectile::~rvSpawnerProjectile ================= */ rvSpawnerProjectile::~rvSpawnerProjectile ( void ) { if ( spawnState == STATE_ADDED && spawner ) { spawner->RemoveSpawnPoint ( this ); } } /* ================= rvSpawnerProjectile::SetSpawner ================= */ void rvSpawnerProjectile::Spawn ( void ) { if ( *spawnArgs.GetString ( "spawner" ) ) { PostEventMS ( &EV_PostSpawn, 0 ); } } /* ================= rvSpawnerProjectile::SetSpawner ================= */ void rvSpawnerProjectile::SetSpawner ( rvSpawner* _spawner ) { spawner = _spawner; } /* ================= rvSpawnerProjectile::Think ================= */ void rvSpawnerProjectile::Think ( void ) { idProjectile::Think ( ); if ( physicsObj.IsAtRest ( ) ) { if ( spawnState == STATE_NONE && spawner ) { spawner->AddSpawnPoint ( this ); spawnState = STATE_ADDED; } } } /* ================= rvSpawnerProjectile::Event_PostSpawn ================= */ void rvSpawnerProjectile::Event_PostSpawn ( void ) { const char* temp; temp = spawnArgs.GetString ( "spawner" ); if ( temp && *temp ) { idEntity* ent; ent = gameLocal.FindEntity ( temp ); if ( !ent ) { gameLocal.Warning ( "spawner entity ('%s') not found for rvSpawnerProjectile '%s'", temp, GetName ( ) ); } else if ( !ent->IsType ( rvSpawner::GetClassType() ) ) { gameLocal.Warning ( "spawner entity ('%s') is not of type rvSpawner for rvSpawnerProjectile '%s'", temp, GetName ( ) ); } else { SetSpawner ( static_cast(ent) ); } } } /* =============================================================================== rvMIRVProjectile =============================================================================== */ idEventDef EV_LaunchWarheads( "launchWarheads" ); CLASS_DECLARATION( idProjectile, rvMIRVProjectile ) EVENT( EV_LaunchWarheads, rvMIRVProjectile::Event_LaunchWarheads ) END_CLASS /* ================ rvMIRVProjectile::rvMIRVProjectile( void ) ================ */ rvMIRVProjectile::rvMIRVProjectile( void ) { } /* ================= rvMIRVProjectile::~rvMIRVProjectile ================= */ rvMIRVProjectile::~rvMIRVProjectile ( void ) { } /* ================ void rvMIRVProjectile::Spawn( void ) ================ */ void rvMIRVProjectile::Spawn( void ) { float launchDelay = spawnArgs.GetFloat("warhead_fuse", "0"); //post event for warhead launch PostEventSec( &EV_LaunchWarheads, launchDelay ); } /* ================ void rvMIRVProjectile::Event_LaunchWarheads( void ) ================ */ void rvMIRVProjectile::Event_LaunchWarheads( void ) { const char* warhead; int count; warhead = spawnArgs.GetString("def_warhead",""); count = spawnArgs.GetFloat("warhead_count","0"); if( warhead && warhead[0] ) { //start launching! float angle = (360.0f / count); idMat3 normalMat = this->GetPhysics()->GetAxis( ); idVec3 normal = normalMat[0]; idVec3 axis = normalMat[1]; float t; idMat3 axisMat; idProjectile * warheadEntity; //hey how 'bout that. normal.Normalize(); for( t = 0; t< count; t++) { //rotate axis around normal by angle degrees. axisMat = axis.ToMat3(); axisMat.RotateArbitrary( normal, angle * t); warheadEntity = static_cast(gameLocal.SpawnEntityDef( warhead)); warheadEntity->Create( this->GetOwner(), this->GetPhysics()->GetOrigin(), axisMat[0], this ); warheadEntity->Launch( this->GetPhysics()->GetOrigin(), axisMat[0], axisMat[0] ); } //foom! gameLocal.PlayEffect( spawnArgs, "fx_impact", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); PostEventSec( &EV_Explode, 0 ); } } // RAVEN END