/*********************************************************************** projectile_missile.script ***********************************************************************/ #define MS_NORMAL 0 #define MS_EXPLODED 1 #define MS_AIRBURST 2 object projectile_missile { void init(); void preinit(); void destroy(); void syncFields(); void Idle(); void Explode( object traceObject, entity collisionEnt ); void ExplodeVel( object traceObject, entity collisionEnt, vector velocity, float newState ); void Fizzle(); void AirBurst(); float OnCollide( object traceObject, vector velocity, float bodyId ); void OnKilled(); void OnStick( entity collisionEnt, vector collisionNormal, string surfaceType, string joint ); void OnTouch( entity other, object traceObject ); void OnRest(); void OnLaunchTimeChanged(); void OnPostThink(); float GetDamagePower() { return getDamagePower(); } void KillFuseThread(); void FuseThread(); void SetRadiusDamageIgnoreEntity( entity collisionEnt ); void DoWaterExplosion( vector position, string surfaceType, vector normal ); void ProjectileMissile_OnStick( entity collisionEnt, vector collisionNormal, string surfaceType, string joint ); void CancelThreads(); void ScheduleExplosion( float delay, float newState ); void ExplosionThread( float delay, float newState ); void CancelExplosion(); void BotOnExplode() { ; } //mal: do nothing here - landmines and grenades will define their own version of this function. void ScheduleFizzle( float delay ); void FizzleThread( float delay ); void CancelFizzle(); void CancelMonitorTrail(); void MonitorTrail(); float CollideEffect( object traceObject, entity collisionEnt, vector velocity ); void DoExplodeEffect( entity collisionEnt ); boolean PendingRemoval(); void ScheduleRemoval( float delay ); void DoRadiusDamage(); void MakeInactive(); void SetupContents(); void SetupContents_Base(); void OnStateChanged(); void RemoveThread( float delay ); void vOnBindMasterDestroyed() { remove(); } void vSetDestroyed(); boolean vGetDestroyed(); float vGetDestroyProficiency(); boolean vDisablePlantCharge() { return true; } void MissileBounce( vector velocity ); void OnUnbind(); void FuseSoundThread( float fuseTimer ); float soundPreDelay; boolean detonateOnWorld; boolean detonateOnActor; boolean detonateOnFuse; boolean detonateOnDeath; boolean detonateOnRest; boolean stickOnContact; boolean detonateUpwards; boolean noTrail; boolean useAirBurst; boolean trailUnderWater; boolean removeTrailAtRest; float removeThread; float damageIndex; float splashDamageIndex; float airBurstDamageIndex; float destroyProficiency; // XP for destroying this via anti-missile entity radiusDamageIgnoreEntity; float fuse; boolean stuck; float armTime; float state; boolean destroyed; float lastBounceSound; float nextBounceTime; boolean inactive; } void projectile_missile::syncFields() { syncBroadcast( "state" ); syncCallback( "state", "OnStateChanged" ); } void projectile_missile::preinit() { removeThread = -1; stuck = false; fuse = getFloatKey( "fuse" ); armTime = getFloatKey( "arm_time" ); damageIndex = GetDamage( getKey( "dmg_damage" ) ); splashDamageIndex = GetDamage( getKey( "dmg_splash_damage" ) ); airBurstDamageIndex = GetDamage( getKey( "dmg_splash_damage_air" ) ); if ( airBurstDamageIndex == -1 ) { airBurstDamageIndex = splashDamageIndex; } detonateOnWorld = getIntKey( "detonate_on_world" ); detonateOnActor = getIntKey( "detonate_on_actor" ); detonateOnFuse = getIntKey( "detonate_on_fuse" ); detonateOnDeath = getIntKey( "detonate_on_death" ); detonateOnRest = getIntKey( "detonate_on_rest" ); stickOnContact = getIntKey( "stick_on_contact" ); detonateUpwards = getIntKey( "detonate_upwards" ); noTrail = getIntKey( "no_trail" ); trailUnderWater = getIntKey( "trailUnderWater" ); useAirBurst = getIntKey( "use_air_burst" ); removeTrailAtRest = getIntKey( "removeTrailAtRest" ); soundPreDelay = getFloatKey( "pre_delay_time" ); nextBounceTime = 0; destroyProficiency = GetProficiency( getKey( "prof_destroy" ) ); state = MS_NORMAL; } void projectile_missile::SetupContents() { SetupContents_Base(); } void projectile_missile::SetupContents_Base() { float contents = CONTENTS_PROJECTILE; if ( getIntKey( "detonate_on_trigger" ) ) { contents |= CONTENTS_TRIGGER; } setContents( contents ); setClipmask( MASK_PROJECTILE | CONTENTS_BODY | CONTENTS_SLIDEMOVER ); } void projectile_missile::OnStateChanged() { if ( state != MS_NORMAL ) { ScheduleExplosion( sys.getFrameTime(), state ); } } void projectile_missile::init() { SetupContents(); setState( "Idle" ); } void projectile_missile::KillFuseThread() { sys.killThread( "FuseThread_" + getName() ); } void projectile_missile::FuseThread() { float launchTime = getLaunchTime(); float delay = ( launchTime + fuse ) - sys.getTime(); if ( delay > 0.f ) { sys.wait( delay ); } if ( detonateOnFuse ) { AirBurst(); } else { Fizzle(); } } void projectile_missile::OnLaunchTimeChanged() { KillFuseThread(); if ( fuse > 0 ) { thread FuseThread(); if ( soundPreDelay != 0.0f && ( fuse - soundPreDelay > 0 ) ) { thread FuseSoundThread( fuse - soundPreDelay ); } } } void projectile_missile::CancelMonitorTrail() { sys.killThread( "MonitorTrail_" + getName() ); } void projectile_missile::MonitorTrail() { float nextTime = sys.getTime() + 0.5f; while ( true ) { if ( !trailUnderWater && isInWater() > 0.5f ) { stopEffect( "fx_trail" ); } vector velocity = getLinearVelocity(); float velSqr = sys.vecLengthSquared( velocity ); if ( velSqr > 5.f ) { nextTime = sys.getTime() + 0.5f; } if ( nextTime < sys.getTime() && removeTrailAtRest ) { if ( !noTrail ) { stopEffect( "fx_trail" ); noTrail = true; } } sys.waitFrame(); } } void projectile_missile::Idle() { if ( !noTrail ) { if ( trailUnderWater || isInWater() < 0.5f ) { handle ent = playEffect( "fx_trail", "", 1 ); if ( getIntKey( "trail_unbindrotation" ) ) { detachRotationBind( ent ); } CancelMonitorTrail(); thread MonitorTrail(); } } startSound( "snd_fly", SND_WEAPON_FIRE ); } void projectile_missile::destroy() { if ( !noTrail ) { stopEffect( "fx_trail" ); } stopSound( SND_WEAPON_FIRE ); } void projectile_missile::OnStick( entity collisionEnt, vector collisionNormal, string surfaceType, string joint ) { ProjectileMissile_OnStick( collisionEnt, collisionNormal, surfaceType, joint ); } void projectile_missile::ProjectileMissile_OnStick( entity collisionEnt, vector collisionNormal, string surfaceType, string joint ) { startSound( "snd_stick", SND_WEAPON_BOUNCE ); stuck = true; freeze( 1.f ); clearContacts(); putToRest(); if ( collisionEnt != $null_entity ) { float jointHandle = collisionEnt.getJointHandle( joint ); if ( jointHandle != INVALID_JOINT ) { bindToJoint( collisionEnt, joint, 1 ); } else { bind( collisionEnt ); } } } // NOTE: If this returns true, all momentum on the object will be cleared, otherwise, it will bounce float projectile_missile::OnCollide( object traceObject, vector velocity, float bodyId ) { float shaderFlags; entity collisionEnt; vector dir; if( stuck ) { return 1.0f; } shaderFlags = traceObject.getTraceSurfaceFlags(); if( shaderFlags & SURF_NOIMPACT ) { ScheduleRemoval( 0 ); return 1.0f; } if ( PendingRemoval() ) { return 1.0f; } collisionEnt = traceObject.getTraceEntity(); player collisionPlayer = collisionEnt; if ( stickOnContact && collisionPlayer == $null_entity ) { if ( shaderFlags & SURF_NOPLANT ) { return false; } if ( collisionEnt.vDisablePlantCharge() ) { return false; } OnStick( collisionEnt, traceObject.getTraceNormal(), traceObject.getTraceSurfaceType(), traceObject.getTraceJoint() ); return 1.0f; } if ( ( armTime > 0 ) && ( ( getLaunchTime() + armTime ) > sys.getTime() ) ) { MissileBounce( velocity ); return 0.0f; } if ( collisionPlayer != $null_entity ) { if( !detonateOnActor ) { return 0.f; } } if ( !detonateOnWorld ) { if ( nextBounceTime < sys.getTime() ) { string keyfx = getKey( "fx_bounce_"+traceObject.getTraceSurfaceType() ); if ( keyfx != "" ) { sys.playWorldEffect( keyfx , '1 1 1', traceObject.getTraceEndPos(), traceObject.getTraceNormal() ); } nextBounceTime = sys.getTime() + 1000; } MissileBounce( velocity ); return 0.0f; } return CollideEffect( traceObject, collisionEnt, velocity ); } float projectile_missile::CollideEffect( object traceObject, entity collisionEnt, vector velocity ) { vector dir; if ( collisionEnt != $null_entity ) { if( collisionEnt.takesDamage() ) { dir = sys.vecNormalize( velocity ); collisionEnt.applyDamage( self, getOwner(), dir, damageIndex, GetDamagePower(), traceObject ); } } ExplodeVel( traceObject, collisionEnt, velocity, MS_EXPLODED ); return 1.0f; } void projectile_missile::MakeInactive() { inactive = true; unbind(); hide(); clearContacts(); putToRest(); forceDisableClip(); setTakesDamage( false ); stuck = true; freeze( 1.f ); stopAllEffects(); } void projectile_missile::SetRadiusDamageIgnoreEntity( entity collisionEnt ) { radiusDamageIgnoreEntity = $null_entity; if ( collisionEnt != $null_entity ) { if ( collisionEnt.takesDamage() ) { radiusDamageIgnoreEntity = collisionEnt; } } } void projectile_missile::DoExplodeEffect( entity collisionEnt ) { float splashDelay; float removeDelay; SetRadiusDamageIgnoreEntity( collisionEnt ); removeDelay = getFloatKeyWithDefault( "removedelay", 0.5 ); DoRadiusDamage(); ScheduleRemoval( removeDelay ); } void projectile_missile::AirBurst() { ExplodeVel( $null_entity, $null_entity, g_vectorZero, MS_AIRBURST ); } void projectile_missile::Explode( object traceObject, entity collisionEnt ) { ExplodeVel( traceObject, collisionEnt, g_vectorZero, MS_EXPLODED ); } void projectile_missile::ExplodeVel( object traceObject, entity collisionEnt, vector velocity, float newState ) { CancelThreads(); if ( PendingRemoval() ) { return; } if( !sys.isClient() ) { BotOnExplode(); state = newState; forceNetworkUpdate(); } MakeInactive(); vector newOrg = getWorldOrigin(); vector normal = '0 0 1'; vector materialColor = g_colorWhite; string surfaceType; boolean hasTraceResults = false; if ( traceObject == $null_entity ) { // scan down a little just to see if theres ground below // it might be resting on the ground when this happens vector currentPos = getWorldOrigin(); if ( sys.tracePoint( currentPos + '0 0 1', currentPos - '0 0 16', MASK_SHOT_RENDERMODEL, self ) < 1.0f ) { newOrg = sys.getTraceEndPos(); normal = sys.getTraceNormal(); materialColor = sys.getTraceSurfaceColor(); surfaceType = sys.getTraceSurfaceType(); hasTraceResults = true; } } else { newOrg = traceObject.getTraceEndPos(); normal = traceObject.getTraceNormal(); materialColor = traceObject.getTraceSurfaceColor(); surfaceType = traceObject.getTraceSurfaceType(); hasTraceResults = true; } vector effectNormal = normal; if ( detonateUpwards ) { effectNormal = '0 0 1'; } if ( !noTrail ) { stopEffect( "fx_trail" ); } stopSound( SND_WEAPON_FIRE ); if ( isInWater() > 0.5f ) { DoWaterExplosion( newOrg, surfaceType, '0 0 1' ); } else { vector reflvel = effectNormal; string effectName; if ( newState == MS_AIRBURST && useAirBurst ) { effectName = "fx_airburst"; } else { effectName = "fx_explode"; } effectName = lookupEffect( effectName, surfaceType ); if ( effectName != "" ) { sys.playWorldEffect( effectName, '1 1 1', newOrg, reflvel ); } if ( hasTraceResults ) { addCheapDecal( collisionEnt, newOrg, normal, "dec_impact", surfaceType );// Will play on the world } } DoExplodeEffect( collisionEnt ); } void projectile_missile::DoWaterExplosion( vector position, string surfaceType, vector normal ) { entitiesOfClass( sys.getTypeHandle( "sdLiquid" ), 0 ); float count = filterEntitiesByTouching( 1 ); if ( count > 0 ) { entity other = getBoundsCacheEntity( 0 ); vector top = other.getAbsMaxs(); position_z = top_z; } playOriginEffect( "fx_explode_water", surfaceType, position, normal, 0 ); } void projectile_missile::Fizzle() { CancelThreads(); if ( PendingRemoval() ) { return; } MakeInactive(); if ( !noTrail ) { stopEffect( "fx_trail" ); } stopSound( SND_WEAPON_FIRE ); ScheduleRemoval( 0 ); } boolean projectile_missile::PendingRemoval() { return removeThread != -1; } void projectile_missile::RemoveThread( float delay ) { if( delay <= 0 ) { delay = sys.getFrameTime(); } sys.wait( delay ); if( !sys.isClient() ) { remove(); } } void projectile_missile::ScheduleRemoval( float delay ) { removeThread = thread RemoveThread( delay ); } void projectile_missile::DoRadiusDamage() { float damageIndex = splashDamageIndex; if ( state == MS_AIRBURST ) { damageIndex = airBurstDamageIndex; } if ( damageIndex != -1 ) { sys.applyRadiusDamage( getWorldOrigin(), self, getOwner(), radiusDamageIgnoreEntity, self, damageIndex, GetDamagePower(), 1.f ); } } void projectile_missile::OnKilled() { if( PendingRemoval() ) { return; } if( detonateOnDeath ) { Explode( $null_entity, $null_entity ); } else { Fizzle(); } } void projectile_missile::OnTouch( entity other, object traceObject ) { } void projectile_missile::OnRest() { if ( inactive ) { return; } if ( detonateOnRest ) { ExplodeVel( $null_entity, $null_entity, g_vectorZero, MS_EXPLODED ); } } void projectile_missile::MissileBounce( vector velocity ) { if( sys.vecLengthSquared( velocity ) < ( 50.f * 50.f ) ) { return; } if( lastBounceSound < sys.getTime() ) { startSound( "snd_bounce", SND_WEAPON_BOUNCE ); lastBounceSound = sys.getTime() + 0.2f; } } void projectile_missile::ScheduleExplosion( float delay, float newState ) { CancelExplosion(); thread ExplosionThread( delay, newState ); } void projectile_missile::ExplosionThread( float delay, float newState ) { sys.wait( delay ); thread ExplodeVel( $null_entity, $null_entity, g_vectorZero, newState ); } void projectile_missile::CancelExplosion() { sys.killThread( "ExplosionThread_" + getName() ); } void projectile_missile::ScheduleFizzle( float delay ) { CancelFizzle(); thread FizzleThread( delay ); } void projectile_missile::FizzleThread( float delay ) { sys.wait( delay ); thread Fizzle(); // in a thread because fizzle will kill this thread } void projectile_missile::CancelFizzle() { sys.killThread( "FizzleThread_" + getName() ); } void projectile_missile::CancelThreads() { CancelExplosion(); CancelFizzle(); CancelMonitorTrail(); } void projectile_missile::vSetDestroyed() { destroyed = true; AirBurst(); } boolean projectile_missile::vGetDestroyed() { return destroyed; } float projectile_missile::vGetDestroyProficiency() { return destroyProficiency; } void projectile_missile::OnUnbind() { Fizzle(); } void projectile_missile::OnPostThink() { if ( isBound() ) { forceRunPhysics(); } } void projectile_missile::FuseSoundThread( float fuseTimer ) { sys.wait( fuseTimer ); startSound( "snd_pre_delay", SND_WEAPON_ARM ); }