// Copyright (C) 2004 Id Software, Inc. // #include "precompiled.h" #pragma hdrstop #include "Game_local.h" /* =============================================================================== idProjectile =============================================================================== */ 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' ); 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 ) END_CLASS /* ================ idProjectile::idProjectile ================ */ idProjectile::idProjectile( void ) { 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; state = SPAWNED; damagePower = 1.0f; 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; netSyncPhysics = false; } /* ================ idProjectile::Spawn ================ */ void idProjectile::Spawn( void ) { physicsObj.SetSelf( this ); physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); physicsObj.SetContents( 0 ); physicsObj.SetClipMask( 0 ); physicsObj.PutToRest(); SetPhysics( &physicsObj ); } /* ================ 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 ); //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles //savefile->WriteInt( (int)lightDefHandle ); savefile->WriteVec3( lightOffset ); savefile->WriteInt( lightStartTime ); savefile->WriteInt( lightEndTime ); savefile->WriteVec3( lightColor ); savefile->WriteParticle( smokeFly ); savefile->WriteInt( smokeFlyTime ); savefile->WriteInt( (int)state ); savefile->WriteFloat( damagePower ); savefile->WriteStaticObject( physicsObj ); //HUMANHEAD rww //savefile->WriteStaticObject( thruster ); //HUMANHEAD PCF mdl 05/04/06 - Save whether the light handle is active savefile->WriteBool( lightDefHandle != -1 ); } /* ================ 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 ); //HUMANHEAD PCF mdl 05/04/06 - Don't save light handles //savefile->ReadInt( (int &)lightDefHandle ); savefile->ReadVec3( lightOffset ); savefile->ReadInt( lightStartTime ); savefile->ReadInt( lightEndTime ); savefile->ReadVec3( lightColor ); savefile->ReadParticle( smokeFly ); savefile->ReadInt( smokeFlyTime ); savefile->ReadInt( (int &)state ); savefile->ReadFloat( damagePower ); savefile->ReadStaticObject( physicsObj ); RestorePhysics( &physicsObj ); //HUMANHEAD rww //savefile->ReadStaticObject( thruster ); //thruster.SetPhysics( &physicsObj ); //HUMANHEAD PCF mdl 05/04/06 - Save whether the light handle is active bool bLight; savefile->ReadBool( bLight ); if ( bLight ) { lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); } if ( smokeFly != NULL ) { idVec3 dir; dir = physicsObj.GetLinearVelocity(); dir.NormalizeFast(); gameLocal.smokeParticles->EmitSmoke( smokeFly, gameLocal.time, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); } } /* ================ idProjectile::GetOwner ================ */ idEntity *idProjectile::GetOwner( void ) const { return owner.GetEntity(); } /* ================ idProjectile::Create HUMANHEAD: AOBMERGE - Overridden ================ */ void idProjectile::Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ) { /* HUMANHEAD pdm: Overridden 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" ); 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; UpdateVisuals(); state = CREATED; if ( spawnArgs.GetBool( "net_fullphysics" ) ) { netSyncPhysics = true; } HUMANHEAD END */ } /* ================= idProjectile::~idProjectile ================= */ idProjectile::~idProjectile() { StopSound( SND_CHANNEL_ANY, false ); FreeLightDef(); } /* ================= idProjectile::FreeLightDef ================= */ void idProjectile::FreeLightDef( void ) { if ( lightDefHandle != -1 ) { gameRenderWorld->FreeLightDef( lightDefHandle ); lightDefHandle = -1; } } /* ================= idProjectile::Launch HUMANHEAD: AOBMERGE - Overridden ================= */ void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { /* HUMANHEAD pdm: overridden 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; } // 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 ); thruster.SetPosition( &physicsObj, 0, idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) ); 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 ); } } 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; } // 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; HUMANHEAD END */ } /* ================ idProjectile::Think ================ */ void idProjectile::Think( void ) { /* HUMANHEAD pdm: overridden if ( thinkFlags & TH_THINK ) { if ( thrust && ( gameLocal.time < thrust_end ) ) { // evaluate force thruster.SetForce( GetPhysics()->GetAxis()[ 0 ] * thrust ); thruster.Evaluate( gameLocal.time ); } } // run physics RunPhysics(); Present(); // add the particles if ( smokeFly != NULL && smokeFlyTime && !IsHidden() ) { idVec3 dir = -GetPhysics()->GetLinearVelocity(); dir.Normalize(); if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.RandomFloat(), GetPhysics()->GetOrigin(), dir.ToMat3() ) ) { 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 + 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::Collide ================= */ bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) { /* HUMANHEAD pdm: overridden idEntity *ent; idEntity *ignore; const char *damageDefName; idVec3 dir; float push; float damageScale; if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk return true; } // predict the explosion if ( gameLocal.isClient ) { if ( ClientPredictionCollide( this, spawnArgs, collision, velocity, !spawnArgs.GetBool( "net_instanthit" ) ) ) { Explode( collision, NULL ); return true; } 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 ); 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 ) { ent->ApplyImpulse( this, collision.c.id, collision.c.point, push * dir ); } // MP: projectiles open doors if ( gameLocal.isMultiplayer && ent->IsType( idDoor::Type ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) { 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 ); // unlink the clip model because we no longer need it GetPhysics()->UnlinkClip(); damageDefName = spawnArgs.GetString( "def_damage" ); ignore = NULL; // 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' ) { ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); ignore = ent; } } // 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 ); } } Explode( collision, ignore ); return true; */ return false; // HUMANHEAD pdm: overridden } /* ================= idProjectile::DefaultDamageEffect ================= */ void idProjectile::DefaultDamageEffect( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity ) { /* HUMANHEAD pdm: not used 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 ) { /* HUMANHEAD pdm: Overridden DefaultDamageEffect( this, spawnArgs, collision, velocity ); if ( gameLocal.isServer && fl.networkSync ) { idBitMsg msg; byte msgBuf[MAX_EVENT_PARAM_SIZE]; int excludeClient; if ( spawnArgs.GetBool( "net_instanthit" ) ) { excludeClient = owner.GetEntityNum(); } else { excludeClient = -1; } msg.Init( 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, excludeClient ); } */ } /* ================ 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( void ) { if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk 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 && *psystem ) { //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 ( gameLocal.isClient ) { 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( void ) { idThread::ReturnInt( state ); } /* ================ idProjectile::Explode ================ */ void idProjectile::Explode( const trace_t &collision, idEntity *ignore ) { /* HUMANHEAD pdm: overridden const char *fxname, *light_shader, *sndExplode; float light_fadetime; idVec3 normal; int removeTime; if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk return; } // 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 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 && *fxname ) ) { 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 ( 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 ( *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" ); 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 ); light_fadetime = spawnArgs.GetFloat( "explode_light_fadetime", "0.5" ); lightStartTime = gameLocal.time; lightEndTime = gameLocal.time + SEC2MS( light_fadetime ); BecomeActive( TH_THINK ); } fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.PutToRest(); state = EXPLODED; if ( gameLocal.isClient ) { 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 || !ent->IsType( idDebris::Type ) ) { gameLocal.Error( "idProjectile: 'projectile_debris' is not an idDebris" ); } 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 || !ent->IsType( idDebris::Type ) ) { gameLocal.Error( "'projectile_shrapnel' is not an idDebris" ); } 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( void ) { /* HUMANHEAD pdm: overridden 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( void ) { Fizzle(); } /* ================ idProjectile::Event_Touch ================ */ void idProjectile::Event_Touch( idEntity *other, trace_t *trace ) { /* HUMANHEAD pdm: Overridden if ( IsHidden() ) { 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::ClientPredictionCollide ================= */ bool idProjectile::ClientPredictionCollide( idEntity *soundEnt, const idDict &projectileDef, const trace_t &collision, const idVec3 &velocity, bool addDamageEffect ) { /* HUMANHEAD pdm: not used 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::ClientPredictionThink ================ */ void idProjectile::ClientPredictionThink( void ) { if (!gameLocal.isNewFrame) { //HUMANHEAD rww return; } //HUMANHEAD rww - let the crawler grenade continue regardless because it uses a proxy model. if ( !IsType(hhProjectileCrawlerGrenade::Type) ) { if (!renderEntity.hModel) { return; } if (IsHidden()) { BecomeInactive(TH_PHYSICS); } } //HUMANHEAD END Think(); } /* ================ idProjectile::WriteToSnapshot ================ */ void idProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const { msg.WriteBits( owner.GetSpawnId(), 32 ); msg.WriteBits( state, 3 ); msg.WriteBits( fl.hidden, 1 ); //RWWMERGE: They added some logic here #if HUMANHEAD //HUMANHEAD rww if (netSyncPhysics) { WriteBindToSnapshot(msg); physicsObj.WriteToSnapshot( msg ); } //HUMANHEAD END #else if ( netSyncPhysics ) { msg.WriteBits( 1, 1 ); physicsObj.WriteToSnapshot( msg ); } else { msg.WriteBits( 0, 1 ); const idVec3 &origin = physicsObj.GetOrigin(); const idVec3 &velocity = physicsObj.GetLinearVelocity(); msg.WriteFloat( origin.x ); msg.WriteFloat( origin.y ); msg.WriteFloat( origin.z ); msg.WriteDeltaFloat( 0.0f, velocity[0], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); msg.WriteDeltaFloat( 0.0f, velocity[1], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); msg.WriteDeltaFloat( 0.0f, velocity[2], RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); } #endif } /* ================ idProjectile::ReadFromSnapshot ================ */ void idProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) { projectileState_t newState; owner.SetSpawnId( msg.ReadBits( 32 ) ); newState = (projectileState_t) msg.ReadBits( 3 ); if ( msg.ReadBits( 1 ) ) { Hide(); if (IsType(hhProjectile::Type) && teamChain) { //HUMANHEAD rww - if server hid the projectile clear it on the client. hhProjectile *hhSelf = static_cast(this); hhSelf->RemoveProjectile(3000); } } else { if (state == EXPLODED) { //HUMANHEAD rww Hide(); } else { Show(); } } //HUMANHEAD rww if (netSyncPhysics) { ReadBindFromSnapshot(msg); physicsObj.ReadFromSnapshot( msg ); } //HUMANHEAD END //store idMat3 ax = GetAxis(); idVec3 dir = ax[0]; idVec3 origin = physicsObj.GetOrigin(); idVec3 pushVel = physicsObj.GetPushedLinearVelocity(); //HUMANHEAD rww - i have seen this happen, not yet sure what causes it. assert(newState <= COLLIDED); //HUMANHEAD END if (state != EXPLODED && state != FIZZLED || state != COLLIDED) { //HUMANHEAD rww while( state != newState ) { switch( state ) { case SPAWNED: { //HUMANHEAD rww - supply origin/axis //Create( owner.GetEntity(), origin, ax ); state = CREATED; break; } case CREATED: { // the right origin and direction are required if you want bullet traces //HUMANHEAD rww if (IsType(hhProjectile::Type)) { hhProjectile *hhSelf = static_cast(this); origin = hhSelf->launchPos; ax = hhSelf->launchQuat.ToMat3(); Create( owner.GetEntity(), origin, ax ); hhSelf->Launch(origin, ax, pushVel); } //HUMAMHEAD END break; } case LAUNCHED: { if (netSyncPhysics) { //HUMANHEAD rww - only things with full physics sync will bother with this if ( newState == FIZZLED ) { Fizzle(); } else { trace_t collision; memset( &collision, 0, sizeof( collision ) ); collision.c.entityNum = ENTITYNUM_WORLD; collision.endAxis = GetPhysics()->GetAxis(); collision.endpos = GetPhysics()->GetOrigin(); collision.c.point = GetPhysics()->GetOrigin(); collision.c.normal.Set( 0, 0, 1 ); //HUMANHEAD rww if (IsType(hhProjectile::Type)) { hhProjectile *hhSelf = static_cast(this); hhSelf->ProcessCollision(&collision, GetPhysics()->GetLinearVelocity()); hhSelf->ProcessCollisionEvent(&collision, GetPhysics()->GetLinearVelocity()); if (newState == EXPLODED && state != EXPLODED) { //crawler grenades etc hhSelf->Explode(&collision, pushVel, 0); } else if (newState == COLLIDED) { //crawler secondary etc state = COLLIDED; } } else { Explode( collision, NULL ); } //HUMANHEAD END } } else { /* netSyncPhysics = true; int lies = gameLocal.time+200; GetPhysics()->Evaluate(lies-gameLocal.previousTime, lies); netSyncPhysics = false; */ ClientHideProjectile(); state = newState; } break; } case FIZZLED: case COLLIDED: //HUMANHEAD bjk case EXPLODED: { StopSound( SND_CHANNEL_BODY2, false ); gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity ); state = SPAWNED; break; } } } } //HUMANHEAD rww - moved to up above //physicsObj.ReadFromSnapshot( msg ); //HUMANHEAD END if ( msg.HasChanged() ) { //Show(); UpdateVisuals(); } } //HUMANHEAD rww void idProjectile::ClientHideProjectile(void) { } //HUMANHEAD END //RWWMERGE: Id's new version: handmerge this /* void idProjectile::ReadFromSnapshot( const idBitMsgDelta &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; } } } if ( msg.ReadBits( 1 ) ) { physicsObj.ReadFromSnapshot( msg ); } else { idVec3 origin; idVec3 velocity; idVec3 tmp; idMat3 axis; origin.x = msg.ReadFloat(); origin.y = msg.ReadFloat(); origin.z = msg.ReadFloat(); velocity.x = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); velocity.y = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); velocity.z = msg.ReadDeltaFloat( 0.0f, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS ); physicsObj.SetOrigin( origin ); physicsObj.SetLinearVelocity( velocity ); // align z-axis of model with the direction velocity.NormalizeFast(); axis = velocity.ToMat3(); tmp = axis[2]; axis[2] = axis[0]; axis[0] = -tmp; physicsObj.SetAxis( axis ); } if ( msg.HasChanged() ) { UpdateVisuals(); } } */ /* ================ idProjectile::ClientReceiveEvent ================ */ extern const int RB_VELOCITY_EXPONENT_BITS; //HUMANHEAD rww extern const int RB_VELOCITY_MANTISSA_BITS; //HUMANHEAD rww bool idProjectile::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) { //HUMANHEAD rww if (!IsType(hhProjectile::Type)) { //desperate attempts to catch terrible hateful crash bug gameLocal.Error("idProjectile::ClientReceiveEvent: this is not of type hhProjectile!"); } switch (event) { case EVENT_PROJECTILE_EXPLOSION: { trace_t collision; memset(&collision, 0, sizeof(collision)); collision.c.contents = msg.ReadBits(32); collision.c.dist = msg.ReadFloat(); collision.c.entityNum = msg.ReadBits(GENTITYNUM_BITS); collision.c.id = msg.ReadShort(); int materialIndex = msg.ReadShort(); const idMaterial *collisionMaterial = NULL; if (materialIndex != -1) { collisionMaterial = static_cast( declManager->DeclByIndex( DECL_MATERIAL, materialIndex ) ); } collision.c.material = collisionMaterial; collision.c.modelFeature = msg.ReadBits(32); collision.c.normal = msg.ReadDir(24); collision.c.point.x = msg.ReadFloat(); collision.c.point.y = msg.ReadFloat(); collision.c.point.z = msg.ReadFloat(); collision.c.trmFeature = msg.ReadBits(32); collision.c.type = (contactType_t)msg.ReadBits(4); collision.fraction = msg.ReadFloat(); //unfortunately, this is needed for proper decal projections idVec3 collisionVel; collisionVel.x = msg.ReadFloat(RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); collisionVel.y = msg.ReadFloat(RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); collisionVel.z = msg.ReadFloat(RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); collision.c.normal.Normalize(); //renormalize the normal collision.endpos = collision.c.point; idVec3 endDir = msg.ReadDir(24); endDir.Normalize(); collision.endAxis = endDir.ToMat3(); //make sure the ent we hit is still valid on the client before doing anything. if (gameLocal.entities[collision.c.entityNum] && gameLocal.entities[collision.c.entityNum]->IsType(idEntity::Type)) { projectileState_t oldState = state; state = LAUNCHED; assert(IsType(hhProjectile::Type)); hhProjectile *hhSelf = static_cast(this); hhSelf->ProcessCollisionEvent(&collision, collisionVel); state = oldState; } return true; } default: { return idEntity::ClientReceiveEvent( event, time, msg ); } } return false; //HUMANHEAD END /* HUMANHEAD pdm: not used 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 ); } } return false; */ } /* =============================================================================== idGuidedProjectile =============================================================================== */ CLASS_DECLARATION( idProjectile, idGuidedProjectile ) END_CLASS /* ================ idGuidedProjectile::idGuidedProjectile ================ */ idGuidedProjectile::idGuidedProjectile( void ) { 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( void ) { } /* ================ idGuidedProjectile::Spawn ================ */ void idGuidedProjectile::Spawn( void ) { } /* ================ 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( void ) { 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" ) / ( float )USERCMD_HZ; 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(); } /* =============================================================================== idSoulCubeMissile (HUMANHEAD pdm: removed) =============================================================================== */ /* =============================================================================== idBFGProjectile (HUMANHEAD pdm: removed) =============================================================================== */ /* =============================================================================== idDebris =============================================================================== */ const idEventDef EV_CheckClip( "" ); // HUMANHEAD mdl CLASS_DECLARATION( idEntity, idDebris ) EVENT( EV_Explode, idDebris::Event_Explode ) EVENT( EV_Fizzle, idDebris::Event_Fizzle ) EVENT( EV_CheckClip, idDebris::Event_CheckClip ) // HUMANHEAD mdl END_CLASS /* ================ idDebris::Spawn ================ */ void idDebris::Spawn( void ) { owner = NULL; smokeFly = NULL; smokeFlyTime = 0; //HUMANHEAD: aob float gravityMagnitude = physicsObj.GetGravity().Length(); collisionSpeed_max = hhUtils::DetermineFinalFallVelocityMagnitude( spawnArgs.GetFloat("collideDist_max", "25"), gravityMagnitude ); collisionSpeed_min = hhUtils::DetermineFinalFallVelocityMagnitude( spawnArgs.GetFloat("collideDist_min", "1"), gravityMagnitude ); //HUMANHEAD END //HUMANHEAD rww solidTest = 0; // mdl if (gameLocal.isClient) { Launch(); } //HUMANHEAD END } /* ================ 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; UpdateVisuals(); } /* ================= idDebris::idDebris ================= */ idDebris::idDebris( void ) { owner = NULL; smokeFly = NULL; smokeFlyTime = 0; sndBounce = NULL; } /* ================= idDebris::~idDebris ================= */ idDebris::~idDebris( void ) { } /* ================= idDebris::Save ================= */ void idDebris::Save( idSaveGame *savefile ) const { owner.Save( savefile ); savefile->WriteStaticObject( physicsObj ); savefile->WriteParticle( smokeFly ); savefile->WriteInt( smokeFlyTime ); savefile->WriteSoundShader( sndBounce ); // HUMANHEAD mdl savefile->WriteFloat( collisionSpeed_max ); savefile->WriteFloat( collisionSpeed_min ); savefile->WriteInt( currentChannel ); // HUMANHEAD END } /* ================= idDebris::Restore ================= */ void idDebris::Restore( idRestoreGame *savefile ) { owner.Restore( savefile ); savefile->ReadStaticObject( physicsObj ); RestorePhysics( &physicsObj ); savefile->ReadParticle( smokeFly ); savefile->ReadInt( smokeFlyTime ); savefile->ReadSoundShader( sndBounce ); // HUMANHEAD mdl savefile->ReadFloat( collisionSpeed_max ); savefile->ReadFloat( collisionSpeed_min ); savefile->ReadInt( currentChannel ); solidTest = 0; // Don't worry about saving this // HUMANHEAD END } //HUMANHEAD rww /* ================= idDebris::ClientPredictionThink ================= */ void idDebris::ClientPredictionThink( void ) { if (!gameLocal.isNewFrame) { return; } Think(); } /* ================ idDebris::WriteToSnapshot ================ */ void idDebris::WriteToSnapshot( idBitMsgDelta &msg ) const { GetPhysics()->WriteToSnapshot(msg); } /* ================ idDebris::ReadFromSnapshot ================ */ void idDebris::ReadFromSnapshot( const idBitMsgDelta &msg ) { GetPhysics()->ReadFromSnapshot(msg); } //HUMANHEAD END /* ================= idDebris::Launch ================= */ void idDebris::Launch( void ) { 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; } //HUMANHEAD rww fl.networkSync = true; //HUMANHEAD END // HUMANHEAD pdm: use local gravity on debris idBounds pointBounds(vec3_origin); gravVec = hhUtils::GetLocalGravity( GetOrigin(), pointBounds ); // HUMANHEAD END 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 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 ( !gameLocal.isClient || fl.clientEntity ) { //HUMANHEAD rww - if clientEntity, post events. 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 ); //ProcessEvent( &EV_CheckClip ); // HUMANHEAD mdl: Check to see if we're inside a solid 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() ); } const char *sndName = spawnArgs.GetString( "snd_bounce" ); if ( *sndName != '\0' ) { sndBounce = declManager->FindSound( sndName ); } UpdateVisuals(); } /* ================ idDebris::Think ================ */ void idDebris::Think( void ) { // run physics RunPhysics(); Present(); if ( smokeFly && smokeFlyTime ) { if ( !gameLocal.smokeParticles->EmitSmoke( smokeFly, smokeFlyTime, gameLocal.random.CRandomFloat(), GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ) ) { 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 ) { //HUMANHEAD: aob - replaced id's bounce sound code AttemptToPlayBounceSound( collision, velocity ); //HUMANHEAD END return false; } /* ================ idDebris::DetermineNextChannel HUMANHEAD: aob - copied from hhMovable ================ */ s_channelType idDebris::DetermineNextChannel() { static int NUM_CHANNELS = 6; currentChannel = (currentChannel + 1) % NUM_CHANNELS; return (s_channelType)((currentChannel == SCHANNEL_ANY) ? ++currentChannel : currentChannel); } /* ================ idDebris::AttemptToPlayBounceSound HUMANHEAD: aob - copied from hhMovable ================ */ void idDebris::AttemptToPlayBounceSound( const trace_t &collision, const idVec3 &velocity ) { s_channelType channel; float len = velocity * -collision.c.normal; if( len > collisionSpeed_min && collision.c.material && !(collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT) ) { channel = DetermineNextChannel(); if( StartSoundShader(sndBounce, channel, 0, true, NULL) ) { // Change volume only after we know the sound played float volume = hhUtils::CalculateSoundVolume( len, collisionSpeed_min, collisionSpeed_max ); HH_SetSoundVolume( volume, channel ); } } } /* ================ idDebris::Fizzle ================ */ void idDebris::Fizzle( void ) { 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() ); } fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.PutToRest(); Hide(); if ( gameLocal.isClient && !fl.clientEntity ) { //HUMANHEAD rww - continue with events for clientEntities. return; } CancelEvents( &EV_Fizzle ); PostEventMS( &EV_Remove, 0 ); } /* ================ idDebris::Explode ================ */ void idDebris::Explode( void ) { 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() ); } fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.PutToRest(); CancelEvents( &EV_Explode ); PostEventMS( &EV_Remove, 0 ); } /* ================ idDebris::Event_Explode ================ */ void idDebris::Event_Explode( void ) { Explode(); } /* ================ idDebris::Event_Fizzle ================ */ void idDebris::Event_Fizzle( void ) { Fizzle(); } // HUMANHEAD mdl: Derived from AF solid test code void idDebris::Event_CheckClip( void ) { trace_t trace; solidTest++; if (solidTest > 10) { gameLocal.DWarning( "%s STILL STUCK, removing.\n", name.c_str() ); PostEventMS(&EV_Remove, 100); return; } if ( gameLocal.clip.Translation( trace, physicsObj.GetOrigin(), physicsObj.GetOrigin(), physicsObj.GetClipModel(), physicsObj.GetAxis(), physicsObj.GetClipMask(), this ) ) { float depth = idMath::Fabs( trace.c.point * trace.c.normal - trace.c.dist ); physicsObj.SetOrigin( physicsObj.GetOrigin() + trace.c.normal * ( depth + 8.0f ) ); gameLocal.DWarning( "%s: debris stuck in %d (normal = %.2f %.2f %.2f, depth = %.2f)", this->name.c_str(), trace.c.contents, trace.c.normal.x, trace.c.normal.y, trace.c.normal.z, depth ); PostEventMS(&EV_CheckClip, 100); } else { gameLocal.DPrintf("Cleared solid after %d tries. \n", solidTest); } } // HUMANHEAD END