#include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" #include "../vehicle/Vehicle.h" #define MAX_MISSILE_JOINTS 4 #define MAX_HOVER_JOINTS 4 class rvMonsterStroggHover : public idAI { public: CLASS_PROTOTYPE( rvMonsterStroggHover ); rvMonsterStroggHover ( void ); ~rvMonsterStroggHover ( void ); void InitSpawnArgsVariables( void ); void Spawn ( void ); void Save ( idSaveGame *savefile ) const; void Restore ( idRestoreGame *savefile ); virtual void Think ( void ); virtual bool Collide ( const trace_t &collision, const idVec3 &velocity ); virtual void Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); virtual void OnDeath ( void ); virtual void DeadMove ( void ); virtual bool SkipImpulse ( idEntity *ent, int id ); virtual int FilterTactical ( int availableTactical ); virtual void GetDebugInfo ( debugInfoProc_t proc, void* userData ); virtual void Hide( void ); virtual void Show( void ); void StartHeadlight ( void ); void StopHeadlight ( void ); protected: // rvAIAction actionRocketAttack; // rvAIAction actionBlasterAttack; rvAIAction actionMGunAttack; rvAIAction actionMissileAttack; rvAIAction actionBombAttack; rvAIAction actionStrafe; rvAIAction actionCircleStrafe; virtual bool CheckActions ( void ); virtual void OnEnemyChange ( idEntity* oldEnemy ); virtual void OnStartMoving ( void ); virtual const char* GetIdleAnimName ( void ); private: idEntityPtr marker; idVec3 attackPosOffset; bool inPursuit; int holdPosTime; int strafeTime; bool strafeRight; bool circleStrafing; float deathPitch; float deathRoll; float deathPitchRate; float deathYawRate; float deathRollRate; float deathSpeed; float deathGrav; int markerCheckTime; bool MarkerPosValid ( void ); void TryStartPursuit ( void ); void Pursue ( void ); void CircleStrafe ( void ); void Evade ( bool left ); int mGunFireRate; int missileFireRate; int bombFireRate; int nextMGunFireTime; int nextMissileFireTime; int nextBombFireTime; int mGunMinShots; int mGunMaxShots; int missileMinShots; int missileMaxShots; int bombMinShots; int bombMaxShots; int shots; int evadeDebounce; int evadeDebounceRate; float evadeChance; float evadeSpeed; float strafeSpeed; float circleStrafeSpeed; rvClientEffectPtr effectDust; rvClientEffectPtr effectHover[MAX_HOVER_JOINTS]; rvClientEffectPtr effectHeadlight; jointHandle_t jointDust; int numHoverJoints; jointHandle_t jointHover[MAX_HOVER_JOINTS]; jointHandle_t jointBomb; jointHandle_t jointMGun; int numMissileJoints; jointHandle_t jointMissile[MAX_MISSILE_JOINTS]; jointHandle_t jointHeadlight; jointHandle_t jointHeadlightControl; renderLight_t renderLight; int lightHandle; // bool lightOn; void DoNamedAttack ( const char* attackName, jointHandle_t joint ); void UpdateLightDef ( void ); bool CheckAction_Strafe ( rvAIAction* action, int animNum ); bool CheckAction_CircleStrafe ( rvAIAction* action, int animNum ); bool CheckAction_BombAttack ( rvAIAction* action, int animNum ); //virtual bool CheckAction_EvadeLeft ( rvAIAction* action, int animNum ); //virtual bool CheckAction_EvadeRight ( rvAIAction* action, int animNum ); // stateResult_t State_Torso_BlasterAttack ( const stateParms_t& parms ); // stateResult_t State_Torso_RocketAttack ( const stateParms_t& parms ); stateResult_t State_Torso_MGunAttack ( const stateParms_t& parms ); stateResult_t State_Torso_MissileAttack ( const stateParms_t& parms ); stateResult_t State_Torso_BombAttack ( const stateParms_t& parms ); // stateResult_t State_Torso_EvadeLeft ( const stateParms_t& parms ); // stateResult_t State_Torso_EvadeRight ( const stateParms_t& parms ); stateResult_t State_Torso_Strafe ( const stateParms_t& parms ); stateResult_t State_Torso_CircleStrafe ( const stateParms_t& parms ); stateResult_t State_CircleStrafe ( const stateParms_t& parms ); stateResult_t State_DeathSpiral ( const stateParms_t& parms ); stateResult_t State_Pursue ( const stateParms_t& parms ); CLASS_STATES_PROTOTYPE ( rvMonsterStroggHover ); }; CLASS_DECLARATION( idAI, rvMonsterStroggHover ) END_CLASS /* ================ rvMonsterStroggHover::rvMonsterStroggHover ================ */ rvMonsterStroggHover::rvMonsterStroggHover ( ) { effectDust = NULL; for ( int i = 0; i < MAX_HOVER_JOINTS; i++ ) { effectHover[i] = NULL; } effectHeadlight = NULL; shots = 0; strafeTime = 0; strafeRight = false; circleStrafing = false; evadeDebounce = 0; deathPitch = 0; deathRoll = 0; deathPitchRate = 0; deathYawRate = 0; deathRollRate = 0; deathSpeed = 0; deathGrav = 0; markerCheckTime = 0; marker = NULL; attackPosOffset.Zero(); inPursuit = false; holdPosTime = 0; nextMGunFireTime = 0; nextMissileFireTime = 0; nextBombFireTime = 0; lightHandle = -1; } /* ================ rvMonsterStroggHover::~rvMonsterStroggHover ================ */ rvMonsterStroggHover::~rvMonsterStroggHover ( ) { if ( lightHandle != -1 ) { gameRenderWorld->FreeLightDef( lightHandle ); lightHandle = -1; } } void rvMonsterStroggHover::InitSpawnArgsVariables( void ) { numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" )); for ( int i = 0; i < numHoverJoints; i++ ) { jointHover[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_hover%d",i+1) ) ); } jointDust = animator.GetJointHandle ( spawnArgs.GetString( "joint_dust" ) ); jointMGun = animator.GetJointHandle ( spawnArgs.GetString( "joint_mgun" ) ); numMissileJoints = idMath::ClampInt(0,MAX_MISSILE_JOINTS,spawnArgs.GetInt( "num_missile_joints", "1" )); for ( int i = 0; i < numMissileJoints; i++ ) { jointMissile[i] = animator.GetJointHandle ( spawnArgs.GetString( va("joint_missile%d",i+1) ) ); } jointBomb = animator.GetJointHandle ( spawnArgs.GetString( "joint_bomb" ) ); mGunFireRate = SEC2MS(spawnArgs.GetFloat( "mgun_fire_rate", "0.1" )); missileFireRate = SEC2MS(spawnArgs.GetFloat( "missile_fire_rate", "0.25" )); bombFireRate = SEC2MS(spawnArgs.GetFloat( "bomb_fire_rate", "0.5" )); mGunMinShots = spawnArgs.GetInt( "mgun_minShots", "20" ); mGunMaxShots = spawnArgs.GetInt( "mgun_maxShots", "40" ); missileMinShots = spawnArgs.GetInt( "missile_minShots", "4" ); missileMaxShots = spawnArgs.GetInt( "missile_maxShots", "12" ); bombMinShots = spawnArgs.GetInt( "bomb_minShots", "5" ); bombMaxShots = spawnArgs.GetInt( "bomb_maxShots", "20" ); evadeDebounceRate = SEC2MS(spawnArgs.GetFloat( "evade_rate", "0" )); evadeChance = spawnArgs.GetFloat( "evade_chance", "0.6" ); evadeSpeed = spawnArgs.GetFloat( "evade_speed", "400" ); strafeSpeed = spawnArgs.GetFloat( "strafe_speed", "500" ); circleStrafeSpeed = spawnArgs.GetFloat( "circle_strafe_speed", "200" ); //LIGHT jointHeadlight = animator.GetJointHandle ( spawnArgs.GetString( "joint_light" ) ); jointHeadlightControl = animator.GetJointHandle ( spawnArgs.GetString( "joint_light_control" ) ); } void rvMonsterStroggHover::Hide( void ) { StopHeadlight(); idAI::Hide(); } void rvMonsterStroggHover::Show( void ) { idAI::Show(); StartHeadlight(); } void rvMonsterStroggHover::StartHeadlight( void ) { if ( jointHeadlight != INVALID_JOINT ) { lightHandle = -1; if ( cvarSystem->GetCVarInteger( "com_machineSpec" ) > 1 && spawnArgs.GetString("mtr_light") ) { idVec3 color; //const char* temp; const idMaterial *headLightMaterial = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); if ( headLightMaterial ) { renderLight.shader = declManager->FindMaterial( spawnArgs.GetString ( "mtr_light", "lights/muzzleflash" ), false ); renderLight.pointLight = spawnArgs.GetBool( "light_pointlight", "1" ); // RAVEN BEGIN // dluetscher: added detail levels to render lights renderLight.detailLevel = DEFAULT_LIGHT_DETAIL_LEVEL; // RAVEN END spawnArgs.GetVector( "light_color", "0 0 0", color ); renderLight.shaderParms[ SHADERPARM_RED ] = color[0]; renderLight.shaderParms[ SHADERPARM_GREEN ] = color[1]; renderLight.shaderParms[ SHADERPARM_BLUE ] = color[2]; renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; renderLight.lightRadius[0] = renderLight.lightRadius[1] = renderLight.lightRadius[2] = (float)spawnArgs.GetInt( "light_radius" ); if ( !renderLight.pointLight ) { renderLight.target = spawnArgs.GetVector( "light_target" ); renderLight.up = spawnArgs.GetVector( "light_up" ); renderLight.right = spawnArgs.GetVector( "light_right" ); renderLight.end = spawnArgs.GetVector( "light_target" );; } //lightOn = spawnArgs.GetBool( "start_on", "1" ); lightHandle = gameRenderWorld->AddLightDef( &renderLight ); } } // Hide flare surface if there is one /* temp = spawnArgs.GetString ( "light_flaresurface", "" ); if ( temp && *temp ) { parent->ProcessEvent ( &EV_HideSurface, temp ); } */ // Sounds shader when turning light //spawnArgs.GetString ( "snd_on", "", soundOn ); // Sound shader when turning light off //spawnArgs.GetString ( "snd_off", "", soundOff); UpdateLightDef ( ); } } void rvMonsterStroggHover::StopHeadlight( void ) { if ( lightHandle != -1 ) { gameRenderWorld->FreeLightDef ( lightHandle ); lightHandle = -1; } memset ( &renderLight, 0, sizeof(renderLight) ); } /* ================ rvMonsterStroggHover::Spawn ================ */ void rvMonsterStroggHover::Spawn ( void ) { // actionRocketAttack.Init ( spawnArgs, "action_rocketAttack", "Torso_RocketAttack", AIACTIONF_ATTACK ); // actionBlasterAttack.Init ( spawnArgs, "action_blasterAttack", "Torso_BlasterAttack", AIACTIONF_ATTACK ); actionMGunAttack.Init ( spawnArgs, "action_mGunAttack", "Torso_MGunAttack", AIACTIONF_ATTACK ); actionMissileAttack.Init ( spawnArgs, "action_missileAttack", "Torso_MissileAttack", AIACTIONF_ATTACK ); actionBombAttack.Init ( spawnArgs, "action_bombAttack", "Torso_BombAttack", AIACTIONF_ATTACK ); actionStrafe.Init ( spawnArgs, "action_strafe", "Torso_Strafe", 0 ); actionCircleStrafe.Init ( spawnArgs, "action_circleStrafe", "Torso_CircleStrafe", AIACTIONF_ATTACK ); InitSpawnArgsVariables(); evadeDebounce = 0; numHoverJoints = idMath::ClampInt(0,MAX_HOVER_JOINTS,spawnArgs.GetInt( "num_hover_joints", "1" )); for ( int i = 0; i < numHoverJoints; i++ ) { if ( jointHover[i] != INVALID_JOINT ) { effectHover[i] = PlayEffect ( "fx_hover", jointHover[i], true ); } } if ( !marker ) { marker = gameLocal.SpawnEntityDef( "target_null" ); } //LIGHT StopHeadlight(); StartHeadlight(); if ( jointHeadlight != INVALID_JOINT ) { effectHeadlight = PlayEffect( "fx_headlight", jointHeadlight, true ); } } /* ================ rvMonsterStroggHover::GetDebugInfo ================ */ void rvMonsterStroggHover::GetDebugInfo( debugInfoProc_t proc, void* userData ) { // Base class first idAI::GetDebugInfo ( proc, userData ); proc ( "rvMonsterStroggHover", "action_mGunAttack", aiActionStatusString[actionMGunAttack.status], userData ); proc ( "rvMonsterStroggHover", "action_missileAttack",aiActionStatusString[actionMissileAttack.status], userData ); proc ( "rvMonsterStroggHover", "action_bombAttack", aiActionStatusString[actionBombAttack.status], userData ); proc ( "rvMonsterStroggHover", "action_strafe", aiActionStatusString[actionStrafe.status], userData ); proc ( "rvMonsterStroggHover", "action_circleStrafe",aiActionStatusString[actionCircleStrafe.status], userData ); proc ( "rvMonsterStroggHover", "inPursuit", inPursuit?"true":"false", userData ); proc ( "rvMonsterStroggHover", "marker", (!inPursuit||marker==NULL)?"0 0 0":va("%f %f %f",marker->GetPhysics()->GetOrigin().x,marker->GetPhysics()->GetOrigin().y,marker->GetPhysics()->GetOrigin().z), userData ); proc ( "rvMonsterStroggHover", "holdPosTime", va("%d",holdPosTime), userData ); proc ( "rvMonsterStroggHover", "circleStrafing", circleStrafing?"true":"false", userData ); proc ( "rvMonsterStroggHover", "strafeRight", strafeRight?"true":"false", userData ); proc ( "rvMonsterStroggHover", "strafeTime", va("%d",strafeTime), userData ); proc ( "rvMonsterStroggHover", "mGunFireRate", va("%d",mGunFireRate), userData ); proc ( "rvMonsterStroggHover", "missileFireRate", va("%d",missileFireRate), userData ); proc ( "rvMonsterStroggHover", "bombFireRate", va("%d",bombFireRate), userData ); proc ( "rvMonsterStroggHover", "mGunMinShots", va("%d",mGunMinShots), userData ); proc ( "rvMonsterStroggHover", "mGunMaxShots", va("%d",mGunMaxShots), userData ); proc ( "rvMonsterStroggHover", "missileMinShots", va("%d",missileMinShots), userData ); proc ( "rvMonsterStroggHover", "missileMaxShots", va("%d",missileMaxShots), userData ); proc ( "rvMonsterStroggHover", "bombMinShots", va("%d",bombMinShots), userData ); proc ( "rvMonsterStroggHover", "bombMaxShots", va("%d",bombMaxShots), userData ); proc ( "rvMonsterStroggHover", "nextMGunFireTime", va("%d",nextMGunFireTime), userData ); proc ( "rvMonsterStroggHover", "nextMissileFireTime",va("%d",nextMissileFireTime), userData ); proc ( "rvMonsterStroggHover", "nextBombFireTime", va("%d",nextBombFireTime), userData ); proc ( "rvMonsterStroggHover", "shots", va("%d",shots), userData ); proc ( "rvMonsterStroggHover", "evadeDebounce", va("%d",evadeDebounce), userData ); proc ( "rvMonsterStroggHover", "evadeDebounceRate", va("%d",evadeDebounceRate), userData ); proc ( "rvMonsterStroggHover", "evadeChance", va("%g",evadeChance), userData ); proc ( "rvMonsterStroggHover", "evadeSpeed", va("%g",evadeSpeed), userData ); proc ( "rvMonsterStroggHover", "strafeSpeed", va("%g",strafeSpeed), userData ); proc ( "rvMonsterStroggHover", "circleStrafeSpeed", va("%g",circleStrafeSpeed), userData ); } /* ===================== rvMonsterStroggHover::UpdateLightDef ===================== */ void rvMonsterStroggHover::UpdateLightDef ( void ) { if ( jointHeadlight != INVALID_JOINT ) { idVec3 origin; idMat3 axis; if ( jointHeadlightControl != INVALID_JOINT ) { idAngles jointAng; jointAng.Zero(); jointAng.yaw = 10.0f * sin( ( (gameLocal.GetTime()%2000)-1000 ) / 1000.0f * idMath::PI ); jointAng.pitch = 7.5f * sin( ( (gameLocal.GetTime()%4000)-2000 ) / 2000.0f * idMath::PI ); animator.SetJointAxis( jointHeadlightControl, JOINTMOD_WORLD, jointAng.ToMat3() ); } GetJointWorldTransform ( jointHeadlight, gameLocal.time, origin, axis ); //origin += (localOffset * axis); // Include this part in the total bounds // FIXME: bounds are local //parent->AddToBounds ( worldOrigin ); //UpdateOrigin ( ); if ( lightHandle != -1 ) { renderLight.origin = origin; renderLight.axis = axis; gameRenderWorld->UpdateLightDef( lightHandle, &renderLight ); } } } /* ================ rvMonsterStroggHover::Think ================ */ void rvMonsterStroggHover::Think ( void ) { idAI::Think ( ); if ( !aifl.dead ) { // If thinking we should play an effect on the ground under us if ( !fl.hidden && !fl.isDormant && (thinkFlags & TH_THINK ) && !aifl.dead ) { trace_t tr; idVec3 origin; idMat3 axis; // Project the effect 80 units down from the bottom of our bbox GetJointWorldTransform ( jointDust, gameLocal.time, origin, axis ); // RAVEN BEGIN // ddynerman: multiple clip worlds gameLocal.TracePoint ( this, tr, origin, origin + axis[0] * (GetPhysics()->GetBounds()[0][2]+80.0f), CONTENTS_SOLID, this ); // RAVEN END // Start the dust effect if not already started if ( !effectDust ) { effectDust = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_dust" ), tr.endpos, tr.c.normal.ToMat3(), true ); } // If the effect is playing we should update its attenuation as well as its origin and axis if ( effectDust ) { effectDust->Attenuate ( 1.0f - idMath::ClampFloat ( 0.0f, 1.0f, (tr.endpos - origin).LengthFast ( ) / 127.0f ) ); effectDust->SetOrigin ( tr.endpos ); effectDust->SetAxis ( tr.c.normal.ToMat3() ); } // If the hover effect is playing we can set its end origin to the ground /* if ( effectHover ) { effectHover->SetEndOrigin ( tr.endpos ); } */ } else if ( effectDust ) { effectDust->Stop ( ); effectDust = NULL; } //Try to circle strafe or pursue if ( circleStrafing ) { CircleStrafe(); } else if ( !inPursuit ) { if ( !aifl.action && move.fl.done && !aifl.scripted ) { if ( GetEnemy() ) { if ( DistanceTo( GetEnemy() ) > 2000.0f || (GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetEnemy()->GetPhysics()->GetOrigin()-GetPhysics()->GetOrigin())) > 1000.0f ) {//enemy is far away or moving away from us at a pretty decent speed TryStartPursuit(); } } } } else { Pursue(); } //Dodge if ( !circleStrafing ) { if( combat.shotAtTime && gameLocal.GetTime() - combat.shotAtTime < 1000.0f ) { if ( nextBombFireTime < gameLocal.GetTime() - 3000 ) { if ( gameLocal.random.RandomFloat() > evadeChance ) { //40% chance of ignoring it - makes them dodge rockets less often but bullets more often? combat.shotAtTime = 0; } else if ( evadeDebounce < gameLocal.GetTime() ) { //ramps down from 400 to 100 over 1 second float speed = evadeSpeed - ((((float)(gameLocal.GetTime()-combat.shotAtTime))/1000.0f)*(evadeSpeed-(evadeSpeed*0.25f))); idVec3 evadeVel = viewAxis[1] * ((combat.shotAtAngle >= 0)?-1:1) * speed; evadeVel.z *= 0.5f; move.addVelocity += evadeVel; move.addVelocity.Normalize(); move.addVelocity *= speed; /* if ( move.moveCommand < NUM_NONMOVING_COMMANDS ) { //just need to do it once? combat.shotAtTime = 0; } */ if ( evadeDebounceRate > 1 ) { evadeDebounce = gameLocal.GetTime() + gameLocal.random.RandomInt( evadeDebounceRate ) + (ceil(((float)evadeDebounceRate)/2.0f)); } } } } } //If using melee rush to nav to him, stop when we're close enough to attack if ( combat.tacticalCurrent == AITACTICAL_MELEE && move.moveCommand == MOVE_TO_ENEMY && !move.fl.done && nextBombFireTime < gameLocal.GetTime() - 3000 && enemy.fl.visible && DistanceTo( GetEnemy() ) < 2000.0f ) { StopMove( MOVE_STATUS_DONE ); ForceTacticalUpdate(); } else { //whenever we're not in the middle of something, force an update of our tactical if ( !aifl.action ) { if ( !aasFind ) { if ( move.fl.done ) { if ( !inPursuit && !circleStrafing ) { ForceTacticalUpdate(); } } } } } } //update light // if ( lightOn ) { UpdateLightDef ( ); // } } /* ============ rvMonsterStroggHover::OnStartMoving ============ */ void rvMonsterStroggHover::OnStartMoving ( void ) { idAI::OnStartMoving(); if ( move.moveCommand == MOVE_TO_ENEMY ) { move.range = combat.meleeRange; } } /* ================ rvMonsterStroggHover::Save ================ */ void rvMonsterStroggHover::Save ( idSaveGame *savefile ) const { savefile->WriteInt ( shots ); savefile->WriteInt ( strafeTime ); savefile->WriteBool ( strafeRight ); savefile->WriteBool ( circleStrafing ); savefile->WriteFloat ( deathPitch ); savefile->WriteFloat ( deathRoll ); savefile->WriteFloat ( deathPitchRate ); savefile->WriteFloat ( deathYawRate ); savefile->WriteFloat ( deathRollRate ); savefile->WriteFloat ( deathSpeed ); savefile->WriteFloat ( deathGrav ); savefile->WriteInt ( markerCheckTime ); savefile->WriteVec3( attackPosOffset ); // actionRocketAttack.Save ( savefile ); // actionBlasterAttack.Save ( savefile ); actionMGunAttack.Save ( savefile ); actionMissileAttack.Save ( savefile ); actionBombAttack.Save ( savefile ); actionStrafe.Save ( savefile ); actionCircleStrafe.Save ( savefile ); for ( int i = 0; i < numHoverJoints; i++ ) { effectHover[i].Save ( savefile ); } effectDust.Save ( savefile ); effectHeadlight.Save ( savefile ); marker.Save( savefile ); savefile->WriteBool ( inPursuit ); savefile->WriteInt ( holdPosTime ); savefile->WriteInt ( nextMGunFireTime ); savefile->WriteInt ( nextMissileFireTime ); savefile->WriteInt ( nextBombFireTime ); savefile->WriteRenderLight ( renderLight ); savefile->WriteInt ( lightHandle ); savefile->WriteInt( evadeDebounce ); } /* ================ rvMonsterStroggHover::Restore ================ */ void rvMonsterStroggHover::Restore ( idRestoreGame *savefile ) { savefile->ReadInt ( shots ); savefile->ReadInt ( strafeTime ); savefile->ReadBool ( strafeRight ); savefile->ReadBool ( circleStrafing ); savefile->ReadFloat ( deathPitch ); savefile->ReadFloat ( deathRoll ); savefile->ReadFloat ( deathPitchRate ); savefile->ReadFloat ( deathYawRate ); savefile->ReadFloat ( deathRollRate ); savefile->ReadFloat ( deathSpeed ); savefile->ReadFloat ( deathGrav ); savefile->ReadInt ( markerCheckTime ); savefile->ReadVec3( attackPosOffset ); // actionRocketAttack.Restore ( savefile ); // actionBlasterAttack.Restore ( savefile ); actionMGunAttack.Restore ( savefile ); actionMissileAttack.Restore ( savefile ); actionBombAttack.Restore ( savefile ); actionStrafe.Restore ( savefile ); actionCircleStrafe.Restore ( savefile ); InitSpawnArgsVariables(); //NOTE: if the def file changes the the number of numHoverJoints, this will be BAD... for ( int i = 0; i < numHoverJoints; i++ ) { effectHover[i].Restore ( savefile ); } effectDust.Restore ( savefile ); effectHeadlight.Restore ( savefile ); marker.Restore( savefile ); savefile->ReadBool ( inPursuit ); savefile->ReadInt ( holdPosTime ); savefile->ReadInt ( nextMGunFireTime ); savefile->ReadInt ( nextMissileFireTime ); savefile->ReadInt ( nextBombFireTime ); savefile->ReadRenderLight ( renderLight ); savefile->ReadInt ( lightHandle ); if ( lightHandle != -1 ) { //get the handle again as it's out of date after a restore! lightHandle = gameRenderWorld->AddLightDef( &renderLight ); } savefile->ReadInt ( evadeDebounce ); } /* ================ rvMonsterStroggHover::Collide ================ */ bool rvMonsterStroggHover::Collide( const trace_t &collision, const idVec3 &velocity ) { if ( aifl.dead ) { StopHeadlight(); //stop headlight if ( effectHeadlight ) { effectHeadlight->Stop ( ); effectHeadlight = NULL; } // Stop the crash & burn effect for ( int i = 0; i < numHoverJoints; i++ ) { if ( effectHover[i] ) { effectHover[i]->Stop ( ); effectHover[i] = NULL; } } gameLocal.PlayEffect( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() ); SetState ( "State_Remove" ); return false; } return idAI::Collide( collision, velocity ); } /* ================ rvMonsterStroggHover::Damage ================ */ void rvMonsterStroggHover::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { if ( attacker == this ) { return; } bool wasDead = aifl.dead; idAI::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); if ( !wasDead && aifl.dead ) { SetState( "State_DeathSpiral" ); } } /* ================ rvMonsterStroggHover::OnDeath ================ */ void rvMonsterStroggHover::OnDeath ( void ) { // Stop the dust effect if ( effectDust ) { effectDust->Stop ( ); effectDust = NULL; } // Stop the hover effect for ( int i = 0; i < numHoverJoints; i++ ) { if ( effectHover[i] ) { effectHover[i]->Stop ( ); effectHover[i] = NULL; } } idAI::OnDeath ( ); } /* ===================== rvMonsterStroggHover::DeadMove ===================== */ void rvMonsterStroggHover::DeadMove( void ) { DeathPush ( ); physicsObj.UseVelocityMove( true ); RunPhysics(); } /* ===================== rvMonsterStroggHover::SkipImpulse ===================== */ bool rvMonsterStroggHover::SkipImpulse( idEntity* ent, int id ) { return ((ent==this) || (move.moveCommand==MOVE_RV_PLAYBACK)); } /* ================ rvMonsterStroggHover::CheckAction_Strafe ================ */ bool rvMonsterStroggHover::CheckAction_Strafe ( rvAIAction* action, int animNum ) { if ( inPursuit && !holdPosTime ) { return false; } if ( !enemy.fl.visible ) { return false; } if ( !enemy.fl.inFov ) { return false; } if ( !move.fl.done ) { return false; } if ( evadeDebounce >= gameLocal.GetTime() ) { return false; } if ( animNum != -1 && !TestAnimMove ( animNum ) ) { //well, at least try a new attack position if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { combat.tacticalUpdateTime = 0; } return false; } return true; } /* ================ rvMonsterStroggHover::CheckAction_CircleStrafe ================ */ bool rvMonsterStroggHover::CheckAction_CircleStrafe ( rvAIAction* action, int animNum ) { if ( inPursuit ) { return false; } if ( !enemy.fl.visible ) { return false; } if ( !enemy.fl.inFov ) { return false; } if ( !move.fl.done ) { return false; } return true; } /* ================ rvMonsterStroggHover::CheckAction_CircleStrafe ================ */ bool rvMonsterStroggHover::CheckAction_BombAttack ( rvAIAction* action, int animNum ) { if ( !GetEnemy() || !enemy.fl.visible ) { return false; } /* if ( GetPhysics()->GetLinearVelocity().Length() < 200.0f ) { //not moving enough return false; } */ if ( GetEnemy()->GetPhysics()->GetLinearVelocity()*(GetPhysics()->GetOrigin()-GetEnemy()->GetPhysics()->GetOrigin()) >= 250.0f ) { //enemy is moving toward me, drop 'em! return true; } return false; } /* ================ rvMonsterStroggHover::Spawn ================ */ bool rvMonsterStroggHover::CheckActions ( void ) { if ( PerformAction ( &actionCircleStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_CircleStrafe ) ) { return true; } /* if ( PerformAction ( &actionRocketAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerSpecialAttack ) ) { return true; } if ( PerformAction ( &actionBlasterAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { return true; } */ if ( PerformAction ( &actionBombAttack, (checkAction_t)&rvMonsterStroggHover::CheckAction_BombAttack ) ) { return true; } if ( PerformAction ( &actionMissileAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { return true; } if ( PerformAction ( &actionMGunAttack, (checkAction_t)&idAI::CheckAction_RangedAttack, &actionTimerRangedAttack ) ) { return true; } if ( idAI::CheckActions ( ) ) { return true; } if ( PerformAction ( &actionStrafe, (checkAction_t)&rvMonsterStroggHover::CheckAction_Strafe ) ) { return true; } return false; } /* ================ rvMonsterStroggHover::OnEnemyChange ================ */ void rvMonsterStroggHover::OnEnemyChange ( idEntity* oldEnemy ) { idAI::OnEnemyChange ( oldEnemy ); if ( !enemy.ent ) { return; } } /* ================ rvMonsterStroggHover::GetIdleAnimName ================ */ const char* rvMonsterStroggHover::GetIdleAnimName ( void ) { /* if ( move.moveType == MOVETYPE_FLY ) { return "flying_idle"; } */ return idAI::GetIdleAnimName ( ); } /* ================ rvMonsterStroggHover::DoNamedAttack ================ */ void rvMonsterStroggHover::DoNamedAttack ( const char* attackName, jointHandle_t joint ) { if ( joint != INVALID_JOINT ) { StartSound ( va("snd_%s_fire",attackName), SND_CHANNEL_ANY, 0, false, NULL ); PlayEffect ( va("fx_%s_flash",attackName), joint ); Attack ( attackName, joint, GetEnemy() ); } } /* ================ rvMonsterStroggHover::FilterTactical ================ */ int rvMonsterStroggHover::FilterTactical ( int availableTactical ) { availableTactical = idAI::FilterTactical( availableTactical ); if ( circleStrafing || inPursuit ) { return 0; } if ( nextBombFireTime >= gameLocal.GetTime() ) { availableTactical &= ~(AITACTICAL_RANGED_BITS); } else if ( enemy.fl.visible ) { availableTactical &= ~AITACTICAL_MELEE_BIT; } return availableTactical; } /* =============================================================================== States =============================================================================== */ CLASS_STATES_DECLARATION ( rvMonsterStroggHover ) // STATE ( "Torso_BlasterAttack", rvMonsterStroggHover::State_Torso_BlasterAttack ) // STATE ( "Torso_RocketAttack", rvMonsterStroggHover::State_Torso_RocketAttack ) STATE ( "Torso_MGunAttack", rvMonsterStroggHover::State_Torso_MGunAttack ) STATE ( "Torso_MissileAttack", rvMonsterStroggHover::State_Torso_MissileAttack ) STATE ( "Torso_BombAttack", rvMonsterStroggHover::State_Torso_BombAttack ) // STATE ( "Torso_EvadeLeft", rvMonsterStroggHover::State_Torso_EvadeLeft ) // STATE ( "Torso_EvadeRight", rvMonsterStroggHover::State_Torso_EvadeRight ) STATE ( "Torso_Strafe", rvMonsterStroggHover::State_Torso_Strafe ) STATE ( "Torso_CircleStrafe", rvMonsterStroggHover::State_Torso_CircleStrafe ) STATE ( "State_CircleStrafe", rvMonsterStroggHover::State_CircleStrafe ) STATE ( "State_DeathSpiral", rvMonsterStroggHover::State_DeathSpiral ) STATE ( "State_Pursue", rvMonsterStroggHover::State_Pursue ) END_CLASS_STATES /* ================ rvMonsterStroggHover::State_Torso_MGunAttack ================ */ stateResult_t rvMonsterStroggHover::State_Torso_MGunAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_LOOP, STAGE_WAITLOOP, }; switch ( parms.stage ) { case STAGE_INIT: shots = gameLocal.random.RandomInt ( mGunMaxShots-mGunMinShots ) + mGunMinShots; return SRESULT_STAGE ( STAGE_LOOP ); case STAGE_LOOP: DoNamedAttack( "mgun", jointMGun ); nextMGunFireTime = gameLocal.GetTime() + mGunFireRate; return SRESULT_STAGE ( STAGE_WAITLOOP ); case STAGE_WAITLOOP: if ( nextMGunFireTime <= gameLocal.GetTime() ) { if ( --shots <= 0 ) { ForceTacticalUpdate(); return SRESULT_DONE; } return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterStroggHover::State_Torso_MissileAttack ================ */ stateResult_t rvMonsterStroggHover::State_Torso_MissileAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_LOOP, STAGE_WAITLOOP, }; switch ( parms.stage ) { case STAGE_INIT: shots = gameLocal.random.RandomInt ( missileMaxShots-missileMinShots ) + missileMinShots; return SRESULT_STAGE ( STAGE_LOOP ); case STAGE_LOOP: DoNamedAttack( "missile", jointMissile[gameLocal.random.RandomInt(numMissileJoints)] ); nextMissileFireTime = gameLocal.GetTime() + missileFireRate; return SRESULT_STAGE ( STAGE_WAITLOOP ); case STAGE_WAITLOOP: if ( nextMissileFireTime <= gameLocal.GetTime() ) { if ( --shots <= 0 || enemy.range < (actionMissileAttack.minRange*0.75f) ) { //out of shots or enemy too close to safely keep launching rockets at ForceTacticalUpdate(); return SRESULT_DONE; } return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterStroggHover::State_Torso_BombAttack ================ */ stateResult_t rvMonsterStroggHover::State_Torso_BombAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_LOOP, STAGE_WAITLOOP, }; idVec3 vel = GetPhysics()->GetLinearVelocity(); if ( vel.z < 150.0f ) { vel.z += 20.0f; physicsObj.UseVelocityMove( true ); GetPhysics()->SetLinearVelocity( vel ); } switch ( parms.stage ) { case STAGE_INIT: move.fly_offset = 800;//go up! shots = gameLocal.random.RandomInt ( bombMaxShots-bombMinShots ) + bombMinShots; if ( GetEnemy() ) { //if I'm not above him, give me a quick boost first float zDiff = GetPhysics()->GetOrigin().z-GetEnemy()->GetPhysics()->GetOrigin().z; if ( zDiff < 150.0f ) { idVec3 vel = GetPhysics()->GetLinearVelocity(); vel.z += 200.0f; if ( zDiff < 0.0f ) { //even more if I'm below him! vel.z -= zDiff*2.0f; } physicsObj.UseVelocityMove( true ); GetPhysics()->SetLinearVelocity( vel ); } } if ( move.moveCommand == MOVE_TO_ATTACK ) { StopMove( MOVE_STATUS_DONE ); } if ( combat.tacticalCurrent == AITACTICAL_RANGED ) { ForceTacticalUpdate(); } if ( move.moveCommand == MOVE_NONE && !inPursuit && !circleStrafing ) { MoveToEnemy(); } return SRESULT_STAGE ( STAGE_LOOP ); case STAGE_LOOP: DoNamedAttack( "bomb", jointBomb ); nextBombFireTime = gameLocal.GetTime() + bombFireRate; return SRESULT_STAGE ( STAGE_WAITLOOP ); case STAGE_WAITLOOP: if ( nextBombFireTime <= gameLocal.GetTime() ) { if ( --shots <= 0 ) { move.fly_offset = spawnArgs.GetFloat("fly_offset","250"); ForceTacticalUpdate(); return SRESULT_DONE; } return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvMonsterStroggHover::State_Torso_BlasterAttack ================ */ /* stateResult_t rvMonsterStroggHover::State_Torso_BlasterAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAITSTART, STAGE_LOOP, STAGE_WAITLOOP, STAGE_WAITEND }; switch ( parms.stage ) { case STAGE_INIT: shots = gameLocal.random.RandomInt ( 8 ) + 4; PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_preshoot", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAITSTART ); case STAGE_WAITSTART: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; case STAGE_LOOP: PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_fire", 0 ); return SRESULT_STAGE ( STAGE_WAITLOOP ); case STAGE_WAITLOOP: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { if ( --shots <= 0 ) { PlayAnim ( ANIMCHANNEL_TORSO, "blaster_1_postshoot", 0 ); return SRESULT_STAGE ( STAGE_WAITEND ); } return SRESULT_STAGE ( STAGE_LOOP ); } return SRESULT_WAIT; case STAGE_WAITEND: if ( AnimDone ( ANIMCHANNEL_TORSO, 4 ) ) { ForceTacticalUpdate(); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } */ /* ================ rvMonsterStroggHover::State_Torso_RocketAttack ================ */ /* stateResult_t rvMonsterStroggHover::State_Torso_RocketAttack ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAITSTART, STAGE_LOOP, STAGE_WAITLOOP, STAGE_WAITEND }; switch ( parms.stage ) { case STAGE_INIT: //DisableAnimState ( ANIMCHANNEL_LEGS ); PlayAnim ( ANIMCHANNEL_TORSO, "rocket_range_attack", parms.blendFrames ); return SRESULT_STAGE ( STAGE_WAITSTART ); case STAGE_WAITSTART: if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) { ForceTacticalUpdate(); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } */ void rvMonsterStroggHover::Evade ( bool left ) { idVec3 vel = GetPhysics()->GetLinearVelocity(); vel += viewAxis[1] * (left?strafeSpeed:-strafeSpeed); physicsObj.UseVelocityMove( true ); GetPhysics()->SetLinearVelocity( vel ); } stateResult_t rvMonsterStroggHover::State_Torso_Strafe ( const stateParms_t& parms ) { //fixme: trace first for visibility & obstruction? if ( gameLocal.random.RandomFloat() > 0.5f ) { Evade( false ); } else { Evade( true ); } return SRESULT_DONE; } stateResult_t rvMonsterStroggHover::State_Torso_CircleStrafe ( const stateParms_t& parms ) { circleStrafing = true; return SRESULT_DONE; } bool rvMonsterStroggHover::MarkerPosValid ( void ) { //debouncer ftw if( markerCheckTime > gameLocal.GetTime() ) { return true; } markerCheckTime = gameLocal.GetTime() + 500 + (gameLocal.random.RandomFloat() * 500); trace_t trace; gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetOrigin(), GetPhysics()->GetClipMask(), NULL ); if ( !(trace.c.contents&GetPhysics()->GetClipMask()) ) {//not in solid gameLocal.TracePoint( this, trace, marker.GetEntity()->GetPhysics()->GetOrigin(), GetEnemy()->GetEyePosition(), MASK_SHOT_BOUNDINGBOX, GetEnemy() ); idActor* enemyAct = NULL; rvVehicle* enemyVeh = NULL; if ( GetEnemy()->IsType( rvVehicle::GetClassType() ) ) { enemyVeh = static_cast(GetEnemy()); } else if ( GetEnemy()->IsType( idActor::GetClassType() ) ) { enemyAct = static_cast(GetEnemy()); } idEntity* hitEnt = gameLocal.entities[trace.c.entityNum]; idActor* hitAct = NULL; if ( hitEnt && hitEnt->IsType( idActor::GetClassType() ) ) { hitAct = static_cast(hitEnt); } if ( trace.fraction >= 1.0f || (enemyAct && enemyAct->IsInVehicle() && enemyAct->GetVehicleController().GetVehicle() == gameLocal.entities[trace.c.entityNum]) || (enemyVeh && hitAct && hitAct->IsInVehicle() && hitAct->GetVehicleController().GetVehicle() == enemyVeh) ) {//have a clear LOS to enemy if ( PointReachableAreaNum( marker.GetEntity()->GetPhysics()->GetOrigin() ) ) {//valid AAS there... return true; } } } return false; } void rvMonsterStroggHover::TryStartPursuit ( void ) { if ( GetEnemy() ) { inPursuit = false; if ( !marker.GetEntity() ) { //wtf?! assert(0); return; } attackPosOffset.Set( gameLocal.random.CRandomFloat()*500.0f, gameLocal.random.CRandomFloat()*500.0f, 0.0f ); if ( attackPosOffset.Length() < 150.0f ) { attackPosOffset.Normalize(); attackPosOffset *= 150.0f; } attackPosOffset.z = (gameLocal.random.CRandomFloat()*30.0f)+50.0f + move.fly_offset; marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset ); if ( MarkerPosValid() ) { if ( MoveToEntity( marker ) ) { inPursuit = true; holdPosTime = 0; SetState( "State_Pursue" ); } } } } void rvMonsterStroggHover::Pursue ( void ) { if ( marker.GetEntity() && GetEnemy() ) { marker.GetEntity()->GetPhysics()->SetOrigin( GetEnemy()->GetPhysics()->GetOrigin()+attackPosOffset ); if ( DebugFilter(ai_debugMove) ) { gameRenderWorld->DebugAxis( marker.GetEntity()->GetPhysics()->GetOrigin(), marker.GetEntity()->GetPhysics()->GetAxis() ); } if ( MarkerPosValid() ) { bool breakOff = false; if ( move.fl.done ) {//even once get there, hold that position for a while... if ( holdPosTime && holdPosTime > gameLocal.GetTime() ) {//held this position long enough breakOff = true; } else { if ( !holdPosTime ) {//just got there, hold position for a bit holdPosTime = gameLocal.random.RandomInt(2000)+3000 + gameLocal.GetTime(); } if ( !MoveToEntity( marker ) ) { breakOff = true; } } } if ( !breakOff ) { return; } } } if ( !move.fl.done ) { StopMove( MOVE_STATUS_DONE ); } inPursuit = false; } stateResult_t rvMonsterStroggHover::State_Pursue ( const stateParms_t& parms ) { if ( inPursuit ) { // Perform actions along the way if ( UpdateAction ( ) ) { return SRESULT_WAIT; } return SRESULT_WAIT; } SetState( "State_Combat" ); return SRESULT_DONE; } void rvMonsterStroggHover::CircleStrafe ( void ) { if ( !GetEnemy() || strafeTime < gameLocal.GetTime() || !enemy.fl.visible || !enemy.fl.inFov ) { //FIXME: also stop if I bump into something circleStrafing = false; strafeTime = 0; SetState( "State_Combat" ); return; } if ( !strafeTime ) { strafeTime = gameLocal.GetTime() + 8000; //FIXME: try to see which side it clear? strafeRight = (gameLocal.random.RandomFloat()>0.5f); } idVec3 vel = GetPhysics()->GetLinearVelocity(); idVec3 strafeVel = viewAxis[1] * (strafeRight?-circleStrafeSpeed:circleStrafeSpeed); strafeVel.z = 0.0f; vel += strafeVel; vel.Normalize(); vel *= circleStrafeSpeed; physicsObj.UseVelocityMove( true ); GetPhysics()->SetLinearVelocity( vel ); TurnToward( GetEnemy()->GetPhysics()->GetOrigin() ); } stateResult_t rvMonsterStroggHover::State_CircleStrafe ( const stateParms_t& parms ) { if ( circleStrafing ) { // Perform actions along the way if ( UpdateAction ( ) ) { return SRESULT_WAIT; } return SRESULT_WAIT; } SetState( "State_Combat" ); return SRESULT_DONE; } stateResult_t rvMonsterStroggHover::State_DeathSpiral ( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_SPIRAL }; switch ( parms.stage ) { case STAGE_INIT: { disablePain = true; // Make sure all animation and attack states stop StopAnimState ( ANIMCHANNEL_TORSO ); StopAnimState ( ANIMCHANNEL_LEGS ); // Start the crash & burn effects for ( int i = 0; i < numHoverJoints; i++ ) { if ( jointHover[i] != INVALID_JOINT ) { PlayEffect ( "fx_hurt", jointHover[i], false ); effectHover[i] = PlayEffect ( "fx_crash", jointHover[i], true ); } } deathPitch = viewAxis.ToAngles()[0]; deathRoll = viewAxis.ToAngles()[2]; deathPitchRate = gameLocal.random.RandomFloat()*0.3f + 0.1f; deathYawRate = gameLocal.random.RandomFloat()*2.0f + 1.5f; deathRollRate = gameLocal.random.RandomFloat()*3.0f + 1.0f; deathSpeed = gameLocal.random.RandomFloat()*300.0f + 500.0f; deathGrav = gameLocal.random.RandomFloat()*6.0f + 6.0f; strafeRight = (gameLocal.random.RandomFloat()>0.5f); StopSound( SND_CHANNEL_HEART, false ); StartSound ( "snd_crash", SND_CHANNEL_HEART, 0, false, NULL ); } return SRESULT_STAGE ( STAGE_SPIRAL ); case STAGE_SPIRAL: { move.current_yaw += (strafeRight?-deathYawRate:deathYawRate); deathPitch = idMath::ClampFloat( -90.0f, 90.0f, deathPitch+deathPitchRate ); deathRoll += (strafeRight?deathRollRate:-deathRollRate); viewAxis = idAngles( deathPitch, move.current_yaw, deathRoll ).ToMat3(); idVec3 vel = GetPhysics()->GetLinearVelocity(); idVec3 strafeVel = viewAxis[0] * deathSpeed; strafeVel.z = 0; vel += strafeVel; vel.Normalize(); vel *= deathSpeed; vel += GetPhysics()->GetGravity()/deathGrav; physicsObj.UseVelocityMove( true ); physicsObj.SetLinearVelocity( vel ); idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); if( emitter ) { refSound.parms.frequencyShift += 0.025f; emitter->ModifySound ( SND_CHANNEL_HEART, &refSound.parms ); } } return SRESULT_WAIT; } return SRESULT_ERROR; }