text:
#include "cbase.h"
#include "asw_alien.h"
#include "asw_ai_senses.h"
#include "asw_game_resource.h"
#include "asw_gamerules.h"
#include "asw_mission_manager.h"
#include "asw_marine_resource.h"
#include "asw_marine.h"
#include "asw_trace_filter_melee.h"
#include "asw_spawner.h"
#include "ai_waypoint.h"
#include "ai_moveprobe.h"
#include "asw_fx_shared.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "EntityFlame.h"
#include "asw_util_shared.h"
#include "asw_burning.h"
#include "asw_door.h"
#include "asw_gamestats.h"
#include "asw_pickup_money.h"
#include "asw_director.h"
#include "asw_physics_prop_statue.h"
#include "te_effect_dispatch.h"
#include "asw_ai_behavior.h"
#include "asw_ai_behavior_combat_stun.h"
#include "asw_ai_behavior_sleep.h"
#include "asw_ai_behavior_flinch.h"
#include "asw_missile_round_shared.h"
#include "datacache/imdlcache.h"
#include "asw_tesla_trap.h"
#include "sendprop_priorities.h"
#include "asw_spawn_manager.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

extern ConVar asw_debug_alien_damage;
extern ConVar ai_show_hull_attacks;
extern ConVar ai_efficiency_override;
extern ConVar ai_frametime_limit;
extern ConVar ai_use_think_optimizations;
extern ConVar ai_use_efficiency;
extern ConVar showhitlocation;
extern ConVar asw_stun_grenade_time;
extern ConVar asw_drone_zig_zagging;
extern ConVar asw_draw_awake_ai;
extern ConVar asw_alien_debug_death_style;

ConVar asw_fire_alien_damage_scale("asw_fire_alien_damage_scale", "3.0", FCVAR_CHEAT, "how much extra damage to do to burning aliens" );
ConVar asw_alien_speed_scale_easy("asw_alien_speed_scale_easy", "0");
ConVar asw_alien_speed_scale_normal("asw_alien_speed_scale_normal", "0");
ConVar asw_alien_speed_scale_hard("asw_alien_speed_scale_hard", "0");
ConVar asw_alien_speed_scale_insane("asw_alien_speed_scale_insane", "0");
ConVar asw_alien_hurt_speed( "asw_alien_hurt_speed", "0.5", FCVAR_CHEAT, "Fraction of speed to use when the alien is hurt after being shot" );
ConVar asw_alien_stunned_speed( "asw_alien_stunned_speed", "0.3", FCVAR_CHEAT, "Fraction of speed to use when the alien is electrostunned" );
ConVar asw_drop_money("asw_drop_money", "1", FCVAR_CHEAT, "Do aliens drop money?");
ConVar asw_alien_money_chance("asw_alien_money_chance", "1.0", FCVAR_CHEAT, "Chance of base aliens dropping money");
ConVar asw_drone_hurl_chance( "asw_drone_hurl_chance", "0.66666666666666", FCVAR_NONE, "Chance that an alien killed by explosives will hurl towards the camera." );
ConVar asw_drone_hurl_interval( "asw_drone_hurl_interval", "10", FCVAR_NONE, "Minimum number of seconds that must pass between alien bodies flung at camera." );
ConVar asw_alien_break_chance ( "asw_alien_break_chance", "0.5", FCVAR_NONE, "chance the alien will break into ragdoll pieces instead of gib");
ConVar asw_alien_fancy_death_chance ( "asw_alien_fancy_death_chance", "0.5", FCVAR_NONE, "If a drone doesn't instagib, this is the chance the alien plays a death anim before ragdolling" );
ConVar asw_debug_npcs( "asw_debug_npcs", "0", FCVAR_CHEAT, "Enables debug overlays for various NPCs" );
ConVar asw_alien_burn_duration( "asw_alien_burn_duration", "5.0f", FCVAR_CHEAT, "Alien burn time" );
extern ConVar asw_money;

// soft alien collision
ConVar asw_springcol( "asw_springcol", "1", FCVAR_CHEAT, "Use soft alien collision" );
ConVar asw_springcol_push_cap( "asw_springcol_push_cap", "33.0", FCVAR_CHEAT, "Cap on the total push vector" );
ConVar asw_springcol_core( "asw_springcol_core", "0.33", FCVAR_CHEAT, "Fraction of the alien's pushaway radius that is a solid capped core" );
ConVar asw_springcol_radius( "asw_springcol_radius", "50.0", FCVAR_CHEAT, "Radius of the alien's pushaway cylinder" );
ConVar asw_springcol_force_scale( "asw_springcol_force_scale", "3.0", FCVAR_CHEAT, "Multiplier for each individual push force" );
ConVar asw_springcol_debug( "asw_springcol_debug", "0", FCVAR_CHEAT, "Display the direction of the pushaway vector. Set to entity index or -1 to show all." );

ConVar asw_no_sleeping( "asw_no_sleeping", "0", FCVAR_CHEAT, "NPCs keep thinking even when they are far from a marine." );

float CASW_Alien::sm_flLastHurlTime = 0;

int ACT_MELEE_ATTACK1_HIT;
int ACT_MELEE_ATTACK2_HIT;
int AE_ALIEN_MELEE_HIT;

int ACT_ALIEN_FLINCH_SMALL;
int ACT_ALIEN_FLINCH_MEDIUM;
int ACT_ALIEN_FLINCH_BIG;
int ACT_ALIEN_FLINCH_GESTURE;
int ACT_DEATH_FIRE;
int ACT_DEATH_ELEC;
int ACT_DIE_FANCY;

int ACT_BURROW_IDLE;
int ACT_BURROW_OUT;

LINK_ENTITY_TO_CLASS( asw_alien, CASW_Alien );

IMPLEMENT_SERVERCLASS_ST(CASW_Alien, DT_ASW_Alien)
	SendPropExclude ( "DT_BaseEntity", "m_vecOrigin" ),
	SendPropVectorXY( SENDINFO( m_vecOrigin ), 				 CELL_BASEENTITY_ORIGIN_CELL_BITS, SPROP_CELL_COORD_LOWPRECISION | SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, CBaseEntity::SendProxy_CellOriginXY, SENDPROP_NONLOCALPLAYER_ORIGINXY_PRIORITY ),
	SendPropFloat   ( SENDINFO_VECTORELEM( m_vecOrigin, 2 ), CELL_BASEENTITY_ORIGIN_CELL_BITS, SPROP_CELL_COORD_LOWPRECISION | SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, CBaseEntity::SendProxy_CellOriginZ, SENDPROP_NONLOCALPLAYER_ORIGINZ_PRIORITY ),

	SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
	SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 0), 10, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesX ),
	SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 1), 10, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesY ),
	SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 2), 10, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesZ ),

	// test
	//SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
	SendPropBool( SENDINFO(m_bElectroStunned) ),// not using ElectroStunned
	//SendPropBool( SENDINFO(m_bElectroShockSmall) ),
	//SendPropBool( SENDINFO(m_bElectroShockBig) ),
	SendPropBool( SENDINFO(m_bOnFire) ),
	SendPropInt( SENDINFO(m_nDeathStyle), CASW_Alien::kDEATHSTYLE_NUM_TRANSMIT_BITS , SPROP_UNSIGNED ),
	SendPropInt		(SENDINFO(m_iHealth), ASW_ALIEN_HEALTH_BITS ),
	//SendPropBool(SENDINFO(m_bGibber)),
END_SEND_TABLE()

BEGIN_DATADESC( CASW_Alien )
	DEFINE_KEYFIELD( m_bVisibleWhenAsleep, FIELD_BOOLEAN, "visiblewhenasleep" ),
	DEFINE_KEYFIELD( m_iMoveCloneName, FIELD_STRING, "MoveClone" ),
	DEFINE_KEYFIELD( m_bStartBurrowed,		FIELD_BOOLEAN,	"startburrowed" ),
	DEFINE_INPUTFUNC( FIELD_VOID,	"BreakWaitForScript", InputBreakWaitForScript ),
	DEFINE_INPUTFUNC( FIELD_STRING, "SetMoveClone", InputSetMoveClone ),
	DEFINE_FIELD( m_flBurrowTime, FIELD_TIME ),
	DEFINE_FIELD(m_bIgnoreMarines, FIELD_BOOLEAN),
	DEFINE_FIELD(m_bFailedMoveTo, FIELD_BOOLEAN),
	DEFINE_FIELD(m_bElectroStunned, FIELD_BOOLEAN),

	//DEFINE_FIELD(m_bPerformingZigZag, FIELD_BOOLEAN),	// don't store this, let the zig zag be cleared each time
	//DEFINE_FIELD(m_bRunAtChasingPathEnds, FIELD_BOOLEAN), // no need to store currently, it's always true form constructor
	DEFINE_FIELD(m_fNextPainSound, FIELD_FLOAT),
	DEFINE_FIELD(m_fNextStunSound, FIELD_FLOAT),
	DEFINE_FIELD(m_fHurtSlowMoveTime, FIELD_TIME),
	//								m_ActBusyBehavior (auto saved by AI)
	DEFINE_FIELD( m_hSpawner, FIELD_EHANDLE ),
	DEFINE_FIELD( m_AlienOrders, FIELD_INTEGER ),
	DEFINE_FIELD( m_vecAlienOrderSpot, FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_AlienOrderObject, FIELD_EHANDLE ),
	DEFINE_FIELD( m_fLastSleepCheckTime, FIELD_FLOAT ),
	DEFINE_FIELD( m_bOnFire, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_iNumASWOrderRetries, FIELD_INTEGER ),
	DEFINE_FIELD( m_flFreezeResistance, FIELD_FLOAT ),
	DEFINE_FIELD( m_flFrozenTime, FIELD_TIME ),

	// soft alien collision
	DEFINE_FIELD( m_vecLastPushAwayOrigin, FIELD_VECTOR ),
	DEFINE_FIELD( m_vecLastPush, FIELD_VECTOR ),
	DEFINE_FIELD( m_bPushed, FIELD_BOOLEAN ),

	DEFINE_KEYFIELD(m_iHealthBonus, FIELD_INTEGER, "healthbonus"),
	DEFINE_KEYFIELD(m_flHealthScale, FIELD_FLOAT, "healthscale"),
	DEFINE_KEYFIELD(m_flSpeedScale, FIELD_FLOAT, "speedscale"),
	DEFINE_KEYFIELD(m_flSizeScale, FIELD_FLOAT, "sizescale"),
	DEFINE_KEYFIELD(m_bFlammable, FIELD_BOOLEAN, "flammable"),
	DEFINE_KEYFIELD(m_bFreezable, FIELD_BOOLEAN, "freezable"),
	DEFINE_KEYFIELD(m_bTeslable, FIELD_BOOLEAN, "teslable"),
	DEFINE_KEYFIELD(m_bFlinches, FIELD_BOOLEAN, "flinches"),
	DEFINE_FIELD(m_bScaled, FIELD_BOOLEAN),

	DEFINE_FIELD( m_bHoldoutAlien, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bDirectorAlien, FIELD_BOOLEAN ),
END_DATADESC()

IMPLEMENT_AUTO_LIST( IAlienAutoList );

#define ASW_ALIEN_SLEEP_CHECK_INTERVAL 1.0f

CASW_Alien::CASW_Alien( void ) :
	m_BehaviorParms( DefLessFunc( const CUtlSymbol ) )
{
	m_bRegisteredAsAwake = false;
	m_pszAlienModelName = NULL;
	m_bRunAtChasingPathEnds = true;
	m_bPerformingZigZag = false;
	m_fNextPainSound = 0;
	m_fNextStunSound = 0;

	m_fHurtSlowMoveTime = 0;
	m_hSpawner = NULL;
	m_AlienOrders = AOT_None;
	m_vecAlienOrderSpot = vec3_origin;
	m_AlienOrderObject = NULL;
	m_bIgnoreMarines = false;
	m_fLastSleepCheckTime = 0;
	m_bVisibleWhenAsleep = false;
	m_fLastMarineCanSeeTime = -100;
	m_bLastMarineCanSee = false;
	//m_bGibber = false;
	m_bTimeToRagdoll = false;
	m_iDeadBodyGroup = 1;
	m_bNeverRagdoll = false;
	m_bNeverInstagib = false;
	m_nDeathStyle = kDIE_RAGDOLLFADE;
	m_flBaseThawRate = 0.5f;
	m_flFrozenTime = 0.0f;

	m_UnburrowActivity = (Activity) ACT_BURROW_OUT;
	m_UnburrowIdleActivity = (Activity) ACT_BURROW_IDLE;
	m_iszUnburrowActivityName = NULL_STRING;
	m_iszUnburrowIdleActivityName = NULL_STRING;

	m_pPreviousBehavior = NULL;
	m_vecLastPush.Init();
	m_bBehaviorParameterChanged = false;

	m_nVolleyType = -1;
	m_flRangeAttackStartTime = 0.0f;
	m_flRangeAttackLastUpdateTime = 0.0f;
	m_vecRangeAttackTargetPosition.Init();
	m_bHoldoutAlien = false;
	m_bDirectorAlien = false;

	m_nAlienCollisionGroup = ASW_COLLISION_GROUP_ALIEN;

	m_iHealthBonus = 0;
	m_flHealthScale = 1.0f;
	m_flSpeedScale = 1.0f;
	m_flSizeScale = 1.0f;
	m_bFlammable = true;
	m_bFreezable = true;
	m_bTeslable = true;
	m_bFlinches = true;
	m_bScaled = false;

	meleeAttack1.Init( 0.0f, 64.0f, 0.7f, false );
	meleeAttack2.Init( 0.0f, 64.0f, 0.7f, false );
	rangeAttack1.Init( 64.0f, 786.0f, 0.5f, false );
	rangeAttack2.Init( 64.0f, 512.0f, 0.5f, false );
}

CASW_Alien::~CASW_Alien()
{
	for( int i = 0; i < m_Behaviors.Count(); i++ )
	{
		if ( m_Behaviors[ i ]->IsAllocated() )
		{
			delete m_Behaviors[ i ];
			m_Behaviors.Remove( i );
			i--;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Alien::Spawn()
{
	if ( asw_debug_npcs.GetBool() )
	{
		m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT | OVERLAY_TASK_TEXT_BIT | OVERLAY_TEXT_BIT;
	}

	ChangeFaction( FACTION_ALIENS );
	ChangeTeam( TEAM_ALIENS );

	// get pointers to behaviors
	GetBehavior( &m_pFlinchBehavior );
	// Precache.
	Precache();
	SetModel( m_pszAlienModelName );

	if (!m_bScaled)
	{
		m_bScaled = true;
		RescaleCustomSettings();
	}
	SetModelScale(m_flSizeScale);
	SetHealthByDifficultyLevel();
	SetHullSizeNormal();

	// Base spawn.
	BaseClass::Spawn();

	// By default NPCs are always solid and cannot be stood upon.
	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetBloodColor( BLOOD_COLOR_GREEN );

	SetCollisionGroup( m_nAlienCollisionGroup );

	SetNavType( NAV_GROUND );
	SetMoveType( MOVETYPE_STEP );

	// Set the initial NPC state.
	m_NPCState	= NPC_STATE_NONE;
	m_vecLastPushAwayOrigin = GetAbsOrigin();
	m_fFancyDeathChance = RandomFloat();

	if ( m_SquadName != NULL_STRING )
	{
		CapabilitiesAdd( bits_CAP_SQUAD );
	}

	if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) == false )
	{
		CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
	}
	NPCInit();
	m_flDistTooFar = 9999999.0f;
	LookupBurrowActivities();

	//See if we're supposed to start burrowed
	if ( m_bStartBurrowed )
	{
		AddEffects( EF_NODRAW );
		AddEffects( EF_NOSHADOW );
		AddFlag( FL_NOTARGET );
		m_spawnflags |= SF_NPC_GAG;

		AddSolidFlags( FSOLID_NOT_SOLID );
		m_takedamage	= DAMAGE_NO;
		m_vecUnburrowEndPoint = GetAbsOrigin();

		// set alien to fly and not collide for the duration of his unburrow
		AddFlag( FL_FLY );
		SetGroundEntity( NULL );
		SetCollisionGroup( COLLISION_GROUP_NPC_SCRIPTED );

		// offset alien's position by the motion in his unburrow sequence
		Activity unburrowActivity = m_UnburrowActivity;
		int nSeq = SelectWeightedSequence( unburrowActivity );
		if ( nSeq != -1 )
		{
			Vector vecLocalDelta;
			GetSequenceLinearMotion( nSeq, &vecLocalDelta );

			// Transform the sequence delta
			matrix3x4_t fRotateMatrix;
			AngleMatrix( GetLocalAngles(), fRotateMatrix );
			Vector vecDelta;
			VectorRotate( vecLocalDelta, fRotateMatrix, vecDelta );

			Vector vecNewPos = GetAbsOrigin() - vecDelta;
			Teleport( &vecNewPos, &GetAbsAngles(), &vec3_origin );
		}

		SetState( NPC_STATE_IDLE );
		SetActivity( m_UnburrowIdleActivity );
		SetSchedule( SCHED_BURROW_WAIT ); // todo: a schedule where they don't crawl out right away?
	}

	if ( m_iMoveCloneName != NULL_STRING )
	{
		SetMoveClone( m_iMoveCloneName, NULL );
	}
}

void CASW_Alien::Precache()
{

	BaseClass::Precache();

	PrecacheModel( m_pszAlienModelName );

	//pre-cache any models used by particle gib effects
	int modelIndex = modelinfo->GetModelIndex( m_pszAlienModelName );
	const model_t *model = modelinfo->GetModel( modelIndex );
	if ( model )
	{
		KeyValues *modelKeyValues = new KeyValues( "" );
		if ( modelKeyValues->LoadFromBuffer( m_ModelName.ToCStr(), modelinfo->GetModelKeyValueText( model ) ) )
		{

			KeyValues *pkvMeshParticleEffect = modelKeyValues->FindKey( "MeshParticles" );
			if ( pkvMeshParticleEffect )
			{

				for ( KeyValues *pSingleEffect = pkvMeshParticleEffect->GetFirstSubKey(); pSingleEffect; pSingleEffect = pSingleEffect->GetNextKey() )
				{
					const char *pszModelName = pSingleEffect->GetString( "modelName", "" );
					if( pszModelName )
					{
						PrecacheModel( pszModelName );
					}
				}
			}
		}
		modelKeyValues->deleteThis();
	}

	PrecacheParticleSystem( "drone_death" );	// death
	PrecacheParticleSystem( "drone_shot" );		// shot
	PrecacheParticleSystem( "freeze_statue_shatter" );
}

// Updates our memory about the enemies we Swarm Sensed
// todo: add various swarm sense conditions?
void CASW_Alien::OnSwarmSensed(int iDistance)
{
	AISightIter_t iter;
	CBaseEntity *pSenseEnt;

	pSenseEnt = GetASWSenses()->GetFirstSwarmSenseEntity( &iter );

	while( pSenseEnt )
	{
		if ( pSenseEnt->IsPlayer() )
		{
			// if we see a client, remember that (mostly for scripted AI)
			//SetCondition(COND_SEE_PLAYER);
		}

		Disposition_t relation = IRelationType( pSenseEnt );

		// the looker will want to consider this entity
		// don't check anything else about an entity that can't be seen, or an entity that you don't care about.
		if ( relation != D_NU )
		{
			if ( pSenseEnt == GetEnemy() )
			{
				// we know this ent is visible, so if it also happens to be our enemy, store that now.
				//SetCondition(COND_SEE_ENEMY);
			}

			// don't add the Enemy's relationship to the conditions. We only want to worry about conditions when
			// we see npcs other than the Enemy.
			switch ( relation )
			{
			case D_HT:
				{
					int priority = IRelationPriority( pSenseEnt );
					if (priority < 0)
					{
						//SetCondition(COND_SEE_DISLIKE);
					}
					else if (priority > 10)
					{
						//SetCondition(COND_SEE_NEMESIS);
					}
					else
					{
						//SetCondition(COND_SEE_HATE);
					}
					UpdateEnemyMemory(pSenseEnt,pSenseEnt->GetAbsOrigin());
					break;

				}
			case D_FR:
				UpdateEnemyMemory(pSenseEnt,pSenseEnt->GetAbsOrigin());
				//SetCondition(COND_SEE_FEAR);
				break;
			case D_LI:
			case D_NU:
				break;
			default:
				DevWarning( 2, "%s can't assess %s\n", GetClassname(), pSenseEnt->GetClassname() );
				break;
			}
		}

		pSenseEnt = GetASWSenses()->GetNextSwarmSenseEntity( &iter );
	}
}

// create our custom senses class
CAI_Senses* CASW_Alien::CreateSenses()
{
	CAI_Senses *pSenses = new CASW_AI_Senses;
	pSenses->SetOuter( this );
	return pSenses;
}

CASW_AI_Senses* CASW_Alien::GetASWSenses()
{
	return assert_cast<CASW_AI_Senses*>(GetSenses());
}

bool CASW_Alien::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
{
	if ( !BaseClass::QuerySeeEntity( pEntity, bOnlyHateOrFearIfNPC ) )
		return false;

	if ( pEntity && pEntity->Classify() == CLASS_ASW_BAIT )
	{
		return GetAbsOrigin().DistToSqr( pEntity->GetAbsOrigin() ) < 90000.0f;	// only see bait within 300 units
	}
	return true;
}

// wake the alien up when a marine gets nearby
void CASW_Alien::UpdateSleepState(bool bInPVS)
{
	if ( GetSleepState() > AISS_AWAKE )		// alien is asleep, check for marines getting near to wake us up
	{
		// wake up if we have a script to run
		if (m_hCine != NULL && GetSleepState() > AISS_AWAKE )
			Wake();

		bInPVS = MarineCanSee(384, 0.1f);
		if ( bInPVS )
			SetCondition( COND_IN_PVS );
		else
			ClearCondition( COND_IN_PVS );

		if ( GetSleepState() > AISS_AWAKE && GetSleepState() != AISS_WAITING_FOR_INPUT )
		{
			if (bInPVS)
			{
				if (asw_draw_awake_ai.GetBool())
					NDebugOverlay::EntityText(entindex(), 1, "WAKING", 60, 255,255,0,255);
				Wake();
			}
		}
	}
	else	// alien is awake, check for going back to ZZZ again :)
	{
		bool bHasOrders = (m_AlienOrders != AOT_None);

		// Don't let an NPC sleep if they're running a script!
		if( !ShouldAlwaysThink() && !bHasOrders && !IsInAScript() && m_NPCState != NPC_STATE_SCRIPT )
		{
			if( m_fLastSleepCheckTime < gpGlobals->curtime + ASW_ALIEN_SLEEP_CHECK_INTERVAL )
			{
				//if (!GetEnemy() && !MarineNearby(1024.0f) )
				if (!GetEnemy() && !MarineCanSee(384, 2.0f) )
				{
					SetSleepState( AISS_WAITING_FOR_PVS );

					if (asw_draw_awake_ai.GetBool())
						NDebugOverlay::EntityText(entindex(), 1, "SLEEPING", 600, 255,255,0,255);

					Sleep();
					if (m_bVisibleWhenAsleep)
						RemoveEffects( EF_NODRAW );
				}
				m_fLastSleepCheckTime = gpGlobals->curtime;
			}
		}
	}
	if ( GetSleepState() == AISS_AWAKE )
	{
		if ( !m_bRegisteredAsAwake )
		{
			ASWSpawnManager()->OnAlienWokeUp( this );
			m_bRegisteredAsAwake = true;
		}
	}
	else
	{
		if ( m_bRegisteredAsAwake )
		{
			ASWSpawnManager()->OnAlienSleeping( this );
			m_bRegisteredAsAwake = false;
		}
	}
}

bool CASW_Alien::ShouldAlwaysThink()
{
	return asw_no_sleeping.GetBool() || BaseClass::ShouldAlwaysThink();
}

void CASW_Alien::UpdateOnRemove()
{
	if ( m_bRegisteredAsAwake )
	{
		m_bRegisteredAsAwake = false;
		ASWSpawnManager()->OnAlienSleeping( this );
	}
	BaseClass::UpdateOnRemove();
}

void CASW_Alien::SetDistSwarmSense( float flDistSense )
{
	if (!GetASWSenses())
		return;
	GetASWSenses()->SetDistSwarmSense( flDistSense );
}

void CASW_Alien::NPCInit()
{
	BaseClass::NPCInit();

	// set default alien swarm sight/sense distances
	SetDistSwarmSense(576.0f);
	SetDistLook( 768.0f );
	m_flDistTooFar = 1500.0f;	// seems this is used as an early out for checking attack conditions or not, also for LOS?

	// if flagged, alien can see twice as far
	if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) )
	{
		SetDistSwarmSense(1152.0f);
		SetDistLook( 1536.0f );
		m_flDistTooFar = 2000.0f;
	}
	SetCollisionBounds( GetHullMins(), GetHullMaxs() );

	CASW_GameStats.Event_AlienSpawned( this );

	m_LagCompensation.Init(this);
}

void CASW_Alien::CallBehaviorThink()
{
	CAI_ASW_Behavior *pCurrent = GetPrimaryASWBehavior();
	if ( pCurrent )
	{
		pCurrent->BehaviorThink();
	}
}

void CASW_Alien::NPCThink( void )
{
	BaseClass::NPCThink();

	if ( asw_springcol.GetBool() && CanBePushedAway() )
	{
		PerformPushaway();
	}
	// stop electro stunning if we're slowed
	if ( m_bElectroStunned && m_lifeState != LIFE_DYING )
	{
		if ( m_fHurtSlowMoveTime < gpGlobals->curtime )
		{
			m_bElectroStunned = false;
		}
		else
		{
			if ( gpGlobals->curtime >= m_fNextStunSound )
			{
				m_fNextStunSound = gpGlobals->curtime + RandomFloat( 0.2f, 0.5f );

				EmitSound( "ASW_Tesla_Laser.Damage" );
			}
		}
	}

	//if (gpGlobals->maxClients > 1)
		m_LagCompensation.StorePositionHistory();
	// Update range attack
	if ( m_nVolleyType >= 0 )
		UpdateRangedAttack();

	UpdateThawRate();

	m_flLastThinkTime = gpGlobals->curtime;
}

bool CASW_Alien::MarineNearby(float radius, bool bCheck3D)
{
	// find the closest marine
	CASW_Game_Resource *pGameResource = ASWGameResource();
	if (!pGameResource)
		return false;

	for (int i=0;i<pGameResource->GetMaxMarineResources();i++)
	{
		CASW_Marine_Resource* pMarineResource = pGameResource->GetMarineResource(i);
		if (!pMarineResource)
			continue;

		CASW_Marine* pMarine = pMarineResource->GetMarineEntity();
		if (!pMarine)
			continue;

		Vector diff = pMarine->GetAbsOrigin() - GetAbsOrigin();
		float dist = bCheck3D ? diff.Length() : diff.Length2D();

		if (dist < radius)
		{
			return true;
		}
	}
	return false;
}

CBaseEntity *CASW_Alien::CheckTraceHullAttack( float flDist, const Vector &mins, const Vector &maxs, float flDamage, int iDmgType, float forceScale, bool bDamageAnyNPC )
{
	// If only a length is given assume we want to trace in our facing direction
	Vector forward;
	AngleVectors( GetAbsAngles(), &forward );
	Vector vStart = GetAbsOrigin();

	// The ideal place to start the trace is in the center of the attacker's bounding box.
	// however, we need to make sure there's enough clearance. Some of the smaller monsters aren't
	// as big as the hull we try to trace with. (SJB)
	float flVerticalOffset = WorldAlignSize().z * 0.5;

	if( flVerticalOffset < maxs.z )
	{
		// There isn't enough room to trace this hull, it's going to drag the ground.
		// so make the vertical offset just enough to clear the ground.
		flVerticalOffset = maxs.z + 1.0;
	}

	vStart.z += flVerticalOffset;
	Vector vEnd = vStart + (forward * flDist );
	return CheckTraceHullAttack( vStart, vEnd, mins, maxs, flDamage, iDmgType, forceScale, bDamageAnyNPC );
}

// alien has been hit by a melee attack
void CASW_Alien::MeleeBleed(CTakeDamageInfo* info)
{
	if ( info->GetDamage() >= 1.0 && !(info->GetDamageType() & DMG_SHOCK )
		&& !(info->GetDamageType() & DMG_BURN ))
	{
		Vector vecDir = -info->GetDamageForce();
		vecDir.NormalizeInPlace();
		UTIL_ASW_DroneBleed( info->GetDamagePosition() + m_LagCompensation.GetLagCompensationOffset(), vecDir, 4 );
	}
}

//-----------------------------------------------------------------------------
// Freezes this NPC in place for a period of time.
//-----------------------------------------------------------------------------
void CASW_Alien::Freeze( float flFreezeAmount, CBaseEntity *pFreezer, Ray_t *pFreezeRay )
{
	if (!m_bFreezable)
		return;

	if ( flFreezeAmount <= 0.0f )
	{
		SetCondition(COND_NPC_FREEZE);
		SetMoveType(MOVETYPE_NONE);
		SetGravity(0);
		SetLocalAngularVelocity(vec3_angle);
		SetAbsVelocity( vec3_origin );
		return;
	}

	if ( flFreezeAmount > 1.0f )
	{
		float flFreezeDuration = flFreezeAmount - 1.0f;

		// if freezing permanently, then reduce freeze duration by freeze resistance
		flFreezeDuration *= ( 1.0f - m_flFreezeResistance );

		BaseClass::Freeze( 1.0f, pFreezer, pFreezeRay );			// make alien fully frozen

		m_flFrozenTime = gpGlobals->curtime + flFreezeDuration;
	}
	else
	{
		// if doing a partial freeze, then freeze resistance reduces that
		flFreezeAmount *= ( 1.0f - m_flFreezeResistance );

		BaseClass::Freeze( flFreezeAmount, pFreezer, pFreezeRay );
	}

	UpdateThawRate();
}

bool CASW_Alien::ShouldBecomeStatue()
{
	// Prevents parent classes from turning this NPC into a statue
	// We handle that ourselves if the alien dies while iced up
	return false;
}

void CASW_Alien::UpdateThawRate()
{
	if ( m_flFrozenTime > gpGlobals->curtime )
	{
		m_flFrozenThawRate = 0.0f;
	}
	else
	{
		m_flFrozenThawRate = m_flBaseThawRate * (1.5f - m_flFrozen);
	}
}

//------------------------------------------------------------------------------
// Purpose :	start and end trace position, amount
//				of damage to do, and damage type. Returns a pointer to
//				the damaged entity in case the NPC wishes to do
//				other stuff to the victim (punchangle, etc)
//
//				Used for many contact-range melee attacks. Bites, claws, etc.
// Input   :
// Output  :
//------------------------------------------------------------------------------
CBaseEntity *CASW_Alien::CheckTraceHullAttack( const Vector &vStart, const Vector &vEnd, const Vector &mins, const Vector &maxs, float flDamage, int iDmgType, float flForceScale, bool bDamageAnyNPC )
{
	// Handy debuging tool to visualize HullAttack trace
	if ( ai_show_hull_attacks.GetBool() )
	{
		float length	 = (vEnd - vStart ).Length();
		Vector direction = (vEnd - vStart );
		VectorNormalize( direction );
		Vector hullMaxs = maxs;
		hullMaxs.x = length + hullMaxs.x;
		NDebugOverlay::BoxDirection(vStart, mins, hullMaxs, direction, 100,255,255,20,1.0);
		NDebugOverlay::BoxDirection(vStart, mins, maxs, direction, 255,0,0,20,1.0);
	}

	CTakeDamageInfo	dmgInfo( this, this, flDamage, DMG_SLASH );

	CASW_Trace_Filter_Melee traceFilter( this, COLLISION_GROUP_NONE, this, bDamageAnyNPC );

	Ray_t ray;
	ray.Init( vStart, vEnd, mins, maxs );

	trace_t tr;
	enginetrace->TraceRay( ray, MASK_SHOT_HULL, &traceFilter, &tr );

	Vector vecAttackerCenter = WorldSpaceCenter();

	for ( int i = 0; i < traceFilter.m_nNumHits; i++ )
	{
		trace_t *tr = &traceFilter.m_HitTraces[i];
		CBaseEntity *pHitEntity = tr->m_pEnt;

		Vector attackDir = pHitEntity->WorldSpaceCenter() - vecAttackerCenter;
		VectorNormalize( attackDir );
		CalculateMeleeDamageForce( &dmgInfo, attackDir, vecAttackerCenter, flForceScale );
		tr->m_pEnt->DispatchTraceAttack( dmgInfo, tr->endpos - vecAttackerCenter, tr );
#ifdef GAME_DLL
		ApplyMultiDamage();
#endif
	}

	CBaseEntity *pEntity = traceFilter.m_pHit;

	return pEntity;
}

//-----------------------------------------------------------------------------
// Purpose:
// NOTE: I removed the ON_GROUND check for Alien Swarm.  Do we need to put it
//	back?
//-----------------------------------------------------------------------------
int	CASW_Alien::MeleeAttack1Conditions( float flDot, float flDist )
{
	// Should we even check this condition?
	if ( !meleeAttack1.m_bCheck )
		return COND_NONE;

	// Check range.
	if ( flDist < meleeAttack1.m_flMinDist )
		return COND_TOO_CLOSE_TO_ATTACK;

	if ( flDist > meleeAttack1.m_flMaxDist )
		return COND_TOO_FAR_TO_ATTACK;

	// Check facing.
	if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK )
	{
		if ( flDot < meleeAttack1.m_flDotAngle )
			return COND_NOT_FACING_ATTACK;
	}

	return COND_CAN_MELEE_ATTACK1;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CASW_Alien::MeleeAttack2Conditions( float flDot, float flDist )
{
	// Should we even check this condition?
	if ( !meleeAttack2.m_bCheck )
		return COND_NONE;

	// Check ranges.
	if ( flDist < meleeAttack2.m_flMinDist )
		return COND_TOO_CLOSE_TO_ATTACK;

	if ( flDist > meleeAttack2.m_flMaxDist )
		return COND_TOO_FAR_TO_ATTACK;

	// Check facing.
	if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK )
	{
		if ( flDot < meleeAttack2.m_flDotAngle )
			return COND_NOT_FACING_ATTACK;
	}

	return COND_CAN_MELEE_ATTACK2;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CASW_Alien::RangeAttack1Conditions ( float flDot, float flDist )
{
	// Should we even check this condition?
	if ( !rangeAttack1.m_bCheck )
		return COND_NONE;

	// Check ranges.
	if ( flDist < rangeAttack1.m_flMinDist )
		return COND_TOO_CLOSE_TO_ATTACK;

	if ( flDist > rangeAttack1.m_flMaxDist )
		return COND_TOO_FAR_TO_ATTACK;

	// Check facing.
	if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK )
	{
		if ( flDot < rangeAttack1.m_flDotAngle )
			return COND_NOT_FACING_ATTACK;
	}

	return COND_CAN_RANGE_ATTACK1;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CASW_Alien::RangeAttack2Conditions ( float flDot, float flDist )
{
	// Should we even check this condition?
	if ( !rangeAttack2.m_bCheck )
		return COND_NONE;

	// Check ranges.
	if ( flDist < rangeAttack2.m_flMinDist )
		return COND_TOO_CLOSE_TO_ATTACK;

	if ( flDist > rangeAttack2.m_flMaxDist )
		return COND_TOO_FAR_TO_ATTACK;

	// Check facing.
	if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK )
	{
		if ( flDot < rangeAttack2.m_flDotAngle )
			return COND_NOT_FACING_ATTACK;
	}

	return COND_CAN_RANGE_ATTACK2;
}

// give our aliens 360 degree vision
bool CASW_Alien::FInViewCone( const Vector &vecSpot )
{
	return true;
}

// always gib
bool CASW_Alien::ShouldGib( const CTakeDamageInfo &info )
{
	return true;
}

// player (and player controlled marines) always avoid drones
bool CASW_Alien::ShouldPlayerAvoid( void )
{
	return true;
}

// catching on fire
int CASW_Alien::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	int result = 0;

	// scale burning damage up
	//if (dynamic_cast<CEntityFlame*>(info.GetAttacker()))
	if (dynamic_cast<CASW_Burning*>(info.GetInflictor()))
	{
		CTakeDamageInfo newDamage = info;
		newDamage.ScaleDamage(asw_fire_alien_damage_scale.GetFloat());
		if (asw_debug_alien_damage.GetBool())
		{
			Msg("%d %s hurt by %f dmg (scaled up by asw_fire_alien_damage_scale)\n", entindex(), GetClassname(), newDamage.GetDamage());
		}
		result = BaseClass::OnTakeDamage_Alive(newDamage);
	}
	else
	{
		if (asw_debug_alien_damage.GetBool())
		{
			Msg("%d %s hurt by %f dmg\n", entindex(), GetClassname(), info.GetDamage());
		}
		result = BaseClass::OnTakeDamage_Alive(info);
	}

	// if we take fire damage, catch on fire
	if ( result > 0 && ( info.GetDamageType() & DMG_BURN ) )
	{
		ASW_Ignite( asw_alien_burn_duration.GetFloat(), 0, info.GetAttacker(), info.GetWeapon() );
	}

	// make the alien move slower for 0.5 seconds
	if (info.GetDamageType() & DMG_SHOCK)
	{
		ElectroStun( asw_stun_grenade_time.GetFloat() );

		m_fNoDamageDecal = true;
	}
	else
	{
		if (m_fHurtSlowMoveTime < gpGlobals->curtime + 0.5f)
			m_fHurtSlowMoveTime = gpGlobals->curtime + 0.5f;
	}

	CASW_Marine* pMarine = NULL;
	if ( info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE )
	{
		pMarine = static_cast<CASW_Marine*>( info.GetAttacker() );
	}
	if (pMarine)
		pMarine->HurtAlien(this, info);

	// Notify gamestats of the damage
	CASW_GameStats.Event_AlienTookDamage( this, info );

	if ( m_RecentDamage.Count() == ASW_NUM_RECENT_DAMAGE )
	{
		m_RecentDamage.RemoveAtHead();
	}
	m_RecentDamage.Insert( info );
	if ( m_pFlinchBehavior && m_bFlinches )
	{
		m_pFlinchBehavior->OnOuterTakeDamage( info );
	}

	return result;
}

// ===================
// schedule/task stuff
// ====================

void CASW_Alien::StartTask( const Task_t *pTask )
{
	//int task = pTask->iTask;
	switch ( pTask->iTask )
	{
	case TASK_BURROW_WAIT:
		if ( pTask->flTaskData == 1.0f )
		{
			//Set our next burrow time
			if (m_flBurrowTime == 0)
				m_flBurrowTime = gpGlobals->curtime + RandomFloat( 0, 1 );
			else
				m_flBurrowTime = gpGlobals->curtime + RandomFloat( 1, 6 );
		}
		break;

	case TASK_UNBURROW:
		Unburrow();
		break;
	case TASK_CHECK_FOR_UNBORROW:
		//if ( ValidBurrowPoint( GetAbsOrigin() ) )
		{
			m_spawnflags &= ~SF_NPC_GAG;
			RemoveSolidFlags( FSOLID_NOT_SOLID );
			TaskComplete();
		}
		break;
	case TASK_SET_UNBURROW_IDLE_ACTIVITY:
		{
			SetIdealActivity( m_UnburrowIdleActivity );
		}
		break;
	case TASK_ASW_WAIT_FOR_ORDER_MOVE:
		{
			if ( IsMovementFrozen() )
			{
				TaskFail(FAIL_FROZEN);
				break;
			}

			if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
			{
				TaskComplete();
				GetNavigator()->ClearGoal();		// Clear residual state
			}
			else if (!GetNavigator()->IsGoalActive())
			{
				SetIdealActivity( GetStoppedActivity() );
			}
			else
			{
				// Check validity of goal type
				ValidateNavGoal();
			}
		}
		break;
	case TASK_ASW_ORDER_RETRY_WAIT:
		//Msg("TASK_ASW_ORDER_RETRY_WAIT %d tries. doorblocked=%d\n", m_iNumASWOrderRetries, m_bBlockedByOpeningDoor);
		m_iNumASWOrderRetries++;
		if (m_iNumASWOrderRetries > 5)
		{
			// Clear orders so we can sleep.
			if (!ShouldAlwaysThink())
				SetAlienOrders(AOT_SpreadThenHibernate, vec3_origin, NULL);
			TaskFail("Failed to find route to order target\n");
		}
		else
		{
			SetWait(MIN(float(m_iNumASWOrderRetries) / 2.0f, 12.0f));	// wait a bit longer in between each try
		}
		break;
	case TASK_ASW_REPORT_BLOCKING_ENT:
		Msg("Last blocking ent was %d:%s\n",
			GetNavigator()->GetBlockingEntity(),
			GetNavigator()->GetBlockingEntity() ? GetNavigator()->GetBlockingEntity()->GetClassname() : "Unknown");
		TaskComplete();
		break;
	case TASK_ASW_ALIEN_ZIGZAG:
		m_bPerformingZigZag = false;
		break;
	// override the default TASK_GET_PATH_TO_ENEMY to make our alien run at the end of the path
	case TASK_GET_PATH_TO_ENEMY:
		{
			if (IsUnreachable(GetEnemy()))
			{
				TaskFail(FAIL_NO_ROUTE);
				return;
			}

			CBaseEntity *pEnemy = GetEnemy();

			if ( pEnemy == NULL )
			{
				TaskFail(FAIL_NO_ENEMY);
				return;
			}

			if ( GetNavigator()->SetGoal( GOALTYPE_ENEMY ) )
			{
				if (m_bRunAtChasingPathEnds)
				{
					//Msg("Setting arrival speed to %f\n", GetIdealSpeed());
					//GetNavigator()->SetArrivalSpeed(GetIdealSpeed()); // asw test
					GetNavigator()->SetArrivalSpeed(300.0f); // asw test
				}
				TaskComplete();
			}
			else
			{
				// no way to get there =(
				DevWarning( 2, "GetPathToEnemy failed!!\n" );
				RememberUnreachable(GetEnemy());
				TaskFail(FAIL_NO_ROUTE);
			}
			return;
		}
	case TASK_GET_PATH_TO_ENEMY_LKP:
		{
			CBaseEntity *pEnemy = GetEnemy();
			if (!pEnemy || IsUnreachable(pEnemy))
			{
				TaskFail(FAIL_NO_ROUTE);
				return;
			}
			AI_NavGoal_t goal( GetEnemyLKP() );

			TranslateNavGoal( pEnemy, goal.dest );

			if ( GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET ) )
			{
				if (m_bRunAtChasingPathEnds)
				{
					//Msg("Setting arrival speed to %f\n", GetIdealSpeed());
					//GetNavigator()->SetArrivalSpeed(GetIdealSpeed()); // asw test
					GetNavigator()->SetArrivalSpeed(300.0f); // asw test
				}
				TaskComplete();
			}
			else
			{
				// no way to get there =(
				DevWarning( 2, "GetPathToEnemyLKP failed!!\n" );
				RememberUnreachable(GetEnemy());
				TaskFail(FAIL_NO_ROUTE);
			}
			break;
		}
	case TASK_ASW_SPREAD_THEN_HIBERNATE:
		{
			// This task really uses 2 parameters, so we have to extract
			// them from a single integer. To send both parameters, the
			// formula is MIN_DIST * 10000 + MAX_DIST
			{
				int iMinDist, iMaxDist;

				iMinDist = 90;
				iMaxDist = 200;

				// try to find a simple vector goal
				if ( GetNavigator()->SetWanderGoal( iMinDist, iMaxDist ) )
					TaskComplete();
				else
				{
					// if we couldn't go for a full path
					if ( GetNavigator()->SetRandomGoal( 150.0f ) )
						TaskComplete();
					else
						TaskFail(FAIL_NO_REACHABLE_NODE);
				}
			}
		}
		break;
	case TASK_ASW_BUILD_PATH_TO_ORDER:
		{
			CBaseEntity *pGoalEnt = m_AlienOrderObject;
			m_bFailedMoveTo = false;
			if (pGoalEnt)
			{
				// if our target is a path corner, do normal AI following of that route
				bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY);
				if (pGoalEnt->ClassMatches("path_corner"))
				{
					SetGoalEnt(pGoalEnt);

					AI_NavGoal_t goal( GOALTYPE_PATHCORNER, pGoalEnt->GetAbsOrigin(),
					   bIsFlying ? ACT_FLY : ACT_WALK,
					   AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);

					SetState( NPC_STATE_IDLE );
					if ( !GetNavigator()->SetGoal( goal ) )
					{
						DevWarning( 2, "Can't Create Route!\n" );
						m_bFailedMoveTo = true;
					}
					else
					{
						TaskComplete();
					}
				}
				else
				{
					// HACKHACK: Call through TranslateNavGoal to fixup this goal position
					// UNDONE: Remove this and have NPCs that need this functionality fix up paths in the
					// movement system instead of when they are specified.
					AI_NavGoal_t goal(pGoalEnt->GetAbsOrigin(), bIsFlying ? ACT_FLY : ACT_WALK, AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);
					TranslateNavGoal( pGoalEnt, goal.dest );

					if (!GetNavigator()->SetGoal( goal ))
					{
						DevWarning( 2, "Can't Create Route!\n" );
						m_bFailedMoveTo = true;
					}
					else
					{
						TaskComplete();
					}
				}
			}
			if (!pGoalEnt || m_bFailedMoveTo)
			{
				//m_AlienOrders = AOT_SpreadThenHibernate;	// make sure our orders are set correctly for this action (may be incorrect if a MoveTo order fails)
				if (pTask->flTaskData <= 0)		// check the taskdata to see if we should do a short spread movement when failing to build a path
				{
					// random nearby position
					if ( GetNavigator()->SetWanderGoal( 90, 200 ) )
					{
						TaskComplete();
					}
					else
					{
						// if we couldn't go for a full path
						if ( GetNavigator()->SetRandomGoal( 150.0f ) )
						{
							TaskComplete();
						}
						TaskFail(FAIL_NO_ROUTE);
					}
				}
				else
				{
					TaskFail(FAIL_NO_ROUTE);
				}
			}
		}
		break;

	default:
		{
			BaseClass::StartTask(pTask);
			break;
		}
	}
}

bool CASW_Alien::IsCurTaskContinuousMove()
{
	const Task_t* pTask = GetTask();
	if( !pTask || pTask->iTask == TASK_ASW_WAIT_FOR_ORDER_MOVE )
		return true;

	return BaseClass::IsCurTaskContinuousMove();
}

void CASW_Alien::RunTask(const Task_t *pTask)
{
	switch (pTask->iTask)
	{
	case TASK_BURROW_WAIT:
		//See if enough time has passed
		if ( m_flBurrowTime < gpGlobals->curtime )
		{
			TaskComplete();
		}
		break;
	case TASK_ASW_WAIT_FOR_ORDER_MOVE:
		{
			if ( IsMovementFrozen() )
			{
				TaskFail(FAIL_FROZEN);
				break;
			}

			bool fTimeExpired = ( pTask->flTaskData != 0 && pTask->flTaskData < gpGlobals->curtime - GetTimeTaskStarted() );

			if (fTimeExpired || GetNavigator()->GetGoalType() == GOALTYPE_NONE)
			{
				TaskComplete();
				GetNavigator()->StopMoving();		// Stop moving
			}
			else if (!GetNavigator()->IsGoalActive())
			{
				SetIdealActivity( GetStoppedActivity() );
			}
			else
			{
				// Check validity of goal type
				ValidateNavGoal();

				if ( m_AlienOrderObject.Get() )
				{
					const Vector &vecGoalPos = m_AlienOrderObject->GetAbsOrigin();
					if ( ( GetNavigator()->GetGoalPos() - vecGoalPos ).LengthSqr() > Square( 60 ) )
					{
						if ( GetNavigator()->GetNavType() != NAV_JUMP )
						{
							if ( !GetNavigator()->UpdateGoalPos( vecGoalPos ) )
							{
								TaskFail( FAIL_NO_ROUTE );
							}
						}
					}
				}
			}
			break;
		}

	case TASK_CHECK_FOR_UNBORROW:
		//Must wait for our next check time
		if ( m_flBurrowTime > gpGlobals->curtime )
			return;

		//See if we can pop up
		// todo: see if a marine is near?
		//if ( ValidBurrowPoint( GetAbsOrigin() ) )
		{
			m_spawnflags &= ~SF_NPC_GAG;
			RemoveSolidFlags( FSOLID_NOT_SOLID );

			TaskComplete();
			return;
		}

		//Try again in a couple of seconds
		m_flBurrowTime = gpGlobals->curtime + random->RandomFloat( 0.5f, 1.0f );
		break;
	case TASK_UNBURROW:
		{
			AutoMovement();
			if ( IsActivityFinished() )
			{
				RemoveFlag( FL_FLY );
				CheckForBlockingTraps();
				SetCollisionGroup( m_nAlienCollisionGroup );
				TaskComplete();
			}
			break;
		}
	case TASK_SET_UNBURROW_IDLE_ACTIVITY:
		{
			AutoMovement();
			if ( IsActivityFinished() )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_ASW_ORDER_RETRY_WAIT:
		{
			if ( IsWaitFinished() )
			{
				TaskComplete();
			}
			break;
		}
	case TASK_ASW_ALIEN_ZIGZAG:
		{
			if ( IsMovementFrozen() )
			{
				TaskFail(FAIL_FROZEN);
				break;
			}

			if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
			{
				m_bPerformingZigZag = false;
				TaskComplete();
				GetNavigator()->StopMoving();		// Stop moving
			}
			else if (!GetNavigator()->IsGoalActive())
			{
				SetIdealActivity( GetStoppedActivity() );
			}
			else if (ValidateNavGoal())
			{
				SetIdealActivity( GetNavigator()->GetMovementActivity() );

				// if we've just spotted our enemy, recompute our path, to try and get a single local path to add a zigzag to
				/*
				if (HasCondition( COND_SEE_ENEMY ) && !m_bLastSeeEnemy)
				{
					if ( !GetNavigator()->RefindPathToGoal( false ) )
					{
						TaskFail( FAIL_NO_ROUTE );
						return;
					}
				}
				m_bLastSeeEnemy = HasCondition( COND_SEE_ENEMY );

				if (GetNavigator()->CurWaypointIsGoal())
				{
					m_bPerformingZigZag = false;
					AddZigZagToPath();
				}*/
			}
			break;
		}
	default:
		{
			BaseClass::RunTask( pTask );
		}
	}
}

#define ZIG_ZAG_SIZE 1500	// was 3600

// don't change our chase goal while we're on a zigzag detour
bool CASW_Alien::ShouldUpdateEnemyPos()
{
	if (GetTask() && GetTask()->iTask == TASK_ASW_ALIEN_ZIGZAG
		//&& (GetNavigator()->GetCurWaypointFlags() & bits_WP_TO_DETOUR) )
		&& m_bPerformingZigZag)
		return false;

	// don't repath if we're heading to a point that shouldn't be simplified
	if (asw_drone_zig_zagging.GetBool() && GetNavigator()->GetCurWaypointFlags() & bits_WP_DONT_SIMPLIFY)
	{
		return false;
	}

	//Msg("updating enemy pos %f\n", gpGlobals->curtime);
	return true;
}

float CASW_Alien::GetGoalRepathTolerance( CBaseEntity *pGoalEnt, GoalType_t type, const Vector &curGoal, const Vector &curTargetPos )
{
	if (!ShouldUpdateEnemyPos())
		return 10000.0f;	// allow huge tolerance to prevent repath

	//return BaseClass::GetGoalRepathTolerance(pGoalEnt, type, curGoal, curTargetPos);
	float distToGoal = ( GetAbsOrigin() - curTargetPos ).Length() - GetNavigator()->GetArrivalDistance();
	float distMoved1Sec = GetSmoothedVelocity().Length();
	float result = 120;  // FIXME: why 120?

	if (distToGoal <= 100)
		return GetMotor()->MinStoppingDist(0.0f);

	if (distMoved1Sec > 0.0)
	{
		float t = distToGoal / distMoved1Sec;

		result = clamp( 120 * t, 0, 120 );
		// Msg("t %.2f : d %.0f  (%.0f)\n", t, result, distMoved1Sec );
	}

	if ( !pGoalEnt->IsPlayer() )
		result *= 1.20;

	return result;
}

void CASW_Alien::AddZigZagToPath(void)
{
	// If already on a detour don't add a zigzag
	if (GetNavigator()->GetCurWaypointFlags() & bits_WP_TO_DETOUR)
	{
		return;
	}

	// If enemy isn't facing me or occluded, don't add a zigzag
	//if (HasCondition(COND_ENEMY_OCCLUDED) || !HasCondition ( COND_ENEMY_FACING_ME ))
	//{
		//return;
	//}

	Vector waypointPos = GetNavigator()->GetCurWaypointPos();
	Vector waypointDir = (waypointPos - GetAbsOrigin());

	// If the distance to the next node is greater than ZIG_ZAG_SIZE
	// then add a random zig/zag to the path
	if (waypointDir.LengthSqr() > ZIG_ZAG_SIZE)
	{
		// Pick a random distance for the zigzag (less that sqrt(ZIG_ZAG_SIZE)
		float fZigZigDistance = random->RandomFloat( 100, 200 ); //30, 60 );
		int iZigZagSide = random->RandomInt(0,1);

		Msg("adding a zig zag of %f units\n", fZigZigDistance);

		// Get me a vector orthogonal to the direction of motion
		VectorNormalize( waypointDir );
		Vector vDirUp(0,0,1);
		Vector vDir;
		CrossProduct( waypointDir, vDirUp, vDir);

		// Pick a random direction (left/right) for the zigzag
		if (iZigZagSide)
		{
			vDir = -1 * vDir;
		}

		// Get zigzag position in direction of target waypoint
		Vector zigZagPos = GetAbsOrigin() + waypointDir * 200;

		// Now offset
		zigZagPos = zigZagPos + (vDir * fZigZigDistance);

		// Now make sure that we can still get to the zigzag position and the waypoint
		AIMoveTrace_t moveTrace1, moveTrace2;
		GetMoveProbe()->MoveLimit( NAV_GROUND, GetAbsOrigin(), zigZagPos, MASK_NPCSOLID, NULL, &moveTrace1);
		GetMoveProbe()->MoveLimit( NAV_GROUND, zigZagPos, waypointPos, MASK_NPCSOLID, NULL, &moveTrace2);
		if ( !IsMoveBlocked( moveTrace1 ) && !IsMoveBlocked( moveTrace2 ) )
		{
			m_bPerformingZigZag = true;
			GetNavigator()->PrependWaypoint( zigZagPos, NAV_GROUND, bits_WP_TO_DETOUR );
		}
	}
}

int CASW_Alien::TranslateSchedule( int scheduleType )
{
	if (scheduleType == SCHED_CHASE_ENEMY)
		return SCHED_ASW_ALIEN_CHASE_ENEMY;

	int i = BaseClass::TranslateSchedule(scheduleType);

	if ( i == SCHED_MELEE_ATTACK1 )
	{
		if ( ShouldStopBeforeMeleeAttack() )
		{
			return SCHED_ASW_ALIEN_SLOW_MELEE_ATTACK1;
		}
		return SCHED_ASW_ALIEN_MELEE_ATTACK1;
	}

	return i;
}

// pushes aliens out of each other
void CASW_Alien::PerformPushaway()
{
	VPROF("CASW_Alien::PerformPushaway");
	SetupPushawayVector();

#ifdef MOVEPROBE_PUSHAWAY
	AIMoveTrace_t moveTrace;
	unsigned testFlags = AITGM_IGNORE_FLOOR;
	GetMoveProbe()->TestGroundMove( GetLocalOrigin(), GetAbsOrigin() + m_vecLastPush, GetAITraceMask(), testFlags, &moveTrace );
	UTIL_SetOrigin( this, moveTrace.vEndPosition, true );
#else
	// check if we can safely move through this push
	Vector dest = GetAbsOrigin() + m_vecLastPush;
	trace_t tr;
	AI_TraceHull( GetAbsOrigin(), dest, WorldAlignMins(), WorldAlignMaxs(), GetAITraceMask(), this, GetCollisionGroup(), &tr );
	if ( !tr.startsolid && (tr.fraction == 1.0) )
	{
		// all was clear, move into new position
		UTIL_SetOrigin(this, dest);
	}
#endif

	// if we're close to stopping, zero our velocity
	if ( GetMotor()->GetCurSpeed() < 1.0f )
	{
		GetMotor()->SetMoveVel( Vector( 0,0,0 ) );
	}
	// if we were pushed, slow us down a bit
	else if ( m_bPushed )
	{
		float scale_vel = 1.0f - ( m_vecLastPush.Length2D() / asw_springcol_push_cap.GetFloat() );
		GetMotor()->SetMoveVel( GetMotor()->GetCurVel() * scale_vel );
	}
}

float CASW_Alien::GetSpringColRadius()
{
	return asw_springcol_radius.GetFloat();
}

void CASW_Alien::SetupPushawayVector()
{
	CASW_Alien *pOtherAlien;
	Vector vecPush;
	vecPush.Init();
	bool m_bPushed = false;
	float flSpringColRadius = GetSpringColRadius();
	// go through all aliens

	int iAliens = IAlienAutoList::AutoList().Count();
	for ( int i = 0; i < iAliens; i++ )
	{
		pOtherAlien = static_cast< CASW_Alien* >( IAlienAutoList::AutoList()[ i ] );
		if ( pOtherAlien != this )
		{
			Vector diff = m_vecLastPushAwayOrigin - pOtherAlien->GetAbsOrigin();
			float dist = diff.Length();
			VectorNormalize( diff );
			// TODO: Customize springcolradius per alien type
			if ( dist < flSpringColRadius )		// if alien is too close, add a force from him to our push vector
			{
				dist -= flSpringColRadius * asw_springcol_core.GetFloat();
				if (dist < 1)
					dist = 1;
				float push_amount = flSpringColRadius / dist;
				vecPush += push_amount * diff * asw_springcol_force_scale.GetFloat();
				m_bPushed = true;
			}
		}
	}

	// cap the push vector
	float over_length = vecPush.Length() / asw_springcol_push_cap.GetFloat();
	if (over_length > 1)
		vecPush /= over_length;

	// push out of players with equal force to total push from other drones
	int iMaxMarines = ASWGameResource()->GetMaxMarineResources();
	for ( int i = 0; i < iMaxMarines; i++ )
	{
		CASW_Marine_Resource *pMR = ASWGameResource()->GetMarineResource( i );
		if ( !pMR )
			continue;

		CASW_Marine *pMarine = pMR->GetMarineEntity();
		if ( !pMarine )
			continue;

		Vector diff = m_vecLastPushAwayOrigin - pMarine->GetAbsOrigin();
		float dist = diff.Length();
		VectorNormalize( diff );
		if ( dist < flSpringColRadius )		// if drone is too close, add a force from him to our push vector
		{
			dist -= flSpringColRadius * asw_springcol_core.GetFloat();
			if ( dist < 1 )
			{
				dist = 1;
			}
			float push_amount = flSpringColRadius / dist;
			vecPush += push_amount * diff * asw_springcol_force_scale.GetFloat();
			m_bPushed = true;
		}
	}
	// push away from tesla traps
	int nTraps = g_aTeslaTraps.Count();
	for ( int i = 0; i < nTraps; i++ )
	{
		Vector diff = m_vecLastPushAwayOrigin - g_aTeslaTraps[i]->GetAbsOrigin();
		float dist = diff.Length();
		VectorNormalize( diff );
		if ( dist < flSpringColRadius )		// if drone is too close, add a force from him to our push vector
		{
			dist -= flSpringColRadius * asw_springcol_core.GetFloat();
			if ( dist < 1 )
			{
				dist = 1;
			}
			float push_amount = flSpringColRadius / dist;
			vecPush += push_amount * diff * asw_springcol_force_scale.GetFloat();
			m_bPushed = true;
		}
	}
	// cap the push vector
	over_length = vecPush.Length() / asw_springcol_push_cap.GetFloat();
	if ( over_length > 1 )
	{
		vecPush /= over_length;
	}

	// smooth the push vector
	m_vecLastPush = (m_vecLastPush * 2.0f + vecPush) / 3.0f;
	//Msg("%d Pushaway vector size %f\n", entindex(), m_vecLastPush.Length2D());
	if ( asw_springcol_debug.GetInt() == -1 ||
		asw_springcol_debug.GetInt() == entindex() )
	{
		float flYaw = UTIL_VecToYaw( m_vecLastPush );
		NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 24 ), flYaw, 64, 8, 255, 255, 0, 0, true, 0.1f );
	}
	m_vecLastPushAwayOrigin = GetAbsOrigin();
}

bool CASW_Alien::CanBePushedAway()
{
	// no pushing away while burrowed
	if ( IsCurSchedule( SCHED_BURROW_WAIT, false ) || IsCurSchedule( SCHED_WAIT_FOR_CLEAR_UNBORROW, false )
		|| IsCurSchedule( SCHED_BURROW_OUT, false ) )
		return false;

	// don't push away aliens that have low sight ('asleep')
	return !IsCurSchedule( CAI_ASW_SleepBehavior::SCHED_SLEEP_UNBURROW );
}

AI_BEGIN_CUSTOM_NPC( asw_alien, CASW_Alien )
	DECLARE_CONDITION( COND_ASW_BEGIN_COMBAT_STUN )
	DECLARE_CONDITION( COND_ASW_FLINCH )

	DECLARE_TASK( TASK_ASW_ALIEN_ZIGZAG )
	DECLARE_TASK( TASK_ASW_SPREAD_THEN_HIBERNATE )
	DECLARE_TASK( TASK_ASW_BUILD_PATH_TO_ORDER )
	DECLARE_TASK( TASK_ASW_ORDER_RETRY_WAIT )
	DECLARE_TASK( TASK_ASW_REPORT_BLOCKING_ENT )
	DECLARE_TASK( TASK_UNBURROW )
	DECLARE_TASK( TASK_CHECK_FOR_UNBORROW )
	DECLARE_TASK( TASK_BURROW_WAIT )
	DECLARE_TASK( TASK_SET_UNBURROW_IDLE_ACTIVITY )
	DECLARE_TASK( TASK_ASW_WAIT_FOR_ORDER_MOVE )

	DECLARE_ACTIVITY( ACT_MELEE_ATTACK1_HIT )
	DECLARE_ACTIVITY( ACT_MELEE_ATTACK2_HIT )
	DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_SMALL )
	DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_MEDIUM )
	DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_BIG )
	DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_GESTURE )
   	DECLARE_ACTIVITY( ACT_DEATH_FIRE )
	DECLARE_ACTIVITY( ACT_DEATH_ELEC )
	DECLARE_ACTIVITY( ACT_DIE_FANCY )
	DECLARE_ACTIVITY( ACT_BURROW_OUT )
	DECLARE_ACTIVITY( ACT_BURROW_IDLE )

	DECLARE_ANIMEVENT( AE_ALIEN_MELEE_HIT )

	//=========================================================
	// > SCHED_ASW_ALIEN_CHASE_ENEMY
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_ASW_ALIEN_CHASE_ENEMY,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
		//"		TASK_SET_TOLERANCE_DISTANCE		24"
		"		TASK_GET_PATH_TO_ENEMY			300"
		"		TASK_RUN_PATH					0"
		//"		TASK_ASW_ALIEN_ZIGZAG			0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_CAN_RANGE_ATTACK1"
		"		COND_CAN_MELEE_ATTACK1"
		"		COND_CAN_RANGE_ATTACK2"
		"		COND_CAN_MELEE_ATTACK2"
		"		COND_TOO_CLOSE_TO_ATTACK"
		"		COND_TASK_FAILED"
		"		COND_HEAR_DANGER"
	)

	// Same as base melee attack1, minus the TASK_STOP_MOVING
	DEFINE_SCHEDULE
	(
		SCHED_ASW_ALIEN_MELEE_ATTACK1,

		"	Tasks"

		"		TASK_FACE_ENEMY			0"
		"		TASK_ANNOUNCE_ATTACK	1"	// 1 = primary attack
		"		TASK_MELEE_ATTACK1		0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_ENEMY_OCCLUDED"
	);
	DEFINE_SCHEDULE
		(
		SCHED_ASW_ALIEN_SLOW_MELEE_ATTACK1,

		"	Tasks"

		"		TASK_STOP_MOVING		1"
		"		TASK_WAIT				0.1"
		"		TASK_FACE_ENEMY			0"
		"		TASK_ANNOUNCE_ATTACK	1"	// 1 = primary attack
		"		TASK_MELEE_ATTACK1		1"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_ENEMY_DEAD"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_ENEMY_OCCLUDED"
		);

	DEFINE_SCHEDULE
	(
		SCHED_ASW_SPREAD_THEN_HIBERNATE,

		"	Tasks"
		"		TASK_STOP_MOVING				0"
		"		TASK_ASW_SPREAD_THEN_HIBERNATE	0"
		"		TASK_WALK_PATH					0"
		"		TASK_WAIT_FOR_MOVEMENT			0"
		"		TASK_STOP_MOVING				0"
		"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_IDLE_STAND"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_SEE_ENEMY"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
	)

	DEFINE_SCHEDULE
	(
		SCHED_ASW_ORDER_MOVE,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_ASW_RETRY_ORDERS"
		"		TASK_ASW_BUILD_PATH_TO_ORDER	0"
		"		TASK_RUN_PATH			0"
		"		TASK_ASW_WAIT_FOR_ORDER_MOVE	0"		// 0 is spread if fail to build path
		"		TASK_WAIT_PVS			0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_SEE_ENEMY"	// in deference to scripted schedule where the enemy was slammed, thus no COND_NEW_ENEMY
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_SMELL"
		"		COND_PROVOKED"
		"		COND_HEAR_COMBAT"
		"		COND_HEAR_BULLET_IMPACT"
	)

	DEFINE_SCHEDULE
	(
		SCHED_ASW_RETRY_ORDERS,

		"	Tasks"
		//"		TASK_ASW_REPORT_BLOCKING_ENT	0"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_ASW_SPREAD_THEN_HIBERNATE"
		"		TASK_ASW_ORDER_RETRY_WAIT		1"	// this will fail the schedule if we've retried too many times
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_ASW_RETRY_ORDERS"
		"		TASK_ASW_BUILD_PATH_TO_ORDER	1"		// 1 is no spread if fail to build path
		"		TASK_WALK_PATH			9999"
		"		TASK_WAIT_FOR_MOVEMENT	0"		// 0 is spread if fail to build path
		"		TASK_WAIT_PVS			0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_SEE_ENEMY"	// in deference to scripted schedule where the enemy was slammed, thus no COND_NEW_ENEMY
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_SMELL"
		"		COND_PROVOKED"
		"		COND_HEAR_COMBAT"
		"		COND_HEAR_BULLET_IMPACT"
	)
	DEFINE_SCHEDULE
	(
		SCHED_BURROW_WAIT,
		"	Tasks"
		"		TASK_SET_UNBURROW_IDLE_ACTIVITY	0"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_BURROW_WAIT"
		"		TASK_BURROW_WAIT			1"
		"		TASK_SET_SCHEDULE					SCHEDULE:SCHED_WAIT_FOR_CLEAR_UNBORROW"
		""
		"	Interrupts"
		"		COND_TASK_FAILED"
	)

	DEFINE_SCHEDULE
	(
		SCHED_WAIT_FOR_CLEAR_UNBORROW,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_BURROW_WAIT"
		"		TASK_CHECK_FOR_UNBORROW		1"
		"		TASK_SET_SCHEDULE					SCHEDULE:SCHED_BURROW_OUT"
		""
		"	Interrupts"
		"		COND_TASK_FAILED"
	)

	DEFINE_SCHEDULE
	(
		SCHED_BURROW_OUT,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_BURROW_WAIT"
		"		TASK_UNBURROW			0"
		""
		"	Interrupts"
		"		COND_TASK_FAILED"
	)

AI_END_CUSTOM_NPC()

// Behaviour stuff to support act busy
bool CASW_Alien::CreateBehaviors()
{
	AddBehavior( &m_ActBusyBehavior );

	return BaseClass::CreateBehaviors();
}

int CASW_Alien::SelectAlienOrdersSchedule()
{
	int schedule = 0;
	// check if we have any alien orders to follow
	switch (m_AlienOrders)
	{
	case AOT_MoveToIgnoringMarines:
		{
			IgnoreMarines(true);
			schedule = SCHED_ASW_ORDER_MOVE;
		}
		break;
	case AOT_SpreadThenHibernate:
	case AOT_MoveTo:
		{
			IgnoreMarines(false);
			schedule = SCHED_ASW_ORDER_MOVE;
		}
		break;
	case AOT_MoveToNearestMarine:
		{
			float marine_distance;
			// refresh our order target, in case the one passed in by the orders is no longer the nearest
			m_AlienOrderObject = UTIL_ASW_NearestMarine( GetAbsOrigin(), marine_distance );
			IgnoreMarines(false);
			schedule = SCHED_ASW_ORDER_MOVE;
		}
		break;
	case AOT_None:
	default:
		{
			// nothing
		}
	}
	return schedule;
}

int CASW_Alien::SelectSchedule( void )
{
	// If we're supposed to be burrowed, stay there
	if ( m_bStartBurrowed )
		return SCHED_BURROW_WAIT;

	// see if our alien orders want to schedule something
	int order_schedule = SelectAlienOrdersSchedule();
	if (order_schedule > 0)
	{
		//Msg("SelectSchedule picked alien orders\n");
		return order_schedule;
	}

	if ( !BehaviorSelectSchedule() )
	{
	}

	return BaseClass::SelectSchedule();
}

ConVar asw_drone_death_force_pitch( "asw_drone_death_force_pitch", "-10", FCVAR_CHEAT, "Tilt the angle of death forces on alien ragdolls" );
ConVar asw_drone_death_force( "asw_drone_death_force", "5", FCVAR_CHEAT, "Scale for alien death forces" );

#define DRONE_MASS 90.0f

Vector CASW_Alien::CalcDeathForceVector( const CTakeDamageInfo &info )
{
	if ( asw_debug_alien_damage.GetBool() )
	{
		Msg( "Death force pre = %f\n", info.GetDamageForce().Length() );
	}

	if ( !( info.GetDamageType() & DMG_BLAST || info.GetDamageType() & DMG_BURN ) )
	{
		// normalize force for non-explosive weapons, as they each have different fire rates/forces
		// TODO: Tilt force vector upwards a bit

		float flDesiredForceScale = asw_drone_death_force.GetFloat() * 10000.0f;
		float flMassScale = 1.0f;
		if ( VPhysicsGetObject() )
		{
			flMassScale = 1.0f / ( VPhysicsGetObject()->GetMass() / DRONE_MASS );		// using drone's mass as a baseline
		}

		CBaseEntity *pForce = info.GetInflictor();
		if ( !pForce )
		{
			pForce = info.GetAttacker();
		}
		Vector forceVector = vec3_origin;

		if ( pForce )
		{
			forceVector = GetAbsOrigin() - pForce->GetAbsOrigin();
			forceVector.NormalizeInPlace();

			if ( asw_debug_alien_damage.GetBool() )
			{
				Msg( "Death force post = %f flDesiredForceScale=%f flMassScale=%f\n", (forceVector * flDesiredForceScale * flMassScale).Length(), flDesiredForceScale, flMassScale );
			}

			return forceVector * flDesiredForceScale * flMassScale;
		}
		else
		{
			forceVector.x = random->RandomFloat( -1.0f, 1.0f );
			forceVector.y = random->RandomFloat( -1.0f, 1.0f );
			forceVector.z = 0.0;

			if ( asw_debug_alien_damage.GetBool() )
			{
				Msg( "Death force post = %f flDesiredForceScale=%f flMassScale=%f\n", (forceVector * flDesiredForceScale * flMassScale).Length(), flDesiredForceScale, flMassScale );
			}

			return forceVector * (flDesiredForceScale/4) * flMassScale;
		}
	}
	return BaseClass::CalcDeathForceVector( info );
}

void CASW_Alien::BreakAlien( const CTakeDamageInfo &info )
{
	Vector	velocity;
	velocity = CalcDeathForceVector( info )/150;
	if ( velocity == vec3_origin )
		velocity = Vector( RandomFloat( 1, 15 ), RandomFloat( 1, 15 ), 75.0f );
	velocity.z *= 1.25;
	velocity.z += 175.0;
	AngularImpulse	angVelocity = RandomAngularImpulse( -500.0f, 500.0f );
	breakablepropparams_t params( GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity );
	params.impactEnergyScale = 1.0f;
	params.defBurstScale = 100.0f;
	params.defCollisionGroup = COLLISION_GROUP_DEBRIS;
	PropBreakableCreateAll( GetModelIndex(), NULL, params, this, -1, true, true );
}

//=========================================================
// GetDeathActivity - determines the best type of death
// anim to play.
//=========================================================
Activity CASW_Alien::GetDeathActivity ( void )
{
	Activity	deathActivity;
	deathActivity = ACT_DIESIMPLE;

	if ( m_nDeathStyle == kDIE_FANCY )
	{
		if ( m_bOnFire )
			deathActivity = ( Activity ) ACT_DEATH_FIRE;
		else if ( m_bElectroStunned )
			deathActivity = ( Activity ) ACT_DEATH_ELEC;
		else
			deathActivity = ( Activity ) ACT_DIE_FANCY;
	}

	//BaseClass::GetDeathActivity();
	return deathActivity;
}

bool CASW_Alien::CanBecomeRagdoll( void )
{
	MDLCACHE_CRITICAL_SECTION();

	int ragdollSequence = SelectWeightedSequence( ACT_DIERAGDOLL );

	if ( m_nDeathStyle == kDIE_FANCY && !m_bTimeToRagdoll )
		return false;

	//Can't cause we don't have a ragdoll sequence.
	if ( ragdollSequence == ACTIVITY_NOT_AVAILABLE )
		return false;

	if ( GetFlags() & FL_TRANSRAGDOLL )
		return false;

	return true;
}

bool CASW_Alien::CanDoFancyDeath()
{
	if ( m_nDeathStyle == kDIE_BREAKABLE || m_nDeathStyle == kDIE_INSTAGIB )
		return false;

	if ( GetNavType() != NAV_GROUND )
		return false;

	if ( IsCurSchedule( SCHED_BURROW_WAIT, false ) || IsCurSchedule( SCHED_WAIT_FOR_CLEAR_UNBORROW, false ) || IsCurSchedule( SCHED_BURROW_OUT, false ) )
		return false;

	if ( m_hMoveClone.Get() )
		return false;

	if ( m_bOnFire )
	{
		// do i have the custom death activities?
		if ( SelectWeightedSequence ( ( Activity ) ACT_DEATH_FIRE ) == ACTIVITY_NOT_AVAILABLE || RandomFloat() < 0.5f )
			return false;
	}
	else if ( m_bElectroStunned )
	{
		// do i have the custom death activities?
		if ( SelectWeightedSequence ( ( Activity ) ACT_DEATH_ELEC ) == ACTIVITY_NOT_AVAILABLE || RandomFloat() < 0.75f )
			return false;
	}
	// do i have the custom death activities?
	else
	{
		if ( m_fFancyDeathChance > asw_alien_fancy_death_chance.GetFloat() )
			return false;

		if ( SelectWeightedSequence ( ( Activity ) ACT_DIE_FANCY ) == ACTIVITY_NOT_AVAILABLE  )
			return false;
	}

	return true;
}

extern ConVar asw_fist_ragdoll_chance;

void CASW_Alien::Event_Killed( const CTakeDamageInfo &info )
{
	if (asw_debug_alien_damage.GetBool())
		Msg("%f alien killed\n", gpGlobals->curtime);
	if (ASWGameRules())
	{
		ASWGameRules()->AlienKilled(this, info);
	}

	CASW_GameStats.Event_AlienKilled( this, info );

	if ( ASWDirector() )
		ASWDirector()->Event_AlienKilled( this, info );

	if (m_hSpawner.Get())
		SetOwnerEntity(m_hSpawner.Get());

	//bool bRagdollCreated = Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_ELECTRICAL );

	//switch to the dead bodygroup if you are not on fire or electrocuted
	if ( HasDeadBodyGroup() && !m_bOnFire && !m_bElectroStunned )
	{
		SetBodygroup( 0, m_iDeadBodyGroup );
	}

	if ( m_flFrozen >= 0.1f )
	{
		bool bShatter = ( RandomFloat() > 0.01f );
		CreateASWServerStatue( this, COLLISION_GROUP_NONE, info, bShatter );
		BaseClass::Event_Killed( CTakeDamageInfo( info.GetAttacker(), info.GetAttacker(), info.GetDamage(), DMG_GENERIC | DMG_REMOVENORAGDOLL | DMG_PREVENT_PHYSICS_FORCE ) );
		RemoveDeferred();
		return;
	}

	// if we died from an explosion, instagib
	if ( !m_bNeverInstagib && info.GetDamage() > 8.0f && (info.GetDamageType() & DMG_BLAST || info.GetDamageType() & DMG_SONIC) )
	{
		m_nDeathStyle = kDIE_INSTAGIB;
	}
	else if ( !m_bNeverInstagib && !m_bElectroStunned && info.GetDamage() > 20.0f && RandomFloat() > 0.05f ) // if the damage inflicted was a high amount of damage, instagib 95% of the time
	{
		m_nDeathStyle = kDIE_INSTAGIB;
	}
	else  // else see if we can break apart
	{
		// if we can do a fancy death or we don't do a break, we will ragdoll to the split version
		if ( CanDoFancyDeath() )
		{
			m_nDeathStyle = kDIE_FANCY;
		}
		// if our model is set up to break and we aren't on fire or electrocuted, break
		else if ( CanBreak() && RandomFloat() < asw_alien_break_chance.GetFloat() && !m_bElectroStunned && !m_bOnFire )
		{
			m_nDeathStyle = kDIE_BREAKABLE;
			//BreakAlien( info );
		}
		// if we are set to never ragdoll or if we are electrified, but dont have anims or decided not to do them, instagib
		else if ( m_bNeverRagdoll || m_bElectroStunned )
		{
			m_nDeathStyle = kDIE_INSTAGIB;
		}
	}

	if ( !ShouldGib( info ) && !m_bNeverRagdoll )
	{
		const unsigned int nDamageTypesThatCauseHurling = DMG_BLAST | DMG_BLAST_SURFACE;
		SetCollisionGroup(ASW_COLLISION_GROUP_PASSABLE);	// don't block marines by dead bodies
		if ( (info.GetDamageType()	&  nDamageTypesThatCauseHurling)							&&
			  gpGlobals->curtime	>  sm_flLastHurlTime + asw_drone_hurl_interval.GetFloat()	&&
			  random->RandomFloat()	<= asw_drone_hurl_chance.GetFloat() )
		{
			m_nDeathStyle = kDIE_HURL;
			sm_flLastHurlTime = gpGlobals->curtime;
		}
		if ( ( info.GetDamageType() & DMG_CLUB ) && info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE )
		{
			CASW_Marine *pMarine = assert_cast<CASW_Marine*>( info.GetAttacker() );

			if ( pMarine->HasPowerFist() && RandomFloat() < asw_fist_ragdoll_chance.GetFloat() )
			{
				m_nDeathStyle = kDIE_MELEE_THROW;
			}
		}
	}

	DropMoney( info );
	if ( ASWGameRules() )
		ASWGameRules()->DropPowerup( this, info, GetClassname() );

	if ( asw_alien_debug_death_style.GetBool() )
		Msg( "'%s' CASW_Alien::Event_Killed: m_nDeathStyle = %d\n", GetClassname(), m_nDeathStyle );

	BaseClass::Event_Killed(info);
}

void CASW_Alien::SetSpawner(CBaseEntity* spawner)
{
	m_hSpawner = spawner;
}

void CASW_Alien::InputBreakWaitForScript(inputdata_t &inputdata)
{
	if (IsCurSchedule( SCHED_WAIT_FOR_SCRIPT ))
	{
		SetSchedule(SCHED_IDLE_STAND);
	}
}

// set orders for our alien
//   select schedule should activate the appropriate orders
void CASW_Alien::SetAlienOrders(AlienOrder_t Orders, Vector vecOrderSpot, CBaseEntity* pOrderObject)
{
	m_AlienOrders = Orders;
	m_vecAlienOrderSpot = vecOrderSpot;	// unused currently
	m_AlienOrderObject = pOrderObject;

	if (Orders == AOT_None)
	{
		ClearAlienOrders();
		return;
	}

	ForceDecisionThink();	// todo: stagger the decision time if at start of mission?

	//Msg("Drone recieved orders\n");
}

AlienOrder_t CASW_Alien::GetAlienOrders()
{
	return m_AlienOrders;
}

void CASW_Alien::ClearAlienOrders()
{
	//Msg("Drone orders cleared\n");
	m_AlienOrders = AOT_None;
	m_vecAlienOrderSpot = vec3_origin;
	m_AlienOrderObject = NULL;
	m_bIgnoreMarines = false;
	m_bFailedMoveTo = false;
}

void CASW_Alien::GatherConditions()
{
	BaseClass::GatherConditions();

	if (m_bIgnoreMarines)	// todo: 'proper' way to ignore conditions?  (using SetIgnoreConditions on receiving orders doesn't work)
	{
		ClearCondition(COND_CAN_MELEE_ATTACK1);
		ClearCondition(COND_CAN_MELEE_ATTACK2);
		ClearCondition(COND_CAN_RANGE_ATTACK1);
		ClearCondition(COND_CAN_RANGE_ATTACK2);
		ClearCondition(COND_ENEMY_DEAD);
		ClearCondition(COND_HEAR_BULLET_IMPACT);
		ClearCondition(COND_HEAR_COMBAT);
		ClearCondition(COND_HEAR_DANGER);
		ClearCondition(COND_HEAR_PHYSICS_DANGER);
		ClearCondition(COND_NEW_ENEMY);
		ClearCondition(COND_PROVOKED);
		ClearCondition(COND_SEE_ENEMY);
		ClearCondition(COND_SEE_FEAR);
		ClearCondition(COND_SMELL);
		ClearCondition(COND_HEAVY_DAMAGE);
		ClearCondition(COND_LIGHT_DAMAGE);
		ClearCondition(COND_RECEIVED_ORDERS);
	}

	if (!m_bFlinches)
	{
		ClearCondition(COND_HEAVY_DAMAGE);
		ClearCondition(COND_LIGHT_DAMAGE);
	}

	if (HasCondition(COND_NEW_ENEMY)
		&& m_AlienOrders != AOT_MoveToIgnoringMarines)	// if we're not ignoring marines, finish our orders once we spot an enemy
	{
		ClearAlienOrders();
	}
}

// if we arrive at our destination, clear our orders
void CASW_Alien::OnMovementComplete()
{
	if ( ShouldClearOrdersOnMovementComplete() )
	{
		ClearAlienOrders();
	}

	BaseClass::OnMovementComplete();
}

bool CASW_Alien::ShouldClearOrdersOnMovementComplete()
{
	if (m_AlienOrders != AOT_None)
	{
		if (m_AlienOrders == AOT_SpreadThenHibernate && m_bFailedMoveTo)
		{
			// should try to repath?
			// or go to a schedule where we wait X seconds, then try to repath?
		}
		// check if we finished trying to chase a marine, but don't have an enemy yet
		//		this means the marine probably moved somewhere else and we need to chase after him again
		if (m_AlienOrders == AOT_MoveToNearestMarine && !GetEnemy())
		{
			float marine_distance;
			CBaseEntity *pMarine = UTIL_ASW_NearestMarine(GetAbsOrigin(), marine_distance);
			if (pMarine)
			{
				// don't clear orders, this'll make the alien's selectschedule do the marine chase again
				return false;
			}
		}
	}

	return true;
};

void CASW_Alien::IgnoreMarines(bool bIgnoreMarines)
{
	static int g_GeneralConditions[] =
	{
		COND_CAN_MELEE_ATTACK1,
		COND_CAN_MELEE_ATTACK2,
		COND_CAN_RANGE_ATTACK1,
		COND_CAN_RANGE_ATTACK2,
		COND_ENEMY_DEAD,
		COND_HEAR_BULLET_IMPACT,
		COND_HEAR_COMBAT,
		COND_HEAR_DANGER,
		COND_HEAR_PHYSICS_DANGER,
		COND_NEW_ENEMY,
		COND_PROVOKED,
		COND_SEE_ENEMY,
		COND_SEE_FEAR,
		COND_SMELL,
	};

	static int g_DamageConditions[] =
	{
		COND_HEAVY_DAMAGE,
		COND_LIGHT_DAMAGE,
		COND_RECEIVED_ORDERS,
	};

	ClearIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) );
	ClearIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) );

	if (bIgnoreMarines)
	{
		SetIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) );
		SetIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) );
		m_bIgnoreMarines = true;
	}
}

// we're blocking a fellow alien from spawning, let's move a short distance
void CASW_Alien::MoveAside()
{
	if (!GetEnemy() && !IsMoving())
	{
		// random nearby position
		if ( !GetNavigator()->SetWanderGoal( 90, 200 ) )
		{
			if ( !GetNavigator()->SetRandomGoal( 150.0f ) )
			{
				return;	// couldn't find a wander spot
			}
		}
		//SetSchedule(SCHED_IDLE_WALK);
	}
}

void CASW_Alien::ASW_Ignite( float flFlameLifetime, float flSize, CBaseEntity *pAttacker, CBaseEntity *pDamagingWeapon )
{
	if (AllowedToIgnite())
	{
		if( IsOnFire() )
			return;

		AddFlag( FL_ONFIRE );
		m_bOnFire = true;
		if (ASWBurning())
			ASWBurning()->BurnEntity(this, pAttacker, flFlameLifetime, 0.4f, 2.5f * 0.4f, pDamagingWeapon );	// 2.5 dps, applied every 0.4 seconds

		m_OnIgnite.FireOutput( this, this );
	}
}

void CASW_Alien::Extinguish()
{
	m_bOnFire = false;
	if (ASWBurning())
		ASWBurning()->Extinguish(this);
	RemoveFlag( FL_ONFIRE );
}

bool CASW_Alien::IsMeleeAttacking()
{
	return (GetTask() && (GetTask()->iTask == TASK_MELEE_ATTACK1));
}

bool CASW_Alien::Knockback(Vector vecForce)
{
	/*
	Vector end = GetAbsOrigin() + vecForce;	// todo: divide by mass, etc
	trace_t tr;
	UTIL_TraceEntity( this, GetAbsOrigin(), end, MASK_NPCSOLID, &tr );
	if (tr.allsolid)
		return false;

	if( tr.fraction > 0 )
	{
		SetAbsOrigin(tr.endpos);
		return true;
	}
	return false;*/
	Vector newVel = GetAbsVelocity();
	SetAbsVelocity(newVel + vecForce);
	SetGroundEntity( NULL );
	return true;
}

void CASW_Alien::UpdateEfficiency( bool bInPVS )
{
	// Sleeping NPCs always dormant
	if ( GetSleepState() != AISS_AWAKE )
	{
		SetEfficiency( AIE_DORMANT );
		return;
	}

	m_bInChoreo = ( GetState() == NPC_STATE_SCRIPT || IsCurSchedule( SCHED_SCENE_GENERIC, false ) );

#ifndef _RETAIL
	if ( !(ai_use_think_optimizations.GetBool() && ai_use_efficiency.GetBool()) )
	{
		SetEfficiency( AIE_NORMAL );
		SetMoveEfficiency( AIME_NORMAL );
		return;
	}
#endif

	bool bInVisibilityPVS = ( UTIL_FindClientInVisibilityPVS( edict() ) != NULL );

	//if ( bInPVS && MarineNearby(1024) )
	if ( bInPVS && MarineCanSee(384, 1.0f) )
	{
		SetMoveEfficiency( AIME_NORMAL );
	}
	else
	{
		SetMoveEfficiency( AIME_EFFICIENT );
	}

	//---------------------------------

	if ( !IsRetail() && ai_efficiency_override.GetInt() > AIE_NORMAL && ai_efficiency_override.GetInt() <= AIE_DORMANT )
	{
		SetEfficiency( (AI_Efficiency_t)ai_efficiency_override.GetInt() );
		return;
	}

	// Some conditions will always force normal
	if ( gpGlobals->curtime - GetLastAttackTime() < .15 )
	{
		SetEfficiency( AIE_NORMAL );
		return;
	}

	bool bFramerateOk = ( gpGlobals->frametime < ai_frametime_limit.GetFloat() );

	if ( IsForceGatherConditionsSet() ||
		 gpGlobals->curtime - GetLastAttackTime() < .2 ||
		 gpGlobals->curtime - m_flLastDamageTime < .2 ||
		 ( GetState() < NPC_STATE_IDLE || GetState() > NPC_STATE_SCRIPT ) ||
		 ( ( bInPVS || bInVisibilityPVS ) &&
		   ( ( GetTask() && !TaskIsRunning() ) ||
			 GetTaskInterrupt() > 0 ||
			 m_bInChoreo ) ) )
	{
		SetEfficiency( ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT );
		return;
	}

	SetEfficiency( ( bFramerateOk ) ? AIE_EFFICIENT : AIE_VERY_EFFICIENT );
}

void CASW_Alien::OnRestore()
{
	BaseClass::OnRestore();
	m_LagCompensation.Init(this);
}

// checks if a marine can see us
//  caches the results and won't recheck unless the specified interval has passed since the last check
bool CASW_Alien::MarineCanSee(int padding, float interval)
{
	if (gpGlobals->curtime >= m_fLastMarineCanSeeTime + interval)
	{
		bool bCorpseCanSee = false;
		m_bLastMarineCanSee = (UTIL_ASW_AnyMarineCanSee(GetAbsOrigin(), padding, bCorpseCanSee) != NULL) || bCorpseCanSee;
		m_fLastMarineCanSeeTime = gpGlobals->curtime;
	}
	return m_bLastMarineCanSee;
}

void CASW_Alien::SetHealthByDifficultyLevel()
{
	// filled in by subclasses
	SetMaxHealth(GetMaxHealth() * m_flHealthScale + m_iHealthBonus);
	SetHealth(GetHealth() * m_flHealthScale + m_iHealthBonus);
}

void CASW_Alien::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
{
	return;	// use ASW_Ignite instead
}

bool CASW_Alien::ShouldMoveSlow() const
{
	return (gpGlobals->curtime < m_fHurtSlowMoveTime);
}

bool CASW_Alien::ModifyAutoMovement( Vector &vecNewPos )
{
	// melee auto movement on the drones seems way too fast
	float fFactor = 1.0f;
	if ( ShouldMoveSlow() )
	{
		if ( m_bElectroStunned.Get() )
		{
			fFactor *= asw_alien_stunned_speed.GetFloat() * 0.1f;
		}
		else
		{
			fFactor *= asw_alien_hurt_speed.GetFloat() * 0.1f;
		}
		Vector vecRelPos = vecNewPos - GetAbsOrigin();
		vecRelPos *= fFactor;
		vecNewPos = GetAbsOrigin() + vecRelPos;
		return true;
	}
	return false;
}

float CASW_Alien::GetIdealSpeed() const
{
	// if the alien is hurt, move slower
	if ( ShouldMoveSlow() )
	{
		if ( m_bElectroStunned.Get() )
		{
			return BaseClass::GetIdealSpeed() * asw_alien_stunned_speed.GetFloat() * m_flSpeedScale;
		}
		else
		{
			return BaseClass::GetIdealSpeed() * asw_alien_hurt_speed.GetFloat() * m_flSpeedScale;
		}
	}

	return BaseClass::GetIdealSpeed() * m_flSpeedScale;
}

void CASW_Alien::DropMoney( const CTakeDamageInfo &info )
{
	if ( !asw_drop_money.GetBool() || !asw_money.GetBool() )
		return;

	int iMoneyCount = GetMoneyCount( info );
	for (int i=0;i<iMoneyCount;i++)
	{
		CASW_Pickup_Money* pMoney = (CASW_Pickup_Money *)CreateEntityByName( "asw_pickup_money" );
		UTIL_SetOrigin( pMoney, WorldSpaceCenter() );
		pMoney->Spawn();
		Vector vel = -info.GetDamageForce();
		vel.NormalizeInPlace();
		vel *= RandomFloat( 30, 50 );
		vel += RandomVector( -20, 20 );
		vel.z = RandomFloat( 30, 50 );
		if ( iMoneyCount >= 3 )	// if we're dropping a lot of money, make it fly up in the air more
		{
			vel.z += RandomFloat( 60, 80 );
		}
		pMoney->SetAbsVelocity( vel );
	}
}

int CASW_Alien::GetMoneyCount( const CTakeDamageInfo &info )
{
	if ( RandomFloat() < asw_alien_money_chance.GetFloat() )
	{
		return 1;
	}
	return 0;
}

void CASW_Alien::ElectroStun( float flStunTime )
{
	if (!m_bTeslable)
		return;

	if (m_fHurtSlowMoveTime < gpGlobals->curtime + flStunTime)
		m_fHurtSlowMoveTime = gpGlobals->curtime + flStunTime;

	m_bElectroStunned = true;

	if ( ASWGameResource() )
	{
		ASWGameResource()->m_iElectroStunnedAliens++;
	}

	// can't jump after being elecrostunned
	CapabilitiesRemove( bits_CAP_MOVE_JUMP );
}

void CASW_Alien::ForceFlinch( const Vector &vecSrc )
{
	SetCondition( COND_HEAVY_DAMAGE );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CASW_AlienVolley *CASW_Alien::GetVolley( const char *pszVolleyName )
{
	for( int nIndex = 0; nIndex < m_volleys.Count(); nIndex++ )
	{
		if ( m_volleys[ nIndex ].m_strName == pszVolleyName )
		{
			return &m_volleys[ nIndex ];
		}
	}
	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CASW_Alien::GetVolleyIndex( const char *pszVolleyName )
{
	for( int nIndex = 0; nIndex < m_volleys.Count(); nIndex++ )
	{
		if ( m_volleys[ nIndex ].m_strName == pszVolleyName )
		{
			return nIndex;
		}
	}
	return -1;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CASW_AlienShot *CASW_Alien::GetShot( const char *pszShotName )
{
	for( int nIndex = 0; nIndex < m_shots.Count(); nIndex++ )
	{
		if ( m_shots[ nIndex ].m_strName == pszShotName )
		{
			return &m_shots[ nIndex ];
		}
	}
	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CASW_Alien::GetShotIndex( const char *pszShotName )
{
	for( int nIndex = 0; nIndex < m_shots.Count(); nIndex++ )
	{
		if ( m_shots[ nIndex ].m_strName == pszShotName )
		{
			return nIndex;
		}
	}
	return -1;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Alien::CreateShot( const char *pszShotName, const CASW_AlienShot *pShot )
{
	CASW_AlienShot *pNewShot = GetShot( pszShotName );
	if ( !pNewShot )
	{
		int nIndex = m_shots.AddToTail();
		pNewShot = &m_shots[ nIndex ];
	}

	*pNewShot = *pShot;
	pNewShot->m_strName = pszShotName;

	// Precache resources
	if ( !pShot->m_strModel.IsEmpty() )
		PrecacheModel( pShot->m_strModel );

	if ( !pShot->m_strSound_spawn.IsEmpty() )
		PrecacheScriptSound( pShot->m_strSound_spawn );

	if ( !pShot->m_strSound_hitNPC.IsEmpty() )
		PrecacheScriptSound( pShot->m_strSound_hitNPC );

	if ( !pShot->m_strSound_hitWorld.IsEmpty() )
		PrecacheScriptSound( pShot->m_strSound_hitWorld );

	if ( !pShot->m_strParticles_trail.IsEmpty() )
		PrecacheParticleSystem( pShot->m_strParticles_trail );

	if ( !pShot->m_strParticles_hit.IsEmpty() )
		PrecacheParticleSystem( pShot->m_strParticles_hit );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Alien::CreateVolley( const char *pszVolleyName, const CASW_AlienVolley *pVolley )
{
	CASW_AlienVolley *pNewVolley = GetVolley( pszVolleyName );
	if ( !pNewVolley )
	{
		int nIndex = m_volleys.AddToTail();
		pNewVolley = &m_volleys[ nIndex ];
	}

	*pNewVolley = *pVolley;
	pNewVolley->m_strName = pszVolleyName;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Alien::CalcMissileVelocity( const Vector &vTargetPos, Vector &vVelocity, float flSpeed, float flAngle )
{
	float flRadians = DEG2RAD( flAngle );
	Vector vForward = ( vTargetPos - GetAbsOrigin() );
	vForward.NormalizeInPlace();
	Vector vRight = vForward.Cross( Vector( 0, 0, 1 ) );
	Vector vDir = vForward * cos( flRadians ) + vRight * sin( flRadians );

	vVelocity = vDir * flSpeed;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Alien::UpdateRangedAttack()
{
	if ( m_nVolleyType < 0 )
		return;

	Vector vecSpawn = GetAbsOrigin() + Vector( 0.0f, 0.0f, 15.0f );
	QAngle vecAngles = QAngle( 0.0f, 0.0f, 0.0f );
	int iAttachment = LookupAttachment( "mouth" );
	if ( iAttachment > 0 )
		GetAttachment( iAttachment, vecSpawn, vecAngles );

	Vector vForward = ( m_vecRangeAttackTargetPosition- GetAbsOrigin() );
	vForward.NormalizeInPlace();
	Vector vRight = vForward.Cross( Vector( 0, 0, 1 ) );

	int nNumFinished = 0;
	CASW_AlienVolley *pVolley = &m_volleys[ m_nVolleyType ];
	for( int nRound = 0; nRound < pVolley->m_rounds.Count(); nRound++ )
	{
		CASW_AlienVolleyRound &round = pVolley->m_rounds[ nRound ];
		if ( round.m_nShot_type < 0 )
		{
			nNumFinished++;
			continue;
		}

		// has round started yet?
		if ( m_flRangeAttackStartTime + round.m_flTime > gpGlobals->curtime )
			continue;

		// is round finished?
		float flEndTime = m_flRangeAttackStartTime + round.m_flTime + round.m_nNumShots * round.m_flShotDelay;
		if ( ( flEndTime < gpGlobals->curtime ) && ( flEndTime < m_flRangeAttackLastUpdateTime ) )
		{
			nNumFinished++;
			continue;
		}

		for( int i = 0; i < round.m_nNumShots; i++ )
		{
			float flShotTime = m_flRangeAttackStartTime + round.m_flTime + i * round.m_flShotDelay;
			if ( ( flShotTime > m_flRangeAttackLastUpdateTime ) && ( flShotTime <= gpGlobals->curtime ) )
			{
				Vector vVelocity;
				CASW_AlienShot &shot = m_shots[ round.m_nShot_type ];
				float flPercent = ( round.m_nNumShots > 1 ) ? ( ( float )i / ( float )( round.m_nNumShots - 1 ) ) : 0.0f;
				float flRadians = DEG2RAD( Lerp( flPercent, round.m_flStartAngle, round.m_flEndAngle ) );
				Vector vDir = vForward * cos( flRadians ) + vRight * sin( flRadians );
				vVelocity = vDir * round.m_flSpeed;
				Vector vStart = vecSpawn + vRight * round.m_flHorizontalOffset;
				CASW_Missile_Round::Missile_Round_Create( shot, vStart, vecAngles, vVelocity, this );
			}
		}
	}

	if ( nNumFinished >= pVolley->m_rounds.Count() )
	{
		m_nVolleyType = -1;
		return;
	}

	m_flRangeAttackLastUpdateTime = gpGlobals->curtime;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Alien::LaunchMissile( const char *pszVolleyName, Vector &vTargetPosition )
{
	m_nVolleyType = GetVolleyIndex( pszVolleyName );
	if ( m_nVolleyType < 0 )
	{
		//FIXME: add warning
		return;
	}

	m_flRangeAttackStartTime = gpGlobals->curtime;
	m_flRangeAttackLastUpdateTime = -1.0f;
	m_vecRangeAttackTargetPosition = vTargetPosition;
	UpdateRangedAttack();
}
void CASW_Alien::AddBehaviorParam( const char *pszParmName, int nDefaultValue )
{
	CUtlSymbol	ParmName = CAI_ASW_Behavior::GetSymbol( pszParmName );

	m_BehaviorParms.Insert( ParmName, nDefaultValue );
}

int CASW_Alien::GetBehaviorParam( CUtlSymbol ParmName )
{
	int nIndex = m_BehaviorParms.Find( ParmName );

	if ( m_BehaviorParms.IsValidIndex( nIndex ) == true )
	{
		return m_BehaviorParms.Element( nIndex );
	}

	return 0;
}

void CASW_Alien::SetBehaviorParam( CUtlSymbol ParmName, int nValue )
{
	if ( ParmName.IsValid() == false )
	{
		return;
	}

	int nIndex = m_BehaviorParms.Find( ParmName );
	if ( m_BehaviorParms.IsValidIndex( nIndex ) == true )
	{
		if ( m_BehaviorParms.Element( nIndex ) != nValue )
		{
			m_BehaviorParms.Element( nIndex ) = nValue;
			m_bBehaviorParameterChanged = true;
		}
	}
	else
	{
		Assert( 0 );
	}
}

void CASW_Alien::OnChangeRunningBehavior( CAI_BehaviorBase *pOldBehavior, CAI_BehaviorBase *pNewBehavior )
{
	BaseClass::OnChangeRunningBehavior( pOldBehavior, pNewBehavior );

	m_pPreviousBehavior = m_pPreviousBehavior;
}

void CASW_Alien::SendBehaviorEvent( CBaseEntity *pInflictor, BehaviorEvent_t Event, int nParm, bool bToAllBehaviors )
{
	if ( bToAllBehaviors )
	{
		for ( int i = 0; i < m_Behaviors.Count(); i++)
		{
			CAI_ASW_Behavior	*pBehavior = static_cast < CAI_ASW_Behavior * >(  m_Behaviors[ i ] );

			pBehavior->HandleBehaviorEvent( pInflictor, Event, nParm );
		}
	}
	else
	{
		CAI_ASW_Behavior	*pBehavior = GetPrimaryASWBehavior();
		if ( pBehavior == NULL )
		{	// we have probably died, try sending this to the last behavior
			pBehavior = static_cast < CAI_ASW_Behavior * >( m_pPreviousBehavior );
		}

		if ( pBehavior != NULL )
		{
			pBehavior->HandleBehaviorEvent( pInflictor, Event, nParm );
		}
		else
		{
			// some how the boomer is coming back with having no primary behavior, which I don't understand.
			// if you see this assert fire, please let RJ know.
			Assert( 0 );
		}
	}
}
void CASW_Alien::BuildScheduleTestBits()
{
	CAI_ASW_CombatStunBehavior *pBehavior = NULL;

	// Check for combat stun behavior, or, if we're not using behaviors, we'll fall back to the combat stun schedule in CASW_Alien
	if ( GetBehavior( &pBehavior ) && pBehavior != m_pPrimaryBehavior )
	{
		SetCustomInterruptCondition( COND_ASW_BEGIN_COMBAT_STUN );
	}

	SetCustomInterruptCondition( COND_BEHAVIOR_PARAMETERS_CHANGED );

	if ( m_pFlinchBehavior )
	{
		SetCustomInterruptCondition( COND_ASW_FLINCH );
	}

	BaseClass::BuildScheduleTestBits();
}
void CASW_Alien::StartTouch( CBaseEntity *pOther )
{
	BaseClass::StartTouch(pOther);

	CAI_ASW_Behavior *pCurrent = GetPrimaryASWBehavior();
	if ( pCurrent )
	{
		pCurrent->StartTouch( pOther );
	}
}
CAI_ASW_Behavior* CASW_Alien::GetPrimaryASWBehavior()
{
	return assert_cast<CAI_ASW_Behavior*>( GetPrimaryBehavior() );
}

void CASW_Alien::HandleAnimEvent( animevent_t *pEvent )
{

	int nEvent = pEvent->Event();
	CTakeDamageInfo info;
	if ( nEvent == AE_NPC_RAGDOLL )
	{
		m_bTimeToRagdoll = true;
		// anim event is telling us to ragdoll.  If we aren't on fire, break instead
		if ( m_nDeathStyle == kDIE_FANCY && !m_bOnFire )
		{
			m_nDeathStyle = kDIE_BREAKABLE;
			//BreakAlien( info );
		}
		//BecomeRagdollOnClient(GetAbsOrigin());
		//return;
	}

	CAI_ASW_Behavior	*pBehavior = GetPrimaryASWBehavior();
	if ( pBehavior && pBehavior->BehaviorHandleAnimEvent( pEvent ) )
		return;

	BaseClass::HandleAnimEvent( pEvent );
}

int	CASW_Alien::DrawDebugTextOverlays()
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT)
	{
		NDebugOverlay::EntityText( entindex(),text_offset,CFmtStr( "Freeze amt.: %f", m_flFrozen.Get() ),0 );
		text_offset++;
		NDebugOverlay::EntityText( entindex(),text_offset,CFmtStr( "Freeze time: %f", m_flFrozenTime - gpGlobals->curtime ),0 );
		text_offset++;
	}
	return text_offset;
}

void CASW_Alien::InputSetMoveClone( inputdata_t &inputdata )
{
	SetMoveClone( inputdata.value.StringID(), inputdata.pActivator );
}

void CASW_Alien::SetMoveClone( string_t EntityName, CBaseEntity *pActivator )
{
	CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, EntityName, NULL, pActivator );

	if ( EntityName != NULL_STRING && pEnt == NULL )
	{
		Msg( "Entity %s(%s) has bad move clone %s\n", STRING(m_iClassname), GetDebugName(), STRING(EntityName) );
	}
	else
	{
		// make sure there isn't any ambiguity
		if ( gEntList.FindEntityByName( pEnt, EntityName, NULL, pActivator ) )
		{
			Msg( "Entity %s(%s) is ambiguously move cloned to %s, because there is more than one entity by that name.\n", STRING(m_iClassname), GetDebugName(), STRING(EntityName) );
		}
		SetMoveClone( pEnt );
	}
}

void CASW_Alien::SetMoveClone( CBaseEntity *pEnt )
{
	m_hMoveClone = pEnt;

	if ( pEnt )
	{
		matrix3x4_t entityToWorld = EntityToWorldTransform();
		matrix3x4_t otherToWorld = pEnt->EntityToWorldTransform();

		matrix3x4_t temp;
		MatrixInvert( otherToWorld, temp );
		ConcatTransforms( temp, entityToWorld, m_moveCloneOffset );
	}
}

bool CASW_Alien::OverrideMove( float flInterval )
{
	if ( m_hMoveClone.Get() )
	{
		matrix3x4_t otherToWorld = m_hMoveClone->EntityToWorldTransform();
		matrix3x4_t temp;
		ConcatTransforms( otherToWorld, m_moveCloneOffset, temp );
		SetLocalTransform( temp );
		return true;
	}
	return false;
}

void CASW_Alien::ClearBurrowPoint( const Vector &origin )
{
	CBaseEntity *pEntity = NULL;
	float		flDist;
	Vector		vecSpot, vecCenter, vecForce;

	//Cause a ruckus
	//UTIL_ScreenShake( origin, 1.0f, 80.0f, 1.0f, 256.0f, SHAKE_START );

	//Iterate on all entities in the vicinity.
	for ( CEntitySphereQuery sphere( origin, 128 ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
	{
		//if ( pEntity->m_takedamage != DAMAGE_NO && pEntity->Classify() != CLASS_PLAYER && pEntity->VPhysicsGetObject() )
		if ( pEntity->Classify() != CLASS_PLAYER && pEntity->VPhysicsGetObject() )
		{
			vecSpot	 = pEntity->BodyTarget( origin );
			vecForce = ( vecSpot - origin ) + Vector( 0, 0, 16 );

			// decrease damage for an ent that's farther from the bomb.
			flDist = VectorNormalize( vecForce );

			//float mass = pEntity->VPhysicsGetObject()->GetMass();
			CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1.0f, 1.0f, 1.0f ), &vecCenter );

			if ( flDist <= 128.0f )
			{
				pEntity->VPhysicsGetObject()->Wake();
				pEntity->VPhysicsGetObject()->ApplyForceOffset( vecForce * 250.0f, vecCenter );
			}
		}
	}
}

void CASW_Alien::Unburrow( void )
{
	m_bStartBurrowed = false;

	ClearBurrowPoint( m_vecUnburrowEndPoint );	// physics blast anything in the way out of the way

	//Become solid again and visible
	m_spawnflags &= ~SF_NPC_GAG;
	RemoveSolidFlags( FSOLID_NOT_SOLID );
	m_takedamage	= DAMAGE_YES;

	RemoveEffects( EF_NODRAW );
	RemoveFlag( FL_NOTARGET );

	SetIdealActivity( m_UnburrowActivity );

	//If we have an enemy, come out facing them
	/*
	if ( GetEnemy() )
	{
	Vector	dir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
	VectorNormalize(dir);

	QAngle angles = GetAbsAngles();
	angles[ YAW ] = UTIL_VecToYaw( dir );
	SetLocalAngles( angles );
	}*/
}

void CASW_Alien::SetUnburrowActivity( string_t iszActivityName )
{
	m_iszUnburrowActivityName = iszActivityName;
}

void CASW_Alien::SetUnburrowIdleActivity( string_t iszActivityName )
{
	m_iszUnburrowIdleActivityName = iszActivityName;
}

void CASW_Alien::LookupBurrowActivities()
{
	if ( m_iszUnburrowActivityName == NULL_STRING )
	{
		m_UnburrowActivity = (Activity) ACT_BURROW_OUT;
	}
	else
	{
		m_UnburrowActivity = (Activity) LookupActivity( STRING( m_iszUnburrowActivityName ) );
		if ( m_UnburrowActivity == ACT_INVALID )
		{
			Warning( "Unknown unburrow activity %s", STRING( m_iszUnburrowActivityName ) );
			if ( m_hSpawner.Get() )
			{
				Warning( "  Spawner is: %d %s at %f %f %f\n", m_hSpawner->entindex(), m_hSpawner->GetDebugName(), VectorExpand( m_hSpawner->GetAbsOrigin() ) );
			}
			m_UnburrowActivity = (Activity) ACT_BURROW_OUT;
		}
	}

	if ( m_iszUnburrowIdleActivityName == NULL_STRING )
	{
		m_UnburrowIdleActivity = (Activity) ACT_BURROW_IDLE;
	}
	else
	{
		m_UnburrowIdleActivity = (Activity) LookupActivity( STRING( m_iszUnburrowIdleActivityName ) );
		if ( m_UnburrowIdleActivity == ACT_INVALID )
		{
			Warning( "Unknown unburrow idle activity %s", STRING( m_iszUnburrowIdleActivityName ) );
			if ( m_hSpawner.Get() )
			{
				Warning( "  Spawner is: %d %s at %f %f %f\n", m_hSpawner->entindex(), m_hSpawner->GetDebugName(), VectorExpand( m_hSpawner->GetAbsOrigin() ) );
			}
			m_UnburrowIdleActivity = (Activity) ACT_BURROW_IDLE;
		}
	}
}

class CASW_Trace_Filter_Disable_Collision_With_Traps : public CTraceFilterEntitiesOnly
{
public:
	// It does have a base, but we'll never network anything below here..
	DECLARE_CLASS_NOBASE( CASW_Trace_Filter_Disable_Collision_With_Traps );

	CASW_Trace_Filter_Disable_Collision_With_Traps( IHandleEntity *passentity, int collisionGroup )
		: m_pPassEnt(passentity), m_collisionGroup(collisionGroup)
	{
	}

	virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
	{
		if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
			return false;

		// Don't test if the game code tells us we should ignore this collision...
		CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
		CBaseEntity *pEntPass = EntityFromEntityHandle( m_pPassEnt );

		// don't hurt ourself
		if ( pEntPass == pEntity )
			return false;

		Class_T entClass = pEntity->Classify();

		if ( entClass == CLASS_ASW_SENTRY_BASE ||
			entClass == CLASS_ASW_TESLA_TRAP
			)
		{
			PhysDisableEntityCollisions( pEntPass, pEntity );
		}

		return false;	// keep iterating over entities in the trace ray
	}

public:
	IHandleEntity		*m_pPassEnt;
	int					m_collisionGroup;
};

// if the alien is stuck inside any player set traps, this will disable collision between them
void CASW_Alien::CheckForBlockingTraps()
{
	Ray_t ray;
	trace_t tr;
	ray.Init( GetAbsOrigin() + Vector( 0, 0, 1 ), GetAbsOrigin(), GetHullMins(), GetHullMaxs() );

	CASW_Trace_Filter_Disable_Collision_With_Traps traceFilter( this, GetCollisionGroup() );
	enginetrace->TraceRay( ray, PhysicsSolidMaskForEntity(), &traceFilter, &tr );
}

void CASW_Alien::SetDefaultEyeOffset()
{
	m_vDefaultEyeOffset = vec3_origin;
	m_vDefaultEyeOffset.z = ( WorldAlignMins().z + WorldAlignMaxs().z ) * 0.75f;

	SetViewOffset( m_vDefaultEyeOffset );
}


text:
module RRSchedule
  class Rule
    attr_accessor :wday, :game_times, :fields

    def initialize(wday: 1, game_times: [ "7:00 PM" ], fields: [ "Field #1" ])
      self.wday       = wday
      self.game_times = Array(game_times)
      self.fields     = Array(fields)
    end

    def wday=(wday)
      @wday = wday ? wday : 1
      raise "Rule#wday must be between 0 and 6 (given #{@wday})" unless (0..6).include?(@wday)
    end

    #Array of game times where games are played. Must be valid DateTime objects in the string form
    def game_times=(game_times)
      @game_times = game_times.collect do |game_time|
        begin
          DateTime.parse(game_time)
        rescue
          raise "game times must be valid time representations in string form (e.g. 3:00 PM, 11:00 AM, 18:20, etc)"
        end
      end
    end

    def <=>(other)
      if self.wday == other.wday
        DateTime.parse(self.game_times.first.to_s) <=> DateTime.parse(other.game_times.first.to_s)
      else
        self.wday <=> other.wday
      end
    end
  end
end

redpajama_set_name: RedPajamaGithub