640 lines
16 KiB
Plaintext
640 lines
16 KiB
Plaintext
/***********************************************************************
|
|
|
|
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 );
|
|
}
|