/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "../idlib/precompiled.h" #pragma hdrstop #include "Game_local.h" /* =============================================================================== idProjectile =============================================================================== */ idCVar g_projectileDebug( "g_projectileDebug", "0", CVAR_BOOL, "Debug projectiles" ); // This is used in MP to simulate frames to catchup a projectile's state. Similiar to how players work much lighter weight. // This is used in MP to simulate frames to catchup a projectile's state. Similiar to how players work much lighter weight. idArray< idProjectile::simulatedProjectile_t, idProjectile::MAX_SIMULATED_PROJECTILES > idProjectile::projectilesToSimulate; static const int BFG_DAMAGE_FREQUENCY = 333; 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_GetProjectileState( "getProjectileState", NULL, 'd' ); const idEventDef EV_CreateProjectile( "projectileCreateProjectile", "evv" ); const idEventDef EV_LaunchProjectile( "projectileLaunchProjectile", "vvv" ); const idEventDef EV_SetGravity( "setGravity", "f" ); 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_GetProjectileState, idProjectile::Event_GetProjectileState ) EVENT( EV_CreateProjectile, idProjectile::Event_CreateProjectile ) EVENT( EV_LaunchProjectile, idProjectile::Event_LaunchProjectile ) EVENT( EV_SetGravity, idProjectile::Event_SetGravity ) END_CLASS /* ================ idProjectile::idProjectile ================ */ idProjectile::idProjectile() : launchOrigin( 0.0f ), launchAxis( mat3_identity ) { owner = NULL; lightDefHandle = -1; thrust = 0.0f; thrust_end = 0; smokeFly = NULL; smokeFlyTime = 0; state = SPAWNED; lightOffset = vec3_zero; lightStartTime = 0; lightEndTime = 0; lightColor = vec3_zero; damagePower = 1.0f; launchedFromGrabber = false; mTouchTriggers = false; mNoExplodeDisappear = false; memset( &projectileFlags, 0, sizeof( projectileFlags ) ); memset( &renderLight, 0, sizeof( renderLight ) ); // note: for net_instanthit projectiles, we will force this back to false at spawn time fl.networkSync = true; } /* ================ idProjectile::Spawn ================ */ void idProjectile::Spawn() { physicsObj.SetSelf( this ); physicsObj.SetClipModel( new( TAG_PHYSICS_CLIP_ENTITY ) idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); physicsObj.SetContents( 0 ); physicsObj.SetClipMask( 0 ); physicsObj.PutToRest(); SetPhysics( &physicsObj ); mNoExplodeDisappear = spawnArgs.GetBool( "no_explode_disappear", mNoExplodeDisappear ); mTouchTriggers = spawnArgs.GetBool( "touch_triggers", mTouchTriggers ); } /* ================ idProjectile::Save ================ */ void idProjectile::Save( idSaveGame* savefile ) const { owner.Save( savefile ); projectileFlags_s flags = projectileFlags; LittleBitField( &flags, sizeof( flags ) ); savefile->Write( &flags, sizeof( flags ) ); savefile->WriteFloat( thrust ); savefile->WriteInt( thrust_end ); savefile->WriteRenderLight( renderLight ); savefile->WriteInt( ( int )lightDefHandle ); savefile->WriteVec3( lightOffset ); savefile->WriteInt( lightStartTime ); savefile->WriteInt( lightEndTime ); savefile->WriteVec3( lightColor ); savefile->WriteParticle( smokeFly ); savefile->WriteInt( smokeFlyTime ); savefile->WriteInt( originalTimeGroup ); savefile->WriteInt( ( int )state ); savefile->WriteFloat( damagePower ); savefile->WriteStaticObject( physicsObj ); savefile->WriteStaticObject( thruster ); } /* ================ idProjectile::Restore ================ */ void idProjectile::Restore( idRestoreGame* savefile ) { owner.Restore( savefile ); savefile->Read( &projectileFlags, sizeof( projectileFlags ) ); LittleBitField( &projectileFlags, sizeof( projectileFlags ) ); savefile->ReadFloat( thrust ); savefile->ReadInt( thrust_end ); savefile->ReadRenderLight( renderLight ); savefile->ReadInt( ( int& )lightDefHandle ); savefile->ReadVec3( lightOffset ); savefile->ReadInt( lightStartTime ); savefile->ReadInt( lightEndTime ); savefile->ReadVec3( lightColor ); savefile->ReadParticle( smokeFly ); savefile->ReadInt( smokeFlyTime ); savefile->ReadInt( originalTimeGroup ); savefile->ReadInt( ( int& )state ); savefile->ReadFloat( damagePower ); savefile->ReadStaticObject( physicsObj ); RestorePhysics( &physicsObj ); savefile->ReadStaticObject( thruster ); thruster.SetPhysics( &physicsObj ); if( smokeFly != NULL ) { idVec3 dir; dir = physicsObj.GetLinearVelocity(); dir.NormalizeFast(); gameLocal.smokeParticles->EmitSmoke( smokeFly, gameLocal.time, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); } if( lightDefHandle >= 0 ) { lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); } } /* ================ idProjectile::GetOwner ================ */ idEntity* idProjectile::GetOwner() const { return owner.GetEntity(); } /* ================ idProjectile::Create ================ */ void idProjectile::Create( idEntity* owner, const idVec3& start, const idVec3& dir ) { idDict args; idStr shaderName; idVec3 light_color; idVec3 light_offset; idVec3 tmp; idMat3 axis; Unbind(); // align z-axis of model with the direction axis = dir.ToMat3(); tmp = axis[2]; axis[2] = axis[0]; axis[0] = -tmp; physicsObj.SetOrigin( start ); physicsObj.SetAxis( axis ); physicsObj.GetClipModel()->SetOwner( owner ); this->owner = owner; memset( &renderLight, 0, sizeof( renderLight ) ); shaderName = spawnArgs.GetString( "mtr_light_shader" ); if( *( const char* )shaderName ) { renderLight.shader = declManager->FindMaterial( shaderName, false ); renderLight.pointLight = true; renderLight.lightRadius[0] = renderLight.lightRadius[1] = renderLight.lightRadius[2] = spawnArgs.GetFloat( "light_radius" ); #ifdef ID_PC renderLight.lightRadius *= 1.5f; renderLight.forceShadows = true; #endif 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; } spawnArgs.GetVector( "light_offset", "0 0 0", lightOffset ); lightStartTime = 0; lightEndTime = 0; smokeFlyTime = 0; damagePower = 1.0f; if( spawnArgs.GetBool( "reset_time_offset", "0" ) ) { renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); } UpdateVisuals(); state = CREATED; } /* ================= idProjectile::~idProjectile ================= */ idProjectile::~idProjectile() { StopSound( SND_CHANNEL_ANY, false ); FreeLightDef(); } /* ================= idProjectile::FreeLightDef ================= */ void idProjectile::FreeLightDef() { 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 launchPower, const float dmgPower ) { float fuse; float startthrust; float endthrust; idVec3 velocity; idAngles angular_velocity; float linear_friction; float angular_friction; float contact_friction; float bounce; float mass; float speed; float gravity; idVec3 gravVec; idVec3 tmp; idMat3 axis; int thrust_start; int contents; int clipMask; // allow characters to throw projectiles during cinematics, but not the player if( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::Type ) ) { cinematic = owner.GetEntity()->cinematic; } else { cinematic = false; } thrust = spawnArgs.GetFloat( "thrust" ); startthrust = spawnArgs.GetFloat( "thrust_start" ); endthrust = spawnArgs.GetFloat( "thrust_end" ); spawnArgs.GetVector( "velocity", "0 0 0", velocity ); speed = velocity.Length() * launchPower; damagePower = dmgPower; spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity ); 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" ); 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" ); if( mass <= 0 ) { gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() ); } thrust *= mass; thrust_start = SEC2MS( startthrust ) + gameLocal.time; thrust_end = SEC2MS( endthrust ) + gameLocal.time; lightStartTime = 0; lightEndTime = 0; if( health ) { fl.takedamage = true; } gravVec = gameLocal.GetGravity(); gravVec.NormalizeFast(); Unbind(); // align z-axis of model with the direction axis = dir.ToMat3(); tmp = axis[2]; axis[2] = axis[0]; axis[0] = -tmp; contents = 0; clipMask = MASK_SHOT_RENDERMODEL; if( spawnArgs.GetBool( "detonate_on_trigger" ) ) { contents |= CONTENTS_TRIGGER; } if( !spawnArgs.GetBool( "no_contents" ) ) { contents |= CONTENTS_PROJECTILE; clipMask |= CONTENTS_PROJECTILE; } if( !idStr::Cmp( this->GetEntityDefName(), "projectile_helltime_killer" ) ) { contents = CONTENTS_MOVEABLECLIP; clipMask = CONTENTS_MOVEABLECLIP; fuse = 10.0f; } // 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 ); if( contact_friction == 0.0f ) { physicsObj.NoContact(); } physicsObj.SetBouncyness( bounce ); physicsObj.SetGravity( gravVec * gravity ); physicsObj.SetContents( contents ); physicsObj.SetClipMask( clipMask ); physicsObj.SetLinearVelocity( axis[ 2 ] * speed + pushVelocity ); physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); physicsObj.SetOrigin( start ); physicsObj.SetAxis( axis ); launchOrigin = start; launchAxis = axis; thruster.SetPosition( &physicsObj, 0, idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) ); if( !common->IsClient() || fl.skipReplication ) { 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 ); } } if( projectileFlags.isTracer ) { StartSound( "snd_tracer", SND_CHANNEL_BODY, 0, false, NULL ); } else { StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); } smokeFlyTime = 0; const char* smokeName = spawnArgs.GetString( "smoke_fly" ); if( *smokeName != '\0' ) { smokeFly = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); smokeFlyTime = gameLocal.time; } originalTimeGroup = timeGroup; // 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(); state = LAUNCHED; } /* ================ idProjectile::Think ================ */ void idProjectile::Think() { if( thinkFlags & TH_THINK ) { if( thrust && ( gameLocal.time < thrust_end ) ) { // evaluate force thruster.SetForce( GetPhysics()->GetAxis()[ 0 ] * thrust ); thruster.Evaluate( gameLocal.time ); } } if( mTouchTriggers ) { TouchTriggers(); } // if the projectile owner is a player if( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) { idPlayer* player = static_cast( owner.GetEntity() ); // Remove any projectiles spectators threw. if( player != NULL && player->spectating ) { PostEventMS( &EV_Remove, 0 ); } } // run physics RunPhysics(); DecayOriginAndAxisDelta(); Present(); AddParticlesAndLight(); } /* ================= idProjectile::AddParticlesAndLight ================= */ void idProjectile::AddParticlesAndLight() { // add the particles if( smokeFly != NULL && smokeFlyTime && !IsHidden() ) { idVec3 dir = -GetPhysics()->GetLinearVelocity(); dir.Normalize(); SetTimeState ts( originalTimeGroup ); if( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), dir.ToMat3(), timeGroup /*_D3XP*/ ) ) { smokeFlyTime = gameLocal.time; } } // 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 ) { 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::Collide ================= */ bool idProjectile::Collide( const trace_t& collision, const idVec3& velocity ) { idEntity* ent; idEntity* ignore; const char* damageDefName; idVec3 dir; float push; float damageScale; if( state == EXPLODED || state == FIZZLED ) { return true; } const bool isHitscan = spawnArgs.GetBool( "net_instanthit" ); // hanlde slow projectiles here. if( common->IsClient() && !isHitscan ) { // This is a replicated slow projectile, predict the explosion. if( ClientPredictionCollide( this, spawnArgs, collision, velocity, !isHitscan ) ) { Explode( collision, NULL ); return true; } } // remove projectile when a 'noimpact' surface is hit if( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { PostEventMS( &EV_Remove, 0 ); common->DPrintf( "Projectile collision no impact\n" ); return true; } // get the entity the projectile collided with ent = gameLocal.entities[ collision.c.entityNum ]; if( ent == owner.GetEntity() ) { assert( 0 ); return true; } // just get rid of the projectile when it hits a player in noclip if( ent->IsType( idPlayer::Type ) && static_cast( ent )->noclip ) { PostEventMS( &EV_Remove, 0 ); return true; } // direction of projectile dir = velocity; dir.Normalize(); // projectiles can apply an additional impulse next to the rigid body physics impulse if( spawnArgs.GetFloat( "push", "0", push ) && push > 0.0f ) { if( !common->IsClient() ) { ent->ApplyImpulse( this, collision.c.id, collision.c.point, push * dir ); } } // MP: projectiles open doors if( common->IsMultiplayer() && ent->IsType( idDoor::Type ) && !static_cast< idDoor* >( ent )->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) { if( !common->IsClient() ) { ent->ProcessEvent( &EV_Activate , this ); } } if( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast( ent )->GetBody()->IsType( idActor::Type ) ) ) { if( !projectileFlags.detonate_on_actor ) { return false; } } else { if( !projectileFlags.detonate_on_world ) { if( !StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ) ) { float len = velocity.Length(); if( len > BOUNCE_SOUND_MIN_VELOCITY ) { SetSoundVolume( len > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ) ); StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, true, NULL ); } } return false; } } SetOrigin( collision.endpos ); SetAxis( collision.endAxis ); // To see the explosion on the collision surface instead of // at the muzzle, clear the deltas. CreateDeltasFromOldOriginAndAxis( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); // unlink the clip model because we no longer need it GetPhysics()->UnlinkClip(); damageDefName = spawnArgs.GetString( "def_damage" ); ignore = NULL; // if the projectile causes a damage effect if( spawnArgs.GetBool( "impact_damage_effect" ) ) { // if the hit entity has a special damage effect if( ent->spawnArgs.GetBool( "bleed" ) ) { ent->AddDamageEffect( collision, velocity, damageDefName ); } else { AddDefaultDamageEffect( collision, velocity ); } } // if the hit entity takes damage if( ent->fl.takedamage ) { if( damagePower ) { damageScale = damagePower; } else { damageScale = 1.0f; } // if the projectile owner is a player if( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) { // if the projectile hit an actor if( ent->IsType( idActor::Type ) ) { idPlayer* player = static_cast( owner.GetEntity() ); player->AddProjectileHits( 1 ); damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE ); } } if( damageDefName[0] != '\0' ) { bool killedByImpact = true; if( ent->health <= 0 ) { killedByImpact = false; } // Only handle the server's own attacks here. Attacks by other players on the server occur through // reliable messages. if( !common->IsMultiplayer() || common->IsClient() || ( common->IsServer() && owner.GetEntityNum() == gameLocal.GetLocalClientNum() ) || ( common->IsServer() && !isHitscan ) ) { ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); } else { if( ent->IsType( idPlayer::Type ) == false && common->IsServer() ) { ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); } } // Check if we are hitting an actor. and see if we killed him. if( !common->IsClient() && ent->health <= 0 && killedByImpact ) { if( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) { if( ent->IsType( idActor::Type ) && ent != owner.GetEntity() ) { idPlayer* player = static_cast( owner.GetEntity() ); player->AddProjectileKills(); } } } ignore = ent; } } Explode( collision, ignore ); if( !common->IsClient() && owner.GetEntity() != NULL && owner.GetEntity()->IsType( idPlayer::Type ) ) { idPlayer* player = static_cast( owner.GetEntity() ); int kills = player->GetProjectileKills(); if( kills >= 2 && common->IsMultiplayer() && strstr( GetName(), "projectile_rocket" ) != 0 ) { player->GetAchievementManager().EventCompletesAchievement( ACHIEVEMENT_MP_KILL_2_GUYS_IN_ROOM_WITH_BFG ); } // projectile is done dealing damage. player->ResetProjectileKills(); } return true; } /* ================= idProjectile::DefaultDamageEffect ================= */ void idProjectile::DefaultDamageEffect( idEntity* soundEnt, const idDict& projectileDef, const trace_t& collision, const idVec3& velocity ) { const char* decal, *sound, *typeName; surfTypes_t materialType; if( collision.c.material != NULL ) { materialType = collision.c.material->GetSurfaceType(); } else { materialType = SURFTYPE_METAL; } // get material type name typeName = gameLocal.sufaceTypeNames[ materialType ]; // play impact sound sound = projectileDef.GetString( va( "snd_%s", typeName ) ); if( *sound == '\0' ) { sound = projectileDef.GetString( "snd_metal" ); } if( *sound == '\0' ) { sound = projectileDef.GetString( "snd_impact" ); } if( *sound != '\0' ) { soundEnt->StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); } // project decal decal = projectileDef.GetString( va( "mtr_detonate_%s", typeName ) ); if( *decal == '\0' ) { decal = projectileDef.GetString( "mtr_detonate" ); } if( *decal != '\0' ) { gameLocal.ProjectDecal( collision.c.point, -collision.c.normal, 8.0f, true, projectileDef.GetFloat( "decal_size", "6.0" ), decal ); } } /* ================= idProjectile::AddDefaultDamageEffect ================= */ void idProjectile::AddDefaultDamageEffect( const trace_t& collision, const idVec3& velocity ) { DefaultDamageEffect( this, spawnArgs, collision, velocity ); if( common->IsServer() && fl.networkSync ) { idBitMsg msg; byte msgBuf[MAX_EVENT_PARAM_SIZE]; lobbyUserID_t excluding; if( spawnArgs.GetBool( "net_instanthit" ) && owner.GetEntityNum() < MAX_PLAYERS ) { excluding = gameLocal.lobbyUserIDs[owner.GetEntityNum()]; } msg.InitWrite( msgBuf, sizeof( msgBuf ) ); msg.BeginWriting(); msg.WriteFloat( collision.c.point[0] ); msg.WriteFloat( collision.c.point[1] ); msg.WriteFloat( collision.c.point[2] ); msg.WriteDir( collision.c.normal, 24 ); msg.WriteLong( ( collision.c.material != NULL ) ? gameLocal.ServerRemapDecl( -1, DECL_MATERIAL, collision.c.material->Index() ) : -1 ); msg.WriteFloat( velocity[0], 5, 10 ); msg.WriteFloat( velocity[1], 5, 10 ); msg.WriteFloat( velocity[2], 5, 10 ); ServerSendEvent( EVENT_DAMAGE_EFFECT, &msg, false, excluding ); } } /* ================ idProjectile::Killed ================ */ void idProjectile::Killed( idEntity* inflictor, idEntity* attacker, int damage, const idVec3& dir, int location ) { if( spawnArgs.GetBool( "detonate_on_death" ) ) { trace_t collision; memset( &collision, 0, sizeof( collision ) ); collision.endAxis = GetPhysics()->GetAxis(); collision.endpos = GetPhysics()->GetOrigin(); collision.c.point = GetPhysics()->GetOrigin(); collision.c.normal.Set( 0, 0, 1 ); Explode( collision, NULL ); physicsObj.ClearContacts(); physicsObj.PutToRest(); } else { Fizzle(); } } /* ================ idProjectile::Fizzle ================ */ void idProjectile::Fizzle() { if( state == EXPLODED || state == FIZZLED ) { return; } StopSound( SND_CHANNEL_BODY, false ); StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); // fizzle FX const char* psystem = spawnArgs.GetString( "smoke_fuse" ); if( psystem != NULL && *psystem != '\0' ) { //FIXME:SMOKE gameLocal.particles->SpawnParticles( GetPhysics()->GetOrigin(), vec3_origin, psystem ); } // we need to work out how long the effects last and then remove them at that time // for example, bullets have no real effects if( smokeFly && smokeFlyTime ) { smokeFlyTime = 0; } fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.GetClipModel()->Unlink(); physicsObj.PutToRest(); Hide(); FreeLightDef(); state = FIZZLED; if( common->IsClient() && !fl.skipReplication ) { return; } CancelEvents( &EV_Fizzle ); PostEventMS( &EV_Remove, spawnArgs.GetInt( "remove_time", "1500" ) ); } /* ================ 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.GetEntity(), ignore, this, splash_damage, damagePower ); } } /* ================ idProjectile::Event_RadiusDamage ================ */ void idProjectile::Event_GetProjectileState() { idThread::ReturnInt( state ); } /* ================ idProjectile::Explode ================ */ void idProjectile::Explode( const trace_t& collision, idEntity* ignore ) { const char* fxname, *light_shader, *sndExplode; float light_fadetime; idVec3 normal; int removeTime; if( mNoExplodeDisappear ) { PostEventMS( &EV_Remove, 0 ); return; } if( state == EXPLODED || state == FIZZLED ) { return; } // activate rumble for player idPlayer* player = gameLocal.GetLocalPlayer(); const bool isHitscan = spawnArgs.GetBool( "net_instanthit" ); if( player != NULL && isHitscan == false ) { // damage const char* damageDefName = spawnArgs.GetString( "def_damage" ); const idDict* damageDef = gameLocal.FindEntityDefDict( damageDefName ); int damage; if( damageDef != NULL ) { damage = damageDef->GetInt( "damage" ); } else { damage = 200; } float damageScale = idMath::ClampFloat( 0.25f, 1.0f, ( float )damage * ( 1.0f / 200.0f ) ); // 50...200 -> min...max rumble // distance float dist = ( GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin() ).LengthFast(); float distScale = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( dist * ( 1.0f / 4000.0f ) ) + 0.25f ); // 0...4000 -> max...min rumble distScale *= damageScale; // apply damage scale here, weaker damage produces less rumble // determine rumble float highMag = distScale; int highDuration = idMath::Ftoi( 300.0f * distScale ); float lowMag = distScale * 0.75f; int lowDuration = idMath::Ftoi( 500.0f * distScale ); player->SetControllerShake( highMag, highDuration, lowMag, lowDuration ); } // stop sound StopSound( SND_CHANNEL_BODY2, false ); // play explode sound switch( ( int ) damagePower ) { case 2: sndExplode = "snd_explode2"; break; case 3: sndExplode = "snd_explode3"; break; case 4: sndExplode = "snd_explode4"; break; default: sndExplode = "snd_explode"; break; } StartSound( sndExplode, SND_CHANNEL_BODY, 0, true, NULL ); // we need to work out how long the effects last and then remove them at that time // for example, bullets have no real effects if( smokeFly && smokeFlyTime ) { smokeFlyTime = 0; } Hide(); FreeLightDef(); if( spawnArgs.GetVector( "detonation_axis", "", normal ) ) { GetPhysics()->SetAxis( normal.ToMat3() ); } GetPhysics()->SetOrigin( collision.endpos + 2.0f * collision.c.normal ); // default remove time if( fl.skipReplication && !spawnArgs.GetBool( "net_instanthit" ) ) { removeTime = spawnArgs.GetInt( "remove_time", "6000" ); } else { removeTime = spawnArgs.GetInt( "remove_time", "1500" ); } // change the model, usually to a PRT fxname = NULL; if( g_testParticle.GetInteger() == TEST_PARTICLE_IMPACT ) { fxname = g_testParticleName.GetString(); } else { fxname = spawnArgs.GetString( "model_detonate" ); } int surfaceType = collision.c.material != NULL ? collision.c.material->GetSurfaceType() : SURFTYPE_METAL; if( !( fxname != NULL && *fxname != '\0' ) ) { if( ( surfaceType == SURFTYPE_NONE ) || ( surfaceType == SURFTYPE_METAL ) || ( surfaceType == SURFTYPE_STONE ) ) { fxname = spawnArgs.GetString( "model_smokespark" ); } else if( surfaceType == SURFTYPE_RICOCHET ) { fxname = spawnArgs.GetString( "model_ricochet" ); } else { fxname = spawnArgs.GetString( "model_smoke" ); } } // If the explosion is in liquid, spawn a particle splash idVec3 testOrg = GetPhysics()->GetOrigin(); int testC = gameLocal.clip.Contents( testOrg, NULL, mat3_identity, CONTENTS_WATER, this ); if( testC & CONTENTS_WATER ) { idFuncEmitter* splashEnt; idDict splashArgs; splashArgs.Set( "model", "sludgebulletimpact.prt" ); splashArgs.Set( "start_off", "1" ); splashEnt = static_cast( gameLocal.SpawnEntityType( idFuncEmitter::Type, &splashArgs ) ); splashEnt->GetPhysics()->SetOrigin( testOrg ); splashEnt->PostEventMS( &EV_Activate, 0, this ); splashEnt->PostEventMS( &EV_Remove, 1500 ); // HACK - if this is a chaingun bullet, don't do the normal effect if( !idStr::Cmp( spawnArgs.GetString( "def_damage" ), "damage_bullet_chaingun" ) ) { fxname = NULL; } } if( fxname && *fxname ) { SetModel( fxname ); renderEntity.shaderParms[SHADERPARM_RED] = renderEntity.shaderParms[SHADERPARM_GREEN] = renderEntity.shaderParms[SHADERPARM_BLUE] = renderEntity.shaderParms[SHADERPARM_ALPHA] = 1.0f; renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); renderEntity.shaderParms[SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat(); Show(); removeTime = ( removeTime > 3000 ) ? removeTime : 3000; } // explosion light light_shader = spawnArgs.GetString( "mtr_explode_light_shader" ); if( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool( "si_midnight" ) ) { light_shader = "lights/midnight_grenade"; } if( *light_shader ) { renderLight.shader = declManager->FindMaterial( light_shader, false ); renderLight.pointLight = true; renderLight.lightRadius[0] = renderLight.lightRadius[1] = renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" ); #ifdef ID_PC renderLight.lightRadius *= 2.0f; renderLight.forceShadows = true; #endif // Midnight ctf if( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool( "si_midnight" ) ) { renderLight.lightRadius[0] = renderLight.lightRadius[1] = renderLight.lightRadius[2] = spawnArgs.GetFloat( "explode_light_radius" ) * 2; } spawnArgs.GetVector( "explode_light_color", "1 1 1", lightColor ); renderLight.shaderParms[SHADERPARM_RED] = lightColor.x; renderLight.shaderParms[SHADERPARM_GREEN] = lightColor.y; renderLight.shaderParms[SHADERPARM_BLUE] = lightColor.z; renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f; renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); // Midnight ctf if( gameLocal.mpGame.IsGametypeFlagBased() && gameLocal.serverInfo.GetBool( "si_midnight" ) ) { light_fadetime = 3.0f; } else { light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime", "0.5" ); } lightStartTime = gameLocal.time; lightEndTime = MSEC_ALIGN_TO_FRAME( gameLocal.time + SEC2MS( light_fadetime ) ); BecomeActive( TH_THINK ); } fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.PutToRest(); state = EXPLODED; if( common->IsClient() && !fl.skipReplication ) { return; } // alert the ai gameLocal.AlertAI( owner.GetEntity() ); // bind the projectile to the impact entity if necesary if( gameLocal.entities[collision.c.entityNum] && spawnArgs.GetBool( "bindOnImpact" ) ) { Bind( gameLocal.entities[collision.c.entityNum], true ); } // splash damage if( !projectileFlags.noSplashDamage ) { 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 ); } } // spawn debris entities int fxdebris = spawnArgs.GetInt( "debris_count" ); if( fxdebris ) { const idDict* debris = gameLocal.FindEntityDefDict( "projectile_debris", false ); if( debris ) { int amount = gameLocal.random.RandomInt( fxdebris ); for( int i = 0; i < amount; i++ ) { idEntity* ent; idVec3 dir; dir.x = gameLocal.random.CRandomFloat() * 4.0f; dir.y = gameLocal.random.CRandomFloat() * 4.0f; dir.z = gameLocal.random.RandomFloat() * 8.0f; dir.Normalize(); gameLocal.SpawnEntityDef( *debris, &ent, false ); if( ent == NULL || !ent->IsType( idDebris::Type ) ) { gameLocal.Error( "'projectile_debris' is not an idDebris" ); return; } idDebris* debris = static_cast( ent ); debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() ); debris->Launch(); } } debris = gameLocal.FindEntityDefDict( "projectile_shrapnel", false ); if( debris ) { int amount = gameLocal.random.RandomInt( fxdebris ); for( int i = 0; i < amount; i++ ) { idEntity* ent; idVec3 dir; dir.x = gameLocal.random.CRandomFloat() * 8.0f; dir.y = gameLocal.random.CRandomFloat() * 8.0f; dir.z = gameLocal.random.RandomFloat() * 8.0f + 8.0f; dir.Normalize(); gameLocal.SpawnEntityDef( *debris, &ent, false ); if( ent == NULL || !ent->IsType( idDebris::Type ) ) { gameLocal.Error( "'projectile_shrapnel' is not an idDebris" ); break; } idDebris* debris = static_cast( ent ); debris->Create( owner.GetEntity(), physicsObj.GetOrigin(), dir.ToMat3() ); debris->Launch(); } } } CancelEvents( &EV_Explode ); PostEventMS( &EV_Remove, removeTime ); } /* ================ idProjectile::GetVelocity ================ */ idVec3 idProjectile::GetVelocity( const idDict* projectile ) { idVec3 velocity; projectile->GetVector( "velocity", "0 0 0", velocity ); return velocity; } /* ================ idProjectile::GetGravity ================ */ idVec3 idProjectile::GetGravity( const idDict* projectile ) { float gravity; gravity = projectile->GetFloat( "gravity" ); return idVec3( 0, 0, -gravity ); } /* ================ idProjectile::Event_Explode ================ */ void idProjectile::Event_Explode() { trace_t collision; memset( &collision, 0, sizeof( collision ) ); collision.endAxis = GetPhysics()->GetAxis(); collision.endpos = GetPhysics()->GetOrigin(); collision.c.point = GetPhysics()->GetOrigin(); collision.c.normal.Set( 0, 0, 1 ); AddDefaultDamageEffect( collision, collision.c.normal ); Explode( collision, NULL ); } /* ================ idProjectile::Event_Fizzle ================ */ void idProjectile::Event_Fizzle() { Fizzle(); } /* ================ idProjectile::Event_Touch ================ */ void idProjectile::Event_Touch( idEntity* other, trace_t* trace ) { if( common->IsClient() ) { return; } if( IsHidden() ) { return; } // Projectiles do not collide with flags if( other->IsType( idItemTeam::Type ) ) { return; } if( other != owner.GetEntity() ) { trace_t collision; memset( &collision, 0, sizeof( collision ) ); collision.endAxis = GetPhysics()->GetAxis(); collision.endpos = GetPhysics()->GetOrigin(); collision.c.point = GetPhysics()->GetOrigin(); collision.c.normal.Set( 0, 0, 1 ); AddDefaultDamageEffect( collision, collision.c.normal ); Explode( collision, NULL ); } } /* ================ idProjectile::CatchProjectile ================ */ void idProjectile::CatchProjectile( idEntity* o, const char* reflectName ) { idEntity* prevowner = owner.GetEntity(); owner = o; physicsObj.GetClipModel()->SetOwner( o ); if( this->IsType( idGuidedProjectile::Type ) ) { idGuidedProjectile* proj = static_cast( this ); proj->SetEnemy( prevowner ); } idStr s = spawnArgs.GetString( "def_damage" ); s += reflectName; const idDict* damageDef = gameLocal.FindEntityDefDict( s, false ); if( damageDef ) { spawnArgs.Set( "def_damage", s ); } } /* ================ idProjectile::GetProjectileState ================ */ int idProjectile::GetProjectileState() { return ( int )state; } /* ================ idProjectile::Event_CreateProjectile ================ */ void idProjectile::Event_CreateProjectile( idEntity* owner, const idVec3& start, const idVec3& dir ) { Create( owner, start, dir ); } /* ================ idProjectile::Event_LaunchProjectile ================ */ void idProjectile::Event_LaunchProjectile( const idVec3& start, const idVec3& dir, const idVec3& pushVelocity ) { Launch( start, dir, pushVelocity ); } /* ================ idProjectile::Event_SetGravity ================ */ void idProjectile::Event_SetGravity( float gravity ) { idVec3 gravVec; gravVec = gameLocal.GetGravity(); gravVec.NormalizeFast(); physicsObj.SetGravity( gravVec * gravity ); } /* ================= idProjectile::ClientPredictionCollide ================= */ bool idProjectile::ClientPredictionCollide( idEntity* soundEnt, const idDict& projectileDef, const trace_t& collision, const idVec3& velocity, bool addDamageEffect ) { idEntity* ent; // remove projectile when a 'noimpact' surface is hit if( collision.c.material && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { return false; } // get the entity the projectile collided with ent = gameLocal.entities[ collision.c.entityNum ]; if( ent == NULL ) { return false; } // don't do anything if hitting a noclip player if( ent->IsType( idPlayer::Type ) && static_cast( ent )->noclip ) { return false; } if( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast( ent )->GetBody()->IsType( idActor::Type ) ) ) { if( !projectileDef.GetBool( "detonate_on_actor" ) ) { return false; } } else { if( !projectileDef.GetBool( "detonate_on_world" ) ) { return false; } } // if the projectile causes a damage effect if( addDamageEffect && projectileDef.GetBool( "impact_damage_effect" ) ) { // if the hit entity does not have a special damage effect if( !ent->spawnArgs.GetBool( "bleed" ) ) { // predict damage effect DefaultDamageEffect( soundEnt, projectileDef, collision, velocity ); } } return true; } /* ================ idProjectile::ClientThink ================ */ void idProjectile::ClientThink( const int curTime, const float fraction, const bool predict ) { if( fl.skipReplication ) { Think(); } else { if( !renderEntity.hModel ) { return; } InterpolatePhysicsOnly( fraction ); Present(); AddParticlesAndLight(); } } /* ================ idProjectile::ClientPredictionThink ================ */ void idProjectile::ClientPredictionThink() { if( !renderEntity.hModel ) { return; } Think(); } /* ================ idProjectile::WriteToSnapshot ================ */ void idProjectile::WriteToSnapshot( idBitMsg& msg ) const { msg.WriteBits( owner.GetSpawnId(), 32 ); msg.WriteBits( state, 3 ); msg.WriteBits( fl.hidden, 1 ); physicsObj.WriteToSnapshot( msg ); } /* ================ idProjectile::ReadFromSnapshot ================ */ void idProjectile::ReadFromSnapshot( const idBitMsg& msg ) { projectileState_t newState; owner.SetSpawnId( msg.ReadBits( 32 ) ); newState = ( projectileState_t ) msg.ReadBits( 3 ); if( msg.ReadBits( 1 ) ) { Hide(); } else { Show(); } while( state != newState ) { switch( state ) { case SPAWNED: { Create( owner.GetEntity(), vec3_origin, idVec3( 1, 0, 0 ) ); break; } case CREATED: { // the right origin and direction are required if you want bullet traces Launch( vec3_origin, idVec3( 1, 0, 0 ), vec3_origin ); break; } case LAUNCHED: { if( newState == FIZZLED ) { Fizzle(); } else { trace_t collision; memset( &collision, 0, sizeof( collision ) ); collision.endAxis = GetPhysics()->GetAxis(); collision.endpos = GetPhysics()->GetOrigin(); collision.c.point = GetPhysics()->GetOrigin(); collision.c.normal.Set( 0, 0, 1 ); Explode( collision, NULL ); } break; } case FIZZLED: case EXPLODED: { StopSound( SND_CHANNEL_BODY2, false ); gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); state = SPAWNED; break; } } } physicsObj.ReadFromSnapshot( msg ); if( msg.HasChanged() ) { UpdateVisuals(); } } /* ================ idProjectile::ClientReceiveEvent ================ */ bool idProjectile::ClientReceiveEvent( int event, int time, const idBitMsg& msg ) { trace_t collision; idVec3 velocity; switch( event ) { case EVENT_DAMAGE_EFFECT: { memset( &collision, 0, sizeof( collision ) ); collision.c.point[0] = msg.ReadFloat(); collision.c.point[1] = msg.ReadFloat(); collision.c.point[2] = msg.ReadFloat(); collision.c.normal = msg.ReadDir( 24 ); int index = gameLocal.ClientRemapDecl( DECL_MATERIAL, msg.ReadLong() ); collision.c.material = ( index != -1 ) ? static_cast( declManager->DeclByIndex( DECL_MATERIAL, index ) ) : NULL; velocity[0] = msg.ReadFloat( 5, 10 ); velocity[1] = msg.ReadFloat( 5, 10 ); velocity[2] = msg.ReadFloat( 5, 10 ); DefaultDamageEffect( this, spawnArgs, collision, velocity ); return true; } default: { return idEntity::ClientReceiveEvent( event, time, msg ); } } } /* ======================== idProjectile::QueueToSimulate ======================== */ void idProjectile::QueueToSimulate( int startTime ) { assert( common->IsMultiplayer() && common->IsServer() ); for( int i = 0; i < MAX_SIMULATED_PROJECTILES; i++ ) { if( projectilesToSimulate[i].projectile == NULL ) { projectilesToSimulate[i].projectile = this; projectilesToSimulate[i].startTime = startTime; if( g_projectileDebug.GetBool() ) { int delta = gameLocal.GetServerGameTimeMs() - startTime; idLib::Printf( "Simulating projectile %d. Approx %d delay.\n", GetEntityNumber(), delta ); } return; } } idLib::Warning( "Unable to simulate more projectiles this frame" ); } /* ======================== idProjectile::SimulateProjectileFrame ======================== */ void idProjectile::SimulateProjectileFrame( int msec, int endTime ) { idVec3 oldOrigin = GetPhysics()->GetOrigin(); GetPhysics()->Evaluate( msec, endTime ); SetOrigin( GetPhysics()->GetOrigin() ); SetAxis( GetPhysics()->GetAxis() ); if( g_projectileDebug.GetBool() ) { float delta = ( GetPhysics()->GetOrigin() - oldOrigin ).Length(); idLib::Printf( "Simulated projectile %d. Delta: %.2f \n", GetEntityNumber(), delta ); //clientGame->renderWorld->DebugLine( idColor::colorYellow, oldOrigin, GetPhysics()->GetOrigin(), 5000 ); } } /* ======================== idProjectile::PostSimulate ======================== */ void idProjectile::PostSimulate( int endTime ) { if( state == EXPLODED || state == FIZZLED ) { // Already exploded. To see the explosion on the collision surface instead of // at the muzzle, don't set the deltas to the launch origin and axis. CreateDeltasFromOldOriginAndAxis( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); } else { CreateDeltasFromOldOriginAndAxis( launchOrigin, launchAxis ); } } /* =============================================================================== idGuidedProjectile =============================================================================== */ const idEventDef EV_SetEnemy( "setEnemy", "E" ); CLASS_DECLARATION( idProjectile, idGuidedProjectile ) EVENT( EV_SetEnemy, idGuidedProjectile::Event_SetEnemy ) END_CLASS /* ================ idGuidedProjectile::idGuidedProjectile ================ */ idGuidedProjectile::idGuidedProjectile() { enemy = NULL; speed = 0.0f; turn_max = 0.0f; clamp_dist = 0.0f; rndScale = ang_zero; rndAng = ang_zero; rndUpdateTime = 0; angles = ang_zero; burstMode = false; burstDist = 0; burstVelocity = 0.0f; unGuided = false; } /* ================= idGuidedProjectile::~idGuidedProjectile ================= */ idGuidedProjectile::~idGuidedProjectile() { } /* ================ idGuidedProjectile::Spawn ================ */ void idGuidedProjectile::Spawn() { } /* ================ idGuidedProjectile::Save ================ */ void idGuidedProjectile::Save( idSaveGame* savefile ) const { enemy.Save( savefile ); savefile->WriteFloat( speed ); savefile->WriteAngles( rndScale ); savefile->WriteAngles( rndAng ); savefile->WriteInt( rndUpdateTime ); savefile->WriteFloat( turn_max ); savefile->WriteFloat( clamp_dist ); savefile->WriteAngles( angles ); savefile->WriteBool( burstMode ); savefile->WriteBool( unGuided ); savefile->WriteFloat( burstDist ); savefile->WriteFloat( burstVelocity ); } /* ================ idGuidedProjectile::Restore ================ */ void idGuidedProjectile::Restore( idRestoreGame* savefile ) { enemy.Restore( savefile ); savefile->ReadFloat( speed ); savefile->ReadAngles( rndScale ); savefile->ReadAngles( rndAng ); savefile->ReadInt( rndUpdateTime ); savefile->ReadFloat( turn_max ); savefile->ReadFloat( clamp_dist ); savefile->ReadAngles( angles ); savefile->ReadBool( burstMode ); savefile->ReadBool( unGuided ); savefile->ReadFloat( burstDist ); savefile->ReadFloat( burstVelocity ); } /* ================ idGuidedProjectile::GetSeekPos ================ */ void idGuidedProjectile::GetSeekPos( idVec3& out ) { idEntity* enemyEnt = enemy.GetEntity(); if( enemyEnt ) { if( enemyEnt->IsType( idActor::Type ) ) { out = static_cast( enemyEnt )->GetEyePosition(); out.z -= 12.0f; } else { out = enemyEnt->GetPhysics()->GetOrigin(); } } else { out = GetPhysics()->GetOrigin() + physicsObj.GetLinearVelocity() * 2.0f; } } /* ================ idGuidedProjectile::Think ================ */ void idGuidedProjectile::Think() { idVec3 dir; idVec3 seekPos; idVec3 velocity; idVec3 nose; idVec3 tmp; idMat3 axis; idAngles dirAng; idAngles diff; float dist; float frac; int i; if( state == LAUNCHED && !unGuided ) { GetSeekPos( seekPos ); if( rndUpdateTime < gameLocal.time ) { rndAng[ 0 ] = rndScale[ 0 ] * gameLocal.random.CRandomFloat(); rndAng[ 1 ] = rndScale[ 1 ] * gameLocal.random.CRandomFloat(); rndAng[ 2 ] = rndScale[ 2 ] * gameLocal.random.CRandomFloat(); rndUpdateTime = gameLocal.time + 200; } nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0]; dir = seekPos - nose; dist = dir.Normalize(); dirAng = dir.ToAngles(); // make it more accurate as it gets closer frac = dist / clamp_dist; if( frac > 1.0f ) { frac = 1.0f; } diff = dirAng - angles + rndAng * frac; // clamp the to the max turn rate diff.Normalize180(); for( i = 0; i < 3; i++ ) { if( diff[ i ] > turn_max ) { diff[ i ] = turn_max; } else if( diff[ i ] < -turn_max ) { diff[ i ] = -turn_max; } } angles += diff; // make the visual model always points the dir we're traveling dir = angles.ToForward(); velocity = dir * speed; if( burstMode && dist < burstDist ) { unGuided = true; velocity *= burstVelocity; } physicsObj.SetLinearVelocity( velocity ); // align z-axis of model with the direction axis = dir.ToMat3(); tmp = axis[2]; axis[2] = axis[0]; axis[0] = -tmp; GetPhysics()->SetAxis( axis ); } idProjectile::Think(); } /* ================= idGuidedProjectile::Launch ================= */ void idGuidedProjectile::Launch( const idVec3& start, const idVec3& dir, const idVec3& pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) { idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); if( owner.GetEntity() ) { if( owner.GetEntity()->IsType( idAI::Type ) ) { enemy = static_cast( owner.GetEntity() )->GetEnemy(); } else if( owner.GetEntity()->IsType( idPlayer::Type ) ) { trace_t tr; idPlayer* player = static_cast( owner.GetEntity() ); idVec3 start = player->GetEyePosition(); idVec3 end = start + player->viewAxis[0] * 1000.0f; gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() ); if( tr.fraction < 1.0f ) { enemy = gameLocal.GetTraceEntity( tr ); } // ignore actors on the player's team if( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast( enemy.GetEntity() )->team == player->team ) ) { enemy = player->EnemyWithMostHealth(); } } } const idVec3& vel = physicsObj.GetLinearVelocity(); angles = vel.ToAngles(); speed = vel.Length(); rndScale = spawnArgs.GetAngles( "random", "15 15 0" ); turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / com_engineHz_latched; clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" ); burstMode = spawnArgs.GetBool( "burstMode" ); unGuided = false; burstDist = spawnArgs.GetFloat( "burstDist", "64" ); burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" ); UpdateVisuals(); } void idGuidedProjectile::SetEnemy( idEntity* ent ) { enemy = ent; } void idGuidedProjectile::Event_SetEnemy( idEntity* ent ) { SetEnemy( ent ); } /* =============================================================================== idSoulCubeMissile =============================================================================== */ CLASS_DECLARATION( idGuidedProjectile, idSoulCubeMissile ) END_CLASS /* ================ idSoulCubeMissile::Spawn() ================ */ void idSoulCubeMissile::Spawn() { startingVelocity.Zero(); endingVelocity.Zero(); accelTime = 0.0f; launchTime = 0; killPhase = false; returnPhase = false; smokeKillTime = 0; smokeKill = NULL; } /* ================= idSoulCubeMissile::~idSoulCubeMissile ================= */ idSoulCubeMissile::~idSoulCubeMissile() { } /* ================ idSoulCubeMissile::Save ================ */ void idSoulCubeMissile::Save( idSaveGame* savefile ) const { savefile->WriteVec3( startingVelocity ); savefile->WriteVec3( endingVelocity ); savefile->WriteFloat( accelTime ); savefile->WriteInt( launchTime ); savefile->WriteBool( killPhase ); savefile->WriteBool( returnPhase ); savefile->WriteVec3( destOrg ); savefile->WriteInt( orbitTime ); savefile->WriteVec3( orbitOrg ); savefile->WriteInt( smokeKillTime ); savefile->WriteParticle( smokeKill ); } /* ================ idSoulCubeMissile::Restore ================ */ void idSoulCubeMissile::Restore( idRestoreGame* savefile ) { savefile->ReadVec3( startingVelocity ); savefile->ReadVec3( endingVelocity ); savefile->ReadFloat( accelTime ); savefile->ReadInt( launchTime ); savefile->ReadBool( killPhase ); savefile->ReadBool( returnPhase ); savefile->ReadVec3( destOrg ); savefile->ReadInt( orbitTime ); savefile->ReadVec3( orbitOrg ); savefile->ReadInt( smokeKillTime ); savefile->ReadParticle( smokeKill ); } /* ================ idSoulCubeMissile::KillTarget ================ */ void idSoulCubeMissile::KillTarget( const idVec3& dir ) { idEntity* ownerEnt; const char* smokeName; idActor* act; ReturnToOwner(); if( enemy.GetEntity() && enemy.GetEntity()->IsType( idActor::Type ) ) { act = static_cast( enemy.GetEntity() ); killPhase = true; orbitOrg = act->GetPhysics()->GetAbsBounds().GetCenter(); orbitTime = gameLocal.time; smokeKillTime = 0; smokeName = spawnArgs.GetString( "smoke_kill" ); if( *smokeName != '\0' ) { smokeKill = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); smokeKillTime = gameLocal.time; } ownerEnt = owner.GetEntity(); if( ( act->health > 0 ) && ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) && ( ownerEnt->health > 0 ) && !act->spawnArgs.GetBool( "boss" ) ) { static_cast( ownerEnt )->GiveHealthPool( act->health ); } act->Damage( this, owner.GetEntity(), dir, spawnArgs.GetString( "def_damage" ), 1.0f, INVALID_JOINT ); act->GetAFPhysics()->SetTimeScale( 0.25 ); StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL ); } } /* ================ idSoulCubeMissile::Think ================ */ void idSoulCubeMissile::Think() { float pct; idVec3 seekPos; idEntity* ownerEnt; if( state == LAUNCHED ) { if( killPhase ) { // orbit the mob, cascading down if( gameLocal.time < orbitTime + 1500 ) { if( !gameLocal.smokeParticles->EmitSmoke( smokeKill, smokeKillTime, gameLocal.random.CRandomFloat(), orbitOrg, mat3_identity, timeGroup /*_D3XP*/ ) ) { smokeKillTime = gameLocal.time; } } } else { if( accelTime && gameLocal.time < launchTime + accelTime * 1000 ) { pct = ( gameLocal.time - launchTime ) / ( accelTime * 1000 ); speed = ( startingVelocity + ( startingVelocity + endingVelocity ) * pct ).Length(); } } idGuidedProjectile::Think(); GetSeekPos( seekPos ); if( ( seekPos - physicsObj.GetOrigin() ).Length() < 32.0f ) { if( returnPhase ) { StopSound( SND_CHANNEL_ANY, false ); StartSound( "snd_return", SND_CHANNEL_BODY2, 0, false, NULL ); Hide(); PostEventSec( &EV_Remove, 2.0f ); ownerEnt = owner.GetEntity(); if( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) { static_cast( ownerEnt )->SetSoulCubeProjectile( NULL ); } state = FIZZLED; } else if( !killPhase ) { KillTarget( physicsObj.GetAxis()[0] ); } } } } /* ================ idSoulCubeMissile::GetSeekPos ================ */ void idSoulCubeMissile::GetSeekPos( idVec3& out ) { if( returnPhase && owner.GetEntity() && owner.GetEntity()->IsType( idActor::Type ) ) { idActor* act = static_cast( owner.GetEntity() ); out = act->GetEyePosition(); return; } if( destOrg != vec3_zero ) { out = destOrg; return; } idGuidedProjectile::GetSeekPos( out ); } /* ================ idSoulCubeMissile::Event_ReturnToOwner ================ */ void idSoulCubeMissile::ReturnToOwner() { speed *= 0.65f; killPhase = false; returnPhase = true; smokeFlyTime = 0; } /* ================= idSoulCubeMissile::Launch ================= */ void idSoulCubeMissile::Launch( const idVec3& start, const idVec3& dir, const idVec3& pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) { idVec3 newStart; idVec3 offs; idEntity* ownerEnt; // push it out a little newStart = start + dir * spawnArgs.GetFloat( "launchDist" ); offs = spawnArgs.GetVector( "launchOffset", "0 0 -4" ); newStart += offs; idGuidedProjectile::Launch( newStart, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); if( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) ) { destOrg = start + dir * 256.0f; } else { destOrg.Zero(); } physicsObj.SetClipMask( 0 ); // never collide.. think routine will decide when to detonate startingVelocity = spawnArgs.GetVector( "startingVelocity", "15 0 0" ); endingVelocity = spawnArgs.GetVector( "endingVelocity", "1500 0 0" ); accelTime = spawnArgs.GetFloat( "accelTime", "5" ); physicsObj.SetLinearVelocity( startingVelocity.Length() * physicsObj.GetAxis()[2] ); launchTime = gameLocal.time; killPhase = false; UpdateVisuals(); ownerEnt = owner.GetEntity(); if( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) { static_cast( ownerEnt )->SetSoulCubeProjectile( this ); } } /* =============================================================================== idBFGProjectile =============================================================================== */ const idEventDef EV_RemoveBeams( "", NULL ); CLASS_DECLARATION( idProjectile, idBFGProjectile ) EVENT( EV_RemoveBeams, idBFGProjectile::Event_RemoveBeams ) END_CLASS /* ================= idBFGProjectile::idBFGProjectile ================= */ idBFGProjectile::idBFGProjectile() { memset( &secondModel, 0, sizeof( secondModel ) ); secondModelDefHandle = -1; nextDamageTime = 0; } /* ================= idBFGProjectile::~idBFGProjectile ================= */ idBFGProjectile::~idBFGProjectile() { FreeBeams(); if( secondModelDefHandle >= 0 ) { gameRenderWorld->FreeEntityDef( secondModelDefHandle ); secondModelDefHandle = -1; } } /* ================ idBFGProjectile::Spawn ================ */ void idBFGProjectile::Spawn() { beamTargets.Clear(); memset( &secondModel, 0, sizeof( secondModel ) ); secondModelDefHandle = -1; const char* temp = spawnArgs.GetString( "model_two" ); if( temp != NULL && *temp != '\0' ) { secondModel.hModel = renderModelManager->FindModel( temp ); secondModel.bounds = secondModel.hModel->Bounds( &secondModel ); secondModel.shaderParms[ SHADERPARM_RED ] = secondModel.shaderParms[ SHADERPARM_GREEN ] = secondModel.shaderParms[ SHADERPARM_BLUE ] = secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; secondModel.noSelfShadow = true; secondModel.noShadow = true; } nextDamageTime = 0; damageFreq = NULL; } /* ================ idBFGProjectile::Save ================ */ void idBFGProjectile::Save( idSaveGame* savefile ) const { int i; savefile->WriteInt( beamTargets.Num() ); for( i = 0; i < beamTargets.Num(); i++ ) { beamTargets[i].target.Save( savefile ); savefile->WriteRenderEntity( beamTargets[i].renderEntity ); savefile->WriteInt( beamTargets[i].modelDefHandle ); } savefile->WriteRenderEntity( secondModel ); savefile->WriteInt( secondModelDefHandle ); savefile->WriteInt( nextDamageTime ); savefile->WriteString( damageFreq ); } /* ================ idBFGProjectile::Restore ================ */ void idBFGProjectile::Restore( idRestoreGame* savefile ) { int i, num; savefile->ReadInt( num ); beamTargets.SetNum( num ); for( i = 0; i < num; i++ ) { beamTargets[i].target.Restore( savefile ); savefile->ReadRenderEntity( beamTargets[i].renderEntity ); savefile->ReadInt( beamTargets[i].modelDefHandle ); if( beamTargets[i].modelDefHandle >= 0 ) { beamTargets[i].modelDefHandle = gameRenderWorld->AddEntityDef( &beamTargets[i].renderEntity ); } } savefile->ReadRenderEntity( secondModel ); savefile->ReadInt( secondModelDefHandle ); savefile->ReadInt( nextDamageTime ); savefile->ReadString( damageFreq ); if( secondModelDefHandle >= 0 ) { secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel ); } } /* ================= idBFGProjectile::FreeBeams ================= */ void idBFGProjectile::FreeBeams() { for( int i = 0; i < beamTargets.Num(); i++ ) { if( beamTargets[i].modelDefHandle >= 0 ) { gameRenderWorld->FreeEntityDef( beamTargets[i].modelDefHandle ); beamTargets[i].modelDefHandle = -1; } } idPlayer* player = gameLocal.GetLocalPlayer(); if( player ) { player->playerView.EnableBFGVision( false ); } } /* ================ idBFGProjectile::Think ================ */ void idBFGProjectile::Think() { if( state == LAUNCHED ) { // update beam targets for( int i = 0; i < beamTargets.Num(); i++ ) { if( beamTargets[i].target.GetEntity() == NULL ) { continue; } idPlayer* player = ( beamTargets[i].target.GetEntity()->IsType( idPlayer::Type ) ) ? static_cast( beamTargets[i].target.GetEntity() ) : NULL; // Major hack for end boss. :( idAnimatedEntity* beamEnt; idVec3 org; bool forceDamage = false; beamEnt = static_cast( beamTargets[i].target.GetEntity() ); if( !idStr::Cmp( beamEnt->GetEntityDefName(), "monster_boss_d3xp_maledict" ) ) { SetTimeState ts( beamEnt->timeGroup ); idMat3 temp; jointHandle_t bodyJoint; bodyJoint = beamEnt->GetAnimator()->GetJointHandle( "Chest1" ); beamEnt->GetJointWorldTransform( bodyJoint, gameLocal.time, org, temp ); forceDamage = true; } else { org = beamEnt->GetPhysics()->GetAbsBounds().GetCenter(); } beamTargets[i].renderEntity.origin = GetPhysics()->GetOrigin(); beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_X ] = org.x; beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Y ] = org.y; beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BEAM_END_Z ] = org.z; beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] = beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] = beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] = beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; if( gameLocal.time > nextDamageTime ) { bool bfgVision = true; if( damageFreq && *( const char* )damageFreq && beamTargets[i].target.GetEntity() && ( forceDamage || beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), org ) ) ) { org = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); org.Normalize(); beamTargets[i].target.GetEntity()->Damage( this, owner.GetEntity(), org, damageFreq, ( damagePower ) ? damagePower : 1.0f, INVALID_JOINT ); } else { beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] = beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] = beamTargets[i].renderEntity.shaderParms[ SHADERPARM_BLUE ] = beamTargets[i].renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 0.0f; bfgVision = false; } if( player ) { player->playerView.EnableBFGVision( bfgVision ); } nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY; } gameRenderWorld->UpdateEntityDef( beamTargets[i].modelDefHandle, &beamTargets[i].renderEntity ); } if( secondModelDefHandle >= 0 ) { secondModel.origin = GetPhysics()->GetOrigin(); gameRenderWorld->UpdateEntityDef( secondModelDefHandle, &secondModel ); } idAngles ang; ang.pitch = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f; ang.yaw = ang.pitch; ang.roll = 0.0f; SetAngles( ang ); ang.pitch = ( gameLocal.time & 2047 ) * 360.0f / -2048.0f; ang.yaw = ang.pitch; ang.roll = 0.0f; secondModel.axis = ang.ToMat3(); UpdateVisuals(); } idProjectile::Think(); } /* ================= idBFGProjectile::Launch ================= */ void idBFGProjectile::Launch( const idVec3& start, const idVec3& dir, const idVec3& pushVelocity, const float timeSinceFire, const float power, const float dmgPower ) { idProjectile::Launch( start, dir, pushVelocity, 0.0f, power, dmgPower ); // dmgPower * radius is the target acquisition area // acquisition should make sure that monsters are not dormant // which will cut down on hitting monsters not actively fighting // but saves on the traces making sure they are visible // damage is not applied until the projectile explodes idEntity* ent; idEntity* entityList[ MAX_GENTITIES ]; int numListedEntities; idBounds bounds; idVec3 damagePoint; float radius; spawnArgs.GetFloat( "damageRadius", "512", radius ); bounds = idBounds( GetPhysics()->GetOrigin() ).Expand( radius ); float beamWidth = spawnArgs.GetFloat( "beam_WidthFly" ); const char* skin = spawnArgs.GetString( "skin_beam" ); memset( &secondModel, 0, sizeof( secondModel ) ); secondModelDefHandle = -1; const char* temp = spawnArgs.GetString( "model_two" ); if( temp != NULL && *temp != '\0' ) { secondModel.hModel = renderModelManager->FindModel( temp ); secondModel.bounds = secondModel.hModel->Bounds( &secondModel ); secondModel.shaderParms[ SHADERPARM_RED ] = secondModel.shaderParms[ SHADERPARM_GREEN ] = secondModel.shaderParms[ SHADERPARM_BLUE ] = secondModel.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; secondModel.noSelfShadow = true; secondModel.noShadow = true; secondModel.origin = GetPhysics()->GetOrigin(); secondModel.axis = GetPhysics()->GetAxis(); secondModelDefHandle = gameRenderWorld->AddEntityDef( &secondModel ); } idVec3 delta( 15.0f, 15.0f, 15.0f ); //physicsObj.SetAngularExtrapolation( extrapolation_t(EXTRAPOLATION_LINEAR|EXTRAPOLATION_NOSTOP), gameLocal.time, 0, physicsObj.GetAxis().ToAngles(), delta, ang_zero ); // get all entities touching the bounds numListedEntities = gameLocal.clip.EntitiesTouchingBounds( bounds, CONTENTS_BODY, entityList, MAX_GENTITIES ); for( int e = 0; e < numListedEntities; e++ ) { ent = entityList[ e ]; assert( ent ); if( ent == this || ent == owner.GetEntity() || ent->IsHidden() || !ent->IsActive() || !ent->fl.takedamage || ent->health <= 0 || !ent->IsType( idActor::Type ) ) { continue; } if( !ent->CanDamage( GetPhysics()->GetOrigin(), damagePoint ) ) { continue; } if( ent->IsType( idPlayer::Type ) ) { idPlayer* player = static_cast( ent ); player->playerView.EnableBFGVision( true ); } beamTarget_t bt; memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) ); bt.renderEntity.origin = GetPhysics()->GetOrigin(); bt.renderEntity.axis = GetPhysics()->GetAxis(); bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth; bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75; bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" ); bt.renderEntity.callback = NULL; bt.renderEntity.numJoints = 0; bt.renderEntity.joints = NULL; bt.renderEntity.bounds.Clear(); bt.renderEntity.customSkin = declManager->FindSkin( skin ); bt.target = ent; bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity ); beamTargets.Append( bt ); } // Major hack for end boss. :( idAnimatedEntity* maledict = static_cast( gameLocal.FindEntity( "monster_boss_d3xp_maledict_1" ) ); if( maledict ) { SetTimeState ts( maledict->timeGroup ); idVec3 realPoint; idMat3 temp; float dist; jointHandle_t bodyJoint; bodyJoint = maledict->GetAnimator()->GetJointHandle( "Chest1" ); maledict->GetJointWorldTransform( bodyJoint, gameLocal.time, realPoint, temp ); dist = idVec3( realPoint - GetPhysics()->GetOrigin() ).Length(); if( dist < radius ) { beamTarget_t bt; memset( &bt.renderEntity, 0, sizeof( renderEntity_t ) ); bt.renderEntity.origin = GetPhysics()->GetOrigin(); bt.renderEntity.axis = GetPhysics()->GetAxis(); bt.renderEntity.shaderParms[ SHADERPARM_BEAM_WIDTH ] = beamWidth; bt.renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; bt.renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; bt.renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; bt.renderEntity.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; bt.renderEntity.shaderParms[ SHADERPARM_DIVERSITY] = gameLocal.random.CRandomFloat() * 0.75; bt.renderEntity.hModel = renderModelManager->FindModel( "_beam" ); bt.renderEntity.callback = NULL; bt.renderEntity.numJoints = 0; bt.renderEntity.joints = NULL; bt.renderEntity.bounds.Clear(); bt.renderEntity.customSkin = declManager->FindSkin( skin ); bt.target = maledict; bt.modelDefHandle = gameRenderWorld->AddEntityDef( &bt.renderEntity ); beamTargets.Append( bt ); numListedEntities++; } } if( numListedEntities ) { StartSound( "snd_beam", SND_CHANNEL_BODY2, 0, false, NULL ); } damageFreq = spawnArgs.GetString( "def_damageFreq" ); nextDamageTime = gameLocal.time + BFG_DAMAGE_FREQUENCY; UpdateVisuals(); } /* ================ idProjectile::Event_RemoveBeams ================ */ void idBFGProjectile::Event_RemoveBeams() { FreeBeams(); UpdateVisuals(); } /* ================ idProjectile::Explode ================ */ void idBFGProjectile::Explode( const trace_t& collision, idEntity* ignore ) { int i; idVec3 dmgPoint; idVec3 dir; float beamWidth; float damageScale; const char* damage; idPlayer* player; idEntity* ownerEnt; ownerEnt = owner.GetEntity(); if( ownerEnt != NULL && ownerEnt->IsType( idPlayer::Type ) ) { player = static_cast< idPlayer* >( ownerEnt ); } else { player = NULL; } beamWidth = spawnArgs.GetFloat( "beam_WidthExplode" ); damage = spawnArgs.GetString( "def_damage" ); for( i = 0; i < beamTargets.Num(); i++ ) { if( ( beamTargets[i].target.GetEntity() == NULL ) || ( ownerEnt == NULL ) ) { continue; } if( !beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), dmgPoint ) ) { continue; } beamTargets[i].renderEntity.shaderParms[SHADERPARM_BEAM_WIDTH] = beamWidth; // if the hit entity takes damage if( damagePower ) { damageScale = damagePower; } else { damageScale = 1.0f; } // if the projectile owner is a player if( player ) { // if the projectile hit an actor if( beamTargets[i].target.GetEntity()->IsType( idActor::Type ) ) { player->SetLastHitTime( gameLocal.time ); player->AddProjectileHits( 1 ); damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE ); } } if( damage[0] && ( beamTargets[i].target.GetEntity()->entityNumber > gameLocal.numClients - 1 ) ) { dir = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); dir.Normalize(); beamTargets[i].target.GetEntity()->Damage( this, ownerEnt, dir, damage, damageScale, ( collision.c.id < 0 ) ? CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) : INVALID_JOINT ); } } if( secondModelDefHandle >= 0 ) { gameRenderWorld->FreeEntityDef( secondModelDefHandle ); secondModelDefHandle = -1; } if( ignore == NULL ) { projectileFlags.noSplashDamage = true; } if( !common->IsClient() || fl.skipReplication ) { if( ignore != NULL ) { PostEventMS( &EV_RemoveBeams, 750 ); } else { PostEventMS( &EV_RemoveBeams, 0 ); } } return idProjectile::Explode( collision, ignore ); } /* =============================================================================== idDebris =============================================================================== */ CLASS_DECLARATION( idEntity, idDebris ) EVENT( EV_Explode, idDebris::Event_Explode ) EVENT( EV_Fizzle, idDebris::Event_Fizzle ) END_CLASS /* ================ idDebris::Spawn ================ */ void idDebris::Spawn() { owner = NULL; smokeFly = NULL; smokeFlyTime = 0; } /* ================ idDebris::Create ================ */ void idDebris::Create( idEntity* owner, const idVec3& start, const idMat3& axis ) { Unbind(); GetPhysics()->SetOrigin( start ); GetPhysics()->SetAxis( axis ); GetPhysics()->SetContents( 0 ); this->owner = owner; smokeFly = NULL; smokeFlyTime = 0; sndBounce = NULL; noGrab = true; UpdateVisuals(); } /* ================= idDebris::idDebris ================= */ idDebris::idDebris() { owner = NULL; smokeFly = NULL; smokeFlyTime = 0; sndBounce = NULL; } /* ================= idDebris::~idDebris ================= */ idDebris::~idDebris() { } /* ================= idDebris::Save ================= */ void idDebris::Save( idSaveGame* savefile ) const { owner.Save( savefile ); savefile->WriteStaticObject( physicsObj ); savefile->WriteParticle( smokeFly ); savefile->WriteInt( smokeFlyTime ); savefile->WriteSoundShader( sndBounce ); } /* ================= idDebris::Restore ================= */ void idDebris::Restore( idRestoreGame* savefile ) { owner.Restore( savefile ); savefile->ReadStaticObject( physicsObj ); RestorePhysics( &physicsObj ); savefile->ReadParticle( smokeFly ); savefile->ReadInt( smokeFlyTime ); savefile->ReadSoundShader( sndBounce ); } /* ================= idDebris::Launch ================= */ void idDebris::Launch() { float fuse; idVec3 velocity; idAngles angular_velocity; float linear_friction; float angular_friction; float contact_friction; float bounce; float mass; float gravity; idVec3 gravVec; bool randomVelocity; idMat3 axis; renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); spawnArgs.GetVector( "velocity", "0 0 0", velocity ); spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity ); 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" ); randomVelocity = spawnArgs.GetBool( "random_velocity" ); if( mass <= 0 ) { gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() ); } if( randomVelocity ) { velocity.x *= gameLocal.random.RandomFloat() + 0.5f; velocity.y *= gameLocal.random.RandomFloat() + 0.5f; velocity.z *= gameLocal.random.RandomFloat() + 0.5f; } if( health ) { fl.takedamage = true; } gravVec = gameLocal.GetGravity(); gravVec.NormalizeFast(); axis = GetPhysics()->GetAxis(); Unbind(); physicsObj.SetSelf( this ); // check if a clip model is set const char* clipModelName; idTraceModel trm; spawnArgs.GetString( "clipmodel", "", &clipModelName ); if( !clipModelName[0] ) { clipModelName = spawnArgs.GetString( "model" ); // use the visual model } // load the trace model if( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) { // default to a box physicsObj.SetClipBox( renderEntity.bounds, 1.0f ); } else { physicsObj.SetClipModel( new( TAG_PHYSICS_CLIP_ENTITY ) idClipModel( trm ), 1.0f ); } physicsObj.GetClipModel()->SetOwner( owner.GetEntity() ); physicsObj.SetMass( mass ); physicsObj.SetFriction( linear_friction, angular_friction, contact_friction ); if( contact_friction == 0.0f ) { physicsObj.NoContact(); } physicsObj.SetBouncyness( bounce ); physicsObj.SetGravity( gravVec * gravity ); physicsObj.SetContents( 0 ); physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] ); physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); physicsObj.SetAxis( axis ); SetPhysics( &physicsObj ); if( !common->IsClient() ) { if( fuse <= 0 ) { // run physics for 1 second RunPhysics(); PostEventMS( &EV_Remove, 0 ); } else if( spawnArgs.GetBool( "detonate_on_fuse" ) ) { if( fuse < 0.0f ) { fuse = 0.0f; } RunPhysics(); PostEventSec( &EV_Explode, fuse ); } else { if( fuse < 0.0f ) { fuse = 0.0f; } PostEventSec( &EV_Fizzle, fuse ); } } StartSound( "snd_fly", SND_CHANNEL_BODY, 0, false, NULL ); smokeFly = NULL; smokeFlyTime = 0; const char* smokeName = spawnArgs.GetString( "smoke_fly" ); if( *smokeName != '\0' ) { smokeFly = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); smokeFlyTime = gameLocal.time; gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); } const char* sndName = spawnArgs.GetString( "snd_bounce" ); if( *sndName != '\0' ) { sndBounce = declManager->FindSound( sndName ); } UpdateVisuals(); } /* ================ idDebris::Think ================ */ void idDebris::Think() { // run physics RunPhysics(); Present(); if( smokeFly && smokeFlyTime ) { if( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ) ) { smokeFlyTime = 0; } } } /* ================ idDebris::Killed ================ */ void idDebris::Killed( idEntity* inflictor, idEntity* attacker, int damage, const idVec3& dir, int location ) { if( spawnArgs.GetBool( "detonate_on_death" ) ) { Explode(); } else { Fizzle(); } } /* ================= idDebris::Collide ================= */ bool idDebris::Collide( const trace_t& collision, const idVec3& velocity ) { if( sndBounce != NULL ) { StartSoundShader( sndBounce, SND_CHANNEL_BODY, 0, false, NULL ); } sndBounce = NULL; return false; } /* ================ idDebris::Fizzle ================ */ void idDebris::Fizzle() { if( IsHidden() ) { // already exploded return; } StopSound( SND_CHANNEL_ANY, false ); StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, false, NULL ); // fizzle FX const char* smokeName = spawnArgs.GetString( "smoke_fuse" ); if( *smokeName != '\0' ) { smokeFly = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); smokeFlyTime = gameLocal.time; gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); } fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.PutToRest(); Hide(); if( common->IsClient() && !fl.skipReplication ) { return; } CancelEvents( &EV_Fizzle ); PostEventMS( &EV_Remove, 0 ); } /* ================ idDebris::Explode ================ */ void idDebris::Explode() { if( IsHidden() ) { // already exploded return; } StopSound( SND_CHANNEL_ANY, false ); StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL ); Hide(); // these must not be "live forever" particle systems smokeFly = NULL; smokeFlyTime = 0; const char* smokeName = spawnArgs.GetString( "smoke_detonate" ); if( *smokeName != '\0' ) { smokeFly = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); smokeFlyTime = gameLocal.time; gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis(), timeGroup /*_D3XP*/ ); } fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.PutToRest(); CancelEvents( &EV_Explode ); PostEventMS( &EV_Remove, 0 ); } /* ================ idDebris::Event_Explode ================ */ void idDebris::Event_Explode() { Explode(); } /* ================ idDebris::Event_Fizzle ================ */ void idDebris::Event_Fizzle() { Fizzle(); } /* =============================================================================== idHomingProjectile =============================================================================== */ CLASS_DECLARATION( idProjectile, idHomingProjectile ) EVENT( EV_SetEnemy, idHomingProjectile::Event_SetEnemy ) END_CLASS /* ================ idHomingProjectile::idHomingProjectile ================ */ idHomingProjectile::idHomingProjectile() { enemy = NULL; speed = 0.0f; turn_max = 0.0f; clamp_dist = 0.0f; rndScale = ang_zero; rndAng = ang_zero; angles = ang_zero; burstMode = false; burstDist = 0; burstVelocity = 0.0f; unGuided = false; seekPos = vec3_origin; } /* ================= idHomingProjectile::~idHomingProjectile ================= */ idHomingProjectile::~idHomingProjectile() { } /* ================ idHomingProjectile::Spawn ================ */ void idHomingProjectile::Spawn() { } /* ================ idHomingProjectile::Save ================ */ void idHomingProjectile::Save( idSaveGame* savefile ) const { enemy.Save( savefile ); savefile->WriteFloat( speed ); savefile->WriteAngles( rndScale ); savefile->WriteAngles( rndAng ); savefile->WriteFloat( turn_max ); savefile->WriteFloat( clamp_dist ); savefile->WriteAngles( angles ); savefile->WriteBool( burstMode ); savefile->WriteBool( unGuided ); savefile->WriteFloat( burstDist ); savefile->WriteFloat( burstVelocity ); savefile->WriteVec3( seekPos ); } /* ================ idHomingProjectile::Restore ================ */ void idHomingProjectile::Restore( idRestoreGame* savefile ) { enemy.Restore( savefile ); savefile->ReadFloat( speed ); savefile->ReadAngles( rndScale ); savefile->ReadAngles( rndAng ); savefile->ReadFloat( turn_max ); savefile->ReadFloat( clamp_dist ); savefile->ReadAngles( angles ); savefile->ReadBool( burstMode ); savefile->ReadBool( unGuided ); savefile->ReadFloat( burstDist ); savefile->ReadFloat( burstVelocity ); savefile->ReadVec3( seekPos ); } /* ================ idHomingProjectile::Think ================ */ void idHomingProjectile::Think() { if( seekPos == vec3_zero ) { // ai def uses a single def_projectile .. guardian has two projectile types so when seekPos is zero, just run regular projectile idProjectile::Think(); return; } idVec3 dir; idVec3 velocity; idVec3 nose; idVec3 tmp; idMat3 axis; idAngles dirAng; idAngles diff; float dist; float frac; int i; nose = physicsObj.GetOrigin() + 10.0f * physicsObj.GetAxis()[0]; dir = seekPos - nose; dist = dir.Normalize(); dirAng = dir.ToAngles(); // make it more accurate as it gets closer frac = ( dist * 2.0f ) / clamp_dist; if( frac > 1.0f ) { frac = 1.0f; } diff = dirAng - angles * frac; // clamp the to the max turn rate diff.Normalize180(); for( i = 0; i < 3; i++ ) { if( diff[ i ] > turn_max ) { diff[ i ] = turn_max; } else if( diff[ i ] < -turn_max ) { diff[ i ] = -turn_max; } } angles += diff; // make the visual model always points the dir we're traveling dir = angles.ToForward(); velocity = dir * speed; if( burstMode && dist < burstDist ) { unGuided = true; velocity *= burstVelocity; } physicsObj.SetLinearVelocity( velocity ); // align z-axis of model with the direction axis = dir.ToMat3(); tmp = axis[2]; axis[2] = axis[0]; axis[0] = -tmp; GetPhysics()->SetAxis( axis ); idProjectile::Think(); } /* ================= idHomingProjectile::Launch ================= */ void idHomingProjectile::Launch( const idVec3& start, const idVec3& dir, const idVec3& pushVelocity, const float timeSinceFire, const float launchPower, float dmgPower ) { idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); if( owner.GetEntity() ) { if( owner.GetEntity()->IsType( idAI::Type ) ) { enemy = static_cast( owner.GetEntity() )->GetEnemy(); } else if( owner.GetEntity()->IsType( idPlayer::Type ) ) { trace_t tr; idPlayer* player = static_cast( owner.GetEntity() ); idVec3 start = player->GetEyePosition(); idVec3 end = start + player->viewAxis[0] * 1000.0f; gameLocal.clip.TracePoint( tr, start, end, MASK_SHOT_RENDERMODEL | CONTENTS_BODY, owner.GetEntity() ); if( tr.fraction < 1.0f ) { enemy = gameLocal.GetTraceEntity( tr ); } // ignore actors on the player's team if( enemy.GetEntity() == NULL || !enemy.GetEntity()->IsType( idActor::Type ) || ( static_cast( enemy.GetEntity() )->team == player->team ) ) { enemy = player->EnemyWithMostHealth(); } } } const idVec3& vel = physicsObj.GetLinearVelocity(); angles = vel.ToAngles(); speed = vel.Length(); rndScale = spawnArgs.GetAngles( "random", "15 15 0" ); turn_max = spawnArgs.GetFloat( "turn_max", "180" ) / com_engineHz_latched; clamp_dist = spawnArgs.GetFloat( "clamp_dist", "256" ); burstMode = spawnArgs.GetBool( "burstMode" ); unGuided = false; burstDist = spawnArgs.GetFloat( "burstDist", "64" ); burstVelocity = spawnArgs.GetFloat( "burstVelocity", "1.25" ); UpdateVisuals(); } void idHomingProjectile::SetEnemy( idEntity* ent ) { enemy = ent; } void idHomingProjectile::SetSeekPos( idVec3 pos ) { seekPos = pos; } void idHomingProjectile::Event_SetEnemy( idEntity* ent ) { SetEnemy( ent ); }