#include "precompiled.h" #pragma hdrstop #include "prey_local.h" const idEventDef EV_SpawnDriverLocal( "", "s" ); const idEventDef EV_SpawnFxFlyLocal( "", "s" ); const idEventDef EV_Collision_Flesh( "", "tv", 'd' ); const idEventDef EV_Collision_Metal( "", "tv", 'd' ); const idEventDef EV_Collision_AltMetal( "", "tv", 'd' ); const idEventDef EV_Collision_Wood( "", "tv", 'd' ); const idEventDef EV_Collision_Stone( "", "tv", 'd' ); const idEventDef EV_Collision_Glass( "", "tv", 'd' ); const idEventDef EV_Collision_Liquid( "", "tv", 'd' ); const idEventDef EV_Collision_Spirit( "", "tv", 'd' ); const idEventDef EV_Collision_Remove( "", "tv", 'd' ); const idEventDef EV_Collision_CardBoard( "", "tv", 'd' ); const idEventDef EV_Collision_Tile( "", "tv", 'd' ); const idEventDef EV_Collision_Forcefield( "", "tv", 'd' ); const idEventDef EV_Collision_Chaff( "", "tv", 'd' ); const idEventDef EV_Collision_Wallwalk( "", "tv", 'd' ); const idEventDef EV_Collision_Pipe( "", "tv", 'd' ); const idEventDef EV_AllowCollision_Flesh( "", "t", 'd' ); const idEventDef EV_AllowCollision_Metal( "", "t", 'd' ); const idEventDef EV_AllowCollision_AltMetal( "", "t", 'd' ); const idEventDef EV_AllowCollision_Wood( "", "t", 'd' ); const idEventDef EV_AllowCollision_Stone( "", "t", 'd' ); const idEventDef EV_AllowCollision_Glass( "", "t", 'd' ); const idEventDef EV_AllowCollision_Liquid( "", "t", 'd' ); const idEventDef EV_AllowCollision_Spirit( "", "t", 'd' ); const idEventDef EV_AllowCollision_CardBoard( "", "t", 'd' ); const idEventDef EV_AllowCollision_Tile( "", "t", 'd' ); const idEventDef EV_AllowCollision_Forcefield( "", "t", 'd' ); const idEventDef EV_AllowCollision_Chaff( "", "t", 'd' ); const idEventDef EV_AllowCollision_Wallwalk( "", "t", 'd' ); const idEventDef EV_AllowCollision_Pipe( "", "t", 'd' ); hhMatterEventDefPartner matterEventsCollision( "collision" ); hhMatterEventDefPartner matterEventsAllowCollision( "collision_allow" ); CLASS_DECLARATION( idProjectile, hhProjectile ) EVENT( EV_Explode, hhProjectile::Event_Fuse_Explode ) EVENT( EV_SpawnDriverLocal, hhProjectile::Event_SpawnDriverLocal ) EVENT( EV_SpawnFxFlyLocal, hhProjectile::Event_SpawnFxFlyLocal ) EVENT( EV_Collision_Flesh, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Metal, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_AltMetal, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Wood, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Stone, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Glass, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Liquid, hhProjectile::Event_Collision_DisturbLiquid ) EVENT( EV_Collision_CardBoard, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Tile, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Forcefield, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Pipe, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Wallwalk, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Chaff, hhProjectile::Event_Collision_Impact ) EVENT( EV_Collision_Remove, hhProjectile::Event_Collision_Remove ) EVENT( EV_AllowCollision_Flesh, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_Metal, hhProjectile::Event_AllowCollision_CollideNoProj ) EVENT( EV_AllowCollision_AltMetal, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_Wood, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_Stone, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_Glass, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_Liquid, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_CardBoard, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_Tile, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_Forcefield, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_Pipe, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_Wallwalk, hhProjectile::Event_AllowCollision_Collide ) EVENT( EV_AllowCollision_Spirit, hhProjectile::Event_AllowCollision_PassThru ) EVENT( EV_AllowCollision_Chaff, hhProjectile::Event_AllowCollision_Collide ) //bjk: shield blocks all END_CLASS /* ================ hhProjectile::Spawn ================ */ void hhProjectile::Spawn() { bDDACounted = false; parentProjectile = NULL; launchTimestamp = 0; collidedPortal = NULL; // cjr collideLocation = vec3_origin; // cjr collideVelocity = vec3_origin; // cjr weaponNum = -1; // cjr - default the weapon index to -1, to denote non-player weapons netSyncPhysics = spawnArgs.GetBool( "net_fullphysics" ); //HUMANHEAD rww bNoCollideWithCrawlers = spawnArgs.GetBool( "noCollideWithCrawlers", "0" ); bProjCollide = spawnArgs.GetBool( "proj_collision", "0" ); flyBySoundDistSq = spawnArgs.GetFloat( "flyby_dist", "0" ); flyBySoundDistSq *= flyBySoundDistSq; if ( flyBySoundDistSq > 0 ) { bPlayFlyBySound = true; } BecomeActive( TH_TICKER ); } /* ================ hhProjectile::~hhProjectile ================ */ hhProjectile::~hhProjectile() { SAFE_REMOVE(fxFly); //HUMANHEAD rww } void hhProjectile::SetParentProjectile( hhProjectile* in_parent ) { parentProjectile.Assign( in_parent ); } void hhProjectile::SetCollidedPortal( hhPortal *newPortal, idVec3 newLocation, idVec3 newVelocity ) { collidedPortal.Assign( newPortal ); collideLocation = newLocation; collideVelocity = newVelocity; BecomeActive(TH_MISC1); physicsObj.PutToRest(); } hhProjectile* hhProjectile::GetParentProjectile( void ) { if( parentProjectile.IsValid() ) { return parentProjectile.GetEntity(); } return NULL; } /* ================ hhProjectile::SetOrigin ================ */ void hhProjectile::SetOrigin( const idVec3& origin ) { idVec3 masterOrigin; idMat3 masterAxis; idVec3 localOrigin( origin ); if( driver.IsValid() && IsBoundTo(driver.GetEntity()) ) { GetMasterPosition( masterOrigin, masterAxis ); localOrigin = (localOrigin - masterOrigin) * masterAxis.Transpose(); } idProjectile::SetOrigin( localOrigin ); } /* ================ hhProjectile::SetAxis ================ */ void hhProjectile::SetAxis( const idMat3& axis ) { idVec3 masterOrigin; idMat3 masterAxis; idMat3 localAxis( axis ); if( driver.IsValid() && IsBoundTo(driver.GetEntity()) ) { GetMasterPosition( masterOrigin, masterAxis ); localAxis *= masterAxis.Transpose(); } idProjectile::SetAxis( localAxis ); } /* ================ hhProjectile::Think ================ */ void hhProjectile::Think( void ) { // HUMANHEAD: cjr - if this projectile recently struck a portal, then attempt to portal it if ( (thinkFlags & TH_MISC1) && collidedPortal.IsValid() ) { GetPhysics()->SetLinearVelocity( collideVelocity ); collidedPortal->PortalProjectile( this, collideLocation, collideLocation + collideVelocity ); collidedPortal = NULL; collideLocation = vec3_origin; collideVelocity = vec3_origin; BecomeInactive(TH_MISC1); } // HUMANHEAD END // run physics RunPhysics(); if( thinkFlags & TH_THINK ) { //HUMANHEAD: aob - added thrust_start check if( thrust && (thrust_start <= gameLocal.GetTime() && gameLocal.GetTime() < thrust_end) ) { // evaluate force //HUMANHEAD rww - get rid of the thruster, needless projectile constructor overhead. //thruster.SetForce( GetAxis()[ 0 ] * thrust ); //thruster.Evaluate( gameLocal.GetTime() ); //replaced the logic for the thing here. idVec3 force = GetAxis()[ 0 ] * thrust; idVec3 point = physicsObj.GetCenterOfMass(); idVec3 p = GetPhysics()->GetOrigin() + point * physics->GetAxis(); GetPhysics()->AddForce( 0, p, force ); } } //HUMANHEAD: aob if( thinkFlags & TH_TICKER ) { Ticker(); } //HUMANHEAD END Present(); if ( thinkFlags & TH_MISC2 ) { UpdateLight(); } //HUMANHEAD jsh flyby sounds if( !gameLocal.isMultiplayer && bPlayFlyBySound && gameLocal.GetLocalPlayer() ) { if ( !owner.IsEqualTo( gameLocal.GetLocalPlayer() ) ) { if ( (GetOrigin() - gameLocal.GetLocalPlayer()->GetOrigin()).LengthSqr() < flyBySoundDistSq ) { BroadcastFxPrefixedRandom( "fx_flyby", EV_SpawnFxFlyLocal ); StartSound( "snd_flyby", SND_CHANNEL_BODY ); bPlayFlyBySound = false; } } } } /* ===================== hhProjectile::PlayImpactSound custom for projectiles. this needs to broadcast, unless we get predicted projectiles operational. however predicted projectiles are not reliable since the client will not always collide them before the server does, and so they will get removed with no fx. not a serious issue but somewhat bothersome nonetheless. ===================== */ int hhProjectile::PlayImpactSound( const idDict* dict, const idVec3 &origin, surfTypes_t type ) { const char *snd = gameLocal.MatterTypeToMatterKey( "snd_impact", type ); if( !snd || !snd[0] || !dict ) { return -1; } int length = 0; StartSoundShader( declManager->FindSound(dict->GetString(snd), false), SND_CHANNEL_BODY, 0, true, &length ); return length; } /* ============== hhProjectile::UpdateLight ============== */ void hhProjectile::UpdateLight() { // Attempt to remove light if( lightStartTime != lightEndTime && gameLocal.GetTime() >= lightEndTime ) { FreeLightDef(); BecomeInactive(TH_MISC2); } if( lightDefHandle != -1 ) { UpdateLightPosition(); UpdateLightFade(); gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); } } /* ============== hhProjectile::UpdateLightPosition ============== */ void hhProjectile::UpdateLightPosition() { renderLight.origin = GetOrigin() + GetAxis() * lightOffset; renderLight.axis = GetAxis(); } /* ============== hhProjectile::UpdateLightFade ============== */ void hhProjectile::UpdateLightFade() { idVec3 color( vec3_zero ); float frac = 0.0f; int time = gameLocal.GetTime(); if( lightStartTime != lightEndTime && time < lightEndTime ) { frac = MS2SEC(gameLocal.GetTime() - lightStartTime) / MS2SEC(lightEndTime - lightStartTime); color.Lerp( lightColor, color, frac ); for( int ix = 0; ix < 3; ++ix ) { renderLight.shaderParms[SHADERPARM_RED + ix] = color[ix]; } } } /* ============== hhProjectile::CreateLight ============== */ int hhProjectile::CreateLight( const char* shaderName, const idVec3& size, const idVec3& color, const idVec3& offset, float fadeTime ) { int fadeDuration = 0; if ( size.x <= 0.0f || !g_projectileLights.GetBool() ) { return 0; } if( size.Compare(vec3_zero, VECTOR_EPSILON) ) { return 0; } SIMDProcessor->Memset( &renderLight, 0, sizeof(renderLight) ); FreeLightDef(); if( !shaderName || !shaderName[0] ) { return 0; } UpdateLightPosition(); renderLight.shader = declManager->FindMaterial( shaderName, false ); renderLight.pointLight = true; renderLight.lightRadius = size; renderLight.shaderParms[SHADERPARM_RED] = color[0]; renderLight.shaderParms[SHADERPARM_GREEN] = color[1]; renderLight.shaderParms[SHADERPARM_BLUE] = color[2]; renderLight.shaderParms[SHADERPARM_ALPHA] = 1.0f; renderLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.GetTime() ); renderLight.noShadows = true; //HUMANHEAD bjk: cheaper fadeDuration = SEC2MS( fadeTime ); lightOffset = offset; lightColor = color; lightStartTime = gameLocal.GetTime(); lightEndTime = lightStartTime + fadeDuration; BecomeActive(TH_MISC2); if( lightDefHandle != -1 ) { gameRenderWorld->UpdateLightDef( lightDefHandle, &renderLight ); } else { lightDefHandle = gameRenderWorld->AddLightDef( &renderLight ); } return fadeDuration; } /* ============== hhProjectile::BounceSplat ============== */ void hhProjectile::BounceSplat( const idVec3& origin, const idVec3& dir ) { float size = hhMath::Lerp( spawnArgs.GetVec2("decal_bounce_size", "1.8 2.2"), gameLocal.random.RandomFloat() ); const char* decal = spawnArgs.RandomPrefix( "mtr_bounce_shader", gameLocal.random ); if( decal && *decal ) { gameLocal.ProjectDecal( origin, dir, 16.0f, true, size, decal ); } } /* ================ hhProjectile::Create HUMANHEAD: AOBMERGE - Overridden ================ */ void hhProjectile::Create( idEntity *owner, const idVec3 &start, const idVec3 &dir ) { Create( owner, start, dir.ToMat3() ); } /* ================ hhProjectile::Launch HUMANHEAD: AOBMERGE - Overridden ================ */ void hhProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { Launch( start, dir.ToMat3(), pushVelocity, timeSinceFire, launchPower, dmgPower ); launchTimestamp = gameLocal.time; //HUMANHEAD: mdc - record launch time } /* ================ hhProjectile::Create HUMANHEAD: AOBMERGE - Overridden ================ */ void hhProjectile::Create( idEntity *owner, const idVec3 &start, const idMat3 &axis ) { Unbind(); SetOrigin( start ); SetAxis( axis ); this->owner = owner; CreateLight( spawnArgs.GetString("mtr_light_shader"), spawnArgs.GetVector("light_size"), spawnArgs.GetVector("light_color", "1 1 1") * spawnArgs.GetFloat("light_intensity", "1.0"), spawnArgs.GetVector("light_offset"), spawnArgs.GetFloat("light_fadetime") ); GetPhysics()->SetContents( 0 ); state = CREATED; } /* ================= hhProjectile::Launch HUMANHEAD: aob - made second parameter idMat3& ================= */ void hhProjectile::Launch( const idVec3 &start, const idMat3 &axis, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { float fuse; idVec3 velocity; idAngles angular_velocity; float linear_friction; float angular_friction; float contact_friction; float bounce; float mass; float gravity; idVec3 gravVec; 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" ); projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" ); projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" ); projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" ); projectileFlags.isLarge = spawnArgs.GetBool( "largeProjectile" ); // HUMANHEAD bjk if ( mass <= 0 ) { gameLocal.Error( "Invalid mass on '%s'\n", GetClassname() ); } thrust = mass * spawnArgs.GetFloat( "thrust" ); thrust_start = SEC2MS( spawnArgs.GetFloat("thrust_start") ) + gameLocal.GetTime(); thrust_end = SEC2MS( spawnArgs.GetFloat("thrust_duration") ) + thrust_start; //HUMANHEAD: aob - if thrust is set then set TH_THINK if( hhMath::Fabs(thrust) >= VECTOR_EPSILON ) { BecomeActive( TH_THINK ); } //HUMANHEAD END if ( health ) { fl.takedamage = true; } gravVec = gameLocal.GetGravity(); gravVec.NormalizeFast(); Unbind(); int contents = DetermineContents(); int clipMask = DetermineClipmask(); //HUMANHEAD rww launchQuat = axis.ToCQuat(); //save off launch orientation launchPos = start; //save off launch pos //HUMANHEAD END physicsObj.SetSelf( this ); physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); //HUMANHEAD: aob - added DetermineClipModelOwner so some projectiles can decide to collide with there owners physicsObj.GetClipModel()->SetOwner( DetermineClipModelOwner() ); //HUMANHEAD END 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[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] + pushVelocity ); /* if (gameLocal.isClient) { gameLocal.Printf("C: (%f %f %f) (%f %f %f) (%f %f %f, %f %f %f, %f %f %f)\n", velocity[0], velocity[1], velocity[2], pushVelocity[0], pushVelocity[1], pushVelocity[2], axis[0][0], axis[0][1], axis[0][2], axis[1][0], axis[1][1], axis[1][2], axis[2][0], axis[2][1], axis[2][2]); } else { gameLocal.Printf("S: (%f %f %f) (%f %f %f) (%f %f %f, %f %f %f, %f %f %f)\n", velocity[0], velocity[1], velocity[2], pushVelocity[0], pushVelocity[1], pushVelocity[2], axis[0][0], axis[0][1], axis[0][2], axis[1][0], axis[1][1], axis[1][2], axis[2][0], axis[2][1], axis[2][2]); } */ physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); physicsObj.SetOrigin( start ); physicsObj.SetAxis( axis ); SetPhysics( &physicsObj ); //HUMANHEAD rww - get rid of the thruster, needless projectile constructor overhead //thruster.SetPosition( &physicsObj, 0, physicsObj.GetCenterOfMass() );//idVec3( GetPhysics()->GetBounds()[ 0 ].x, 0, 0 ) ); //HUMANHEAD rww - debug projectile position and axis (for client side projectiles) /* extern void Debug_ClearDebugLines(void); extern void Debug_AddDebugLine(idVec3 &start, idVec3 &end, int color); idVec3 p = start; idVec3 prj; Debug_ClearDebugLines(); prj = p + (axis[0]*32.0f); Debug_AddDebugLine(p, prj, 1); prj = p + (axis[1]*32.0f); Debug_AddDebugLine(p, prj, 2); prj = p + (axis[2]*32.0f); Debug_AddDebugLine(p, prj, 3); */ if ( !gameLocal.isClient || fl.clientEntity ) //HUMANHEAD rww - if clientEntity { 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 ); } } StartSound( "snd_fly", SND_CHANNEL_BODY, 0, true ); //HUMANHEAD: aob - replaces id's smoke_fly code // CJR: Changed so that we can randomly choose between multiple fx_fly systems on a single projectile BroadcastFxPrefixedRandom( "fx_fly", EV_SpawnFxFlyLocal ); //HUMANHEAD END // 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; } // HUMANHEAD CJR: if launched by a player, set the projectile's weapon index appropriately if ( owner.IsValid() && owner->IsType( hhPlayer::Type ) ) { hhPlayer *player = static_cast( owner.GetEntity() ); weaponNum = player->GetCurrentWeapon(); } else { // Otherwise, default the weaponNum to denote a non-player weapon weaponNum = -1; }// HUMANHEAD END state = LAUNCHED; if (gameLocal.isClient) { //HUMANHEAD rww launchTimestamp = gameLocal.time; //HUMANHEAD: mdc - record launch time return; } // Notify the AI about this launch gameLocal.SendMessageAI( this, GetOrigin(), spawnArgs.GetFloat("ai_notify_launch", "0"), MA_OnProjectileLaunch ); BroadcastEntityDef( spawnArgs.GetString("def_driver"), EV_SpawnDriverLocal ); launchTimestamp = gameLocal.time; //HUMANHEAD: mdc - record launch time } /* ================ hhProjectile::DetermineClipModelOwner HUMANHEAD: aob ================ */ idEntity* hhProjectile::DetermineClipModelOwner() { return (spawnArgs.GetBool("collideWithOwner")) ? this : owner.GetEntity(); } /* ================ hhProjectile::Collide ================ */ extern const int RB_VELOCITY_EXPONENT_BITS; //HUMANHEAD rww extern const int RB_VELOCITY_MANTISSA_BITS; //HUMANHEAD rww bool hhProjectile::Collide( const trace_t& collision, const idVec3& velocity ) { if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk return false; } // HUMANHEAD CJR: Don't allow the collision if the projectile has recently hit a portal if ( collidedPortal.IsValid() ) { return false; } // HUMANHEAD END if ( gameLocal.isClient ) { //HUMANHEAD rww - our projectile stuff is pretty different from id's at this point, so i'm just making some new prediction code. if (state == EXPLODED || state == FIZZLED || state == COLLIDED) { //HUMANHEAD bjk ProcessCollision(&collision, velocity); return false; } //HUMANHEAD END } //HUMANHEAD rww - commented out to allow projectiles to collide with owner after portalling /* if (gameLocal.isClient) { //HUMANHEAD rww idEntity* entHit = gameLocal.GetTraceEntity(collision); if ( entHit == owner.GetEntity() ) { return true; } } else { // get the entity the projectile collided with idEntity* entHit = gameLocal.GetTraceEntity(collision); if ( entHit == owner.GetEntity() ) { assert( 0 ); return false; } } */ // remove projectile when a 'noimpact' surface is hit if ( collision.c.material && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { common->DPrintf("removing projectile that hit noimpact surface: mat=[%s]\n", collision.c.material->GetName() ); RemoveProjectile( 0 ); return false; } //HUMANHEAD rww - send events for collisions on the server for pseudo-sync'd projectiles if (gameLocal.isServer && fl.networkSync && !netSyncPhysics) { idBitMsg msg; byte msgBuf[MAX_EVENT_PARAM_SIZE]; msg.Init( msgBuf, sizeof( msgBuf ) ); msg.BeginWriting(); msg.WriteBits(collision.c.contents, 32); msg.WriteFloat(collision.c.dist); msg.WriteBits(collision.c.entityNum, GENTITYNUM_BITS); msg.WriteShort(collision.c.id); if (!collision.c.material) { msg.WriteShort(-1); } else { msg.WriteShort(collision.c.material->Index()); } msg.WriteBits(collision.c.modelFeature, 32); //ensure it is normalized properly first idVec3 normal = collision.c.normal; normal.Normalize(); msg.WriteDir(normal, 24); msg.WriteFloat(collision.c.point.x); msg.WriteFloat(collision.c.point.y); msg.WriteFloat(collision.c.point.z); msg.WriteBits(collision.c.trmFeature, 32); msg.WriteBits(collision.c.type, 4); msg.WriteFloat(collision.fraction); //unfortunately, this is needed for proper decal projections msg.WriteFloat(velocity.x, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); msg.WriteFloat(velocity.y, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); msg.WriteFloat(velocity.z, RB_VELOCITY_EXPONENT_BITS, RB_VELOCITY_MANTISSA_BITS); msg.WriteDir(collision.endAxis[0], 24); ServerSendPVSEvent(EVENT_PROJECTILE_EXPLOSION, &msg, collision.endpos); } //HUMANHEAD END //HUMANHEAD rww - our projectile stuff is pretty different from id's at this point, so i'm just making some new prediction code. if (gameLocal.isClient) { //ProcessCollision(&collision, velocity); if (fl.networkSync && !netSyncPhysics) { //if this projectile is psuedo-sync'd, we will be expecting impact results from the server. ClientHideProjectile(); return 0; } return ProcessCollisionEvent( &collision, velocity ); } //HUMANHEAD END if ( !gameLocal.isMultiplayer && spawnArgs.GetString( "hit_notify" ) && owner.IsValid() && owner->IsType( hhMonsterAI::Type ) ) { gameLocal.SendMessageAI( this, GetOrigin(), spawnArgs.GetFloat("ai_notify_yes", "0"), MA_OnProjectileHit ); } return ProcessCollisionEvent( &collision, velocity ); } /* ================ hhProjectile::ProcessCollisionEvent ================ */ bool hhProjectile::ProcessCollisionEvent( const trace_t* collision, const idVec3& velocity ) { assert( collision ); idEntity* ent = gameLocal.entities[ collision->c.entityNum ]; const idEventDef* eventDef = matterEventsCollision.GetPartner( ent, collision->c.material ); assert( eventDef ); ProcessEvent( eventDef, collision, velocity ); return gameLocal.program.GetReturnedBool(); } /* ================ hhProjectile::ProcessCollision HUMANHEAD: aob ================ */ int hhProjectile::ProcessCollision( const trace_t* collision, const idVec3& velocity ) { PROFILE_SCOPE("ProjectileCollision", PROFMASK_PHYSICS|PROFMASK_COMBAT); idEntity* entHit = gameLocal.entities[ collision->c.entityNum ]; SetOrigin( collision->endpos ); SetAxis( collision->endAxis ); if( fxFly.IsValid() ) { fxFly->Stop(); } SAFE_REMOVE( fxFly ); FreeLightDef(); CancelEvents( &EV_Fizzle ); if (entHit) { //rww - may be null on client. if (!gameLocal.isClient || (fl.networkSync && !netSyncPhysics)) { //don't do this on the client, unless this is a sync'd projectile DamageEntityHit( collision, velocity, entHit ); } } fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.PutToRest(); surfTypes_t matterType = gameLocal.GetMatterType( entHit, collision->c.material, "hhProjectile::ProcessCollision" ); return PlayImpactSound( gameLocal.FindEntityDefDict(spawnArgs.GetString("def_damage")), collision->endpos, matterType ); } /* ================ hhProjectile::DamageEntityHit HUMANHEAD: aob ================ */ void hhProjectile::DamageEntityHit( const trace_t* collision, const idVec3& velocity, idEntity* entHit ) { PROFILE_SCOPE("DamageEntityHit", PROFMASK_COMBAT); if (GERMAN_VERSION || g_nogore.GetBool()) { if (entHit->IsType(idActor::Type)) { idActor *actor = reinterpret_cast (entHit); if ( !actor->fl.takedamage || ( actor->IsActiveAF() && !actor->spawnArgs.GetBool( "not_gory", "0" ) ) ) { // Don't process hits on ragdolls return; } } } float push = 0.0f; float damageScale = 1.0f; const char *damage = NULL; if (gameLocal.isMultiplayer) { //rww - check for special mp damage def damage = spawnArgs.GetString( "def_damage_mp" ); } if (!damage || !damage[0]) { damage = spawnArgs.GetString( "def_damage" ); } hhPlayer* playerHit = (entHit->IsType(hhPlayer::Type)) ? static_cast(entHit) : NULL; idAFEntity_Base* afHit = (entHit->IsType(idAFEntity_Base::Type)) ? static_cast(entHit) : NULL; idVec3 dir = velocity.ToNormal(); // non-radius damage defs can also apply an additional impulse to the rigid body physics impulse const idDeclEntityDef *def = gameLocal.FindEntityDef( damage, false ); if ( def ) { if (entHit->IsType(hhProjectile::Type)) { push = 0.0f; // mdl: Don't let projectiles push each other } else if (afHit && afHit->IsActiveAF() ) { push = def->dict.GetFloat( "push_ragdoll" ); } else { push = def->dict.GetFloat( "push" ); } } if (!gameLocal.isClient) { //rww if( playerHit ) { // pdm: save collision location in case we want to project a blob there playerHit->playerView.SetDamageLoc( collision->endpos ); } if( DamageIsValid(collision, damageScale) && entHit->fl.takedamage ) { UpdateBalanceInfo( collision, entHit ); if( damage && damage[0] ) { idEntity *killer = owner.GetEntity(); if (killer && killer->IsType(hhVehicle::Type)) { //rww - handle vehicle projectiles killing people hhVehicle *veh = static_cast(killer); if (veh->GetPilot()) { killer = veh->GetPilot(); } } entHit->Damage( this, killer, dir, damage, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE(collision->c.id) ); if ( playerHit && def->dict.GetInt( "freeze_duration" ) > 0 ) { playerHit->Freeze( def->dict.GetInt( "freeze_duration" ) ); } } } // HUMANHEAD bjk: moved to after damage so impulse can be applied to ragdoll if ( push > 0.0f ) { if (g_debugImpulse.GetBool()) { gameRenderWorld->DebugArrow(colorYellow, collision->c.point, collision->c.point + (push*dir), 25, 2000); } entHit->ApplyImpulse( this, collision->c.id, collision->c.point, push * dir ); } } if ( entHit->fl.applyDamageEffects ) { ApplyDamageEffect( entHit, collision, velocity, damage ); } } /* ================ hhProjectile::DamageIsValid HUMANHEAD: aob ================ */ bool hhProjectile::DamageIsValid( const trace_t* collision, float& damageScale ) { damageScale = DetermineDamageScale( collision ); return true; } /* ================ hhProjectile::ApplyDamageEffect HUMANHEAD: aob ================ */ void hhProjectile::ApplyDamageEffect( idEntity* hitEnt, const trace_t* collision, const idVec3& velocity, const char* damageDefName ) { if( hitEnt ) { hitEnt->AddDamageEffect( *collision, velocity, damageDefName, (!fl.networkSync || netSyncPhysics) ); } } int hhProjectile::DetermineContents() { return (spawnArgs.GetBool("proj_collision")) ? CONTENTS_PROJECTILE | CONTENTS_SHOOTABLE : CONTENTS_PROJECTILE; } int hhProjectile::DetermineClipmask() { return MASK_SHOT_RENDERMODEL; } /* ================ hhProjectile::UpdateBalanceInfo HUMANHEAD: aob ================ */ void hhProjectile::UpdateBalanceInfo( const trace_t* collision, const idEntity* hitEnt ) { hhPlayer* player = (owner.IsValid() && owner->IsType(hhPlayer::Type)) ? static_cast(owner.GetEntity()) : NULL; if( hitEnt->IsType(idActor::Type) && player ) { player->SetLastHitTime( gameLocal.GetTime() ); player->AddProjectileHits( 1 ); } } /* ================ hhProjectile::SpawnProjectile HUMANHEAD: aob ================ */ hhProjectile* hhProjectile::SpawnProjectile( const idDict* args ) {//FIXME: Broadcast assert( args ); assert(!gameLocal.isClient); idEntity* ent = NULL; gameLocal.SpawnEntityDef( *args, &ent ); HH_ASSERT( ent && ent->IsType(hhProjectile::Type) ); return static_cast(ent); } /* ================ hhProjectile::SpawnClientProjectile HUMANHEAD: rww ================ */ hhProjectile* hhProjectile::SpawnClientProjectile( const idDict* args ) {//FIXME: Broadcast assert( args ); idEntity* ent = NULL; gameLocal.SpawnEntityDef( *args, &ent, true, gameLocal.isClient ); HH_ASSERT( ent && ent->IsType(hhProjectile::Type) ); ent->fl.networkSync = false; ent->fl.clientEvents = true; return static_cast(ent); } /* ================ hhProjectile::Killed ================ */ void hhProjectile::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { //HUMANHEAD: aob - added collision so we can get explosion to show up when killed trace_t collision; if ( spawnArgs.GetBool( "detonate_on_death" ) ) { memset( &collision, 0, sizeof( trace_t ) ); collision.fraction = 0.0f; collision.endpos = GetOrigin(); collision.endAxis = GetAxis(); collision.c.entityNum = attacker->entityNumber; collision.c.normal = inflictor->GetOrigin() - GetOrigin(); collision.c.normal.Normalize(); Explode( &collision, GetPhysics()->GetLinearVelocity(), 0 ); } else { Fizzle(); } } /* ================ hhProjectile::Fizzle ================ */ void hhProjectile::Fizzle( void ) { if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk return; } int removeTime = StartSound( "snd_fizzle", SND_CHANNEL_BODY, 0, true ); if (!gameLocal.isClient) { // fizzle FX hhFxInfo fxInfo; fxInfo.SetNormal( -GetAxis()[0] ); fxInfo.RemoveWhenDone( true ); BroadcastFxInfoPrefixed( "fx_fuse", GetOrigin(), GetAxis(), &fxInfo ); } SAFE_REMOVE( fxFly ); fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.GetClipModel()->Unlink(); physicsObj.PutToRest(); FreeLightDef(); state = FIZZLED; RemoveProjectile( removeTime ); } /* ================= hhProjectile::RemoveProjectile ================= */ void hhProjectile::RemoveProjectile( const int removeDelay ) { Hide(); BecomeInactive( TH_TICKER|TH_THINK ); RemoveBinds();//Remove any fx we have because they aren't hidden PostEventMS( &EV_Remove, removeDelay ); } /* ================ hhProjectile::Explode ================ */ void hhProjectile::Explode( const trace_t* collision, const idVec3& velocity, int removeDelay ) { const char *fxname = NULL; int length = 0; if ( state == EXPLODED || state == FIZZLED || state == COLLIDED ) { //HUMANHEAD bjk return; } if( !collision ) { return; } //HUMANHEAD: aob StartSound( "snd_explode", SND_CHANNEL_BODY, 0, true, &length ); removeDelay = hhMath::hhMax( length, removeDelay ); // explosion light //FIXME: may need to be Broadcast removeDelay = hhMath::hhMax( removeDelay, CreateLight(spawnArgs.GetString("mtr_explode_light_shader"), spawnArgs.GetVector("explode_light_size"), spawnArgs.GetVector("explode_light_color", "1 1 1") * spawnArgs.GetFloat("explode_light_intensity", "1.0"), spawnArgs.GetVector("explode_light_offset"), spawnArgs.GetFloat("explode_light_fadetime")) ); //HUMANHEAD: aob - moved logic to helper function SpawnExplosionFx( collision ); SpawnDebris( collision->c.normal, velocity.ToNormal() ); //HUMANHEAD END state = EXPLODED; //HUMANHEAD rww if (!gameLocal.isClient) { idEntity *killer = owner.GetEntity(); if (killer && killer->IsType(hhVehicle::Type)) { //rww - handle vehicle projectiles killing people hhVehicle *veh = static_cast(killer); if (veh->GetPilot()) { killer = veh->GetPilot(); } } // splash damage if (!gameLocal.isClient) { //SplashDamage( collision->endpos, killer, this, this, spawnArgs.GetString("def_splash_damage") ); SplashDamage( GetOrigin(), killer, this, this, spawnArgs.GetString("def_splash_damage") ); } } //HUMANHEAD END //HUMANHEAD: aob - moved logic to helper function RemoveProjectile( removeDelay ); //HUMANHEAD END } /* ================ hhProjectile::SplashDamage HUMANHEAD: aob ================ */ void hhProjectile::SplashDamage( const idVec3& origin, idEntity* attacker, idEntity* ignoreDamage, idEntity* ignorePush, const char* splashDamageDefName ) { if( splashDamageDefName && splashDamageDefName[0] ) { gameLocal.RadiusDamage( origin, this, attacker, ignoreDamage, ignorePush, splashDamageDefName ); } } /* ================ hhProjectile::SpawnExplosionFx HUMANHEAD: aob ================ */ void hhProjectile::SpawnExplosionFx( const trace_t* collision ) { idEntity* hitEnt = NULL; hhFxInfo fxInfo; const idDict* dict = gameLocal.FindEntityDefDict( spawnArgs.GetString("def_detonateFx"), false ); if( !dict || !collision || collision->fraction >= 1.0f ) { return; } hitEnt = gameLocal.entities[ collision->c.entityNum ]; fxInfo.SetNormal( collision->c.normal ); fxInfo.SetIncomingVector( GetAxis()[0] ); fxInfo.SetBounceVector( hhProjectile::GetBounceDirection( physicsObj.GetLinearVelocity(), collision->c.normal, this, hitEnt ) ); fxInfo.RemoveWhenDone( true ); surfTypes_t matterType = gameLocal.GetMatterType( hitEnt, collision->c.material, "hhProjectile::SpawnExplosionFx" ); const char* fxKey = gameLocal.MatterTypeToMatterKey( "fx", matterType ); BroadcastFxInfo( dict->RandomPrefix(fxKey, gameLocal.random), GetOrigin(), GetAxis(), &fxInfo, NULL, false ); //rww - no broadcast, only locally. //FIXME: Once BroadcastFxInfo() is gone and we are spawning the entityfx directly, mark it as fl.neverdormant here } /* ================ hhProjectile::SpawnDebris HUMANHEAD: aob ================ */ void hhProjectile::SpawnDebris( const idVec3& collisionNormal, const idVec3& collisionDir ) { static char debrisKey[] = "def_debris"; static char countKey[] = "debris_count"; static char spreadKey[] = "debris_spread"; idDebris *debris = NULL; idEntity *ent = NULL; idStr indexStr; int amount = 0; const idDict *dict = NULL; const idKeyValue* defKV = NULL; for( defKV = spawnArgs.MatchPrefix(debrisKey, NULL); defKV; defKV = spawnArgs.MatchPrefix(debrisKey, defKV) ) { if( !defKV->GetValue().Length() ) { continue; } dict = gameLocal.FindEntityDefDict( defKV->GetValue().c_str(), false ); if( !dict ) { continue; } indexStr = defKV->GetKey(); indexStr.Strip( debrisKey ); if (gameLocal.isMultiplayer) { //rww - for decreasing the count for things in mp on a design basis amount = hhMath::Lerp( spawnArgs.GetVec2(va("%s_mp%s", countKey, indexStr.c_str())), gameLocal.random.RandomFloat() ); if (!amount) { amount = hhMath::Lerp( spawnArgs.GetVec2(va("%s%s", countKey, indexStr.c_str())), gameLocal.random.RandomFloat() ); } } else { amount = hhMath::Lerp( spawnArgs.GetVec2(va("%s%s", countKey, indexStr.c_str())), gameLocal.random.RandomFloat() ); } for ( int i = 0; i < amount; i++ ) { //HUMANHEAD: aob idVec3 dir = hhUtils::RandomSpreadDir( collisionNormal.ToMat3(), DEG2RAD(spawnArgs.GetFloat(va("%s%s", spreadKey, indexStr.c_str()))) ); //HUMAMHEAD END gameLocal.SpawnEntityDef( *dict, &ent, true, gameLocal.isClient ); //HUMANHEAD rww - make them local non-broadcast entities. if ( !ent || !ent->IsType( idDebris::Type ) ) { gameLocal.Error( "hhProjectile: 'projectile_debris' is not an idDebris" ); } debris = static_cast(ent); debris->Create( owner.GetEntity(), GetOrigin() + collisionNormal*10, dir.ToMat3() ); // HUMANHEAD bjk: displace out of surface debris->Launch(); debris->fl.networkSync = false; //HUMANHEAD rww debris->fl.clientEvents = true; //HUMANHEAD rww } } } /* ================ hhProjectile::SetGravity ================ */ void hhProjectile::SetGravity( const idVec3 &newGravity ) { float relativeMagnitude = spawnArgs.GetFloat( "gravity" ); idVec3 newGravityVector( vec3_zero ); if( GetGravity().Compare(newGravity, VECTOR_EPSILON) ) { return; } if( relativeMagnitude > 0.0f ) { newGravityVector = newGravity; relativeMagnitude *= newGravityVector.Normalize() / gameLocal.GetGravity().Length(); newGravityVector *= relativeMagnitude; } GetPhysics()->SetGravity( newGravityVector ); } /* ================ hhProjectile::ProcessAllowCollisionEvent ================ */ bool hhProjectile::ProcessAllowCollisionEvent( const trace_t* collision ) { assert( collision ); idEntity* ent = gameLocal.entities[ collision->c.entityNum ]; const idEventDef* eventDef = matterEventsAllowCollision.GetPartner( ent, collision->c.material ); assert( eventDef ); ProcessEvent( eventDef, collision ); return gameLocal.program.GetReturnedBool(); } //============================================================================= // // hhProjectile::Portalled // // The projectile was just portalled. Update the fx info to the bound fly fx system // HUMANHEAD CJR //============================================================================= void hhProjectile::Portalled(idEntity *portal) { if ( fxFly.IsValid() ) { hhFxInfo fxInfo; fxInfo.SetNormal( -GetAxis()[0] ); fxInfo.SetEntity( this ); fxInfo.RemoveWhenDone( false ); fxFly->SetFxInfo( fxInfo ); // Reset the fx system fxFly->Stop(); fxFly->Start( gameLocal.time ); } if (physicsObj.GetClipModel()) { //HUMANHEAD rww - allow projectiles to collide with owner after portalling physicsObj.GetClipModel()->SetOwner(this); } } // END HUMANHEAD //============================================================================= // // hhProjectile::AllowCollision // // Determines if the projectile can strike a given entity. Here, all // projectiles will pass-through Wraiths (other than arrows, which is handled // in the arrow code) //============================================================================= bool hhProjectile::AllowCollision( const trace_t& collision ) { return ProcessAllowCollisionEvent( &collision ); } /* ================ hhProjectile::Event_Fuse_Explode ================ */ void hhProjectile::Event_Fuse_Explode() { trace_t collision; SIMDProcessor->Memset( &collision, 0, sizeof(trace_t) ); collision.endpos = GetOrigin(); collision.endAxis = GetAxis(); collision.c.normal = -gameLocal.GetGravityNormal(); Explode( &collision, GetPhysics()->GetLinearVelocity(), 0 ); } /* ================ hhProjectile::Event_Collision_Explode ================ */ void hhProjectile::Event_Collision_Explode( const trace_t* collision, const idVec3& velocity ) { Explode( collision, velocity, ProcessCollision(collision, velocity) ); idThread::ReturnInt( 1 ); } /* ================ hhProjectile::Event_Collision_Impact ================ */ void hhProjectile::Event_Collision_Impact( const trace_t* collision, const idVec3& velocity ) { CancelEvents( &EV_Explode ); RemoveProjectile( ProcessCollision(collision, velocity) ); state = COLLIDED; idThread::ReturnInt( 1 ); } /* ================ hhProjectile::Event_Collision_DisturbLiquid ================ */ void hhProjectile::Event_Collision_DisturbLiquid( const trace_t* collision, const idVec3& velocity ) { CancelEvents( &EV_Explode ); RemoveProjectile( ProcessCollision(collision, velocity) ); idThread::ReturnInt( 1 ); } /* ================ hhProjectile::Event_Collision_Remove ================ */ void hhProjectile::Event_Collision_Remove( const trace_t* collision, const idVec3& velocity ) { RemoveProjectile( 0 ); idThread::ReturnInt( 1 ); } /* ================ hhProjectile::Event_AllowCollision_CollideNoProj ================ */ void hhProjectile::Event_AllowCollision_CollideNoProj( const trace_t* collision ) { idEntity* ent = gameLocal.entities[ collision->c.entityNum ]; if( ent->IsType( hhProjectile::Type ) ) { if ( static_cast(ent)->ProjCollide() ) { idThread::ReturnInt( 1 ); return; } } if( ent->IsType( hhProjectile::Type ) && // If we're colliding with another projectile ( !bNoCollideWithCrawlers || // AND we're not set to collide with crawlers !( ent->IsType( hhProjectileRocketLauncher::Type ) && // OR we're not a rocket launcher projectile ent->IsType( hhProjectileCrawlerGrenade::Type ) ) ) ) { // AND we're not a crawler projectile idThread::ReturnInt( 0 ); // Pass through return; } hhProjectile::Event_AllowCollision_Collide( collision ); } /* ================ hhProjectile::Event_AllowCollision_Collide ================ */ void hhProjectile::Event_AllowCollision_Collide( const trace_t* collision ) { idThread::ReturnInt( 1 ); } /* ================ hhProjectile::Event_AllowCollision_PassThru ================ */ void hhProjectile::Event_AllowCollision_PassThru( const trace_t* collision ) { idThread::ReturnInt( 0 ); } /* ================ hhProjectile::Event_SpawnDriverLocal HUMANHEAD: aob ================ */ void hhProjectile::Event_SpawnDriverLocal( const char* defName ) { if( !defName || !defName[ 0 ] ) { return; } driver = static_cast( gameLocal.SpawnObject(defName) ); driver->SetPassenger( this ); //Not sure if this should be pulled out of the def_driver dict or not float roll = hhMath::Lerp( spawnArgs.GetVec2("driver_rollRange"), gameLocal.random.RandomFloat() ); driver->SetAxis( idAngles( 0.0f, 0.0f, roll ).ToMat3() * driver->GetAxis() ); } /* ================ hhProjectile::Event_SpawnFxFlyLocal HUMANHEAD: aob ================ */ void hhProjectile::Event_SpawnFxFlyLocal( const char* defName ) { if( !defName || !defName[0] ) { return; } SAFE_REMOVE(fxFly); hhFxInfo fxInfo; fxInfo.SetNormal( -GetAxis()[0] ); fxInfo.SetEntity( this ); fxInfo.RemoveWhenDone( false ); fxFly = SpawnFxLocal( defName, GetOrigin(), GetAxis(), &fxInfo, true ); //rww - client (local) entity. if (fxFly.IsValid()) { fxFly->fl.neverDormant = true; //rww fxFly->fl.networkSync = false; fxFly->fl.clientEvents = true; } } //================ //hhProjectile::Save //================ void hhProjectile::Save( idSaveGame *savefile ) const { driver.Save( savefile ); fxFly.Save( savefile ); savefile->WriteInt( thrust_start ); savefile->WriteBool( bDDACounted ); parentProjectile.Save( savefile ); savefile->WriteInt( launchTimestamp ); savefile->WriteInt( weaponNum ); savefile->WriteBool( bPlayFlyBySound ); savefile->WriteFloat( flyBySoundDistSq ); collidedPortal.Save( savefile ); savefile->WriteVec3( collideLocation ); savefile->WriteVec3( collideVelocity ); } //================ //hhProjectile::Restore //================ void hhProjectile::Restore( idRestoreGame *savefile ) { driver.Restore( savefile ); fxFly.Restore( savefile ); savefile->ReadInt( thrust_start ); savefile->ReadBool( bDDACounted ); parentProjectile.Restore( savefile ); savefile->ReadInt( launchTimestamp ); savefile->ReadInt( weaponNum ); savefile->ReadBool( bPlayFlyBySound ); savefile->ReadFloat( flyBySoundDistSq ); bNoCollideWithCrawlers = spawnArgs.GetBool( "noCollideWithCrawlers", "0" ); bProjCollide = spawnArgs.GetBool( "proj_collision", "0" ); collidedPortal.Restore( savefile ); savefile->ReadVec3( collideLocation ); savefile->ReadVec3( collideVelocity ); } /* ================ hhProjectile::WriteToSnapshot ================ */ void hhProjectile::WriteToSnapshot( idBitMsgDelta &msg ) const { //rww - we capture the launch orientation/pos for predicting the projectile launch, and (usually) don't sync physics at all if (fabsf(launchQuat.ToAngles().roll) > 0.001f) { //is it going to translate to a direction happily, or do we need a real orientation? msg.WriteBits(1, 1); msg.WriteFloat(launchQuat.x); msg.WriteFloat(launchQuat.y); msg.WriteFloat(launchQuat.z); } else { msg.WriteBits(0, 1); msg.WriteDir(launchQuat.ToMat3()[0], 24); } msg.WriteFloat(launchPos.x); msg.WriteFloat(launchPos.y); msg.WriteFloat(launchPos.z); idProjectile::WriteToSnapshot(msg); } /* ================ hhProjectile::ReadFromSnapshot ================ */ void hhProjectile::ReadFromSnapshot( const idBitMsgDelta &msg ) { //rww - we capture the launch orientation for predicting the projectile launch, and (usually) don't sync physics at all bool fullOrientation = !!msg.ReadBits(1); if (fullOrientation) { launchQuat.x = msg.ReadFloat(); launchQuat.y = msg.ReadFloat(); launchQuat.z = msg.ReadFloat(); } else { idVec3 dir = msg.ReadDir(24); launchQuat = dir.ToMat3().ToCQuat(); } launchPos.x = msg.ReadFloat(); launchPos.y = msg.ReadFloat(); launchPos.z = msg.ReadFloat(); idProjectile::ReadFromSnapshot(msg); } /* ================ hhProjectile::ClientHideProjectile ================ */ void hhProjectile::ClientHideProjectile(void) { Hide(); FreeLightDef(); BecomeInactive(TH_THINK|TH_PHYSICS); GetPhysics()->PutToRest(); if (fxFly.IsValid()) { fxFly->Stop(); SAFE_REMOVE( fxFly ); } } /* =============== hhProjectile::GetBounceDirection =============== */ idVec3 hhProjectile::GetBounceDirection( const idVec3 &incoming, const idVec3 &surface_normal, const idEntity *incoming_entity, const idEntity *surface_entity ) { idVec3 bounceDir; idVec3 tanget; idVec3 normal; float dot = 0.0f; float eProjectile = 1.0f; // Elasticity constants float eTarget = 1.0f; float fProjectile = 0.0f; // Friction constants float fTarget = 0.0f; if ( incoming_entity ) { eProjectile = incoming_entity->spawnArgs.GetFloat("bounce", "1"); fProjectile = incoming_entity->spawnArgs.GetFloat("contact_friction", "0"); } if ( surface_entity ) { eTarget = surface_entity->spawnArgs.GetFloat("bounce", "1"); fTarget = surface_entity->spawnArgs.GetFloat("contact_friction", "0"); } dot = incoming * surface_normal; normal = surface_normal * dot; tanget = incoming - normal; bounceDir = tanget * (1.0f - fProjectile) * (1.0f - fTarget) - normal * (eProjectile * eTarget); //? Should this all be seperated out? Ie, what if they don't want it normalized? if ( bounceDir.Length() < .0001f ) { bounceDir = surface_normal; } //HUMANHEAD: nla/aob - removed normalize to give return value more dependence on inputs. return( bounceDir ); }