/* =========================================================================== Copyright (C) 2015 the OpenMoHAA team This file is part of OpenMoHAA source code. OpenMoHAA source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. OpenMoHAA source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenMoHAA source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // playerbot.cpp: Multiplayer bot system. #include "g_local.h" #include "actor.h" #include "playerbot.h" #include "consoleevent.h" #include "debuglines.h" #include "scriptexception.h" // We assume that we have limited access to the server-side // and that most logic come from the playerstate_s structure cvar_t *bot_manualmove; CLASS_DECLARATION( Player, PlayerBot, NULL ) { { &EV_Killed, &PlayerBot::Killed }, { &EV_GotKill, &PlayerBot::GotKill }, { &EV_Player_StuffText, &PlayerBot::EventStuffText }, { NULL, NULL } }; PlayerBot::botfunc_t PlayerBot::botfuncs[ MAX_BOT_FUNCTIONS ]; PlayerBot::PlayerBot() { entflags |= EF_BOT; if( LoadingSavegame ) { return; } m_Path.SetFallHeight( 96 ); m_bPathing = false; m_bTempAway = false; m_bDeltaMove = true; m_botCmd.serverTime = 0; m_botCmd.msec = 0; m_botCmd.buttons = 0; m_botCmd.angles[ 0 ] = ANGLE2SHORT( 0 ); m_botCmd.angles[ 1 ] = ANGLE2SHORT( 0 ); m_botCmd.angles[ 2 ] = ANGLE2SHORT( 0 ); m_botCmd.forwardmove = 0; m_botCmd.rightmove = 0; m_botCmd.upmove = 0; m_botEyes.angles[ 0 ] = 0; m_botEyes.angles[ 1 ] = 0; m_botEyes.ofs[ 0 ] = 0; m_botEyes.ofs[ 1 ] = 0; m_botEyes.ofs[ 2 ] = viewheight; m_vAngSpeed = vec_zero; m_vTargetAng = vec_zero; m_vCurrentAng = vec_zero; m_iCheckPathTime = 0; m_iTempAwayTime = 0; m_fYawSpeedMult = 1.0f; m_iCuriousTime = 0; m_iAttackTime = 0; m_StateFlags = 0; m_RunLabel.TrySetScript("global/bot_run.scr"); } void PlayerBot::Init ( void ) { bot_manualmove = gi.Cvar_Get( "bot_manualmove", "0", 0 ); for( int i = 0; i < MAX_BOT_FUNCTIONS; i++ ) { botfuncs[ i ].BeginState = &PlayerBot::State_DefaultBegin; botfuncs[ i ].EndState = &PlayerBot::State_DefaultEnd; } InitState_Attack( &botfuncs[ 0 ] ); InitState_Curious( &botfuncs[ 1 ] ); InitState_Grenade( &botfuncs[ 2 ] ); InitState_Idle( &botfuncs[ 3 ] ); InitState_Weapon( &botfuncs[ 4 ] ); } float AngleDifference( float ang1, float ang2 ) { float diff; diff = ang1 - ang2; if( ang1 > ang2 ) { if( diff > 180.0 ) diff -= 360.0; } else { if( diff < -180.0 ) diff += 360.0; } return diff; } void PlayerBot::TurnThink ( void ) { float diff, factor, maxchange, anglespeed, desired_speed; int i; if( m_vTargetAng[ PITCH ] > 180 ) m_vTargetAng[ PITCH ] -= 360; factor = 0.25f; maxchange = 360; if( maxchange < 240 ) maxchange = 240; maxchange *= level.frametime; for( i = 0; i < 2; i++ ) { //over reaction view model m_vCurrentAng[ i ] = AngleMod( m_vCurrentAng[ i ] ); m_vTargetAng[ i ] = AngleMod( m_vTargetAng[ i ] ); diff = AngleDifference( m_vCurrentAng[ i ], m_vTargetAng[ i ] ); desired_speed = diff * factor; m_vAngSpeed[ i ] += ( m_vAngSpeed[ i ] - desired_speed ); if( m_vAngSpeed[ i ] > 180 ) m_vAngSpeed[ i ] = maxchange; if( m_vAngSpeed[ i ] < -180 ) m_vAngSpeed[ i ] = -maxchange; anglespeed = m_vAngSpeed[ i ]; if( anglespeed > maxchange ) anglespeed = maxchange; if( anglespeed < -maxchange ) anglespeed = -maxchange; m_vCurrentAng[ i ] += anglespeed; m_vCurrentAng[ i ] = AngleMod( m_vCurrentAng[ i ] ); //demping m_vAngSpeed[ i ] *= 0.45 * ( 1 - factor ); } if( m_vCurrentAng[ PITCH ] > 180 ) m_vCurrentAng[ PITCH ] -= 360; m_botEyes.angles[ 0 ] = m_vCurrentAng[ 0 ]; m_botEyes.angles[ 1 ] = m_vCurrentAng[ 1 ]; m_botCmd.angles[ 0 ] = ANGLE2SHORT( m_vCurrentAng[ 0 ] ) - client->ps.delta_angles[ 0 ]; m_botCmd.angles[ 1 ] = ANGLE2SHORT( m_vCurrentAng[ 1 ] ) - client->ps.delta_angles[ 1 ]; m_botCmd.angles[ 2 ] = ANGLE2SHORT( m_vCurrentAng[ 2 ] ) - client->ps.delta_angles[ 2 ]; } void PlayerBot::CheckAttractiveNodes ( void ) { for( int i = m_attractList.NumObjects(); i > 0; i-- ) { nodeAttract_t *a = m_attractList.ObjectAt( i ); if( a->m_pNode == NULL || !a->m_pNode->CheckTeam( this ) || level.time > a->m_fRespawnTime ) { delete a; m_attractList.RemoveObjectAt( i ); } } } void PlayerBot::MoveThink ( void ) { Vector vDir; Vector vAngles; Vector vWishDir; m_botCmd.forwardmove = 0; m_botCmd.rightmove = 0; CheckAttractiveNodes(); if( !IsMoving() ) { return; } if( m_bTempAway && level.inttime >= m_iTempAwayTime ) { m_bTempAway = false; m_Path.FindPath( origin, m_vTargetPos, this, 0, NULL, 0 ); } if( m_Path.CurrentNode() ) { m_Path.UpdatePos( origin, 8 ); if( m_bDeltaMove ) m_vCurrentGoal = origin + m_Path.CurrentDelta(); else m_vCurrentGoal = m_Path.CurrentNode()->point; if( m_Path.Complete( origin ) ) { // Clear the path m_Path.Clear(); } } else if( !m_bTempAway ) { m_vCurrentGoal = origin; } if( ai_debugpath->integer ) { G_DebugLine( centroid, m_vCurrentGoal + Vector( 0, 0, 36 ), 1, 1, 0, 1 ); } // Check if we're blocked if( level.inttime >= m_iCheckPathTime ) { Vector end; m_bDeltaMove = false; m_iCheckPathTime = level.inttime + 2000; if( m_Path.CurrentNode() && m_Path.CurrentNode() != m_Path.LastNode() ) { end = m_Path.NextNode()->point; } else { end = m_vCurrentGoal; } if( GetMoveResult() >= MOVERESULT_BLOCKED || velocity.lengthSquared() <= 8 * 8 ) { gi.DPrintf( "Updating path for bot client %i\n", edict - g_entities ); m_bTempAway = true; m_bDeltaMove = false; m_iTempAwayTime = level.inttime + 750; // Try to backward a little //vDir = end - origin; //VectorNormalizeFast( vDir ); //m_Path.FindPathAway( origin, origin + vDir * 16, vDir, this, 256, NULL, 0 ); //m_Path.FindPathAway( origin, origin + m_vLastValidDir * 16, -m_vLastValidDir, this, 256, NULL, 0 ); m_Path.Clear(); m_vCurrentGoal = m_vLastValidGoal - m_vLastValidDir * 256; return; } } if( ai_debugpath->integer ) { PathInfo *pos = m_Path.CurrentNode(); if( pos != NULL ) { while( pos != m_Path.LastNode() ) { Vector vStart = pos->point + Vector( 0, 0, 32 ); pos--; Vector vEnd = pos->point + Vector( 0, 0, 32 ); G_DebugLine( vStart, vEnd, 1, 0, 0, 1 ); } } } if( ( m_vTargetPos - origin ).lengthSquared() <= 16 * 16 ) { ClearMove(); } vDir = m_vCurrentGoal - centroid; // Rotate the dir //vDir = m_Path.CurrentDelta(); VectorNormalize( vDir ); vAngles = vDir.toAngles() - angles; vAngles.AngleVectorsLeft( &vWishDir ); m_vLastValidDir = vWishDir; m_vLastValidGoal = m_vCurrentGoal; // Forward to the specified direction float x = vWishDir.x * 255.0f; float y = -vWishDir.y * 255.0f; m_botCmd.forwardmove = ( signed char )( x > 127 ? 127 : x < -127 ? -127 : x ); m_botCmd.rightmove = ( signed char )( y > 127 ? 127 : y < -127 ? -127 : y ); CheckJump(); Weapon *pWeap = GetActiveWeapon( WEAPON_MAIN ); if( pWeap && !pWeap->ShouldReload() ) { m_RunLabel.Execute(this); } } void PlayerBot::CheckJump ( void ) { Vector start; Vector end; Vector dir; trace_t trace; if( !m_Path.CurrentNode() ) return; dir = m_Path.CurrentDelta(); VectorNormalizeFast( dir ); G_DebugLine( origin + Vector( 0, 0, STEPSIZE ), origin + Vector( 0, 0, STEPSIZE ) + dir * 32, 1, 0, 1, 1 ); G_DebugLine( origin + Vector( 0, 0, 56 ), origin + Vector( 0, 0, 56 ) + dir * 32, 1, 0, 1, 1 ); Vector vStart = origin + Vector( 0, 0, STEPSIZE ); Vector vEnd = origin + Vector( 0, 0, STEPSIZE ) + dir * 4; // Check if the bot needs to jump trace = G_Trace( vStart, mins, maxs, vEnd, this, MASK_PLAYERSOLID, false, "PlayerBot::CheckJump" ); // No need to jump if( trace.fraction >= 0.95f ) { m_botCmd.upmove = 0; return; } start = origin + Vector( 0, 0, 56 ); end = origin + Vector( 0, 0, 56 ) + dir * 4; // Check if the bot can jump trace = G_Trace( start, mins, maxs, end, this, MASK_PLAYERSOLID, true, "PlayerBot::CheckJump" ); // Make the bot climb walls if( trace.fraction >= 0.95f ) { if( !m_botCmd.upmove ) m_botCmd.upmove = 127; else m_botCmd.upmove = 0; } else { m_botCmd.upmove = 0; } } void PlayerBot::CheckEndPos ( void ) { Vector start; Vector end; trace_t trace; if( !m_Path.LastNode() ) return; start = m_Path.LastNode()->point; end = m_vTargetPos; trace = G_Trace( start, mins, maxs, end, this, MASK_TARGETPATH, true, "PlayerBot::CheckEndPos" ); if( trace.fraction < 0.95f ) m_vTargetPos = trace.endpos; } void PlayerBot::CheckUse ( void ) { Vector dir; Vector start; Vector end; trace_t trace; m_botCmd.buttons &= ~BUTTON_USE; angles.AngleVectorsLeft( &dir ); start = origin + Vector( 0, 0, viewheight ); end = origin + Vector( 0, 0, viewheight ) + dir * 32; trace = G_Trace( start, vec_zero, vec_zero, end, this, MASK_USABLE, false, "PlayerBot::CheckUse" ); // It may be a door if( ( trace.allsolid || trace.startsolid || trace.fraction != 1.0f ) && trace.entityNum ) m_botCmd.buttons |= BUTTON_USE; } void PlayerBot::GetUsercmd ( usercmd_t *ucmd ) { *ucmd = m_botCmd; } void PlayerBot::GetEyeInfo ( usereyes_t *eyeinfo ) { *eyeinfo = m_botEyes; } void PlayerBot::UpdateBotStates ( void ) { if( bot_manualmove->integer ) { memset( &m_botCmd, 0, sizeof( usercmd_t ) ); return; } if( IsDead() ) { // The bot should respawn m_botCmd.buttons ^= BUTTON_ATTACKLEFT; return; } m_botCmd.buttons |= BUTTON_RUN; m_botEyes.ofs[ 0 ] = 0; m_botEyes.ofs[ 1 ] = 0; m_botEyes.ofs[ 2 ] = viewheight; m_botEyes.angles[ 0 ] = 0; m_botEyes.angles[ 1 ] = 0; SetTargetAngles( Vector( 0, 90, 0 ) ); CheckStates(); MoveThink(); TurnThink(); CheckUse(); } void PlayerBot::SendCommand ( const char *text ) { char *buffer; char *data; size_t len; ConsoleEvent ev; len = strlen( text ) + 1; buffer = ( char * )gi.Malloc( len ); data = buffer; Q_strncpyz( data, text, len ); const char *com_token = COM_Parse( &data ); if( !com_token ) { return; } m_lastcommand = com_token; if( !Event::GetEvent( com_token ) ) { return; } ev = ConsoleEvent( com_token ); if( !( ev.GetEventFlags( ev.eventnum ) & EV_CONSOLE ) ) { gi.Free( buffer ); return; } ev.SetConsoleEdict( edict ); while( 1 ) { com_token = COM_Parse( &data ); if( !com_token || !*com_token ) { break; } ev.AddString( com_token ); } gi.Free( buffer ); try { ProcessEvent( ev ); } catch( ScriptException& exc ) { gi.DPrintf( "*** Bot Command Exception *** %s\n", exc.string.c_str() ); } } void PlayerBot::setAngles ( Vector ang ) { Entity::setAngles( ang ); SetTargetAngles( angles ); } void PlayerBot::updateOrigin ( void ) { Entity::updateOrigin(); if( origin == client->ps.origin ) { // no change because of Pmove return; } m_pPrimaryAttract = NULL; if( m_Path.CurrentNode() ) { // recalculate paths because of a new origin m_Path.ReFindPath( origin, this ); } } /* ==================== SetTargetAngles Set the bot's angle ==================== */ void PlayerBot::SetTargetAngles ( Vector vAngles ) { m_vTargetAng = vAngles; } /* ==================== AimAt Make the bot face to the specified direction ==================== */ void PlayerBot::AimAt ( Vector vPos ) { Vector vDelta = vPos - centroid; VectorNormalize( vDelta ); vectoangles( vDelta, m_vTargetAng ); } /* ==================== AimAtAimNode Make the bot face toward the current path ==================== */ void PlayerBot::AimAtAimNode ( void ) { if( m_Path.CurrentDelta() ) { AimAt( origin + m_Path.CurrentDelta() ); } else { AimAt( m_vCurrentGoal ); } m_vTargetAng[ PITCH ] = 0; } /* ==================== CheckReload Make the bot reload if necessary ==================== */ void PlayerBot::CheckReload ( void ) { Weapon *weap = GetActiveWeapon( WEAPON_MAIN ); if( weap && weap->CheckReload( FIRE_PRIMARY ) ) { SendCommand( "reload" ); } } /* ==================== MoveTo Move to the specified position ==================== */ void PlayerBot::MoveTo ( Vector vPos, float *vLeashHome, float fLeashRadius ) { m_bPathing = true; m_vTargetPos = vPos; m_Path.FindPath( origin, vPos, this, 0, vLeashHome, fLeashRadius * fLeashRadius ); if( !m_Path.CurrentNode() ) { m_bPathing = false; return; } CheckEndPos(); } /* ==================== MoveTo Move to the nearest attractive point with a minimum priority Returns true if no attractive point was found ==================== */ bool PlayerBot::MoveToBestAttractivePoint ( int iMinPriority ) { Container< AttractiveNode * > list; AttractiveNode *bestNode; float bestDistanceSquared; int bestPriority; if( m_pPrimaryAttract ) { MoveTo( m_pPrimaryAttract->origin ); if( !IsMoving() ) { m_pPrimaryAttract = NULL; } else { if( MoveDone() ) { if( !m_fAttractTime ) { m_fAttractTime = level.time + m_pPrimaryAttract->m_fMaxStayTime; } if( level.time > m_fAttractTime ) { nodeAttract_t *a = new nodeAttract_t; a->m_fRespawnTime = level.time + m_pPrimaryAttract->m_fRespawnTime; a->m_pNode = m_pPrimaryAttract; m_pPrimaryAttract = NULL; } } return true; } } if( !attractiveNodes.NumObjects() ) { return false; } bestNode = NULL; bestDistanceSquared = 99999999.0f; bestPriority = iMinPriority; for( int i = attractiveNodes.NumObjects(); i > 0; i-- ) { AttractiveNode *node = attractiveNodes.ObjectAt( i ); float distSquared; bool m_bRespawning = false; for( int j = m_attractList.NumObjects(); j > 0; j-- ) { AttractiveNode *node2 = m_attractList.ObjectAt( j )->m_pNode; if( node2 == node ) { m_bRespawning = true; break; } } if( m_bRespawning ) { continue; } if( node->m_iPriority < bestPriority ) { continue; } if( !node->CheckTeam( this ) ) { continue; } distSquared = VectorLengthSquared( origin - node->origin ); if( node->m_fMaxDistanceSquared >= 0 && distSquared > node->m_fMaxDistanceSquared ) { continue; } if( !CanMoveTo( node->origin ) ) { continue; } if( distSquared < bestDistanceSquared ) { bestDistanceSquared = distSquared; bestNode = node; bestPriority = node->m_iPriority; } } if( bestNode ) { m_pPrimaryAttract = bestNode; m_fAttractTime = 0; MoveTo( bestNode->origin ); return true; } else { // No attractive point found return false; } } /* ==================== CanMoveTo Returns true if the bot has done moving ==================== */ bool PlayerBot::CanMoveTo ( Vector vPos ) { return m_Path.DoesTheoreticPathExist( origin, vPos, NULL, 0, NULL, 0 ); } /* ==================== MoveDone Returns true if the bot has done moving ==================== */ bool PlayerBot::MoveDone ( void ) { return m_Path.Complete( origin ); } /* ==================== IsMoving Returns true if the bot has a current path ==================== */ bool PlayerBot::IsMoving ( void ) { return m_bPathing; } /* ==================== ClearMove Stop the bot from moving ==================== */ void PlayerBot::ClearMove ( void ) { m_Path.Clear(); m_bPathing = false; } /* ==================== MoveNear Move near the specified position within the radius ==================== */ void PlayerBot::MoveNear ( Vector vNear, float fRadius, float *vLeashHome, float fLeashRadius ) { m_bPathing = true; m_Path.FindPathNear( origin, vNear, this, 0, fRadius * fRadius, vLeashHome, fLeashRadius * fLeashRadius ); if( !m_Path.CurrentNode() ) { m_bPathing = false; return; } m_vTargetPos = m_Path.LastNode()->point; } /* ==================== AvoidPath Avoid the specified position within the radius and start from a direction ==================== */ void PlayerBot::AvoidPath ( Vector vAvoid, float fAvoidRadius, Vector vPreferredDir, float *vLeashHome, float fLeashRadius ) { Vector vDir; if( vPreferredDir == vec_zero ) { vDir = origin - vAvoid; VectorNormalizeFast( vDir ); } else { vDir = vPreferredDir; } m_bPathing = true; m_Path.FindPathAway( origin, vAvoid, vDir, this, fAvoidRadius, vLeashHome, fLeashRadius * fLeashRadius ); if( !m_Path.CurrentNode() ) { m_bPathing = false; return; } m_vTargetPos = m_Path.LastNode()->point; } /* ==================== NoticeEvent Warn the bot of an event ==================== */ void PlayerBot::NoticeEvent ( Vector vPos, int iType, Entity *pEnt, float fDistanceSquared, float fRadiusSquared ) { // Ignore teammates if( pEnt->IsSubclassOfPlayer() ) { Player *p = ( Player * )pEnt; if( p->GetTeam() == GetTeam() ) { return; } } else if( pEnt->IsSubclassOfWeapon() ) { Weapon *pWeap = ( Weapon * )pEnt; if( pWeap->GetOwner() ) { Player *p = ( Player * )pWeap->GetOwner(); if( p->IsSubclassOfPlayer() && p->GetTeam() == GetTeam() ) { return; } } } switch( iType ) { case AI_EVENT_MISC: case AI_EVENT_MISC_LOUD: break; case AI_EVENT_WEAPON_FIRE: case AI_EVENT_WEAPON_IMPACT: case AI_EVENT_EXPLOSION: case AI_EVENT_AMERICAN_VOICE: case AI_EVENT_GERMAN_VOICE: case AI_EVENT_AMERICAN_URGENT: case AI_EVENT_GERMAN_URGENT: case AI_EVENT_FOOTSTEP: case AI_EVENT_GRENADE: default: m_iCuriousTime = level.inttime + 20000; m_vLastCuriousPos = vPos; break; } } /* ==================== ClearEnemy Clear the bot's enemy ==================== */ void PlayerBot::ClearEnemy ( void ) { m_iAttackTime = 0; m_pEnemy = NULL; m_vOldEnemyPos = vec_zero; m_vLastEnemyPos = vec_zero; } /* ==================== Bot states -------------------- ____________________ -------------------- ____________________ -------------------- ____________________ -------------------- ____________________ ==================== */ void PlayerBot::CheckStates ( void ) { m_StateCount = 0; for( int i = 0; i < MAX_BOT_FUNCTIONS; i++ ) { botfunc_t *func = &botfuncs[ i ]; if( func->CheckCondition ) { if( ( this->*func->CheckCondition )() ) { if( !( m_StateFlags & ( 1 << i ) ) ) { m_StateFlags |= 1 << i; if( func->BeginState ) { ( this->*func->BeginState )( ); } } if( func->ThinkState ) { m_StateCount++; ( this->*func->ThinkState )( ); } } else { if( ( m_StateFlags & ( 1 << i ) ) ) { m_StateFlags &= ~( 1 << i ); if( func->EndState ) { ( this->*func->EndState )( ); } } } } else { if( func->ThinkState ) { m_StateCount++; ( this->*func->ThinkState )( ); } } } assert( m_StateCount ); if( !m_StateCount ) { gi.DPrintf( "*** WARNING *** %s was stuck with no states !!!", client->pers.netname ); State_Reset(); } } /* ==================== Default state ==================== */ void PlayerBot::State_DefaultBegin ( void ) { ClearMove(); } void PlayerBot::State_DefaultEnd ( void ) { } void PlayerBot::State_Reset ( void ) { m_iCuriousTime = 0; m_iAttackTime = 0; m_vLastCuriousPos = vec_zero; m_vOldEnemyPos = vec_zero; m_vLastEnemyPos = vec_zero; m_vLastDeathPos = vec_zero; m_pEnemy = NULL; } /* ==================== Idle state Make the bot move to random directions ==================== */ void PlayerBot::InitState_Idle ( botfunc_t *func ) { func->CheckCondition = &PlayerBot::CheckCondition_Idle; func->ThinkState = &PlayerBot::State_Idle; } bool PlayerBot::CheckCondition_Idle ( void ) { if( m_iCuriousTime ) return false; if( m_iAttackTime ) return false; return true; } void PlayerBot::State_Idle ( void ) { AimAtAimNode(); CheckReload(); if( !MoveToBestAttractivePoint() && !IsMoving() ) { if( m_vLastDeathPos != vec_zero ) { MoveTo( m_vLastDeathPos ); if( MoveDone() ) { m_vLastDeathPos = vec_zero; } } else { AvoidPath( origin - Vector( orientation[ 0 ] ) * 128.0f, 1024 ); } } } /* ==================== Curious state Forward to the last event position ==================== */ void PlayerBot::InitState_Curious ( botfunc_t *func ) { func->CheckCondition = &PlayerBot::CheckCondition_Curious; func->ThinkState = &PlayerBot::State_Curious; } bool PlayerBot::CheckCondition_Curious ( void ) { if( m_iAttackTime ) { m_iCuriousTime = 0; return false; } if( level.inttime > m_iCuriousTime ) { if( m_iCuriousTime ) { ClearMove(); m_iCuriousTime = 0; } return false; } return true; } void PlayerBot::State_Curious ( void ) { //AimAt( m_vLastCuriousPos ); AimAtAimNode(); if( !MoveToBestAttractivePoint( 3 ) && !IsMoving() ) { MoveTo( m_vLastCuriousPos ); } if( MoveDone() ) { m_iCuriousTime = 0; } } /* ==================== Attack state Attack the enemy ==================== */ void PlayerBot::InitState_Attack ( botfunc_t *func ) { func->CheckCondition = &PlayerBot::CheckCondition_Attack; func->EndState = &PlayerBot::State_EndAttack; func->ThinkState = &PlayerBot::State_Attack; } static Vector bot_origin; static int sentients_compare( const void *elem1, const void *elem2 ) { Entity *e1, *e2; float delta[ 3 ]; float d1, d2; e1 = *( Entity ** )elem1; e2 = *( Entity ** )elem2; VectorSubtract( bot_origin, e1->origin, delta ); d1 = VectorLengthSquared( delta ); VectorSubtract( bot_origin, e2->origin, delta ); d2 = VectorLengthSquared( delta ); if( d2 <= d1 ) { return d1 > d2; } else { return -1; } } bool PlayerBot::CheckCondition_Attack ( void ) { Container< Sentient * > sents = SentientList; bot_origin = origin; sents.Sort( sentients_compare ); for( int i = 1; i <= sents.NumObjects(); i++ ) { Sentient *sent = sents.ObjectAt( i ); Player *player = ( Player * )sent; if( sent == this ) continue; if( sent->hidden() ) continue; if( sent->IsDead() ) continue; if( sent->IsSubclassOfPlayer() ) { if( g_gametype->integer >= GT_TEAM && player->GetTeam() == GetTeam() ) continue; } else { if( sent->m_Team == m_Team ) continue; } if( CanSee( sent, 80, world->m_fAIVisionDistance, false ) ) { m_pEnemy = sent; m_iAttackTime = level.inttime + 10000; return true; } } if( level.inttime > m_iAttackTime ) { if( m_iAttackTime ) { ClearMove(); m_iAttackTime = 0; } return false; } return true; } void PlayerBot::State_EndAttack ( void ) { m_botCmd.buttons &= ~( BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT ); } void PlayerBot::State_Attack ( void ) { Player *p = ( Player * )m_pEnemy.Pointer(); bool bMelee = false; float fMinDistance = 128; float fMinDistanceSquared = fMinDistance * fMinDistance; if( !m_pEnemy || m_pEnemy->IsDead() || ( !p->IsSubclassOfPlayer() && m_pEnemy->m_Team == m_Team ) || ( g_gametype->integer >= GT_TEAM && p->GetTeam() == GetTeam() ) ) { m_iAttackTime = 0; return; } float fDistanceSquared = ( m_pEnemy->origin - origin ).lengthSquared(); if( CanSee( m_pEnemy, 20, world->m_fAIVisionDistance, false ) ) { Weapon *pWeap = GetActiveWeapon( WEAPON_MAIN ); if( !pWeap ) { return; } float fPrimaryBulletRange = pWeap->GetBulletRange( FIRE_PRIMARY ) / 1.25f; float fPrimaryBulletRangeSquared = fPrimaryBulletRange * fPrimaryBulletRange; float fSecondaryBulletRange = pWeap->GetBulletRange( FIRE_SECONDARY ); float fSecondaryBulletRangeSquared = fSecondaryBulletRange * fSecondaryBulletRange; fMinDistance = fPrimaryBulletRange; if( fMinDistance > 256 ) { fMinDistance = 256; } fMinDistanceSquared = fMinDistance * fMinDistance; if( client->ps.stats[ STAT_AMMO ] > 0 || client->ps.stats[ STAT_CLIPAMMO ] > 0 ) { if( fDistanceSquared <= fPrimaryBulletRangeSquared ) { if( pWeap->IsSemiAuto() ) { m_botCmd.buttons ^= BUTTON_ATTACKLEFT; } else { m_botCmd.buttons |= BUTTON_ATTACKLEFT; } } else { m_botCmd.buttons &= ~BUTTON_ATTACKLEFT; } } else if( pWeap->GetFireType( FIRE_SECONDARY ) == FT_MELEE ) { bMelee = true; if( fDistanceSquared <= fSecondaryBulletRangeSquared ) { m_botCmd.buttons ^= BUTTON_ATTACKRIGHT; } else { m_botCmd.buttons &= ~BUTTON_ATTACKRIGHT; } } else { m_botCmd.buttons &= ~( BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT ); } } else { m_botCmd.buttons &= ~( BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT ); fMinDistanceSquared = 0; } AimAt( m_vLastEnemyPos ); m_vOldEnemyPos = m_vLastEnemyPos; m_vLastEnemyPos = m_pEnemy->centroid; if( ( !MoveToBestAttractivePoint( 5 ) && !IsMoving() ) || ( m_vOldEnemyPos != m_vLastEnemyPos && !MoveDone() ) ) { if( !bMelee ) { if( ( origin - m_vLastEnemyPos ).lengthSquared() < fMinDistanceSquared ) { Vector vDir = origin - m_vLastEnemyPos; VectorNormalizeFast( vDir ); AvoidPath( m_vLastEnemyPos, fMinDistance, vDir ); } else { MoveNear( m_vLastEnemyPos, fMinDistance ); } } else { MoveTo( m_vLastEnemyPos ); } } } /* ==================== Grenade state Avoid any grenades ==================== */ void PlayerBot::InitState_Grenade ( botfunc_t *func ) { func->CheckCondition = &PlayerBot::CheckCondition_Grenade; func->ThinkState = &PlayerBot::State_Grenade; } bool PlayerBot::CheckCondition_Grenade ( void ) { // FIXME: TODO return false; } void PlayerBot::State_Grenade ( void ) { // FIXME: TODO } /* ==================== Weapon state Change weapon when necessary ==================== */ void PlayerBot::InitState_Weapon ( botfunc_t *func ) { func->CheckCondition = &PlayerBot::CheckCondition_Weapon; func->BeginState = &PlayerBot::State_BeginWeapon; } bool PlayerBot::CheckCondition_Weapon ( void ) { return GetActiveWeapon( WEAPON_MAIN ) != BestWeapon( NULL, false, WEAPON_CLASS_THROWABLE ); } void PlayerBot::State_BeginWeapon ( void ) { Weapon *weap = BestWeapon( NULL, false, WEAPON_CLASS_THROWABLE ); if( weap == NULL ) { SendCommand( "safeholster 1" ); return; } SendCommand( va( "use \"%s\"", weap->model.c_str() ) ); } void PlayerBot::Spawned ( void ) { ClearEnemy(); m_iCuriousTime = 0; Player::Spawned(); } void PlayerBot::Killed ( Event *ev ) { Player::Killed( ev ); // send the respawn buttons if( !( m_botCmd.buttons & BUTTON_ATTACKLEFT ) ) m_botCmd.buttons |= BUTTON_ATTACKLEFT; else m_botCmd.buttons &= ~BUTTON_ATTACKLEFT; m_botEyes.ofs[ 0 ] = 0; m_botEyes.ofs[ 1 ] = 0; m_botEyes.ofs[ 2 ] = 0; m_botEyes.angles[ 0 ] = 0; m_botEyes.angles[ 1 ] = 0; m_vLastDeathPos = origin; } void PlayerBot::GotKill ( Event *ev ) { Player::GotKill( ev ); ClearEnemy(); m_iCuriousTime = 0; } void PlayerBot::EventStuffText ( Event *ev ) { SendCommand( ev->GetString( 1 ) ); }