hlsdk-xash3d/dlls/cthulhu/Stukabat.cpp

1952 lines
44 KiB
C++
Executable File

/***
*
* Copyright (c) 1999, 2000 Valve LLC. All rights reserved.
*
* This product contains software technology licensed from Id
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
* All Rights Reserved.
*
* This source code contains proprietary and confidential information of
* Valve LLC and its suppliers. Access to this code is restricted to
* persons who have executed a written SDK license with Valve. Any access,
* use or distribution of this code by or to any unlicensed person is illegal.
*
****/
#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD )
#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "schedule.h"
#include "flyingmonster.h"
#include "nodes.h"
#include "soundent.h"
#include "animation.h"
#include "effects.h"
#include "weapons.h"
#include "stukagrenade.h"
#include "Stukabat.h"
LINK_ENTITY_TO_CLASS( monster_stukabat, CStukabat );
LINK_ENTITY_TO_CLASS( info_node_stukabat, CStukabatNode );
//=========================================================
// stukabat nodes
//=========================================================
TYPEDESCRIPTION CStukabatNode::m_SaveData[] =
{
DEFINE_FIELD( CStukabatNode, mbInUse, FIELD_BOOLEAN ),
};
IMPLEMENT_SAVERESTORE( CStukabatNode, CBaseEntity );
//=========================================================
// nodes start out as ents in the world. As they are spawned,
// the node info is recorded then the ents are discarded.
//=========================================================
void CStukabatNode :: KeyValue( KeyValueData *pkvd )
{
CBaseEntity::KeyValue( pkvd );
}
//=========================================================
//=========================================================
void CStukabatNode :: Spawn( void )
{
pev->movetype = MOVETYPE_NONE;
pev->solid = SOLID_NOT;// always solid_not
mbInUse = false;
pev->classname = MAKE_STRING("info_node_stukabat");
}
//=========================================================
// stukabat
//=========================================================
#define SEARCH_RETRY 16
#define STUKABAT_SPEED 200
extern CGraph WorldGraph;
#define STUKABAT_AE_BOMB 1
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
TYPEDESCRIPTION CStukabat::m_SaveData[] =
{
DEFINE_FIELD( CStukabat, m_SaveVelocity, FIELD_VECTOR ),
DEFINE_FIELD( CStukabat, m_idealDist, FIELD_FLOAT ),
DEFINE_FIELD( CStukabat, m_flNextMeleeAttack, FIELD_FLOAT ),
DEFINE_FIELD( CStukabat, m_flNextRangedAttack, FIELD_FLOAT ),
//DEFINE_FIELD( CStukabat, m_bOnAttack, FIELD_BOOLEAN ),
DEFINE_FIELD( CStukabat, m_flMaxSpeed, FIELD_FLOAT ),
DEFINE_FIELD( CStukabat, m_flMinSpeed, FIELD_FLOAT ),
DEFINE_FIELD( CStukabat, m_flMaxDist, FIELD_FLOAT ),
DEFINE_FIELD( CStukabat, m_flNextAlert, FIELD_TIME ),
DEFINE_FIELD( CStukabat, m_flLandTime, FIELD_TIME ),
DEFINE_FIELD( CStukabat, m_iMode, FIELD_INTEGER ),
DEFINE_FIELD( CStukabat, meMedium, FIELD_INTEGER ),
DEFINE_FIELD( CStukabat, mpCeilingNode, FIELD_CLASSPTR ),
};
IMPLEMENT_SAVERESTORE( CStukabat, CFlyingMonster );
const char *CStukabat::pIdleSounds[] =
{
"stukabat/stukabat_idle1.wav",
"stukabat/stukabat_idle2.wav",
"stukabat/stukabat_idle3.wav",
"stukabat/stukabat_idle4.wav",
};
const char *CStukabat::pAlertSounds[] =
{
"stukabat/ng_alert1.wav",
};
const char *CStukabat::pAttackSounds[] =
{
"stukabat/ng_attack1.wav",
};
const char *CStukabat::pSlashSounds[] =
{
"zombie/claw_strike1.wav",
"zombie/claw_strike2.wav",
"zombie/claw_strike3.wav",
};
const char *CStukabat::pPainSounds[] =
{
"stukabat/ng_pain1.wav",
"stukabat/ng_pain2.wav",
"stukabat/ng_pain3.wav",
};
const char *CStukabat::pDieSounds[] =
{
"stukabat/ng_die1.wav",
"stukabat/ng_die2.wav",
};
#define EMIT_STUKABAT_SOUND( chan, array ) \
EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, 0.6, 0, RANDOM_LONG(95,105) );
void CStukabat :: IdleSound( void )
{
EMIT_STUKABAT_SOUND( CHAN_VOICE, pIdleSounds );
}
void CStukabat :: AlertSound( void )
{
EMIT_STUKABAT_SOUND( CHAN_VOICE, pAlertSounds );
}
void CStukabat :: AttackSound( void )
{
EMIT_STUKABAT_SOUND( CHAN_VOICE, pAttackSounds );
}
void CStukabat :: SlashSound( void )
{
EMIT_STUKABAT_SOUND( CHAN_WEAPON, pSlashSounds );
}
void CStukabat :: DeathSound( void )
{
EMIT_STUKABAT_SOUND( CHAN_VOICE, pDieSounds );
}
void CStukabat :: PainSound( void )
{
EMIT_STUKABAT_SOUND( CHAN_VOICE, pPainSounds );
}
//=========================================================
// monster-specific tasks and states
//=========================================================
enum
{
TASK_STUKABAT_CIRCLE_ENEMY = LAST_COMMON_TASK + 1,
TASK_STUKABAT_FLY,
TASK_STUKABAT_MOVETOGROUND,
TASK_STUKABAT_LANDGROUND,
TASK_STUKABAT_TAKEOFFGROUND,
TASK_STUKABAT_FIND_NODE,
TASK_STUKABAT_LANDCEILING,
TASK_STUKABAT_TAKEOFFCEILING,
TASK_STUKABAT_BOMB_OK,
TASK_STUKABAT_FALL
};
// Note: tried hovering, didn't like it
//=========================================================
// AI Schedules Specific to this monster
//=========================================================
static Task_t tlFlyAround[] =
{
{ TASK_SET_ACTIVITY, (float)ACT_FLY },
{ TASK_STUKABAT_FLY, 0.0 },
};
static Schedule_t slFlyAround[] =
{
{
tlFlyAround,
ARRAYSIZE(tlFlyAround),
bits_COND_LIGHT_DAMAGE |
bits_COND_HEAVY_DAMAGE |
bits_COND_SEE_ENEMY |
bits_COND_NEW_ENEMY |
bits_COND_HEAR_SOUND,
bits_SOUND_PLAYER |
bits_SOUND_COMBAT,
"FlyAround"
},
};
static Task_t tlFlyAgitated[] =
{
{ TASK_STOP_MOVING, (float) 0 },
{ TASK_SET_ACTIVITY, (float)ACT_RUN },
{ TASK_WAIT, (float)2.0 },
};
static Schedule_t slFlyAgitated[] =
{
{
tlFlyAgitated,
ARRAYSIZE(tlFlyAgitated),
0,
0,
"FlyAgitated"
},
};
static Task_t tlCircleEnemy[] =
{
{ TASK_SET_ACTIVITY, (float)ACT_FLY },
{ TASK_STUKABAT_CIRCLE_ENEMY, 0.0 },
};
static Schedule_t slCircleEnemy[] =
{
{
tlCircleEnemy,
ARRAYSIZE(tlCircleEnemy),
bits_COND_NEW_ENEMY |
bits_COND_LIGHT_DAMAGE |
bits_COND_HEAVY_DAMAGE |
bits_COND_CAN_MELEE_ATTACK1 |
bits_COND_CAN_RANGE_ATTACK1,
0,
"CircleEnemy"
},
};
Task_t tlStukabatTwitchDie[] =
{
{ TASK_STOP_MOVING, 0 },
{ TASK_SOUND_DIE, (float)0 },
{ TASK_STUKABAT_FALL, (float)0 },
{ TASK_DIE, (float)0 },
};
Schedule_t slStukabatTwitchDie[] =
{
{
tlStukabatTwitchDie,
ARRAYSIZE( tlStukabatTwitchDie ),
0,
0,
"Die"
},
};
static Task_t tlLandGround[] =
{
{ TASK_STUKABAT_MOVETOGROUND, 0 },
{ TASK_SET_ACTIVITY, (float)ACT_LAND },
{ TASK_STUKABAT_LANDGROUND, 0 },
{ TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE },
};
static Schedule_t slLandGround[] =
{
{
tlLandGround,
ARRAYSIZE(tlLandGround),
0,
0,
"LandGround"
},
};
static Task_t tlTakeOffGround[] =
{
{ TASK_SET_ACTIVITY, (float)ACT_LEAP },
{ TASK_STUKABAT_TAKEOFFGROUND, 0 },
{ TASK_SET_ACTIVITY, (float)ACT_FLY },
};
static Schedule_t slTakeOffGround[] =
{
{
tlTakeOffGround,
ARRAYSIZE(tlTakeOffGround),
0,
0,
"TakeOffGround"
},
};
static Task_t tlLandCeiling[] =
{
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_FAIL },
{ TASK_STUKABAT_FIND_NODE, 0 },
{ TASK_GET_PATH_TO_TARGET, (float)0 },
{ TASK_WALK_PATH, (float)0 },
{ TASK_WAIT_FOR_MOVEMENT, (float)0 },
{ TASK_SET_ACTIVITY, (float)ACT_STAND },
{ TASK_STUKABAT_LANDCEILING, 0 },
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
};
static Schedule_t slLandCeiling[] =
{
{
tlLandCeiling,
ARRAYSIZE(tlLandCeiling),
0,
0,
"LandCeiling"
},
};
static Task_t tlTakeOffCeiling[] =
{
{ TASK_STUKABAT_TAKEOFFCEILING, 0 },
{ TASK_SET_ACTIVITY, (float)ACT_FLY },
};
static Schedule_t slTakeOffCeiling[] =
{
{
tlTakeOffCeiling,
ARRAYSIZE(tlTakeOffCeiling),
0,
0,
"TakeOffCeiling"
},
};
static Task_t tlBombAttack[] =
{
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_FAIL },
{ TASK_GET_PATH_TO_SPOT, (float)0 }, // todo: overwrite this...
{ TASK_WALK_PATH, (float)0 },
{ TASK_WAIT_FOR_MOVEMENT, (float)0 },
{ TASK_STUKABAT_BOMB_OK, (float)0 }, // checks 2D length <= 128, z length <= 1024 && > 128, path is unobstructed
{ TASK_RANGE_ATTACK1, (float)0 },
};
static Schedule_t slBombAttack[] =
{
{
tlBombAttack,
ARRAYSIZE(tlBombAttack),
0,
0,
"BombAttack"
},
};
DEFINE_CUSTOM_SCHEDULES(CStukabat)
{
slFlyAround,
slFlyAgitated,
slCircleEnemy,
slStukabatTwitchDie,
slLandGround,
slTakeOffGround,
slLandCeiling,
slTakeOffCeiling,
slBombAttack,
};
IMPLEMENT_CUSTOM_SCHEDULES(CStukabat, CFlyingMonster);
//=========================================================
// Classify - indicates this monster's place in the
// relationship table.
//=========================================================
int CStukabat :: Classify ( void )
{
return m_iClass?m_iClass:CLASS_ALIEN_MONSTER;
}
//=========================================================
// CheckMeleeAttack1
//=========================================================
BOOL CStukabat :: CheckMeleeAttack1 ( float flDot, float flDist )
{
if ( flDist <= 64 && flDot >= 0.7 && m_flNextMeleeAttack <= gpGlobals->time )
{
return TRUE;
}
return FALSE;
}
/*
void CStukabat::SlashTouch( CBaseEntity *pOther )
{
// slash if we hit who we want to eat
if ( pOther == m_hEnemy )
{
m_flNextMeleeAttack = gpGlobals->time;
m_bOnAttack = TRUE;
}
}
*/
void CStukabat::CombatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
//if ( !ShouldToggle( useType, m_bOnAttack ) )
if ( !ShouldToggle( useType ) )
return;
/*
if (m_bOnAttack)
{
m_bOnAttack = 0;
}
else
{
m_bOnAttack = 1;
}
*/
}
//=========================================================
// CheckRangeAttack1 - Fly in for a chomp
//
//=========================================================
BOOL CStukabat :: CheckRangeAttack1 ( float flDot, float flDist )
{
//if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 384 && m_idealDist <= 384)))
if ( flDot > -0.7 && flDist <= 2048 && m_flNextRangedAttack <= gpGlobals->time)
{
return TRUE;
}
return FALSE;
}
//=========================================================
// CheckRangeAttack2 - Fly in for a chomp
//
//=========================================================
BOOL CStukabat :: CheckRangeAttack2 ( float flDot, float flDist )
{
//if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 384 && m_idealDist <= 384)))
if ( flDot > -0.7 && ( flDist <= 384 && m_idealDist <= 384) && m_flNextMeleeAttack <= gpGlobals->time)
{
return TRUE;
}
return FALSE;
}
//=========================================================
// SetYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
void CStukabat :: SetYawSpeed ( void )
{
pev->yaw_speed = 100;
}
//=========================================================
// Killed - overrides CFlyingMonster.
//
void CStukabat :: Killed( entvars_t *pevAttacker, int iGib )
{
pev->velocity = Vector(0,0,0);
pev->gravity = 1.0;
pev->angles.x = 0;
pev->deadflag = DEAD_DYING;
//CBaseMonster::Killed( pevAttacker, iGib );
CFlyingMonster::Killed( pevAttacker, iGib );
pev->movetype = MOVETYPE_TOSS;
}
void CStukabat::BecomeDead( void )
{
pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses.
// give the corpse half of the monster's original maximum health.
pev->health = pev->max_health / 2;
pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place.
}
void CStukabat :: Slash( void )
{
if (m_hEnemy != NULL && FVisible( m_hEnemy ))
{
CBaseEntity *pHurt = m_hEnemy;
Vector vecShootDir = ShootAtEnemy( pev->origin );
UTIL_MakeAimVectors ( pev->angles );
if (DotProduct( vecShootDir, gpGlobals->v_forward ) > 0.707)
{
//m_bOnAttack = TRUE;
pHurt->pev->punchangle.z = -18;
pHurt->pev->punchangle.x = 5;
pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 100;
//pHurt->TakeDamage( pev, pev, gSkillData.stukabatDmgSlash, DMG_SLASH );
pHurt->TakeDamage( pev, pev, gSkillData.nightgauntDmgSlash, DMG_SLASH );
}
}
SlashSound();
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CStukabat :: HandleAnimEvent( MonsterEvent_t *pEvent )
{
switch( pEvent->event )
{
case STUKABAT_AE_BOMB:
{
Vector vecAngle;
UTIL_MakeVectors(vecAngle);
vecAngle.Normalize();
vecAngle = 20.0 * vecAngle;
EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM);
// todo: need to set hackedgunposition
CStukaGrenade::ShootContact( pev, GetGunPosition(), vecAngle );
m_flNextRangedAttack = gpGlobals->time + RANDOM_FLOAT(7.0, 10.0);
}
break;
default:
CFlyingMonster::HandleAnimEvent( pEvent );
break;
}
}
//=========================================================
// Spawn
//=========================================================
void CStukabat :: Spawn()
{
Precache( );
if (pev->model)
SET_MODEL(ENT(pev), STRING(pev->model)); //LRC
else
SET_MODEL(ENT(pev), "models/stukabat.mdl");
UTIL_SetSize( pev, Vector( -16, -16, -2 ), Vector( 16, 16, 16 ) );
pev->solid = SOLID_BBOX;
pev->movetype = MOVETYPE_FLY;
m_bloodColor = BLOOD_COLOR_GREEN;
if (pev->health == 0)
// pev->health = gSkillData.stukabatHealth;
// pev->health = gSkillData.zombieHealth;
pev->health = 5;
pev->view_ofs = Vector ( 0, 0, 16 );
m_flFieldOfView = VIEW_FIELD_WIDE;
m_MonsterState = MONSTERSTATE_NONE;
SetBits(pev->flags, FL_FLY);
SetFlyingSpeed( STUKABAT_SPEED );
SetFlyingMomentum( 2.5 ); // Set momentum constant
m_afCapability = bits_CAP_RANGE_ATTACK1 | bits_CAP_RANGE_ATTACK2 | bits_CAP_FLY;
meMedium = SB_INAIR;
m_iMode = STUKABAT_IDLE;
MonsterInit();
// PhilG: hack to allow CheckMeleeAttack1 to be used
m_afCapability |= bits_CAP_MELEE_ATTACK1;
//SetTouch( SlashTouch );
m_flNextRangedAttack = gpGlobals->time;
m_flNextMeleeAttack = gpGlobals->time;
SetTouch( NULL );
SetUse( CombatUse );
m_idealDist = 384;
m_flMinSpeed = 80;
m_flMaxSpeed = 300;
m_flMaxDist = 384;
m_flLandTime = gpGlobals->time;
Vector Forward;
UTIL_MakeVectorsPrivate(pev->angles, Forward, 0, 0);
pev->velocity = m_flightSpeed * Forward.Normalize();
m_SaveVelocity = pev->velocity;
mpCeilingNode = NULL;
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CStukabat :: Precache()
{
if (pev->model)
PRECACHE_MODEL((char*)STRING(pev->model)); //LRC
else
PRECACHE_MODEL("models/stukabat.mdl");
PRECACHE_SOUND_ARRAY( pIdleSounds );
PRECACHE_SOUND_ARRAY( pAlertSounds );
PRECACHE_SOUND_ARRAY( pAttackSounds );
PRECACHE_SOUND_ARRAY( pSlashSounds );
PRECACHE_SOUND_ARRAY( pDieSounds );
PRECACHE_SOUND_ARRAY( pPainSounds );
PRECACHE_SOUND("weapons/glauncher.wav");
}
void CStukabat :: SetActivity ( Activity NewActivity )
{
int iSequence = ACTIVITY_NOT_AVAILABLE;
void *pmodel = GET_MODEL_PTR( ENT(pev) );
switch ( NewActivity)
{
case ACT_RANGE_ATTACK2:
// get aimable sequence
iSequence = LookupSequence( "Attack_claw" );
default:
iSequence = LookupActivity ( NewActivity );
break;
}
// Set to the desired anim, or default anim if the desired is not present
if ( iSequence > ACTIVITY_NOT_AVAILABLE )
{
if ( pev->sequence != iSequence || !m_fSequenceLoops )
{
pev->frame = 0;
}
pev->sequence = iSequence; // Set to the reset anim (if it's there)
ResetSequenceInfo( );
SetYawSpeed();
}
else
{
// Not available try to get default anim
ALERT ( at_console, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity );
pev->sequence = 0; // Set to the reset anim (if it's there)
}
m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present
// In case someone calls this with something other than the ideal activity
m_IdealActivity = m_Activity;
}
//=========================================================
// GetSchedule
//=========================================================
Schedule_t* CStukabat::GetSchedule()
{
// ALERT( at_console, "GetSchedule( )\n" );
switch(m_MonsterState)
{
case MONSTERSTATE_IDLE:
m_flightSpeed = STUKABAT_SPEED / 4;
switch (meMedium)
{
case SB_ONGROUND:
return GetScheduleOfType( SCHED_IDLE_STAND );
break;
case SB_ONCEILING:
return GetScheduleOfType( SCHED_IDLE_STAND );
break;
case SB_INAIR:
default:
return GetScheduleOfType( SCHED_IDLE_WALK );
break;
}
case MONSTERSTATE_ALERT:
m_flightSpeed = STUKABAT_SPEED - 50;
return GetScheduleOfType( SCHED_IDLE_WALK );
case MONSTERSTATE_COMBAT:
switch (meMedium)
{
case SB_INAIR:
{
m_flMaxSpeed = STUKABAT_SPEED + 100;
// chase them down and eat them
if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) )
{
return GetScheduleOfType( SCHED_RANGE_ATTACK1 );
}
// can we claw? (which is defined as a range attack for some unknown reason)
if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) )
{
if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) )
{
return GetScheduleOfType( SCHED_RANGE_ATTACK2 );
}
else
{
return GetScheduleOfType( SCHED_CHASE_ENEMY );
}
}
/*
if ( HasConditions( bits_COND_HEAVY_DAMAGE ) )
{
m_bOnAttack = TRUE;
}
if ( pev->health < pev->max_health - 20 )
{
m_bOnAttack = TRUE;
}
*/
return GetScheduleOfType( SCHED_STANDOFF );
}
break;
case SB_ONGROUND:
case SB_ONCEILING:
default:
return GetScheduleOfType( SCHED_COMBAT_STAND );
break;
}
}
return CFlyingMonster :: GetSchedule();
}
//=========================================================
//=========================================================
Schedule_t* CStukabat :: GetScheduleOfType ( int Type )
{
int iRand;
// ALERT( at_console, "GetScheduleOfType( %d ) %d\n", Type, m_bOnAttack );
switch ( Type )
{
case SCHED_IDLE_STAND:
case SCHED_IDLE_WALK:
switch (meMedium)
{
case SB_ONGROUND:
// randomly want to take off, if we have been on ground for at least 10 secs
if (RANDOM_LONG(0,30) == 0 && gpGlobals->time > m_flLandTime + 10.0)
{
return slTakeOffGround;
}
return CBaseMonster :: GetScheduleOfType( Type );
break;
case SB_ONCEILING:
// randomly want to take off, if we have been on ground for at least 10 secs
if (RANDOM_LONG(0,10) == 0 && gpGlobals->time > m_flLandTime + 10.0)
{
return slTakeOffCeiling;
}
return CBaseMonster :: GetScheduleOfType( Type );
break;
case SB_INAIR:
default:
// randomly want to land if we have been in the air at least 10 secs
if (gpGlobals->time > m_flLandTime + 10.0)
{
iRand = RANDOM_LONG(0,10);
if (iRand == 1)
{
return slLandGround;
}
else if (iRand < 4)
{
return slLandCeiling;
}
}
return slFlyAround;
break;
}
case SCHED_STANDOFF:
return slCircleEnemy;
case SCHED_COMBAT_STAND:
switch (meMedium)
{
case SB_ONGROUND:
return slTakeOffGround;
break;
case SB_ONCEILING:
return slTakeOffCeiling;
break;
default:
case SB_INAIR:
// this should never happen
return slFlyAround;
break;
}
break;
case SCHED_RANGE_ATTACK1:
return slBombAttack;
case SCHED_FAIL:
return slFlyAgitated;
case SCHED_DIE:
return slStukabatTwitchDie;
case SCHED_CHASE_ENEMY:
AttackSound( );
}
return CBaseMonster :: GetScheduleOfType( Type );
}
//=========================================================
// Start task - selects the correct activity and performs
// any necessary calculations to start the next task on the
// schedule.
//=========================================================
void CStukabat::StartTask(Task_t *pTask)
{
switch (pTask->iTask)
{
case TASK_STUKABAT_CIRCLE_ENEMY:
break;
case TASK_STUKABAT_FLY:
break;
case TASK_STUKABAT_MOVETOGROUND:
// angle downwards
//if (pev->angles.x > -60) pev->angles.x = -60;
break;
case TASK_STUKABAT_LANDGROUND:
SetActivity(ACT_LAND);
LandGround();
break;
case TASK_STUKABAT_TAKEOFFGROUND:
SetActivity(ACT_LEAP);
TakeOffGround();
break;
case TASK_STUKABAT_FIND_NODE:
FindNode();
m_hTargetEnt = mpCeilingNode;
m_movementActivity = ACT_FLY;
break;
case TASK_STUKABAT_LANDCEILING:
// stop moving
m_flightSpeed = 0.0;
m_flGroundSpeed = 0.0;
pev->velocity = Vector(0,0,0);
SetActivity(ACT_STAND);
LandCeiling();
break;
case TASK_STUKABAT_TAKEOFFCEILING:
TakeOffGround();
break;
case TASK_STUKABAT_BOMB_OK:
{
bool bOK = false;
// do we have an enemy
if (m_hEnemy != NULL)
{
//CBaseEntity* pEnemy = CBaseEntity::Instance(m_hEnemy);
CBaseEntity* pEnemy = (m_hEnemy);
if (pEnemy && pEnemy->IsAlive())
{
// are we at least 128 (2D) away from enemy
if ((pev->origin - pEnemy->pev->origin).Length2D() <= 128)
{
float dz = pev->origin.z - pEnemy->pev->origin.z;
// are we a good height above
if (dz > 128 && dz <= 1024)
{
TraceResult tr;
UTIL_TraceLine( pev->origin, pEnemy->pev->origin, ignore_monsters, NULL, &tr );
// are we obstructed (at least before 95% of the distance
if (tr.flFraction > 0.95)
{
bOK = true;
}
}
}
}
}
if (bOK)
{
TaskComplete();
}
else
{
TaskFail();
}
}
break;
case TASK_SMALL_FLINCH:
if (m_idealDist > 128)
{
m_flMaxDist = 512;
m_idealDist = 512;
}
else
{
//m_bOnAttack = TRUE;
}
CFlyingMonster::StartTask(pTask);
break;
case TASK_STUKABAT_FALL:
m_movementActivity = ACT_FALL;
//m_IdealActivity = ACT_FALL;
SetActivity ( ACT_FALL );
//SetSequenceByName( "fall_cycler" );
break;
// overridden task
case TASK_WALK_PATH:
{
switch (meMedium)
{
case SB_ONGROUND:
m_movementActivity = ACT_WALK;
break;
case SB_ONCEILING:
case SB_INAIR:
default:
m_movementActivity = ACT_FLY;
break;
}
TaskComplete();
break;
}
case TASK_GET_PATH_TO_SPOT:
{
// get the target location
if (m_hEnemy == NULL)
{
TaskFail();
break;
}
//CBaseEntity* pEnemy = CBaseEntity::Instance(m_hEnemy);
CBaseEntity* pEnemy = (m_hEnemy);
TraceResult tr;
// trace directly up for 1024
Vector up = pEnemy->pev->origin;
up.z += 1024;
UTIL_TraceLine( pEnemy->pev->origin, up, ignore_monsters, NULL, &tr );
// choose highest point (minus a bit)
m_vecMoveGoal = tr.vecEndPos;
m_vecMoveGoal.z -= 64;
// will we be too close to his head?
if ((pEnemy->pev->origin - m_vecMoveGoal).Length() < 128)
{
TaskFail();
break;
}
if ( BuildRoute ( m_vecMoveGoal, bits_MF_TO_LOCATION, NULL ) )
{
TaskComplete();
}
else
{
// no way to get there =(
ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" );
TaskFail();
}
}
break;
default:
CFlyingMonster::StartTask(pTask);
break;
}
}
void CStukabat :: RunTask ( Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_STUKABAT_CIRCLE_ENEMY:
if (m_hEnemy == NULL)
{
TaskComplete( );
}
else if (FVisible( m_hEnemy ))
{
Vector vecFrom = m_hEnemy->EyePosition( );
Vector vecDelta = (pev->origin - vecFrom).Normalize( );
Vector vecFly = CrossProduct( vecDelta, Vector( 0, 0, 1 ) ).Normalize( );
if (DotProduct( vecFly, m_SaveVelocity ) < 0)
vecFly = vecFly * -1.0;
Vector vecPos = vecFrom + vecDelta * m_idealDist + vecFly * 32;
// ALERT( at_console, "vecPos %.0f %.0f %.0f\n", vecPos.x, vecPos.y, vecPos.z );
TraceResult tr;
UTIL_TraceHull( vecFrom, vecPos, ignore_monsters, large_hull, m_hEnemy->edict(), &tr );
if (tr.flFraction > 0.5)
vecPos = tr.vecEndPos;
m_SaveVelocity = m_SaveVelocity * 0.8 + 0.2 * (vecPos - pev->origin).Normalize() * m_flightSpeed;
// ALERT( at_console, "m_SaveVelocity %.2f %.2f %.2f\n", m_SaveVelocity.x, m_SaveVelocity.y, m_SaveVelocity.z );
if (HasConditions( bits_COND_ENEMY_FACING_ME ) && m_hEnemy->FVisible( this ))
{
m_flNextAlert -= 0.1;
if (m_idealDist < m_flMaxDist)
{
m_idealDist += 4;
}
if (m_flightSpeed > m_flMinSpeed)
{
m_flightSpeed -= 2;
}
else if (m_flightSpeed < m_flMinSpeed)
{
m_flightSpeed += 2;
}
if (m_flMinSpeed < m_flMaxSpeed)
{
m_flMinSpeed += 0.5;
}
}
else
{
m_flNextAlert += 0.1;
if (m_idealDist > 128)
{
m_idealDist -= 4;
}
if (m_flightSpeed < m_flMaxSpeed)
{
m_flightSpeed += 4;
}
}
// ALERT( at_console, "%.0f\n", m_idealDist );
}
else
{
m_flNextAlert = gpGlobals->time + 0.2;
}
if (m_flNextAlert < gpGlobals->time)
{
// ALERT( at_console, "AlertSound()\n");
AlertSound( );
m_flNextAlert = gpGlobals->time + RANDOM_FLOAT( 3, 5 );
}
// PhilG: they will circle me forever unless I break it...
if (RANDOM_LONG(0,50) == 1)
{
TaskComplete();
}
break;
case TASK_STUKABAT_FLY:
//if (m_fSequenceFinished)
// the sequence loops, so it is very rare that the 'finished' flag is set
// set we randomly finish the task, which will recall the result in the
// getting of a new schedule (possibly the same one again)
if (RANDOM_LONG(0,20) == 1)
{
TaskComplete( );
}
break;
case TASK_STUKABAT_MOVETOGROUND:
{
// how far above the floor are we?
float fz = FloorZ(pev->origin);
if (pev->origin.z - fz < 6)
{
// we are very close to the floor, so drop the last little bit
DROP_TO_FLOOR(ENT(pev));
TaskComplete( );
}
else
{
int iContents = UTIL_PointContents(pev->origin + Vector(0,0,-6));
if ((iContents == CONTENTS_WATER) ||
(iContents == CONTENTS_LAVA) ||
(iContents == CONTENTS_SLIME))
{
TaskFail();
break;
}
// note: the stukabat origin is 2 units above the bottom of its hull
// hence if we are checking for 6 units (above), we can only safely push down 4 units
if ( CheckLocalMove ( pev->origin, pev->origin + Vector(0,0,-4), NULL, NULL ) == LOCALMOVE_VALID)
{
pev->origin.z -= 4;
}
}
}
break;
case TASK_STUKABAT_LANDGROUND:
if (m_fSequenceFinished)
{
TaskComplete( );
}
break;
case TASK_STUKABAT_TAKEOFFGROUND:
if (m_fSequenceFinished)
{
TaskComplete( );
}
break;
case TASK_STUKABAT_FIND_NODE:
if (mpCeilingNode)
{
TaskComplete( );
}
else
{
TaskFail();
}
break;
case TASK_STUKABAT_LANDCEILING:
if (m_fSequenceFinished)
{
mpCeilingNode->mbInUse = true;
TaskComplete( );
}
break;
case TASK_STUKABAT_TAKEOFFCEILING:
{
mpCeilingNode->mbInUse = false;
mpCeilingNode = NULL;
TaskComplete( );
}
break;
case TASK_RANGE_ATTACK2:
{
MakeIdealYaw ( m_vecEnemyLKP );
ChangeYaw ( pev->yaw_speed );
if ( m_fSequenceFinished )
{
m_Activity = ACT_RESET;
TaskComplete();
}
if (gpGlobals->time > m_flNextMeleeAttack)
{
// do the attack
Slash();
// set the next melee attack time
m_flNextMeleeAttack = gpGlobals->time + RANDOM_FLOAT(3.0,6.0);
}
}
break;
case TASK_STUKABAT_FALL:
if (FBitSet(pev->flags, FL_ONGROUND))
{
if ( RANDOM_LONG(0,4) == 1 )
{
m_IdealActivity = ACT_DIEVIOLENT;
}
else
{
m_IdealActivity = ACT_DIESIMPLE;
}
TaskComplete( );
}
else
{
if (m_fSequenceFinished)
{
SetActivity ( ACT_FALL );
}
MoveExecute(NULL, Vector(0,0,-1), 0.1);
}
break;
case TASK_DIE:
if ( m_fSequenceFinished )
{
CFlyingMonster :: RunTask ( pTask );
pev->deadflag = DEAD_DEAD;
TaskComplete( );
}
break;
default:
CFlyingMonster :: RunTask ( pTask );
break;
}
}
float CStukabat::VectorToPitch( const Vector &vec )
{
float pitch;
if (vec.z == 0 && vec.x == 0)
pitch = 0;
else
{
pitch = (int) (atan2(vec.z, sqrt(vec.x*vec.x+vec.y*vec.y)) * 180 / M_PI);
if (pitch < 0)
pitch += 360;
}
return pitch;
}
//=========================================================
void CStukabat::FindNode()
{
mpCeilingNode = NULL;
Vector vDistance;
const int MAX_NODE = 1000;
int iNode = 0;
CStukabatNode* pNodesInSphere[MAX_NODE];
CBaseEntity* pNode = UTIL_FindEntityByClassname(NULL, "info_node_stukabat");
while (pNode)
{
vDistance = pev->origin - pNode->pev->origin;
if (vDistance.Length() <= 1024.0 && !((CStukabatNode*)pNode)->mbInUse)
{
pNodesInSphere[iNode] = (CStukabatNode*)pNode;
iNode++;
}
pNode = UTIL_FindEntityByClassname(pNode, "info_node_stukabat");
}
// no viable nodes found
if (iNode == 0) return;
// choose a viable node at random
int iRand = RANDOM_LONG(0,iNode-1);
mpCeilingNode = pNodesInSphere[iRand];
}
int CStukabat :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist )
{
int iContents = UTIL_PointContents(vecEnd);
if ((iContents == CONTENTS_WATER) ||
(iContents == CONTENTS_LAVA) ||
(iContents == CONTENTS_SLIME))
{
return FALSE;
}
TraceResult tr;
UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, edict(), &tr );
// ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z );
// ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z );
if (pflDist)
{
*pflDist = ( tr.vecEndPos - vecStart ).Length();// get the distance.
}
// ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction );
if (tr.fStartSolid || tr.flFraction < 1.0)
{
// PhilG
if (gpGlobals->trace_ent)
{
CBaseEntity* pBlocker = CBaseEntity::Instance(gpGlobals->trace_ent);
}
if ( tr.flFraction < 1.0 && pTarget && pTarget->edict() == gpGlobals->trace_ent )
return LOCALMOVE_VALID;
return LOCALMOVE_INVALID;
}
return LOCALMOVE_VALID;
}
void CStukabat::Move(float flInterval)
{
if (meMedium == SB_INAIR)
{
CFlyingMonster::Move( flInterval );
}
else
{
float flWaypointDist;
Vector vecApex;
// local move to waypoint.
flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length2D();
MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation );
ChangeYaw ( pev->yaw_speed );
UTIL_MakeVectors( pev->angles );
if ( RANDOM_LONG(0,7) == 1 )
{
// randomly check for blocked path.(more random load balancing)
if ( !WALK_MOVE( ENT(pev), pev->ideal_yaw, 4, WALKMOVE_NORMAL ) )
{
// stuck, so just pick a new spot to run off to
PickNewDest( m_iMode );
}
}
WALK_MOVE( ENT(pev), pev->ideal_yaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL );
// if the waypoint is closer than step size, then stop after next step (ok for rat to overshoot)
if ( flWaypointDist <= m_flGroundSpeed * flInterval )
{
// take truncated step and stop
SetActivity ( ACT_CROUCHIDLE );
if ( m_iMode == STUKABAT_SMELL_FOOD )
{
m_iMode = STUKABAT_EAT;
}
else
{
m_iMode = STUKABAT_IDLE;
}
}
}
}
float CStukabat::FlPitchDiff( void )
{
float flPitchDiff;
float flCurrentPitch;
flCurrentPitch = UTIL_AngleMod( pev->angles.z );
if ( flCurrentPitch == pev->idealpitch )
{
return 0;
}
flPitchDiff = pev->idealpitch - flCurrentPitch;
if ( pev->idealpitch > flCurrentPitch )
{
if (flPitchDiff >= 180)
flPitchDiff = flPitchDiff - 360;
}
else
{
if (flPitchDiff <= -180)
flPitchDiff = flPitchDiff + 360;
}
return flPitchDiff;
}
float CStukabat :: ChangePitch( int speed )
{
if ( pev->movetype == MOVETYPE_FLY )
{
float diff = FlPitchDiff();
float target = 0;
if ( m_IdealActivity != GetStoppedActivity() )
{
if (diff < -20)
target = 45;
else if (diff > 20)
target = -45;
}
pev->angles.x = UTIL_Approach(target, pev->angles.x, 220.0 * 0.1 );
}
return 0;
}
float CStukabat::ChangeYaw( int speed )
{
if ( pev->movetype == MOVETYPE_FLY )
{
float diff = FlYawDiff();
float target = 0;
if ( m_IdealActivity != GetStoppedActivity() )
{
if ( diff < -20 )
target = 20;
else if ( diff > 20 )
target = -20;
}
pev->angles.z = UTIL_Approach( target, pev->angles.z, 220.0 * 0.1 );
}
return CFlyingMonster::ChangeYaw( speed );
}
Activity CStukabat:: GetStoppedActivity( void )
{
//if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else
// return ACT_IDLE;
//return ACT_WALK;
switch (meMedium)
{
case SB_ONGROUND:
return ACT_CROUCHIDLE;
break;
case SB_ONCEILING:
return ACT_IDLE;
break;
case SB_INAIR:
default:
return ACT_FLY;
break;
}
}
void CStukabat::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval )
{
m_SaveVelocity = vecDir * m_flightSpeed;
CFlyingMonster::MoveExecute(pTargetEnt, vecDir, flInterval);
}
void CStukabat::MonsterThink ( void )
{
float flInterval = 0.1;
if (!IsAlive() || pev->deadflag == DEAD_DYING) m_iMode = STUKABAT_DEAD;
if (pev->deadflag == DEAD_NO)
{
if (m_MonsterState != MONSTERSTATE_SCRIPT)
{
switch (meMedium)
{
case SB_ONCEILING:
MonsterThinkOnCeiling();
break;
case SB_ONGROUND:
MonsterThinkOnGround();
break;
case SB_INAIR:
default:
MonsterThinkInAir();
break;
}
}
}
else if (pev->deadflag == DEAD_DYING)
{
if (m_MonsterState != MONSTERSTATE_SCRIPT)
{
SetNextThink( 0.1 );// keep monster thinking.
RunAI();
DispatchAnimEvents( );
StudioFrameAdvance( );
}
}
}
void CStukabat::MonsterThinkInAir ( void )
{
// we just want to run the activity if we are doing any of the following
// taking off from the ground
// landing (ground or ceiling)
// attacking
if ((m_IdealActivity == ACT_LEAP) ||
(m_IdealActivity == ACT_LAND) ||
(m_IdealActivity == ACT_STAND))
{
SetNextThink( 0.1 );// keep monster thinking
RunAI();
StudioFrameAdvance( ); // animate
return;
}
CFlyingMonster::MonsterThink( );
// if we are not dead, in the air and not attacking
if (m_MonsterState != MONSTERSTATE_DEAD &&
meMedium == SB_INAIR &&
m_IdealActivity != ACT_RANGE_ATTACK1 &&
m_IdealActivity != ACT_RANGE_ATTACK2)
{
Fly();
}
}
void CStukabat::MonsterThinkOnCeiling ( void )
{
CBaseMonster::MonsterThink( );
}
void CStukabat::MonsterThinkOnGround ( void )
{
if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) )
SetNextThink( RANDOM_FLOAT(1,1.5) );
else
SetNextThink( 0.1 );// keep monster thinking
RunAI();
if (m_IdealActivity == ACT_IDLE)
{
pev->sequence = LookupActivity ( ACT_CROUCHIDLE );
m_Activity = ACT_CROUCHIDLE;
m_IdealActivity = ACT_CROUCHIDLE;
}
if (m_IdealActivity == ACT_CROUCHIDLE && m_fSequenceFinished)
{
SetActivity(ACT_CROUCHIDLE);
}
float flInterval = StudioFrameAdvance( ); // animate
// if we are landing, do nothing
if (m_IdealActivity == ACT_LAND) return;
switch (m_iMode)
{
case STUKABAT_IDLE:
case STUKABAT_EAT:
{
// if not moving, sample environment to see if anything scary is around. Do a radius search 'look' at random.
if ( RANDOM_LONG(0,1) == 1 )
{
Look( 150 );
if (HasConditions(bits_COND_SEE_FEAR))
{
// if see something scary
//ALERT ( at_aiconsole, "Scared\n" );
Eat( 30 + ( RANDOM_LONG(0,14) ) );// rat will ignore food for 30 to 45 seconds
PickNewDest( STUKABAT_SCARED_BY_ENT );
SetActivity ( ACT_RUN );
}
else if ( RANDOM_LONG(0,1) == 1 )
{
// if rat doesn't see anything, there's still a chance that it will move. (boredom)
//ALERT ( at_aiconsole, "Bored\n" );
PickNewDest( STUKABAT_BORED );
SetActivity ( ACT_WALK );
if ( m_iMode == STUKABAT_EAT )
{
// rat will ignore food for 30 to 45 seconds if it got bored while eating.
Eat( 30 + ( RANDOM_LONG(0,14) ) );
}
}
}
// don't do this stuff if eating!
if ( m_iMode == STUKABAT_IDLE )
{
if ( FShouldEat() )
{
Listen();
}
if ( HasConditions(bits_COND_SMELL_FOOD) )
{
CSound *pSound;
pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList );
// rat smells food and is just standing around. Go to food unless food isn't on same z-plane.
if ( pSound && abs( pSound->m_vecOrigin.z - pev->origin.z ) <= 3 )
{
PickNewDest( STUKABAT_SMELL_FOOD );
SetActivity ( ACT_WALK );
}
}
else
{
//SetActivity ( ACT_CROUCHIDLE );
}
}
break;
}
case STUKABAT_DEAD:
return;
break;
}
if ( m_flGroundSpeed != 0 )
{
Move( 0.1 );
}
}
void CStukabat :: Stop( void )
{
//if (!m_bOnAttack)
m_flightSpeed = 80.0;
}
void CStukabat::Fly( )
{
int retValue = 0;
Vector start = pev->origin;
Vector Angles;
Vector Forward, Right, Up;
/*
if (FBitSet( pev->flags, FL_ONGROUND))
{
pev->angles.x = 0;
pev->angles.y += RANDOM_FLOAT( -45, 45 );
ClearBits( pev->flags, FL_ONGROUND );
Angles = Vector( -pev->angles.x, pev->angles.y, pev->angles.z );
UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up);
pev->velocity = Forward * 200 + Up * 200;
return;
}
*/
//if (meMedium == SB_ONGROUND)
//{
// pev->angles.x = 0;
// SetActivity( ACT_WALK );
// return;
//}
//if (m_bOnAttack && m_flightSpeed < m_flMaxSpeed)
if (m_flightSpeed < m_flMaxSpeed)
{
m_flightSpeed += 40;
}
SetActivity( ACT_FLY );
/*
if (m_flightSpeed < 180)
{
SetActivity( ACT_FLY );
//if (m_IdealActivity == ACT_RUN)
// SetActivity( ACT_WALK );
//if (m_IdealActivity == ACT_WALK)
// pev->framerate = m_flightSpeed / 150.0;
// ALERT( at_console, "walk %.2f\n", pev->framerate );
}
else
{
switch (meMedium)
{
case SB_ONGROUND:
//SetActivity( ACT_WALK );
break;
case SB_ONCEILING:
SetActivity( ACT_WALK );
break;
default:
SetActivity( ACT_FLY );
break;
}
//if (m_IdealActivity == ACT_WALK)
// SetActivity( ACT_RUN );
//if (m_IdealActivity == ACT_RUN)
// pev->framerate = m_flightSpeed / 150.0;
// ALERT( at_console, "run %.2f\n", pev->framerate );
}
*/
#define PROBE_LENGTH 150
Angles = UTIL_VecToAngles( m_SaveVelocity );
Angles.x = -Angles.x;
UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up);
Vector f, u, l, r, d;
f = DoProbe(start + PROBE_LENGTH * Forward);
r = DoProbe(start + PROBE_LENGTH/3 * (Forward+Right));
l = DoProbe(start + PROBE_LENGTH/3 * (Forward-Right));
u = DoProbe(start + PROBE_LENGTH/3 * (Forward+Up));
d = DoProbe(start + PROBE_LENGTH/3 * (Forward-Up));
Vector SteeringVector = f+r+l+u+d;
m_SaveVelocity = (m_SaveVelocity + SteeringVector/2).Normalize();
Angles = Vector( -pev->angles.x, pev->angles.y, pev->angles.z );
UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up);
// ALERT( at_console, "%f : %f\n", Angles.x, Forward.z );
float flDot = DotProduct( Forward, m_SaveVelocity );
if (flDot > 0.5)
pev->velocity = m_SaveVelocity = m_SaveVelocity * m_flightSpeed;
else if (flDot > 0)
pev->velocity = m_SaveVelocity = m_SaveVelocity * m_flightSpeed * (flDot + 0.5);
else
pev->velocity = m_SaveVelocity = m_SaveVelocity * 80;
Angles = UTIL_VecToAngles( m_SaveVelocity );
// Smooth Pitch
//
if (Angles.x > 180)
Angles.x = Angles.x - 360;
pev->angles.x = UTIL_Approach(Angles.x, pev->angles.x, 50 * 0.1 );
if (pev->angles.x < -80) pev->angles.x = -80;
if (pev->angles.x > 80) pev->angles.x = 80;
// Smooth Yaw and generate Roll
//
float turn = 360;
// ALERT( at_console, "Y %.0f %.0f\n", Angles.y, pev->angles.y );
if (fabs(Angles.y - pev->angles.y) < fabs(turn))
{
turn = Angles.y - pev->angles.y;
}
if (fabs(Angles.y - pev->angles.y + 360) < fabs(turn))
{
turn = Angles.y - pev->angles.y + 360;
}
if (fabs(Angles.y - pev->angles.y - 360) < fabs(turn))
{
turn = Angles.y - pev->angles.y - 360;
}
float speed = m_flightSpeed * 0.1;
// ALERT( at_console, "speed %.0f %f\n", turn, speed );
if (fabs(turn) > speed)
{
if (turn < 0.0)
{
turn = -speed;
}
else
{
turn = speed;
}
}
pev->angles.y += turn;
pev->angles.z -= turn;
pev->angles.y = fmod((pev->angles.y + 360.0), 360.0);
static float yaw_adj;
yaw_adj = yaw_adj * 0.8 + turn;
// ALERT( at_console, "yaw %f : %f\n", turn, yaw_adj );
SetBoneController( 0, -yaw_adj / 4.0 );
// Roll Smoothing
//
turn = 360;
if (fabs(Angles.z - pev->angles.z) < fabs(turn))
{
turn = Angles.z - pev->angles.z;
}
if (fabs(Angles.z - pev->angles.z + 360) < fabs(turn))
{
turn = Angles.z - pev->angles.z + 360;
}
if (fabs(Angles.z - pev->angles.z - 360) < fabs(turn))
{
turn = Angles.z - pev->angles.z - 360;
}
speed = m_flightSpeed/2 * 0.1;
if (fabs(turn) < speed)
{
pev->angles.z += turn;
}
else
{
if (turn < 0.0)
{
pev->angles.z -= speed;
}
else
{
pev->angles.z += speed;
}
}
if (pev->angles.z < -20) pev->angles.z = -20;
if (pev->angles.z > 20) pev->angles.z = 20;
UTIL_MakeVectorsPrivate( Vector( -Angles.x, Angles.y, Angles.z ), Forward, Right, Up);
}
void CStukabat::LandGround(void)
{
ClearBits( pev->flags, FL_FLY );
meMedium = SB_ONGROUND;
m_afCapability = bits_CAP_RANGE_ATTACK2;
pev->movetype = MOVETYPE_STEP;
pev->angles.x = 0;
//pev->angles.z = 0;
m_flLandTime = gpGlobals->time;
}
void CStukabat::LandCeiling(void)
{
meMedium = SB_ONCEILING;
m_afCapability = bits_CAP_FLY;
pev->movetype = MOVETYPE_FLY;
pev->angles.x = 0;
//pev->angles.z = 0;
m_flLandTime = gpGlobals->time;
}
void CStukabat::TakeOffGround(void)
{
SetBits( pev->flags, FL_FLY );
meMedium = SB_INAIR;
m_afCapability = bits_CAP_RANGE_ATTACK2 | bits_CAP_FLY;
pev->movetype = MOVETYPE_FLY;
m_flLandTime = gpGlobals->time;
}
Vector CStukabat::DoProbe(const Vector &Probe)
{
Vector WallNormal = Vector(0,0,1); // AIR normal is Straight Up for flying thing.
float frac;
BOOL bBumpedSomething = ProbeZ(pev->origin, Probe, &frac);
TraceResult tr;
TRACE_MONSTER_HULL(edict(), pev->origin, Probe, dont_ignore_monsters, edict(), &tr);
if ( tr.fAllSolid || tr.flFraction < 0.99 )
{
if (tr.flFraction < 0.0) tr.flFraction = 0.0;
if (tr.flFraction > 1.0) tr.flFraction = 1.0;
if (tr.flFraction < frac)
{
frac = tr.flFraction;
bBumpedSomething = TRUE;
WallNormal = tr.vecPlaneNormal;
}
}
if (bBumpedSomething && (m_hEnemy == NULL || tr.pHit != m_hEnemy->edict()))
{
Vector ProbeDir = Probe - pev->origin;
Vector NormalToProbeAndWallNormal = CrossProduct(ProbeDir, WallNormal);
Vector SteeringVector = CrossProduct( NormalToProbeAndWallNormal, ProbeDir);
float SteeringForce = m_flightSpeed * (1-frac) * (DotProduct(WallNormal.Normalize(), m_SaveVelocity.Normalize()));
if (SteeringForce < 0.0)
{
SteeringForce = -SteeringForce;
}
SteeringVector = SteeringForce * SteeringVector.Normalize();
return SteeringVector;
}
return Vector(0, 0, 0);
}
//=========================================================
// Picks a new spot for rat to run to.(
//=========================================================
void CStukabat :: PickNewDest ( int iCondition )
{
Vector vecNewDir;
Vector vecDest;
float flDist;
m_iMode = iCondition;
if ( m_iMode == STUKABAT_SMELL_FOOD )
{
// find the food and go there.
CSound *pSound;
pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList );
if ( pSound )
{
m_Route[ 0 ].vecLocation.x = pSound->m_vecOrigin.x + ( 3 - RANDOM_LONG(0,5) );
m_Route[ 0 ].vecLocation.y = pSound->m_vecOrigin.y + ( 3 - RANDOM_LONG(0,5) );
m_Route[ 0 ].vecLocation.z = pSound->m_vecOrigin.z;
m_Route[ 0 ].iType = bits_MF_TO_LOCATION;
m_movementGoal = RouteClassify( m_Route[ 0 ].iType );
return;
}
}
do
{
// picks a random spot, requiring that it be at least 128 units away
// else, the rat will pick a spot too close to itself and run in
// circles. this is a hack but buys me time to work on the real monsters.
vecNewDir.x = RANDOM_FLOAT( -1, 1 );
vecNewDir.y = RANDOM_FLOAT( -1, 1 );
flDist = 256 + ( RANDOM_LONG(0,255) );
vecDest = pev->origin + vecNewDir * flDist;
} while ( ( vecDest - pev->origin ).Length2D() < 128 );
m_Route[ 0 ].vecLocation.x = vecDest.x;
m_Route[ 0 ].vecLocation.y = vecDest.y;
m_Route[ 0 ].vecLocation.z = pev->origin.z;
m_Route[ 0 ].iType = bits_MF_TO_LOCATION;
m_movementGoal = RouteClassify( m_Route[ 0 ].iType );
if ( RANDOM_LONG(0,9) == 1 )
{
// every once in a while, a rat will play a skitter sound when they decide to run
EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "roach/rch_walk.wav", 1, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) );
}
}
#endif