/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET 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 3 of the License, or (at your option) any later version. Wolf ET 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 Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ /***************************************************************************** * name: ai_dmnet_mp.c * * desc: Wolf bot AI * * *****************************************************************************/ #include "../game/g_local.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "../game/be_ea.h" #include "../game/be_ai_char.h" #include "../game/be_ai_chat.h" #include "../game/be_ai_gen.h" #include "../game/be_ai_goal.h" #include "../game/be_ai_move.h" #include "../game/be_ai_weap.h" #include "../botai/botai.h" // #include "ai_main.h" #include "ai_team.h" #include "ai_dmq3.h" #include "ai_cmd.h" #include "ai_dmnet_mp.h" //data file headers #include "chars.h" //characteristics #include "inv.h" //indexes into the inventory #include "syn.h" //synonyms #include "match.h" //string matching types and vars /* ================== BotMP_MoveToGoal ================== */ void BotMP_MoveToGoal( bot_state_t *bs, bot_moveresult_t * result, int movestate, bot_goal_t * goal, int travelflags ) { #ifdef _DEBUG if ( bot_debug.integer == 3 && level.clients[0].sess.spectatorClient == bs->client ) { goal->flags |= GFL_DEBUGPATH; } else { goal->flags &= ~GFL_DEBUGPATH; } #endif // _DEBUG trap_BotMoveToGoal( result, movestate, goal, travelflags ); } /* ================== AIEnter_MP_Intermission() ================== */ void AIEnter_MP_Intermission( bot_state_t *bs ) { //reset the bot state BotResetState( bs ); bs->ainode = AINode_MP_Intermission; bs->ainodeText = "AINode_MP_Intermission"; } /* ================== AINode_MP_Intermission() ================== */ int AINode_MP_Intermission( bot_state_t *bs ) { //if the intermission ended if ( !BotIntermission( bs ) ) { AIEnter_MP_Stand( bs ); } return qtrue; } /* ================== AIEnter_MP_Observer() ================== */ void AIEnter_MP_Observer( bot_state_t *bs ) { //reset the bot state BotResetState( bs ); bs->ainode = AINode_MP_Observer; bs->ainodeText = "AINode_MP_Observer"; } /* ================== AINode_MP_Observer() ================== */ int AINode_MP_Observer( bot_state_t *bs ) { //if the bot left observer mode if ( !BotIsObserver( bs ) ) { AIEnter_MP_Stand( bs ); } return qtrue; } /* ================== AIEnter_MP_Stand() ================== */ void AIEnter_MP_Stand( bot_state_t *bs ) { //bs->standfindenemy_time = trap_AAS_Time() + 1; bs->respawn_time = trap_AAS_Time() + 20; // after this long just standing around, suicide bs->ignore_specialgoal_time = 0; bs->ainode = AINode_MP_Stand; bs->ainodeText = "AINode_MP_Stand"; } /* ================== AINode_MP_Stand() ================== */ int AINode_MP_Stand( bot_state_t *bs ) { bot_goal_t goal; vec3_t pos; // Gordon: pow, so just stand around till scripted if ( BotIsPOW( bs ) ) { return qtrue; } if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } if ( bs->standfindenemy_time < trap_AAS_Time() ) { if ( BotFindEnemyMP( bs, -1, qfalse ) ) { AIEnter_MP_Battle_Fight( bs ); return qfalse; } //bs->standfindenemy_time = trap_AAS_Time() + 1; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } if ( bs->stand_time < trap_AAS_Time() ) { //trap_BotEnterChat(bs->cs, bs->client, bs->chatto); //bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); if ( bs->ainode != AINode_MP_Stand ) { return qfalse; } else if ( !bs->areanum ) { // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); } else { // stand for a bit longer bs->stand_time = trap_AAS_Time() + 0.4 + 0.4 * random(); } } else { // look for health/ammo packs if ( BotFindNearbyGoal( bs ) ) { AIEnter_MP_Seek_NBG( bs ); return qfalse; } } // check for dangerous elements VectorCopy( bs->origin, goal.origin ); goal.areanum = bs->areanum; goal.entitynum = bs->client; if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // if we are outside HALF autonomy range, get back there if ( BotGetMovementAutonomyPos( bs, pos ) ) { float halfDist = 0.5 * BotGetMovementAutonomyRange( bs, NULL ); if ( VectorDistanceSquared( bs->origin, pos ) > ( halfDist * halfDist ) ) { AIEnter_MP_MoveToAutonomyRange( bs ); return qfalse; } } // if we have been standing for too long if ( bs->respawn_time < trap_AAS_Time() ) { Cmd_Kill_f( &g_entities[bs->client] ); } // return qtrue; } /* ================== AIEnter_MP_Respawn() ================== */ void AIEnter_MP_Respawn( bot_state_t *bs ) { //reset some states trap_BotResetMoveState( bs->ms ); trap_BotResetGoalState( bs->gs ); trap_BotResetAvoidGoals( bs->gs ); trap_BotResetAvoidReach( bs->ms ); bs->respawn_time = trap_AAS_Time() + 1 + random(); bs->respawnchat_time = 0; // bs->flags &= ~BFL_MISCFLAG; bs->lastClassCheck = 0; //set respawn state bs->respawn_wait = qfalse; bs->ainode = AINode_MP_Respawn; bs->ainodeText = "AINode_MP_Respawn"; } /* ================== AINode_MP_Respawn() ================== */ int AINode_MP_Respawn( bot_state_t *bs ) { qboolean do_respawn = qfalse; gentity_t *ent; int testtime; // RF, only hit jump if reinforcement time is about to run out ent = BotGetEntity( bs->entitynum ); // disabled, if medic has troubles finding us, we'll be waiting forever // if (ent->missionLevel < level.time) { // else medic is heading to us to revive if ( ent->client->sess.sessionTeam == TEAM_AXIS ) { testtime = level.time % g_redlimbotime.integer; if ( testtime > g_redlimbotime.integer - 2000 ) { do_respawn = qtrue; } } else if ( ent->client->sess.sessionTeam == TEAM_ALLIES ) { testtime = level.time % g_bluelimbotime.integer; if ( testtime > g_bluelimbotime.integer - 2000 ) { do_respawn = qtrue; } } // } // if ( bs->lastClassCheck < level.time - 4000 ) { // check for a better class bs->mpClass = BotSuggestClass( bs, bs->mpTeam ); ent->client->sess.latchPlayerType = bs->mpClass; if ( bs->mpClass != ent->client->sess.playerType ) { bs->flags |= BFL_MISCFLAG; } bs->lastClassCheck = level.time + rand() % 1000; // sometimes when we die, we should re-evaluate our weapon selection if ( ( bs->flags & BFL_MISCFLAG ) || ( random() < 0.3 ) ) { bs->mpWeapon = BotSuggestWeapon( bs, bs->sess.sessionTeam ); ent->client->sess.latchPlayerWeapon = bs->mpWeapon; } } if ( bs->respawn_wait ) { if ( !BotIsDead( bs ) ) { // perhaps we should tell everyone who we are if ( bs->flags & BFL_MISCFLAG ) { static int lastCall; if ( lastCall > level.time || lastCall < level.time - 2000 ) { lastCall = level.time; switch ( bs->mpClass ) { case PC_SOLDIER: BotVoiceChatAfterIdleTime( bs->client, "IamSoldier", SAY_TEAM, 1000 + rand() % 5000, BOT_SHOWTEXT, 20000, qfalse ); break; case PC_MEDIC: BotVoiceChatAfterIdleTime( bs->client, "IamMedic", SAY_TEAM, 1000 + rand() % 5000, BOT_SHOWTEXT, 20000, qfalse ); break; case PC_FIELDOPS: BotVoiceChatAfterIdleTime( bs->client, "IamLieutenant", SAY_TEAM, 1000 + rand() % 5000, BOT_SHOWTEXT, 20000, qfalse ); break; case PC_ENGINEER: BotVoiceChatAfterIdleTime( bs->client, "IamEngineer", SAY_TEAM, 1000 + rand() % 5000, BOT_SHOWTEXT, 20000, qfalse ); break; } } } else if ( bs->sess.sessionTeam == level.attackingTeam ) { if ( rand() % 2 ) { BotVoiceChatAfterIdleTime( bs->client, "LetsGo", SAY_TEAM, 1000 + rand() % 2000, qfalse, 20000, qfalse ); } } // BotDefaultNode( bs ); } else { trap_EA_Respawn( bs->client ); // RF, Wolf uses jump if ( do_respawn ) { trap_EA_Jump( bs->client ); } } } else if ( bs->respawn_time < trap_AAS_Time() ) { //wait until respawned bs->respawn_wait = qtrue; //elementary action respawn trap_EA_Respawn( bs->client ); // RF, Wolf uses jump if ( do_respawn ) { trap_EA_Jump( bs->client ); } // if ( bs->respawnchat_time ) { //trap_BotEnterChat(bs->cs, bs->client, bs->chatto); bs->enemy = -1; } } if ( bs->respawnchat_time && bs->respawnchat_time < trap_AAS_Time() - 0.5 ) { trap_EA_Talk( bs->client ); } // return qtrue; } /* ================== AIEnter_MP_Seek_ActivateEntity() ================== */ void AIEnter_MP_Seek_ActivateEntity( bot_state_t *bs ) { bs->ainode = AINode_MP_Seek_ActivateEntity; bs->ainodeText = "AINode_MP_Seek_ActivateEntity"; } /* ================== AINode_MP_Seek_Activate_Entity() ================== */ int AINode_MP_Seek_ActivateEntity( bot_state_t *bs ) { bot_goal_t *goal; vec3_t target, dir; bot_moveresult_t moveresult; if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // if ( bot_grapple.integer ) { bs->tfl |= TFL_GRAPPLEHOOK; } //if in lava or slime the bot should be able to get out if ( BotInLava( bs ) ) { bs->tfl |= TFL_LAVA; } if ( BotInSlime( bs ) ) { bs->tfl |= TFL_SLIME; } //no enemy bs->enemy = -1; // goal = &bs->activategoal; //if the bot has no goal if ( !goal ) { bs->activate_time = 0; } //if the bot touches the current goal else if ( trap_BotTouchingGoal( bs->origin, goal ) ) { BotChooseWeapon( bs ); #ifdef DEBUG BotAI_Print( PRT_MESSAGE, "touched button or trigger\n" ); #endif //DEBUG bs->activate_time = 0; } // if ( bs->activate_time < trap_AAS_Time() ) { AIEnter_MP_Seek_NBG( bs ); return qfalse; } //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); } //check if the bot is blocked BotAIBlocked( bs, &moveresult, qtrue ); // if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else if ( !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( trap_BotMovementViewTarget( bs->ms, goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else { //vectoangles(moveresult.movedir, bs->ideal_viewangles); } bs->ideal_viewangles[2] *= 0.5; } //if the weapon is used for the bot movement if ( moveresult.flags & MOVERESULT_MOVEMENTWEAPON ) { bs->weaponnum = moveresult.weapon; } //if there is an enemy if ( BotFindEnemyMP( bs, -1, qfalse ) ) { if ( BotWantsToRetreat( bs ) ) { //keep the current long term goal and retreat // !!! TODO } else { trap_BotResetLastAvoidReach( bs->ms ); //empty the goal stack trap_BotEmptyGoalStack( bs->gs ); //go fight AIEnter_MP_Battle_Fight( bs ); } } return qtrue; } /* ================== AIEnter_MP_Seek_NBG() ================== */ void AIEnter_MP_Seek_NBG( bot_state_t *bs ) { //level.clients[0].sess.spectatorClient = bs->client; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_Seek_NBG; bs->ainodeText = "AINode_MP_Seek_NBG"; } /* ================== AINode_MP_Seek_NBG() ================== */ int AINode_MP_Seek_NBG( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *ent; // goal = bs->nearbygoal; // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // // should we stop pursuing this target? ent = BotGetEntity( goal.entitynum ); if ( !ent->inuse ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } else if ( ent->s.eType == ET_SUPPLIER ) { if ( !ClientNeedsAmmo( bs->client ) ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } } else if ( ent->s.eType == ET_HEALER ) { if ( BotHealthScale( bs->client ) >= 1.0 ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } } else if ( ent->s.eType == ET_ITEM && ent->touch ) { if ( /*trap_BotTouchingGoal( bs->origin, &goal ) ||*/ ( ent->r.svFlags & SVF_NOCLIENT ) ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } } //choose the best weapon to fight with BotChooseWeapon( bs ); //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); // fail bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } // BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } // return qtrue; } /* ================== AIEnter_MP_AvoidDanger() ================== */ void AIEnter_MP_AvoidDanger( bot_state_t *bs ) { int bestarea; // if this is dynamite if ( g_entities[bs->avoid_goal.entitynum].s.eType == ET_MISSILE && g_entities[bs->avoid_goal.entitynum].methodOfDeath == MOD_DYNAMITE ) { if ( !( rand() % 3 ) ) { BotVoiceChatAfterIdleTime( bs->client, "FireInTheHole", SAY_TEAM, 500, qfalse, 3000, qfalse ); } } bs->flags &= ~BFL_MISCFLAG; if ( !( bestarea = trap_AAS_AvoidDangerArea( bs->origin, bs->areanum, bs->avoid_goal.origin, BotPointAreaNum( -1, bs->avoid_goal.origin ), bs->avoid_goal.number + 100, bs->tfl ) ) ) { // no hiding spot, ignore it bs->flags |= BFL_MISCFLAG; } else { trap_AAS_AreaWaypoint( bestarea, bs->avoid_goal.origin ); bs->avoid_goal.areanum = bestarea; } bs->ainode = AINode_MP_AvoidDanger; bs->ainodeText = "AINode_MP_AvoidDanger"; } /* ================== AINode_MP_AvoidDanger() ================== */ int AINode_MP_AvoidDanger( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *trav; int bestarea; qboolean moved = qfalse; memset( &moveresult, 0, sizeof( moveresult ) ); goal = bs->avoid_goal; // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } //if the target has gone trav = &g_entities[goal.entitynum]; if ( !trav->inuse ) { // just look for a goal bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } else if ( trav->client && trav->health <= 0 ) { // just look for a goal bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } else if ( trav->s.eType == ET_CONSTRUCTIBLE ) { if ( ( g_entities[bs->client].client->lastConstructibleBlockingWarnEnt != goal.entitynum ) || ( ( level.time - g_entities[bs->client].client->lastConstructibleBlockingWarnTime ) > 5000 ) ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } } // if the avoid entity has changed if ( bs->avoid_spawnCount != trav->spawnCount ) { // just look for a goal bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // if the thing we're avoiding is a landmine, then we don't really need to run away from it, just take a few steps // don't need to move, is this a landmine? if ( ( trav->methodOfDeath == MOD_LANDMINE ) && VectorDistanceSquared( bs->origin, trav->r.currentOrigin ) > SQR( 256 ) ) { // we're done running bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // make sure the current goal origin is still safe // is this entity dangerous? if ( trav->client ) { // is this player dangerous? if ( !( trav->client->ps.weapon == WP_PANZERFAUST && trav->client->ps.weaponDelay ) ) { // not dangerous bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } VectorCopy( trav->r.currentOrigin, target ); } else if ( trav->s.eType == ET_CONSTRUCTIBLE ) { } else { if ( trav->s.eType == ET_MISSILE && trav->s.weapon == WP_DYNAMITE ) { VectorCopy( trav->r.currentOrigin, target ); } else { if ( !G_PredictMissile( trav, trav->nextthink - level.time, target, ( trav->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) ) ) { // not dangerous bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } } if ( ( bs->last_avoiddangerarea < level.time - 200 ) && ( VectorDistanceSquared( target, goal.origin ) < ( SQR( trav->splashRadius + 100 ) ) ) ) { bs->last_avoiddangerarea = level.time + rand() % 200; if ( !( bestarea = trap_AAS_AvoidDangerArea( bs->origin, bs->areanum, target, BotPointAreaNum( -1, target ), trav->splashRadius + 100, bs->tfl ) ) ) { // move away from the danger bs->flags |= BFL_MISCFLAG; } else { trap_AAS_AreaWaypoint( bestarea, bs->avoid_goal.origin ); bs->avoid_goal.areanum = bestarea; goal = bs->avoid_goal; } } } //update goal information // check for emergency targets (flags, etc) //if (BotCheckEmergencyTargets( bs )) { // return qfalse; //} if ( bs->flags & BFL_MISCFLAG ) { moved = qtrue; //initialize the movement state BotSetupForMovement( bs ); // move away from danger VectorSubtract( target, bs->origin, dir ); VectorNormalize( dir ); trap_EA_Move( bs->client, dir, 400 ); // randomly strafe also if ( level.time % 2000 < 1000 ) { trap_EA_MoveLeft( bs->client ); } else { trap_EA_MoveRight( bs->client );} } else { // are we close enough to the goal? if ( VectorDistanceSquared( bs->origin, goal.origin ) > ( 24 * 24 ) ) { // MOVEMENT REQUIRED // moved = qtrue; //choose the best weapon to fight with BotChooseWeapon( bs ); //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); } // BotAIBlocked( bs, &moveresult, qtrue ); } } // //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( !moved ) { VectorSubtract( g_entities[goal.entitynum].r.currentOrigin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } // return qtrue; } /* ================== AIEnter_MP_GiveAmmo() ================== */ void AIEnter_MP_GiveAmmo( bot_state_t *bs ) { //level.clients[0].sess.spectatorClient = bs->client; bs->give_health_time = 0; bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_GiveAmmo; bs->ainodeText = "AINode_MP_GiveAmmo"; } /* ================== AINode_MP_GiveAmmo() ================== */ int AINode_MP_GiveAmmo( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *trav; goal = bs->target_goal; //if we have changed class if ( bs->sess.playerType != PC_FIELDOPS ) { BotDefaultNode( bs ); return qfalse; } //if we have to wait // Gordon: FIXME: this looks wrong if ( bs->cur_ps.classWeaponTime > level.time - 8000 ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } //if the target is dead trav = BotGetEntity( goal.entitynum ); // FIXME: temp hack in dealing with NULL returns from BotGetEntity (??) if ( trav == NULL ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } if ( !trav->inuse || !trav->client || ( trav->client->ps.pm_type != PM_NORMAL ) ) { // let them roam again trav->awaitingHelpTime = 0; // just look for a goal BotDefaultNode( bs ); return qfalse; } // do they have enough ammo now? if ( !ClientNeedsAmmo( trav->s.number ) ) { if ( VectorDistanceSquared( bs->origin, goal.origin ) < SQR( 72 ) ) { // we just helped them bs->last_helped_client = trav->s.number; bs->last_helped_time = level.time; // they should thank us if ( trav->r.svFlags & SVF_BOT ) { BotVoiceChatAfterIdleTime( trav->s.number, "Thanks", SAY_TEAM, 1000 + rand() % 2000, qfalse, 3000 + rand() % 2000, qfalse ); } } // let them roam again trav->awaitingHelpTime = 0; // just look for a goal BotDefaultNode( bs ); return qfalse; } //update goal information VectorCopy( trav->r.currentOrigin, bs->target_goal.origin ); bs->target_goal.areanum = BotPointAreaNum( trav->s.number, trav->r.currentOrigin ); if ( !bs->target_goal.areanum ) { BotDefaultNode( bs ); return qfalse; } goal = bs->target_goal; if ( VectorDistanceSquared( bs->origin, goal.origin ) < SQR( 100 ) && BotEntityVisible( bs->client, bs->eye, bs->viewangles, 360, trav->s.number, NULL ) ) { // make sure other bots dont head for this target also trav->awaitingHelpTime = level.time + 1500; } // are we close enough to the goal? if ( VectorDistanceSquared( bs->origin, goal.origin ) < SQR( 72 ) ) { if ( !bs->give_health_time ) { bs->give_health_time = level.time + 8000; } else if ( bs->give_health_time < level.time ) { BotDefaultNode( bs ); return qfalse; } // make sure other bots dont head for this target also trav->awaitingHelpTime = level.time + 1500; trav->botIgnoreAmmoTime = level.time + 1000; // switch to regen and pump away bs->weaponnum = WP_AMMO; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // aim directly at the dynamite VectorCopy( bs->origin, target ); VectorSubtract( trav->r.currentOrigin, target, dir ); dir[2] *= 0.5; VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); // hold fire if ( bs->cur_ps.weapon == WP_AMMO && BotWeaponCharged( bs, WP_AMMO ) ) { trap_EA_Attack( bs->client ); } // return qtrue; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // MOVEMENT REQUIRED // //choose the best weapon to fight with BotChooseWeapon( bs ); //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); } // BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } // reload? if ( ( bs->last_fire != level.time ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * ( GetAmmoTableData( bs->cur_ps.weapon ) )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { trap_EA_Reload( bs->client ); } // return qtrue; } /* ================== AIEnter_MP_MedicGiveHealth() ================== */ void AIEnter_MP_MedicGiveHealth( bot_state_t *bs ) { bs->give_health_time = 0; bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_MedicGiveHealth; bs->ainodeText = "AINode_MP_MedicGiveHealth"; } /* ================== AINode_MP_MedicGiveHealth() ================== */ int AINode_MP_MedicGiveHealth( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *trav; goal = bs->target_goal; //if we have changed class if ( bs->sess.playerType != PC_MEDIC ) { BotDefaultNode( bs ); return qfalse; } if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } //if the target has full health trav = &g_entities[goal.entitynum]; if ( !trav->inuse || !trav->client || ( trav->client->ps.pm_type != PM_NORMAL ) || ( trav->client->ps.pm_flags & PMF_LIMBO ) ) { // let them roam again trav->awaitingHelpTime = 0; // just look for a goal BotDefaultNode( bs ); return qfalse; } if ( BotHealthScale( trav->s.number ) >= 1.0 ) { if ( VectorDistanceSquared( bs->origin, goal.origin ) < SQR( 72 ) ) { // we just helped them bs->last_helped_client = trav->s.number; bs->last_helped_time = level.time; // they should thank us if ( trav->r.svFlags & SVF_BOT ) { BotVoiceChatAfterIdleTime( trav->s.number, "Thanks", SAY_TEAM, 1000 + rand() % 2000, qfalse, 3000 + rand() % 2000, qfalse ); } } // let them roam again trav->awaitingHelpTime = 0; // just look for a goal BotDefaultNode( bs ); return qfalse; } //update goal information VectorCopy( trav->r.currentOrigin, BotGetOrigin( trav->s.number ) ); bs->target_goal.areanum = BotGetArea( trav->s.number ); if ( !bs->target_goal.areanum ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } goal = bs->target_goal; // are we close enough to the goal? if ( VectorDistanceSquared( bs->origin, goal.origin ) < SQR( 72 ) ) { if ( !bs->give_health_time ) { bs->give_health_time = level.time + 8000; } else if ( bs->give_health_time < level.time ) { BotDefaultNode( bs ); return qfalse; } // make sure other bots dont head for this target also trav->awaitingHelpTime = level.time + 1500; trav->botIgnoreHealthTime = level.time + 1000; // switch to regen and pump away bs->weaponnum = WP_MEDKIT; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // aim directly at the player VectorCopy( bs->origin, target ); VectorSubtract( trav->r.currentOrigin, target, dir ); dir[2] *= 0.5; VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); // hold fire if ( bs->cur_ps.weapon == WP_MEDKIT && BotWeaponCharged( bs, WP_MEDKIT ) ) { trap_EA_Attack( bs->client ); } return qtrue; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // MOVEMENT REQUIRED // //choose the best weapon to fight with BotChooseWeapon( bs ); //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); } // BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } // reload? if ( ( bs->last_fire != level.time ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * GetAmmoTableData( bs->cur_ps.weapon )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { trap_EA_Reload( bs->client ); } // return qtrue; } /* ================== AIEnter_MP_MedicRevive() ================== */ void AIEnter_MP_MedicRevive( bot_state_t *bs ) { bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_MedicRevive; bs->ainodeText = "AINode_MP_MedicRevive"; } /* ================== AINode_MP_MedicRevive() ================== */ int AINode_MP_MedicRevive( bot_state_t *bs ) { bot_goal_t goal, target; vec3_t targetpos, dir; bot_moveresult_t moveresult; int range; gentity_t *trav; goal = bs->target_goal; //if we have changed class if ( bs->sess.playerType != PC_MEDIC ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } //if the target is not dead or is in limbo trav = BotGetEntity( goal.entitynum ); if ( // START Gordon changes, 23/8/2002 !trav || // END Gordon changes, 23/8/2002 !trav->inuse || !trav->client || ( trav->client->ps.pm_flags & PMF_LIMBO ) ) { // just look for a goal bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // if someone is close to them, only they should continue reviving if ( g_entities[goal.entitynum].botIgnoreHealthTime >= level.time ) { int list[MAX_CLIENTS], numList; float ourDist, *distances; // // if there are too many defenders, and we are the furthest away, stop defending if ( ( numList = BotNumTeamMatesWithTarget( bs, goal.entitynum, list, MAX_CLIENTS ) ) > 0 ) { ourDist = VectorDistanceSquared( bs->origin, g_entities[goal.entitynum].r.currentOrigin ); if ( !trap_InPVS( bs->origin, g_entities[goal.entitynum].r.currentOrigin ) ) { ourDist += ( 2048 * 2048 ); } distances = BotSortPlayersByDistance( g_entities[goal.entitynum].r.currentOrigin, list, numList ); if ( distances[numList - 1] < ourDist ) { // we are the furthest bs->ignore_specialgoal_time = 0; bs->leader = -1; BotDefaultNode( bs ); return qfalse; } } } // if they are alive if ( trav->client->ps.pm_type != PM_DEAD ) { if ( VectorDistanceSquared( bs->origin, goal.origin ) < SQR( 72 ) ) { // we just helped them bs->last_helped_client = trav->s.number; bs->last_helped_time = level.time; // they should thank us if ( trav->r.svFlags & SVF_BOT ) { BotVoiceChatAfterIdleTime( trav->s.number, "Thanks", SAY_TEAM, 1000 + rand() % 2000, qfalse, 4000 + rand() % 2000, qfalse ); } } // look for someone to heal (like the person we just revived) if ( BotClass_MedicCheckGiveHealth( bs, 200, &target ) ) { bs->target_goal = target; AIEnter_MP_MedicGiveHealth( bs ); return qtrue; } // just look for a goal bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } //update goal information VectorCopy( trav->r.currentOrigin, bs->target_goal.origin ); bs->target_goal.areanum = BotPointAreaNum( trav->s.number, trav->r.currentOrigin ); if ( !bs->target_goal.areanum ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } goal = bs->target_goal; //look for closer revives range = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, goal.areanum, bs->tfl ); // if ( range < 200 && bs->enemy < 0 ) { // switch to regen bs->weaponnum = WP_MEDIC_SYRINGE; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // make sure other bots dont head for this target also g_entities[goal.entitynum].botIgnoreHealthTime = level.time + 500; } // are we close enough to the goal? if ( VectorDistanceSquared( bs->eye, goal.origin ) < SQR( 42 ) ) { // make sure other bots dont head for this target also g_entities[goal.entitynum].botIgnoreHealthTime = level.time + 1500; // crouch down trap_EA_Crouch( bs->client ); // switch to regen and pump away bs->weaponnum = WP_MEDIC_SYRINGE; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // aim directly at the dynamite VectorCopy( bs->origin, targetpos ); targetpos[2] += bs->cur_ps.viewheight; VectorSubtract( trav->r.currentOrigin, targetpos, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); // hold fire if ( bs->cur_ps.weapon == WP_MEDIC_SYRINGE ) { trap_EA_Attack( bs->client ); } // return qtrue; } // if ( range > 200 ) { // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } } // // MOVEMENT REQUIRED // //choose the best weapon to fight with BotChooseWeapon( bs ); //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); } // BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, targetpos ); VectorSubtract( targetpos, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, targetpos ) ) { VectorSubtract( targetpos, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, targetpos ); VectorSubtract( targetpos, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } // return qtrue; } /* ================== AIEnter_MP_PanzerTarget() ================== */ void AIEnter_MP_PanzerTarget( bot_state_t *bs ) { bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_PanzerTarget; bs->ainodeText = "AINode_MP_PanzerTarget"; bs->enemy = ENTITYNUM_WORLD; // fast view } /* ================== AINode_MP_PanzerTarget() ================== */ int AINode_MP_PanzerTarget( bot_state_t *bs ) { vec3_t vec; // if ( BotIsDead( bs ) ) { bs->enemy = -1; AIEnter_MP_Respawn( bs ); return qfalse; } // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } // if ( !BotWeaponWantScale( bs, WP_PANZERFAUST ) ) { bs->enemy = -1; bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } bs->weaponnum = WP_PANZERFAUST; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // // have we fired already? if ( !BotWeaponCharged( bs, bs->weaponnum ) ) { bs->enemy = -1; bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // look at the target VectorSubtract( bs->target_goal.origin, bs->eye, vec ); VectorNormalize( vec ); vectoangles( vec, bs->ideal_viewangles ); // if ( ( bs->cur_ps.weapon == bs->weaponnum ) && ( AngleDifference( bs->ideal_viewangles[YAW], bs->viewangles[YAW] ) < 0.5 ) && ( AngleDifference( bs->ideal_viewangles[PITCH], bs->viewangles[PITCH] ) < 0.5 ) ) { trap_EA_Attack( bs->client ); } // return qtrue; } /* ================== AIEnter_MP_AttackTarget() ================== */ void AIEnter_MP_AttackTarget( bot_state_t *bs ) { bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_AttackTarget; bs->ainodeText = "AINode_MP_AttackTarget"; bs->enemy = bs->target_goal.entitynum; // fast view } /* ================== AINode_MP_AttackTarget() ================== */ int AINode_MP_AttackTarget( bot_state_t *bs ) { vec3_t vec; bot_goal_t goal; gentity_t *check; // goal = bs->target_goal; bs->weaponnum = BotBestTargetWeapon( bs, goal.entitynum ); if ( bs->weaponnum == WP_NONE ) { bs->enemy = -1; BotDefaultNode( bs ); return qfalse; } // if ( BotIsDead( bs ) ) { bs->enemy = -1; AIEnter_MP_Respawn( bs ); return qfalse; } // if ( BotIsObserver( bs ) ) { bs->enemy = -1; AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { bs->enemy = -1; AIEnter_MP_Intermission( bs ); return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { bs->enemy = -1; AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // check = BotGetVisibleDamagableScriptMover( bs ); if ( !check || ( check->s.number != goal.entitynum ) ) { bs->enemy = -1; BotDefaultNode( bs ); return qfalse; } // trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // look at the target VectorSubtract( bs->target_goal.origin, bs->eye, vec ); VectorNormalize( vec ); vectoangles( vec, bs->ideal_viewangles ); // if ( ( bs->cur_ps.weapon == bs->weaponnum ) && ( AngleDifference( bs->ideal_viewangles[YAW], bs->viewangles[YAW] ) < 0.5 ) && ( AngleDifference( bs->ideal_viewangles[PITCH], bs->viewangles[PITCH] ) < 0.5 ) ) { if ( bs->cur_ps.weapon == WP_GRENADE_LAUNCHER || bs->cur_ps.weapon == WP_GRENADE_PINEAPPLE ) { if ( BotSinglePlayer() || BotCoop() ) { // release immediately in single player } else if ( bs->cur_ps.grenadeTimeLeft ) { // release grenade } else { // hold onto it trap_EA_Attack( bs->client ); } } else { trap_EA_Attack( bs->client ); } } // return qtrue; } /* ================== AIEnter_MP_FixMG42() ================== */ void AIEnter_MP_FixMG42( bot_state_t *bs ) { //level.clients[0].sess.spectatorClient = bs->client; bs->arrive_time = level.time; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_FixMG42; bs->ainodeText = "AINode_MP_FixMG42"; } /* ================== AINode_MP_FixMG42() ================== */ int AINode_MP_FixMG42( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *ent; trace_t tr; // goal = bs->target_goal; ent = BotGetEntity( bs->target_goal.entitynum ); // return to this sniper spot if we go off temporarily ent->botIgnoreTime = 0; // if ( ent->melee->takedamage ) { bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { ent->botIgnoreTime = level.time + 5000; // other bots should avoid this spot AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { // return to this sniper spot after we're done ent->botIgnoreTime = 0; return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { // return to this sniper spot after we're done ent->botIgnoreTime = 0; AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // have we been waiting here for too long? if ( bs->arrive_time < level.time - 40000 ) { ent->botIgnoreTime = level.time + 5000; // other bots should avoid this spot if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // // other bots should avoid this spot ent->botIgnoreTime = level.time + 5000; VectorSubtract( bs->origin, goal.origin, dir ); if ( fabs( dir[2] ) < 100 ) { dir[2] = 0; } // is the destination blocked? if ( VectorLengthSquared( dir ) < SQR( 64 ) ) { trap_Trace( &tr, ent->s.origin, NULL, NULL, ent->s.origin, bs->client, MASK_PLAYERSOLID ); if ( tr.startsolid || tr.allsolid ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } } if ( VectorLengthSquared( dir ) > SQR( 8 ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); // bs->arrive_time = level.time; // wait a bit longer //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); if ( BotFindSpecialGoals( bs ) ) { return qfalse; } // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); // fail bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } else if ( moveresult.flags & MOVERESULT_WAITING ) { //if waiting for something if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { if ( VectorDistanceSquared( bs->origin, goal.origin ) > SQR( 32 ) ) { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; bs->enemyposition_time = 0; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( VectorLengthSquared( moveresult.movedir ) ) { //FIXME: look at cluster portals? vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } } } if ( VectorDistanceSquared( bs->origin, goal.origin ) < SQR( 16 ) ) { // We are at the spot, so start fixing bs->weaponnum = WP_PLIERS; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; bs->enemyposition_time = 0; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { if ( VectorDistanceSquared( bs->origin, bs->enemyorigin ) < SQR( 512 ) ) { // if they are real close, abort sniper mode if ( VectorDistanceSquared( bs->origin, g_entities[bs->enemy].r.currentOrigin ) < SQR( 1024 ) ) { AIEnter_MP_Battle_Fight( bs ); return qfalse; } } else { bs->enemy = -1; } } } else { // face the mg42 and start repairing VectorSubtract( ent->melee->r.currentOrigin, bs->eye, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); // hold fire trap_EA_Attack( bs->client ); // dont abort until finished bs->arrive_time = level.time; } } else if ( ( bs->last_fire != level.time ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * GetAmmoTableData( bs->cur_ps.weapon )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { // reload? trap_EA_Reload( bs->client ); } return qtrue; } /* ================== AIEnter_MP_Battle_MobileMG42 ================== */ void AIEnter_MP_Battle_MobileMG42( bot_state_t *bs ) { BotDebugViewClient( bs->client ); // bs->arrive_time = level.time; bs->lasthealth = g_entities[bs->client].health; bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_Battle_MobileMG42; bs->ainodeText = "AINode_MP_Battle_MobileMG42"; } /* ================== AINode_MP_Battle_MobileMG42() ================== */ int AINode_MP_Battle_MobileMG42( bot_state_t *bs ) { bot_goal_t goal; vec3_t dir, ang; int tookDamage = 0; bs->weaponnum = WP_MOBILE_MG42; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); bs->mobileMG42ProneTime = level.time; // if our health has dropped, abort tookDamage = bs->lasthealth - g_entities[bs->client].health; // bs->lasthealth = g_entities[bs->client].health; if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // if we have run out of ammo if ( !BotGotEnoughAmmoForWeapon( bs, bs->weaponnum ) ) { BotDefaultNode( bs ); return qtrue; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // look for something better to do if ( level.captureFlagMode && BotFlagAtBase( bs->sess.sessionTeam, NULL ) == qfalse ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // have we been waiting here for too long? or been hit too much? if ( tookDamage > 20 || g_entities[bs->client].health < 40 || bs->arrive_time < level.time - 5000 ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } BotAimAtEnemy( bs ); // wait until we are facing them correctly before going prone if ( !( bs->cur_ps.eFlags & EF_PRONE ) && ( fabs( AngleDifference( bs->ideal_viewangles[YAW], bs->viewangles[YAW] ) ) > 5.0f ) ) { return qtrue; } // stay prone trap_EA_Prone( bs->client ); // look for enemies // if we have an enemy, make sure they are still within view limits if ( bs->enemy >= 0 ) { if ( !BotEntityWithinView( bs, bs->enemy ) ) { bs->enemy = -1; } } if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy < 0 ) { // if we still have no enemy, then after a slight pause, allow us to escape prone mode if we find an // enemy outside our view if ( bs->arrive_time < level.time - 1500 ) { BotFindEnemyMP( bs, -1, qtrue ); // if we found one this time, escape prone mode if ( bs->enemy >= 0 ) { AIEnter_MP_Battle_Fight( bs ); return qfalse; } } } // if we took damage, check that there isn't a closer enemy we can attack if ( tookDamage > 0 ) { int oldEnemy; // oldEnemy = bs->enemy; bs->enemy = -1; BotFindEnemyMP( bs, -1, qtrue ); if ( bs->enemy >= 0 && bs->enemy != oldEnemy ) { // found someone else to attack AIEnter_MP_Battle_Fight( bs ); return qfalse; } // otherwise continue bs->enemy = oldEnemy; } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; bs->enemyposition_time = 0; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } if ( bs->enemy >= 0 ) { VectorSubtract( entinfo.origin, bs->eye, dir ); VectorNormalize( dir ); vectoangles( dir, ang ); if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 120, bs->enemy, NULL ) ) { // if they are real close, abort mg42 mode if ( VectorDistanceSquared( bs->origin, g_entities[bs->enemy].r.currentOrigin ) < SQR( 400 ) ) { AIEnter_MP_Battle_Fight( bs ); return qfalse; } // bs->arrive_time = level.time; // wait a bit longer //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible trap_EA_Attack( bs->client ); } else { bs->enemy = -1; } } } else { // int spotNum; // // TODO: cycle through visible enemy sniper spots // NOTE: remember last visible enemy positions, so we can stay on them longer than usual if ( bs->enemyposition_time > trap_AAS_Time() - 5.0 ) { VectorSubtract( bs->enemyorigin, bs->origin, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( bs->viewchangetime > level.time ) { // use same angles /* } else if ((spotNum = BotGetRandomVisibleSniperSpot( bs )) > -1) { // look at new spot VectorSubtract( g_entities[spotNum].s.origin, bs->origin, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); // bs->viewchangetime = level.time + 1000 + rand()%1500; */ } else { // use mg42 angles VectorCopy( level.clients[bs->client].pmext.mountedWeaponAngles, bs->ideal_viewangles ); // add some random angle bs->ideal_viewangles[YAW] += crandom() * 20.f * 0.45; bs->ideal_viewangles[PITCH] += crandom() * 20.f * 0.15; // bs->viewchangetime = level.time + 1000 + rand() % 1500; } } // // stay mounted bs->flags &= ~BFL_DISMOUNT_MG42; return qtrue; } /* ================== AIEnter_MP_MG42Scan() ================== */ void AIEnter_MP_MG42Scan( bot_state_t *bs ) { //level.clients[0].sess.spectatorClient = bs->client; bs->arrive_time = level.time; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->lasthealth = g_entities[bs->client].health; bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_MG42Scan; bs->ainodeText = "AINode_MP_MG42Scan"; } /* ================== AINode_MP_MG42Scan() ================== */ int AINode_MP_MG42Scan( bot_state_t *bs ) { bot_goal_t goal; vec3_t dir, ang; gentity_t *ent, *mg42; // // set the dismount flag. clear it if we want to stay on bs->flags |= BFL_DISMOUNT_MG42; // goal = bs->target_goal; ent = BotGetEntity( bs->target_goal.entitynum ); mg42 = ent->melee; // ent->botIgnoreTime = 0; // set this now, only reset it to ignore if we want to ignore it // // if it's the wrong one if ( VectorDistanceSquared( bs->origin, ent->r.currentOrigin ) > SQR( 64 ) ) { trap_EA_Activate( bs->client ); return qfalse; } // return to this sniper spot if we go off temporarily ent->botIgnoreTime = 0; // if our health has dropped, abort if ( bs->lasthealth > g_entities[bs->client].health + 40 ) { ent->botIgnoreTime = level.time + 5000; bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // if we are not mounted on an mg42 if ( !( g_entities[bs->client].s.eFlags & EF_MG42_ACTIVE ) ) { bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { // return to this sniper spot after we're done ent->botIgnoreTime = 0; return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { // return to this sniper spot after we're done ent->botIgnoreTime = 0; // AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } /* // check for special class actions if (BotCheckClassActions(bs)) { // return to this sniper spot after we're done ent->missionLevel = 0; return qfalse; } */ // look for something better to do if ( BotFlagAtBase( bs->sess.sessionTeam, NULL ) == qfalse ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // have we been waiting here for too long? if ( bs->arrive_time < level.time - 40000 ) { ent->missionLevel = level.time; // other bots should avoid this spot ent->botIgnoreTime = level.time + 20000; // we should ignore it for a while now if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // if this spot is disabled now if ( mg42->aiInactive & ( 1 << bs->sess.sessionTeam ) ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // // other bots should avoid this spot if ( ent->botIgnoreTime < level.time + 5000 ) { ent->botIgnoreTime = level.time + 5000; } // // look for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; bs->enemyposition_time = 0; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { VectorSubtract( entinfo.origin, mg42->r.currentOrigin, dir ); VectorNormalize( dir ); vectoangles( dir, ang ); if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 120, bs->enemy, NULL ) ) { if ( ( fabs( AngleDifference( ang[PITCH], mg42->s.angles[PITCH] ) ) >= mg42->varc ) || ( fabs( AngleDifference( ang[YAW], mg42->s.angles[YAW] ) ) >= mg42->harc ) ) { ent->botIgnoreTime = level.time + 5000; AIEnter_MP_Battle_Fight( bs ); return qfalse; } // if they are real close, abort mg42 mode if ( VectorDistanceSquared( bs->origin, g_entities[bs->enemy].r.currentOrigin ) < SQR( 512 ) ) { ent->botIgnoreTime = level.time + 5000; AIEnter_MP_Battle_Fight( bs ); return qfalse; } // bs->arrive_time = level.time; // wait a bit longer //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible trap_EA_Attack( bs->client ); } else { bs->enemy = -1; } } } else { // int spotNum; // // TODO: cycle through visible enemy sniper spots // NOTE: remember last visible enemy positions, so we can stay on them longer than usual if ( bs->enemyposition_time > trap_AAS_Time() - 5.0 ) { VectorSubtract( bs->enemyorigin, bs->origin, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( bs->viewchangetime > level.time ) { // use same angles /* } else if ((spotNum = BotGetRandomVisibleSniperSpot( bs )) > -1) { // look at new spot VectorSubtract( g_entities[spotNum].s.origin, bs->origin, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); // bs->viewchangetime = level.time + 1000 + rand()%1500; */ } else { vec3_t start, end; trace_t tr; // use mg42 angles VectorCopy( mg42->s.angles, bs->ideal_viewangles ); // add some random angle bs->ideal_viewangles[YAW] += crandom() * mg42->harc * 0.45; bs->ideal_viewangles[PITCH] += crandom() * mg42->varc * 0.15; // // trace out to get the ground AngleVectors( bs->ideal_viewangles, dir, NULL, NULL ); VectorMA( mg42->r.currentOrigin, 48, dir, start ); VectorMA( start, 4096, dir, end ); trap_Trace( &tr, start, NULL, NULL, end, bs->client, MASK_SHOT ); if ( tr.fraction > 0.2 ) { VectorCopy( tr.endpos, start ); VectorCopy( tr.endpos, end ); end[2] -= 1024 * tr.fraction; trap_Trace( &tr, start, NULL, NULL, end, bs->client, MASK_SHOT ); VectorSubtract( tr.endpos, bs->eye, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); } // bs->viewchangetime = level.time + 1000 + rand() % 1500; } } // // stay mounted bs->flags &= ~BFL_DISMOUNT_MG42; // return qtrue; } /* ================== AIEnter_MP_MG42Mount() ================== */ void AIEnter_MP_MG42Mount( bot_state_t *bs ) { //level.clients[0].sess.spectatorClient = bs->client; bs->arrive_time = level.time; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_MG42Mount; bs->ainodeText = "AINode_MP_MG42Mount"; } /* ================== AINode_MP_MG42Mount() ================== */ int AINode_MP_MG42Mount( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *ent; trace_t tr; goal = bs->target_goal; ent = BotGetEntity( bs->target_goal.entitynum ); // return to this sniper spot if we go off temporarily ent->botIgnoreTime = 0; // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // if we have mounted an mg42 if ( g_entities[bs->client].s.eFlags & EF_MG42_ACTIVE ) { // if it's the wrong one if ( VectorDistanceSquared( bs->origin, ent->r.currentOrigin ) > SQR( 64 ) ) { trap_EA_Activate( bs->client ); return qfalse; } AIEnter_MP_MG42Scan( bs ); return qfalse; } // if the mg42 is broken, or being used if ( ent->melee->health <= 0 || ( ent->melee->entstate != STATE_DEFAULT ) || ent->melee->active ) { bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { // AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } /* // check for special class actions if (BotCheckClassActions(bs)) { return qfalse; } */ // look for something better to do if ( BotFindSpecialGoals( bs ) ) { return qfalse; } // have we been waiting here for too long? if ( bs->arrive_time < level.time - 40000 ) { ent->botIgnoreTime = level.time + 15000; // other bots should avoid this spot if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // // other bots should avoid this spot ent->botIgnoreTime = level.time + 5000; // VectorSubtract( bs->origin, goal.origin, dir ); if ( fabs( dir[2] ) < 100 ) { dir[2] = 0; } // is the destination blocked? if ( VectorLengthSquared( dir ) < SQR( 64 ) ) { trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, ent->r.currentOrigin, bs->client, MASK_PLAYERSOLID ); if ( tr.startsolid || tr.allsolid ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } } if ( VectorLengthSquared( dir ) > SQR( 8 ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); // bs->arrive_time = level.time; // wait a bit longer //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); if ( BotFindSpecialGoals( bs ) ) { return qfalse; } // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); // fail bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } // BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { if ( VectorDistanceSquared( bs->origin, goal.origin ) > SQR( 32 ) ) { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; bs->enemyposition_time = 0; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { if ( VectorDistanceSquared( bs->origin, goal.origin ) > SQR( 128 ) || VectorDistanceSquared( bs->origin, g_entities[bs->enemy].r.currentOrigin ) > SQR( 256 ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { // go fight them AIEnter_MP_Battle_Fight( bs ); return qfalse; } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } } } // if ( VectorDistanceSquared( bs->origin, goal.origin ) < SQR( 64 ) ) { vec3_t forward, right, up, offset; // look at the gun AngleVectors( bs->viewangles, forward, right, up ); CalcMuzzlePointForActivate( &g_entities[bs->client], forward, right, up, offset ); VectorSubtract( ent->melee->r.currentOrigin, offset, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); if ( bs->arrive_time < level.time - 500 ) { // randomize the angles a bit to solve some times when a direct sight isn't "enough" if ( rand() % 4 == 0 ) { bs->ideal_viewangles[YAW] += crandom() * 10.0; bs->ideal_viewangles[PITCH] += crandom() * 10.0; } // if we have been waiting longer, move backwards slowly if ( bs->arrive_time < level.time - 2000 ) { VectorInverse( dir ); dir[2] = 0; VectorNormalize( dir ); trap_EA_Move( bs->client, dir, 40 ); } } // hit activate so we mount it if ( rand() % 2 ) { trap_EA_Activate( bs->client ); } } // reload? if ( ( bs->last_fire != level.time ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * GetAmmoTableData( bs->cur_ps.weapon )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { trap_EA_Reload( bs->client ); } // return qtrue; } /* ================== AIEnter_MP_ScanForLandmines() ================== */ void AIEnter_MP_ScanForLandmines( bot_state_t *bs ) { bs->arrive_time = level.time; bs->altenemy = 0; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_ScanForLandmines; bs->ainodeText = "AINode_MP_ScanForLandmines"; } #define DETECT_RADIUS 225 /* ================== AINode_MP_ScanForLandmines() ================== */ int AINode_MP_ScanForLandmines( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *ent; trace_t tr; goal = bs->target_goal; ent = &g_entities[bs->target_goal.entitynum]; if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); ent->missionLevel = level.time; return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); ent->missionLevel = level.time; return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); ent->missionLevel = level.time; return qfalse; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { ent->missionLevel = level.time; return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &bs->target_goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes ent->missionLevel = level.time; return qfalse; } // is this spot disabled now? if ( ent->aiInactive & ( 1 << bs->sess.sessionTeam ) ) { if ( BotFindSpecialGoals( bs ) ) { ent->missionLevel = level.time; return qfalse; } } // is the destination blocked? trap_Trace( &tr, ent->s.origin, NULL, NULL, ent->s.origin, bs->client, MASK_PLAYERSOLID ); if ( tr.startsolid || tr.allsolid ) { if ( BotFindSpecialGoals( bs ) ) { ent->missionLevel = level.time; return qfalse; } } VectorSubtract( bs->origin, goal.origin, dir ); if ( fabs( dir[2] ) < 16 ) { dir[2] = 0; } if ( VectorLengthSquared( dir ) > SQR( 32 ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); bs->arrive_time = level.time; // wait a bit longer //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); if ( BotFindSpecialGoals( bs ) ) { return qfalse; } // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); // fail bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } else if ( moveresult.flags & MOVERESULT_WAITING ) { //if waiting for something if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { if ( VectorDistanceSquared( bs->origin, goal.origin ) > SQR( 32 ) ) { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; bs->enemyposition_time = 0; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( VectorLengthSquared( moveresult.movedir ) ) { //FIXME: look at cluster portals? vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } } } if ( VectorLengthSquared( dir ) < SQR( 32 ) ) { // start zooming if we aren't already bs->weaponnum = WP_BINOCULARS; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); if ( bs->altenemy <= 0 ) { vec3_t target; if ( bs->target_goal.number >= 3 ) { BotChooseWeapon( bs ); BotDefaultNode( bs ); ent->missionLevel = level.time; return qfalse; } // we're going to modify the point we're looking at, based on the current counter number VectorCopy( ent->s.origin2, target ); // if our index is 0 or 2, we move it left switch ( bs->target_goal.number ) { case 0: target[1] += DETECT_RADIUS; break; case 1: target[0] += DETECT_RADIUS; break; case 2: target[0] -= DETECT_RADIUS; target[1] -= DETECT_RADIUS; break; } // look at the proper spot VectorSubtract( target, bs->eye, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); bs->altenemy = AngleNormalize360( RAD2DEG( 2 * tan( DETECT_RADIUS / VectorDistance( bs->eye, target ) ) ) ); // set the time of our next view update bs->viewchangetime = level.time + 100 + rand() % 150; // next target! bs->target_goal.number++; } else if ( bs->viewchangetime < level.time ) { // we're doing a sweep - on the 2nd pass we sweep the reverse direction of the other 2 passes // move viewangle by a bit switch ( bs->target_goal.number ) { case 2: bs->ideal_viewangles[1]--; break; default: bs->ideal_viewangles[1]++; break; } bs->altenemy--; // set the time of our next view update bs->viewchangetime = level.time + 100 + rand() % 150; } } return qtrue; } /* ================== AIEnter_MP_SniperSpot() ================== */ void AIEnter_MP_SniperSpot( bot_state_t *bs ) { bs->arrive_time = level.time; bs->enemyposition_time = 0; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_SniperSpot; bs->ainodeText = "AINode_MP_SniperSpot"; } /* ================== AINode_MP_SniperSpot() ================== */ int AINode_MP_SniperSpot( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *ent; trace_t tr; goal = bs->target_goal; ent = &g_entities[bs->target_goal.entitynum]; // return to this sniper spot if we go off temporarily ent->missionLevel = 0; bs->flags |= BFL_SNIPING; if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { ent->missionLevel = level.time; // other bots should avoid this spot AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { // return to this sniper spot after we're done ent->missionLevel = 0; return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { // return to this sniper spot after we're done ent->missionLevel = 0; // AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // look for something better to do if ( !BotFlagAtBase( bs->sess.sessionTeam, NULL ) ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // have we got enough ammo? if ( !BotCanSnipe( bs, qfalse ) ) { if ( !BotFindSpecialGoals( bs ) ) { BotDefaultNode( bs ); } return qfalse; } // is this spot disabled now? if ( ent->aiInactive & ( 1 << bs->sess.sessionTeam ) ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // // other bots should avoid this spot ent->missionLevel = level.time; // is the destination blocked? trap_Trace( &tr, ent->s.origin, NULL, NULL, ent->s.origin, bs->client, MASK_PLAYERSOLID ); if ( tr.startsolid || tr.allsolid ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } VectorSubtract( bs->origin, goal.origin, dir ); if ( fabs( dir[2] ) < 100 ) { dir[2] = 0; } if ( VectorLengthSquared( dir ) > SQR( 32 ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); bs->arrive_time = level.time; // wait a bit longer //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); if ( BotFindSpecialGoals( bs ) ) { return qfalse; } // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); // fail bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } else if ( moveresult.flags & MOVERESULT_WAITING ) { //if waiting for something if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { if ( VectorDistanceSquared( bs->origin, goal.origin ) > SQR( 32 ) ) { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; bs->enemyposition_time = 0; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( VectorLengthSquared( moveresult.movedir ) ) { //FIXME: look at cluster portals? vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } } } if ( VectorLengthSquared( dir ) < SQR( 32 ) ) { // have we been waiting here for too long? if ( bs->arrive_time < level.time - 40000 ) { ent->missionLevel = level.time; // other bots should avoid this spot if ( !BotFindSpecialGoals( bs ) ) { BotDefaultNode( bs ); } return qfalse; } // Gordon: crouching spot if ( ent->spawnflags & 2 ) { trap_EA_Crouch( bs->client ); } // We are at the sniper spot, so start sniping bs->weaponnum = BotCanSnipe( bs, qfalse ); trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( EntityIsDead( &entinfo ) ) { bs->enemy = -1; } if ( bs->enemy >= 0 ) { if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 50, bs->enemy, NULL ) ) { // if they are real close, abort sniper mode if ( VectorDistanceSquared( bs->origin, g_entities[bs->enemy].r.currentOrigin ) < SQR( 512 ) ) { AIEnter_MP_Battle_Fight( bs ); return qfalse; } bs->arrive_time = level.time; // wait a bit longer // remember this position bs->enemyposition_time = trap_AAS_Time(); VectorCopy( BotGetOrigin( bs->enemy ), bs->enemyorigin ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon && BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 5, bs->enemy, NULL ) ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } else { int spotNum; if ( bs->arrive_time < level.time - 20000 ) { // if not suggesting sniper, suicide so we can change if ( !BG_IsScopedWeapon( BotSuggestWeapon( bs, bs->sess.sessionTeam ) ) ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } } // NOTE: remember last visible enemy positions, so we can stay on them longer than usual if ( bs->enemyposition_time > trap_AAS_Time() - 5.0 ) { VectorSubtract( bs->enemyorigin, bs->origin, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( bs->viewchangetime > level.time ) { // // use same angles // } else if ( !( rand() % ( 1 + BotGetNumVisibleSniperSpots( bs ) ) ) && ( spotNum = BotGetRandomVisibleSniperSpot( bs ) ) > -1 ) { // look at new spot VectorSubtract( g_entities[spotNum].s.origin, bs->origin, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); // bs->viewchangetime = level.time + 1500 + rand() % 3000; } else if ( rand() % 2 && bs->enemyposition_time && ( bs->enemyposition_time > trap_AAS_Time() - 60.0 ) ) { // look at last enemy pos VectorSubtract( bs->enemyorigin, bs->origin, dir ); // if it's not too far away, use non-zoomed view if ( VectorNormalize( dir ) < 3000 ) { // switch to non-zoomed view if ( bs->weaponnum >= WP_BEGINSECONDARY && bs->weaponnum <= WP_LASTSECONDARY ) { bs->weaponnum = weapAlts[bs->weaponnum]; } trap_EA_SelectWeapon( bs->client, bs->weaponnum ); } vectoangles( dir, bs->ideal_viewangles ); bs->viewchangetime = level.time + 2000 + rand() % 2000; } else if ( !VectorCompare( ent->s.angles, vec3_origin ) ) { // just face direction of sniper spot // switch to non-zoomed view if ( bs->weaponnum >= WP_BEGINSECONDARY && bs->weaponnum <= WP_LASTSECONDARY ) { bs->weaponnum = weapAlts[bs->weaponnum]; } trap_EA_SelectWeapon( bs->client, bs->weaponnum ); VectorCopy( ent->s.angles, bs->ideal_viewangles ); // bs->viewchangetime = level.time + 2500 + rand() % 3000; } } } // reload? if ( ( level.time - bs->last_fire > 2000 ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * GetAmmoTableData( bs->cur_ps.weapon )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { trap_EA_Reload( bs->client ); } return qtrue; } /* ================== AIEnter_MP_DefendTarget() ================== */ void AIEnter_MP_DefendTarget( bot_state_t *bs ) { //VectorCopy( bs->origin, bs->aimtarget ); //choose the best weapon to fight with bs->arrive_time = 0; BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_DefendTarget; bs->ainodeText = "AINode_MP_DefendTarget"; } /* ================== AINode_MP_DefendTarget() ================== */ int AINode_MP_DefendTarget( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *ent, *flag; qboolean move; trace_t tr; goal = bs->target_goal; bs->defendgoal = bs->target_goal; if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } /* // check for special class actions if (BotCheckClassActions(bs)) { return qfalse; } */ // look for health/ammo packs if ( BotFindNearbyGoal( bs ) ) { AIEnter_MP_Seek_NBG( bs ); return qfalse; } if ( goal.entitynum < 0 ) { BotDefaultNode( bs ); return qfalse; } // look for better goals if ( BotFindSpecialGoals( bs ) ) { return qfalse; } // if we are defending a leader, and they are not valid anymore, then stop if ( goal.flags & GFL_LEADER ) { // make sure our leader is still valid if ( bs->leader > -1 ) { if ( !g_entities[bs->leader].inuse || !g_entities[bs->leader].client || ( g_entities[bs->leader].client->ps.pm_flags & PMF_LIMBO ) ) { bs->leader = -1; BotDefaultNode( bs ); return qfalse; } else if ( bs->sess.playerType == PC_MEDIC ) { if ( g_entities[bs->leader].health <= 0 && trap_AAS_PointAreaNum( g_entities[bs->leader].r.currentOrigin ) && BotGoalForEntity( bs, bs->leader, &bs->target_goal, BGU_MEDIUM ) ) { // revive g_entities[bs->target_goal.entitynum].missionLevel = level.time + 3000; AIEnter_MP_MedicRevive( bs ); return qfalse; } else if ( BotHealthScale( bs->leader ) <= 0.7 && trap_AAS_PointAreaNum( g_entities[bs->leader].r.currentOrigin ) && BotGoalForEntity( bs, bs->leader, &bs->target_goal, BGU_MEDIUM ) ) { // health stock? // make this our goal g_entities[bs->target_goal.entitynum].missionLevel = level.time + 3000; AIEnter_MP_MedicGiveHealth( bs ); return qfalse; } } else if ( ( VectorLengthSquared( g_entities[bs->leader].client->ps.velocity ) < SQR( 10 ) ) && ( VectorLengthSquared( bs->cur_ps.velocity ) < SQR( 10 ) ) ) { if ( !( g_entities[bs->leader].r.svFlags & SVF_BOT ) ) { BotVoiceChatAfterIdleTime( bs->client, "WhereTo", SAY_BUDDY, 1000 + rand() % 3000, BOT_SHOWTEXT, 12000, qfalse ); } } // if ( !g_entities[bs->leader].inuse || g_entities[bs->leader].health <= 0 || VectorDistanceSquared( g_entities[bs->leader].r.currentOrigin, bs->origin ) > SQR( MAX_BOTLEADER_DIST ) ) { bs->leader = -1; } } // if ( bs->leader == -1 ) { BotDefaultNode( bs ); return qfalse; } } // //if there is an enemy if ( BotFindEnemyMP( bs, -1, qfalse ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); // if we made it to the destination, let us roam to fight people if ( BotCarryingFlag( bs->enemy ) || ( !( goal.flags & GFL_LEADER ) && ( bs->flags & BFL_MISCFLAG ) ) ) { if ( !BotCarryingFlag( bs->enemy ) && VectorLengthSquared( bs->cur_ps.velocity ) && BotWantsToRetreat( bs ) ) { //keep the current long term goal and retreat } else { trap_BotResetLastAvoidReach( bs->ms ); //go fight AIEnter_MP_Battle_Fight( bs ); return qfalse; } } } // if the target has been cleared if ( goal.entitynum < 0 ) { BotDefaultNode( bs ); return qfalse; } // should we stop pursuing this target? ent = &g_entities[bs->target_goal.entitynum]; switch ( ent->s.eType ) { case ET_TRIGGER_FLAGONLY: if ( BotFlagAtBase( bs->sess.sessionTeam, &flag ) == qtrue ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } break; case ET_TRIGGER_FLAGONLY_MULTIPLE: if ( !BotEnemyCarryingFlag( bs->client ) ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } break; case ET_ITEM: if ( !Q_stricmp( ent->classname, "team_CTF_redflag" ) && !( ent->flags & FL_DROPPED_ITEM ) ) { if ( bs->sess.sessionTeam != TEAM_AXIS ) { // this is the enemy flag, so abort "defending" it when it shows up if ( BotFlagAtBase( TEAM_AXIS, NULL ) == qtrue ) { BotDefaultNode( bs ); return qfalse; } else if ( BotCarryingFlag( bs->client ) ) { // we have it! BotDefaultNode( bs ); return qfalse; } } } else if ( !Q_stricmp( ent->classname, "team_CTF_blueflag" ) && !( ent->flags & FL_DROPPED_ITEM ) ) { if ( bs->sess.sessionTeam != TEAM_ALLIES ) { // this is the enemy flag, so abort "defending" it when it shows up if ( BotFlagAtBase( TEAM_ALLIES, NULL ) == qtrue ) { BotDefaultNode( bs ); return qfalse; } else if ( BotCarryingFlag( bs->client ) ) { // we have it! BotDefaultNode( bs ); return qfalse; } } } else if ( ent->touch ) { if ( ent->r.svFlags & SVF_NOCLIENT ) { BotDefaultNode( bs ); return qfalse; } } break; case ET_TRAP: if ( !Q_stricmp( ent->classname, "team_WOLF_checkpoint" ) ) { if ( ent->count == ( bs->sess.sessionTeam == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS ) ) { // the enemy is controlling this checkpoint now BotDefaultNode( bs ); return qfalse; } } break; case ET_TRIGGER_MULTIPLE: // if we are within range, stop here if ( bs->arrive_time < level.time - 5000 ) { if ( ( VectorDistanceSquared( bs->origin, BotGetOrigin( ent->s.number ) ) < SQR( 600 ) ) && trap_InPVS( bs->origin, BotGetOrigin( ent->s.number ) ) ) { // check the trace trap_Trace( &tr, bs->eye, vec3_origin, vec3_origin, BotGetOrigin( ent->s.number ), -1, (MASK_SHOT) &~( CONTENTS_BODY | CONTENTS_CORPSE ) ); if ( tr.entityNum != ENTITYNUM_WORLD ) { VectorCopy( bs->origin, bs->target_goal.origin ); goal = bs->target_goal; bs->arrive_time = level.time; } } } break; case ET_PLAYER: if ( ent->client && ent->health <= 0 ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } break; case ET_ATTRACTOR_HINT: default: // rain break; } // if we are defending a player, and they have moved, get a new sparse defend area if ( ent->client && ( bs->target_goal.number < level.time - 300 ) && !VectorCompare( ent->r.currentOrigin, bs->defendgoal_origin ) ) { int list[MAX_CLIENTS], numList; float ourDist, *distances; // // if there are too many defenders, and we are the furthest away, stop defending if ( BotCarryingFlag( bs->target_goal.entitynum ) ) { if ( ( numList = BotNumTeamMatesWithTarget( bs, goal.entitynum, list, MAX_CLIENTS ) ) > BOT_FLAG_CARRIER_DEFENDERS ) { ourDist = VectorDistanceSquared( bs->origin, g_entities[goal.entitynum].r.currentOrigin ); if ( !trap_InPVS( bs->origin, g_entities[goal.entitynum].r.currentOrigin ) ) { ourDist += 2048 * 2048; } distances = BotSortPlayersByDistance( g_entities[goal.entitynum].r.currentOrigin, list, numList ); if ( distances[numList - 1] < ourDist ) { // we are the furthest bs->ignore_specialgoal_time = 0; bs->leader = -1; BotDefaultNode( bs ); return qfalse; } } } // look for a new defend pos VectorCopy( ent->r.currentOrigin, bs->target_goal.origin ); bs->target_goal.areanum = BotPointAreaNum( ent->s.number, ent->r.currentOrigin ); // BotFindSparseDefendArea( bs, &bs->target_goal, qtrue ); VectorCopy( ent->r.currentOrigin, bs->defendgoal_origin ); bs->target_goal.number = level.time + rand() % 100; goal = bs->target_goal; bs->defendgoal = bs->target_goal; } // // remember the last position we couldnt see the DESTINATION from, so we can look there once we arrive if ( !trap_InPVS( bs->origin, goal.origin ) ) { VectorCopy( bs->origin, bs->aimtarget ); } // // do we need to get closer to the goal? // move = qfalse; if ( !( bs->target_goal.flags & GFL_DEFEND_CLOSE ) ) { if ( !move && VectorDistanceSquared( bs->origin, goal.origin ) > SQR( 384 ) ) { move = qtrue; } if ( !move && trap_InPVS( bs->origin, goal.origin ) ) { trace_t tr; trap_Trace( &tr, bs->origin, NULL, NULL, goal.origin, -1, MASK_SHOT & ~( CONTENTS_BODY | CONTENTS_CORPSE ) ); if ( tr.startsolid || tr.allsolid || tr.fraction < 1.0 ) { move = qtrue; } } } else { if ( !move && VectorDistanceSquared( bs->origin, goal.origin ) > ( ( goal.flags & GFL_LEADER ) ? SQR( 80 ) : SQR( 32 ) ) ) { move = qtrue; } } if ( move ) { //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { if ( goal.flags & GFL_LEADER ) { bs->leader = -1; } //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly? //trap_EA_Jump(bs->client); //trap_EA_Move(bs->client, tv(crandom(), crandom(), crandom()), 100+random()*200 ); // fail bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } if ( bs->blockentTime > level.time - 500 && bs->blockent < level.maxclients ) { // if we are within range, then stop here if ( goal.flags & GFL_LEADER ) { if ( ( BotPointWithinMovementAutonomy( bs, &goal, bs->origin ) ) && ( VectorDistanceSquared( bs->origin, g_entities[bs->leader].r.currentOrigin ) < SQR( 512 ) ) ) { VectorCopy( bs->origin, bs->target_goal.origin ); bs->target_goal.areanum = bs->areanum; } } } BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } else if ( moveresult.flags & MOVERESULT_WAITING ) { //if waiting for something if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { // under special circumstances, we should go into battle mode if ( !BotCarryingFlag( bs->target_goal.entitynum ) && ( ( bs->weaponnum == WP_LUGER ) || ( bs->weaponnum == WP_COLT ) || ( bs->weaponnum == WP_MOBILE_MG42 ) ) ) { AIEnter_MP_Battle_Fight( bs ); return qfalse; } else { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } } else { bs->enemy = -1; } } } if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( VectorLengthSquared( moveresult.movedir ) ) { //FIXME: look at cluster portals? vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; // check for giving ourselves some ammo if ( bs->sess.playerType == PC_FIELDOPS && ClientNeedsAmmo( bs->client ) ) { // switch to regen and pump away bs->weaponnum = WP_AMMO; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); if ( bs->cur_ps.weapon == WP_AMMO && BotWeaponCharged( bs, WP_AMMO ) ) { trap_EA_Attack( bs->client ); } } // check for giving ourselves some health if ( bs->sess.playerType == PC_MEDIC && BotHealthScale( bs->client ) < 1.0 ) { // switch to regen and pump away bs->weaponnum = WP_MEDKIT; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); if ( bs->cur_ps.weapon == WP_MEDKIT && BotWeaponCharged( bs, WP_MEDKIT ) ) { trap_EA_Attack( bs->client ); } } } } } else { gentity_t *objtarg; // NOTE: dont bother looking for enemies, since we have reached the destination, so we are free to // pursue enemies now // // call for an engineer (if required) if ( ( ent->s.eType == ET_OID_TRIGGER ) && ( objtarg = ent->target_ent ) ) { if ( objtarg->spawnflags & 64 ) { if ( ent->lastHintCheckTime < level.time && rand() % 2 ) { // if there is no dynamite planted here int list[10], numList, i; // numList = BotGetTargetExplosives( bs->sess.sessionTeam, list, 10, qfalse ); for ( i = 0; i < numList; i++ ) { if ( list[i] == ent->s.number ) { break; } } // if this objective doesn ont have armed dynamite if ( numList && i < numList ) { BotVoiceChatAfterIdleTime( bs->client, "NeedEngineer", SAY_TEAM, 500 + rand() % 4000, qfalse, 5000 + rand() % 5000, qfalse ); } } // if we are an engineer, throw out an air-strike if ( bs->sess.playerType == PC_FIELDOPS && ( level.time - bs->cur_ps.classWeaponTime > ( level.lieutenantChargeTime[bs->sess.sessionTeam - 1] * 0.5f ) ) ) { // select smoke grenade bs->weaponnum = WP_SMOKE_MARKER; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // look upwards bs->ideal_viewangles[PITCH] = -70; if ( bs->cur_ps.weapon == bs->weaponnum && bs->viewangles[PITCH] < -60 ) { trap_EA_Attack( bs->client ); } return qtrue; } } } // set flag so we know that we made it to the destination bs->flags |= BFL_MISCFLAG; // // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } else { if ( bs->viewchangetime < level.time ) { // look for a better pos if ( !bs->arrive_time && random() < 0.1 ) { if ( bs->target_goal.entitynum ) { BotFindSparseDefendArea( bs, &bs->target_goal, qfalse ); } } if ( ++bs->viewtype > 1 ) { bs->viewtype = 0; } bs->viewchangetime = level.time + 500 + rand() % 3000; } // if guarding a carrier, look towards the flag goal if ( goal.entitynum > -1 && goal.entitynum < level.maxclients && BotCarryingFlag( goal.entitynum ) ) { bot_goal_t fgoal; gentity_t *flaggoal; qboolean setdir = qfalse; // flaggoal = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY ); if ( !flaggoal ) { flaggoal = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY_MULTIPLE ); } if ( flaggoal ) { if ( BotGoalForEntity( NULL, flaggoal->s.number, &fgoal, BGU_HIGH ) ) { if ( trap_BotMovementViewTarget( bs->ms, &fgoal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); setdir = qtrue; } } } if ( !setdir ) { // look away from the defense object VectorAdd( g_entities[bs->target_goal.entitynum].r.absmax, g_entities[bs->target_goal.entitynum].r.absmin, target ); VectorScale( target, 0.5, target ); VectorSubtract( target, bs->origin, dir ); VectorNormalize( dir ); VectorInverse( dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[PITCH] = 0; } } else if ( bs->viewtype == 0 ) { // face the current aimtarget VectorSubtract( bs->aimtarget, bs->origin, dir ); if ( VectorNormalize( dir ) ) { if ( fabs( dir[2] ) < 0.8 ) { vectoangles( dir, bs->ideal_viewangles ); } bs->ideal_viewangles[PITCH] = 0; } } else if ( bs->target_goal.entitynum > 0 ) { // look at the defense object VectorAdd( g_entities[bs->target_goal.entitynum].r.absmax, g_entities[bs->target_goal.entitynum].r.absmin, target ); VectorScale( target, 0.5, target ); VectorSubtract( target, bs->origin, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[PITCH] = 0; } } } // reload? if ( ( bs->last_fire != level.time ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * GetAmmoTableData( bs->cur_ps.weapon )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { trap_EA_Reload( bs->client ); } return qtrue; } /* ================== AIEnter_MP_TouchTarget() ================== */ void AIEnter_MP_TouchTarget( bot_state_t *bs ) { //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_TouchTarget; bs->ainodeText = "AINode_MP_TouchTarget"; } /* ================== AINode_MP_TouchTarget() ================== */ int AINode_MP_TouchTarget( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *ent; qboolean altroute = qfalse; vec3_t mins, maxs; goal = bs->target_goal; if ( bs->alt_goal.number && bs->alt_goal.entitynum == goal.entitynum && bs->alt_goal.number == goal.number ) { if ( ( bs->alt_goal.number < level.time - 12000 ) || ( trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, bs->target_goal.areanum, bs->tfl ) < 700 ) ) { // stop pursuing altgoal after some time BotClearGoal( &bs->alt_goal ); bs->target_goal.number = 0; goal = bs->target_goal; } else { goal = bs->alt_goal; altroute = qtrue; } } if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } /* // check for special class actions if (BotCheckClassActions(bs)) { return qfalse; } */ // look for health/ammo packs if ( BotFindNearbyGoal( bs ) ) { AIEnter_MP_Seek_NBG( bs ); return qfalse; } // // should we stop pursuing this target? ent = &g_entities[bs->target_goal.entitynum]; if ( ent->aiInactive & ( 1 << bs->sess.sessionTeam ) ) { if ( !BotFindSpecialGoals( bs ) ) { BotDefaultNode( bs ); } return qfalse; } if ( BotFindSpecialGoals( bs ) ) { return qfalse; } if ( ent->s.eType == ET_TRAP && !Q_stricmp( ent->classname, "team_WOLF_checkpoint" ) ) { if ( ent->count == bs->sess.sessionTeam ) { // we have captured it bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } } else if ( ent->s.eType == ET_ITEM && ent->touch ) { if ( ent->r.svFlags & SVF_NOCLIENT ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } } else { if ( ent->s.eType == ET_TRIGGER_MULTIPLE ) { // if ( !ent->r.linked || ent->nextthink > level.time + 1000 ) { BotDefaultNode( bs ); return qfalse; } // if we are currently touching the target, then use this as our spot if ( bs->arrive_time + 3000 < level.time ) { VectorAdd( bs->origin, bs->cur_ps.mins, mins ); VectorAdd( bs->origin, bs->cur_ps.maxs, maxs ); if ( trap_EntityContactCapsule( mins, maxs, ent ) ) { VectorCopy( bs->origin, goal.origin ); bs->arrive_time = level.time; } } // if the current destination is no longer touching the brush, get a new position VectorAdd( goal.origin, bs->cur_ps.mins, mins ); VectorAdd( goal.origin, bs->cur_ps.maxs, maxs ); if ( !trap_EntityContactCapsule( mins, maxs, ent ) || ( rand() % 50 == 0 ) ) { // need a new position if ( !BotGetReachableEntityArea( bs, ent->s.number, &goal ) ) { BotDefaultNode( bs ); return qfalse; } // check this position VectorAdd( goal.origin, bs->cur_ps.mins, mins ); VectorAdd( goal.origin, bs->cur_ps.maxs, maxs ); if ( !trap_EntityContactCapsule( mins, maxs, ent ) ) { BotDefaultNode( bs ); return qfalse; } // this is the new goal bs->target_goal = goal; } } if ( !Q_stricmp( ent->classname, "trigger_flagonly" ) ) { // if we dont have the flag anymore, then stop if ( !BotCarryingFlag( bs->client ) ) { BotDefaultNode( bs ); return qfalse; } } } // VectorCopy( goal.origin, target ); if ( fabs( target[2] - bs->origin[2] ) < 80 ) { target[2] = bs->origin[2]; } if ( altroute && VectorDistanceSquared( goal.origin, bs->origin ) < SQR( 80 ) ) { // made it to the altroute BotClearGoal( &bs->alt_goal ); bs->target_goal.number = 0; goal = bs->target_goal; } // // if we have an enemy, then we should look for an alternate route away from them if ( ( BotCarryingFlag( bs->client ) ) && ( goal.number < level.time - 8000 ) && ( BotNumTeamMatesWithTarget( bs, bs->client, NULL, -1 ) < 2 ) && ( trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, bs->target_goal.areanum, bs->tfl ) > 1500 ) ) { if ( bs->enemy > -1 ) { aas_altroutegoal_t altroutegoals[40]; int numList, i; bot_goal_t tempgoal; vec3_t edir; // // get the vector towards the enemy VectorSubtract( g_entities[bs->enemy].r.currentOrigin, bs->origin, edir ); VectorNormalize( edir ); // if we are running towards them if ( DotProduct( edir, bs->cur_ps.velocity ) > 0 ) { // dont do this check again for a while bs->alt_goal.number = level.time; bs->target_goal.number = level.time; //initialize the movement state BotSetupForMovement( bs ); // check for an alternate route if ( ( numList = trap_AAS_AlternativeRouteGoals( bs->origin, bs->target_goal.origin, bs->tfl, altroutegoals, 40, 0 ) ) ) { for ( i = 0; i < numList; i++ ) { BotClearGoal( &tempgoal ); tempgoal.areanum = altroutegoals[i].areanum; VectorCopy( altroutegoals[i].origin, tempgoal.origin ); if ( qtrue ) { //trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { //VectorSubtract(target, bs->origin, dir); VectorSubtract( tempgoal.origin, bs->origin, dir ); VectorNormalize( dir ); if ( DotProduct( dir, edir ) < 0 ) { // moving away from the enemy //make this the altroutegoal bs->alt_goal = bs->target_goal; trap_AAS_AreaWaypoint( altroutegoals[i].areanum, bs->alt_goal.origin ); bs->alt_goal.areanum = trap_AAS_PointAreaNum( bs->alt_goal.origin ); break; } } } } } } else if ( bs->enemy < 0 ) { int list[MAX_CLIENTS], numList; int i, t; bot_state_t *tbs; // if we have defenders, stop every now and then to let them get infront of us if ( ( bs->stand_time < level.time - 3000 ) && ( trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, bs->target_goal.areanum, bs->tfl ) > 600 ) && ( numList = BotNumTeamMatesWithTarget( bs, bs->client, list, MAX_CLIENTS ) ) ) { // if one of them has a goal infront of us, and they are not near it, we should pause for a bit for ( i = 0; i < numList; i++ ) { tbs = &botstates[list[i]]; // if ( ( VectorDistanceSquared( tbs->origin, tbs->defendgoal.origin ) < SQR( 1500 ) ) && ( VectorDistanceSquared( tbs->origin, bs->origin ) < SQR( 1024 ) ) && ( trap_InPVS( tbs->origin, bs->origin ) ) && ( trap_AAS_AreaTravelTimeToGoalArea( tbs->defendgoal.areanum, tbs->defendgoal.origin, goal.areanum, bs->tfl ) < trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, bs->target_goal.areanum, bs->tfl ) ) && ( trap_AAS_AreaTravelTimeToGoalArea( tbs->areanum, tbs->origin, goal.areanum, bs->tfl ) - trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, goal.areanum, bs->tfl ) ) ) { t = trap_AAS_AreaTravelTimeToGoalArea( tbs->areanum, tbs->origin, tbs->defendgoal.areanum, bs->tfl ); if ( t > 300 ) { t = 300; } if ( t > 100 ) { bs->stand_time = level.time + 1000 + 5 * t; } } } } } } //choose the best weapon to fight with BotChooseWeapon( bs ); // //initialize the movement state BotSetupForMovement( bs ); // if ( bs->stand_time < level.time ) { qboolean move = qtrue; //move towards the goal if ( ent->s.eType == ET_TRAP && !Q_stricmp( ent->classname, "team_WOLF_checkpoint" ) && ( VectorDistanceSquared( goal.origin, bs->origin ) < SQR( 128 ) ) ) { // move straight torward it VectorAdd( ent->r.absmin, ent->r.absmax, target ); VectorScale( target, 0.5, target ); VectorSubtract( target, bs->origin, dir ); dir[2] = 0; VectorNormalize( dir ); trap_EA_Move( bs->client, dir, 300 ); // randomly jump if ( rand() % 10 == 0 ) { trap_EA_Jump( bs->client ); } move = qfalse; } else if ( ent->s.eType == ET_TRIGGER_MULTIPLE ) { int i; // if we are touching the brush, we dont need to move VectorAdd( bs->origin, bs->cur_ps.mins, mins ); VectorAdd( bs->origin, bs->cur_ps.maxs, maxs ); // make them a bit smaller to be safe for ( i = 0; i < 3; i++ ) { mins[i] += 2; maxs[i] -= 2; } if ( trap_EntityContactCapsule( mins, maxs, ent ) ) { move = qfalse; } } if ( move ) { //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); // fail bs->ainode = NULL; bs->ainodeText = "NULL"; return qtrue; } // BotAIBlocked( bs, &moveresult, qtrue ); } } //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { /* // if we are not going for an urgent goal, go into battle mode if (!BotCarryingFlag(bs->client) && bs->ignore_specialgoal_time < level.time && BotWantsToChase(bs) && (VectorDistanceSquared( goal.origin, bs->origin ) > SQR(384)) && (VectorDistanceSquared( bs->enemyorigin, bs->origin ) < SQR(2048)) && Q_stricmp(g_entities[goal.entitynum].classname, "team_CTF_redflag") && Q_stricmp(g_entities[goal.entitynum].classname, "team_CTF_blueflag")) { AIEnter_MP_Battle_Fight(bs); return qfalse; } */ //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( ent->s.eType == ET_TRIGGER_MULTIPLE && trap_InPVS( bs->origin, goal.origin ) && Distance( bs->origin, goal.origin ) < 512 ) { VectorSubtract( ent->r.currentOrigin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; // check for giving ourselves some ammo if ( bs->sess.playerType == PC_FIELDOPS && ClientNeedsAmmo( bs->client ) ) { // switch to regen and pump away bs->weaponnum = WP_AMMO; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); if ( bs->cur_ps.weapon == WP_AMMO && BotWeaponCharged( bs, WP_AMMO ) ) { trap_EA_Attack( bs->client ); } } // check for giving ourselves some health if ( bs->sess.playerType == PC_MEDIC && BotHealthScale( bs->client ) < 1.0 ) { // switch to regen and pump away bs->weaponnum = WP_MEDKIT; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); if ( bs->cur_ps.weapon == WP_MEDKIT && BotWeaponCharged( bs, WP_MEDKIT ) ) { trap_EA_Attack( bs->client ); } } } } // reload? if ( ( bs->last_fire != level.time ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * GetAmmoTableData( bs->cur_ps.weapon )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { trap_EA_Reload( bs->client ); } return qtrue; } /* ================== AIEnter_MP_SatchelChargeTarget ================== */ void AIEnter_MP_SatchelChargeTarget( bot_state_t *bs ) { bs->target_goal.flags &= ~GFL_NOSLOWAPPROACH; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_SatchelChargeTarget; bs->ainodeText = "AINode_MP_SatchelChargeTarget"; } /* ================== AINode_MP_SatchelChargeTarget ================== */ int AINode_MP_SatchelChargeTarget( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *trav; int list[10], numList, i; float goalDist; aas_entityinfo_t entinfo; goal = bs->target_goal; //if we have changed class if ( bs->sess.playerType != PC_COVERTOPS ) { BotDefaultNode( bs ); return qfalse; } if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } //if there are 2 bots going for this goal, then we should abort if we are further away goalDist = VectorDistanceSquared( bs->origin, goal.origin ); if ( ( numList = BotNumTeamMatesWithTargetByClass( bs, goal.entitynum, list, 10, PC_ENGINEER ) ) ) { if ( goalDist > SQR( 256 ) ) { goalDist = SQR( 256 ); // only abort if one of our teammates is close to the goal } for ( i = 0; i < numList; i++ ) { if ( botstates[list[i]].ainode == AINode_MP_SatchelChargeTarget ) { if ( VectorDistanceSquared( botstates[list[i]].origin, goal.origin ) < goalDist ) { // make sure other bots dont head for this target also g_entities[goal.entitynum].lastHintCheckTime = level.time + 5000; BotDefaultNode( bs ); return qfalse; } } } } VectorSubtract( bs->origin, goal.origin, dir ); if ( fabs( dir[2] ) < 128 ) { dir[2] = 0; } goalDist = VectorLengthSquared( dir ); // are we close enough to the goal? if ( goalDist < SQR( 32 ) ) { // have we recently dropped some dynamite? for ( trav = G_FindSatchels( NULL ); trav; trav = G_FindSatchels( trav ) ) { if ( !trav->parent || trav->parent->s.number != bs->client ) { continue; } if ( VectorDistanceSquared( bs->origin, trav->r.currentOrigin ) > SQR( 64 ) ) { continue; } // found some! bs->flags |= BFL_MISCFLAG; VectorCopy( trav->r.currentOrigin, bs->target_goal.origin ); bs->target_goal.origin[2] += 24; break; } if ( !trav ) { // no dynamite found, keep trying to plant some if ( bs->flags & BFL_MISCFLAG ) { // it's gone, so reset goal BotDefaultNode( bs ); return qfalse; } if ( BotWeaponCharged( bs, WP_SATCHEL ) ) { // VOICE: cover me! BotVoiceChatAfterIdleTime( bs->client, "CoverMe", SAY_TEAM, 500 + rand() % 1000, qfalse, 10000, qfalse ); // make sure other bots dont head for this target also g_entities[bs->target_goal.entitynum].lastHintCheckTime = level.time + 2000; // crouch down trap_EA_Crouch( bs->client ); // look downwards bs->ideal_viewangles[PITCH] = 70; // select the dynamite bs->weaponnum = WP_SATCHEL; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // hold fire if ( !VectorLengthSquared( bs->velocity ) && fabs( bs->viewangles[PITCH] - bs->ideal_viewangles[PITCH] ) < 2 && bs->cur_ps.weapon == WP_SATCHEL && !bs->cur_ps.grenadeTimeLeft ) { trap_EA_Attack( bs->client ); } return qtrue; } } else if ( ( trav = G_FindSatchel( &g_entities[bs->client] ) ) ) { /* // check for emergency targets (flags, etc) if (BotCheckEmergencyTargets( bs )) { return qfalse; } // check for dangerous elements if (BotDangerousGoal(bs, &goal)) { AIEnter_MP_AvoidDanger(bs); // avoid this danger until it passes return qfalse; } // just look for a goal BotDefaultNode(bs); return qfalse;*/ } } if ( !( bs->flags & BFL_MISCFLAG ) ) { if ( goalDist > SQR( 128 ) ) { // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } //choose the best weapon to fight with BotChooseWeapon( bs ); // are we close enough to the goal? if ( goalDist >= SQR( 32 ) ) { // MOVEMENT REQUIRED // //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); } // BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } } // return qtrue; } /* ================== AIEnter_MP_DynamiteTarget() ================== */ void AIEnter_MP_DynamiteTarget( bot_state_t *bs ) { bs->toggleHidingTime = level.time; bs->target_goal.flags &= ~GFL_NOSLOWAPPROACH; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_DynamiteTarget; bs->ainodeText = "AINode_MP_DynamiteTarget"; } /* ================== AINode_MP_DynamiteTarget() ================== */ int AINode_MP_DynamiteTarget( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *trav; int list[10], numList, i; float goalDist; aas_entityinfo_t entinfo; goal = bs->target_goal; //if we have changed class if ( bs->sess.playerType != PC_ENGINEER ) { BotDefaultNode( bs ); return qfalse; } // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } //if there are 2 bots going for this goal, then we should abort if we are further away goalDist = VectorDistanceSquared( bs->origin, goal.origin ); if ( ( numList = BotNumTeamMatesWithTargetByClass( bs, goal.entitynum, list, 10, PC_ENGINEER ) ) ) { if ( goalDist > SQR( 256 ) ) { goalDist = SQR( 256 ); // only abort if one of our teammates is close to the goal } for ( i = 0; i < numList; i++ ) { if ( list[i] == bs->client ) { continue; } if ( botstates[list[i]].ainode == AINode_MP_DynamiteTarget ) { if ( VectorDistanceSquared( botstates[list[i]].origin, goal.origin ) < goalDist ) { // make sure other bots dont head for this target also g_entities[goal.entitynum].lastHintCheckTime = level.time + 5000; BotDefaultNode( bs ); return qfalse; } } } } // //map specific code BotMapScripts( bs ); // VectorSubtract( bs->origin, goal.origin, dir ); if ( fabs( dir[2] ) < 128 ) { dir[2] = 0; } goalDist = VectorLengthSquared( dir ); // are we close enough to the goal? if ( goalDist < SQR( 32 ) ) { // have we recently dropped some dynamite? for ( trav = G_FindDynamite( NULL ); trav; trav = G_FindDynamite( trav ) ) { if ( !trav->parent || trav->parent->s.number != bs->client ) { continue; } if ( VectorDistanceSquared( bs->origin, trav->r.currentOrigin ) > SQR( 100 ) ) { continue; } if ( trav->spawnTime < bs->toggleHidingTime ) { // armed before we started this node continue; } // found some! bs->flags |= BFL_MISCFLAG; VectorCopy( trav->r.currentOrigin, bs->target_goal.origin ); bs->target_goal.origin[2] += 24; break; } if ( !trav ) { // no dynamite found, keep trying to plant some if ( bs->flags & BFL_MISCFLAG ) { // it's gone, so reset goal BotDefaultNode( bs ); return qfalse; } if ( BotWeaponCharged( bs, WP_DYNAMITE ) ) { // VOICE: cover me! BotVoiceChatAfterIdleTime( bs->client, "CoverMe", SAY_TEAM, 500 + rand() % 1000, qfalse, 10000, qfalse ); // make sure other bots dont head for this target also g_entities[bs->target_goal.entitynum].lastHintCheckTime = level.time + 2000; // crouch down trap_EA_Crouch( bs->client ); // look downwards bs->ideal_viewangles[PITCH] = 70; // select the dynamite bs->weaponnum = WP_DYNAMITE; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // hold fire if ( rand() % 2 && !VectorLengthSquared( bs->velocity ) && fabs( bs->viewangles[PITCH] - bs->ideal_viewangles[PITCH] ) < 2 && bs->cur_ps.weapon == WP_DYNAMITE && !bs->cur_ps.grenadeTimeLeft ) { trap_EA_Attack( bs->client ); } return qtrue; } } else if ( trav->s.teamNum >= 4 ) { // the dynamite is not armed // switch to pliers and arm the dynamite bs->weaponnum = WP_PLIERS; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // aim directly at the dynamite VectorCopy( bs->origin, target ); target[2] += bs->cur_ps.viewheight; VectorSubtract( trav->r.currentOrigin, target, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); // hold fire if ( bs->cur_ps.weapon == WP_PLIERS ) { trap_EA_Attack( bs->client ); } return qtrue; } else { // the dynamite is armed, get out of here! // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // just look for a goal BotDefaultNode( bs ); // if we are still in dynamite mode, then we may be trying to plant more dynamite at same place bs->toggleHidingTime = level.time; return qfalse; } } // if ( !( bs->flags & BFL_MISCFLAG ) ) { if ( goalDist > SQR( 128 ) ) { // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // //choose the best weapon to fight with BotChooseWeapon( bs ); // // are we close enough to the goal? if ( goalDist >= SQR( 32 ) ) { // MOVEMENT REQUIRED // //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); } // BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } } // return qtrue; } /* ================== AIEnter_MP_ConstructibleTarget() ================== */ void AIEnter_MP_ConstructibleTarget( bot_state_t *bs ) { bs->target_goal.flags &= ~GFL_NOSLOWAPPROACH; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_ConstructibleTarget; bs->ainodeText = "AINode_MP_ConstructibleTarget"; } /* ================== AINode_MP_ConstructibleTarget() ================== */ int AINode_MP_ConstructibleTarget( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *trav, *ent, *constructible; int list[10], numList, i; float goalDist; ent = &g_entities[bs->client]; trav = &g_entities[bs->target_goal.entitynum]; constructible = G_ConstructionForTeam( trav, bs->sess.sessionTeam ); goal = bs->target_goal; /* if(bot_profile.integer == 2) { // update this in case it is moving if (bs->lastLeaderMoveTime < level.time - 500) { BotGoalForEntity( bs, goal.entitynum, &goal, BGU_HIGH ); bs->lastLeaderMoveTime = level.time; } }*/ //if we have changed class if ( bs->sess.playerType != PC_ENGINEER ) { BotDefaultNode( bs ); return qfalse; } if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // is this constructible finished building? if ( !BotIsConstructible( bs->sess.sessionTeam, goal.entitynum ) ) { // finished building BotDefaultNode( bs ); return qfalse; } //if there are 2 bots going for this goal, then we should abort if we are further away if ( ( numList = BotNumTeamMatesWithTarget( bs, goal.entitynum, list, 10 ) ) > botgoalMaxCloser[BFG_CONSTRUCT] ) { goalDist = VectorDistanceSquared( bs->origin, goal.origin ); if ( goalDist > SQR( 1024 ) ) { goalDist = SQR( 1024 ); // only abort if one of our teammates is close to the goal } for ( i = 0; i < numList; i++ ) { if ( botstates[list[i]].ainode == AINode_MP_ConstructibleTarget ) { if ( VectorDistanceSquared( botstates[list[i]].origin, goal.origin ) < goalDist ) { // only abort if there is something else to do if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } } } } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // are we close enough to the goal? if ( ent->client->touchingTOI && ent->client->touchingTOI == trav ) { // if the construction has changed level, we should reset the construction flag so we wait for full power again if ( ( bs->flags & BFL_MISCFLAG ) && constructible->count2 && !constructible->s.angles2[0] ) { bs->flags &= ~BFL_MISCFLAG; } // if we haven't started yet, make sure we wait until we have reasonable power before starting if ( !( bs->flags & BFL_MISCFLAG ) ) { if ( bs->cur_ps.classWeaponTime > ( level.time - level.engineerChargeTime[bs->sess.sessionTeam - 1] / 4 ) ) { // just crouch, and look ahead trap_EA_Crouch( bs->client ); bs->ideal_viewangles[PITCH] = 0; // look for things to attack BotFindAndAttackEnemy( bs ); return qtrue; } } // we have started // if we are out of power, wait until it's full before starting again if ( !ReadyToConstruct( ent, constructible, qfalse ) ) { bs->flags &= ~BFL_MISCFLAG; return qtrue; } bs->flags |= BFL_MISCFLAG; // VOICE: cover me! BotVoiceChatAfterIdleTime( bs->client, "CoverMe", SAY_TEAM, 500 + rand() % 1000, qfalse, 10000, qfalse ); // look downwards bs->ideal_viewangles[PITCH] = 70; // select pliers (the all-in-one handyman tool) bs->weaponnum = WP_PLIERS; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // hold fire if ( bs->cur_ps.weapon == WP_PLIERS ) { trap_EA_Attack( bs->client ); } return qtrue; } //choose the best weapon to fight with BotChooseWeapon( bs ); // // MOVEMENT REQUIRED // //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed goalDist = VectorDistanceSquared( bs->origin, goal.origin ); if ( moveresult.failure || ( VectorLengthSquared( bs->cur_ps.velocity ) < SQR( 3 ) && goalDist < ( 128 * 128 ) ) ) { // allow for boxes at construction site that may get in the way //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); } BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } else if ( moveresult.flags & MOVERESULT_WAITING ) { // if waiting for something if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } // return qtrue; } /* ================== AIEnter_MP_PlantMine() ================== */ void AIEnter_MP_PlantMine( bot_state_t *bs ) { bs->target_goal.flags &= ~GFL_NOSLOWAPPROACH; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_PlantMine; bs->ainodeText = "AINode_MP_PlantMine"; } /* ================== AINode_MP_PlantMine() ================== */ int AINode_MP_PlantMine( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *trav; goal = bs->target_goal; //if we have changed class if ( bs->sess.playerType != PC_ENGINEER ) { BotDefaultNode( bs ); return qfalse; } // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // if I have no landmines, I can't do this if ( !BotGotEnoughAmmoForWeapon( bs, WP_LANDMINE ) ) { BotDefaultNode( bs ); return qfalse; } // are we close enough to the goal? if ( VectorDistanceSquared( bs->origin, goal.origin ) < SQR( 32 ) ) { // VOICE: cover me! BotVoiceChatAfterIdleTime( bs->client, "CoverMe", SAY_TEAM, 500 + rand() % 1000, qfalse, 60000, qfalse ); // crouch down trap_EA_Crouch( bs->client ); // look downwards bs->ideal_viewangles[PITCH] = 70; // have we recently dropped a land mine? trav = NULL; while ( ( trav = G_FindLandmine( trav ) ) ) { if ( !trav->parent || trav->parent->s.number != bs->client ) { continue; } if ( VectorDistanceSquared( bs->target_goal.origin, trav->r.currentOrigin ) > SQR( 100 ) ) { continue; } if ( trav->awaitingHelpTime < level.time - 5000 ) { continue; } if ( G_LandmineArmed( trav ) ) { bs->last_dangerousgoal = 0; // Gordon: force a reset if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } } // found some! bs->flags |= BFL_MISCFLAG; VectorCopy( trav->r.currentOrigin, bs->target_goal.origin ); bs->target_goal.origin[2] += 24; break; } if ( !trav ) { // select the land mine bs->weaponnum = WP_LANDMINE; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // hold fire if ( !VectorLengthSquared( bs->velocity ) && fabs( bs->viewangles[PITCH] - bs->ideal_viewangles[PITCH] ) < 2 && bs->cur_ps.weapon == bs->weaponnum && !bs->cur_ps.grenadeTimeLeft ) { trap_EA_Attack( bs->client ); } } else if ( trav->s.teamNum >= 4 ) { // the dynamite is not armed // switch to pliers and arm the dynamite bs->weaponnum = WP_PLIERS; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // aim directly at the dynamite VectorCopy( bs->origin, target ); target[2] += bs->cur_ps.viewheight; VectorSubtract( trav->r.currentOrigin, target, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); // hold fire if ( bs->cur_ps.weapon == WP_PLIERS ) { trap_EA_Attack( bs->client ); } } else { // the dynamite is armed, get out of here! BotDefaultNode( bs ); return qfalse; } return qtrue; } //choose the best weapon to fight with BotChooseWeapon( bs ); // MOVEMENT REQUIRED // //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); BotDefaultNode( bs ); return qfalse; } BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } else if ( moveresult.flags & MOVERESULT_WAITING ) { // if waiting for something if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { BotFindAndAttackEnemy( bs ); BotUpdateViewAngles( bs, &goal, moveresult ); } return qtrue; } /* ================== AIEnter_MP_DisarmDynamite() ================== */ void AIEnter_MP_DisarmDynamite( bot_state_t *bs ) { // VOICE: cover me! BotVoiceChatAfterIdleTime( bs->client, "CoverMe", SAY_TEAM, 500 + rand() % 1000, qfalse, 4000, qfalse ); bs->target_goal.flags &= ~GFL_NOSLOWAPPROACH; //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_DisarmDynamite; bs->ainodeText = "AINode_MP_DisarmDynamite"; } /* ================== AINode_MP_DisarmDynamite() ================== */ int AINode_MP_DisarmDynamite( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; gentity_t *trav; float goalDist; goal = bs->target_goal; //if we have changed class if ( bs->sess.playerType != PC_ENGINEER ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } //if there are 2 bots going for this goal, then we should abort if we are further away goalDist = VectorDistanceSquared( bs->origin, goal.origin ); /* if (numList = BotNumTeamMatesWithTarget( bs, goal.entitynum, list, 10 )) { for (i=0; iclient) continue; if (botstates[list[i]].ainode == AINode_MP_DisarmDynamite) { if (VectorDistanceSquared( botstates[list[i]].origin, goal.origin ) < goalDist) { // make sure other bots dont head for this target also bs->ignore_specialgoal_time = 0; g_entities[goal.entitynum].missionLevel = level.time + 5000; BotDefaultNode( bs ); return qfalse; } } } } */ // trav = BotGetEntity( goal.entitynum ); // FIXME: temp hack in dealing with NULL returns from BotGetEntity (??) if ( trav == NULL ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } if ( !trav->inuse || trav->s.eType != ET_MISSILE || trav->s.weapon != WP_DYNAMITE ) { // it's gone, so reset goal bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } if ( trav->s.teamNum >= 4 ) { // the dynamite is not armed // make sure other bots dont head for this target also bs->ignore_specialgoal_time = 0; g_entities[goal.entitynum].missionLevel = level.time + 5000; BotDefaultNode( bs ); return qfalse; } // make sure other bots dont head for this target also trav->missionLevel = level.time + 1000; // //map specific code BotMapScripts( bs ); // // are we close enough to the goal? if ( goalDist < SQR( 32 ) ) { // crouch down trap_EA_Crouch( bs->client ); // // switch to pliers and disarm the dynamite bs->weaponnum = WP_PLIERS; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); // aim directly at the dynamite VectorCopy( bs->origin, target ); target[2] += bs->cur_ps.viewheight; VectorSubtract( trav->r.currentOrigin, target, dir ); VectorNormalize( dir ); vectoangles( dir, bs->ideal_viewangles ); // hold fire if ( bs->cur_ps.weapon == WP_PLIERS ) { trap_EA_Attack( bs->client ); } // return qtrue; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } //choose the best weapon to fight with BotChooseWeapon( bs ); // MOVEMENT REQUIRED // //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // jump randomly? trap_EA_Jump( bs->client ); trap_EA_Move( bs->client, tv( crandom(), crandom(), crandom() ), 100 + random() * 200 ); } // BotAIBlocked( bs, &moveresult, qtrue ); //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } } // reload? if ( ( bs->last_fire != level.time ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * GetAmmoTableData( bs->cur_ps.weapon )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { trap_EA_Reload( bs->client ); } // return qtrue; } /* ================== AIEnter_MP_Battle_Fight() ================== */ void AIEnter_MP_Battle_Fight( bot_state_t *bs ) { BotClearGoal( &bs->target_goal ); trap_BotResetLastAvoidReach( bs->ms ); bs->ainode = AINode_MP_Battle_Fight; bs->ainodeText = "AINode_MP_Battle_Fight"; } /* ================== AINode_MP_Battle_Fight() ================== */ int AINode_MP_Battle_Fight( bot_state_t *bs ) { int areanum; aas_entityinfo_t entinfo; bot_moveresult_t moveresult; bot_goal_t goal; float enemydist; memset( &moveresult, 0, sizeof( moveresult ) ); if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } //if no enemy if ( bs->enemy < 0 ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // if using MOBILE MG42, conditionally go into special prone mode if ( bs->weaponnum == WP_MOBILE_MG42 ) { if ( VectorDistanceSquared( bs->origin, BotGetOrigin( bs->enemy ) ) > SQR( 700 ) ) { vec3_t dir, ang, mountedWeaponAngles; trace_t tr; vec3_t end; // make sure they are within pitch range VectorSubtract( BotGetOrigin( bs->enemy ), bs->eye, dir ); VectorNormalize( dir ); vectoangles( dir, ang ); // VectorCopy( bs->origin, end ); end[2] -= 40; trap_Trace( &tr, bs->origin, vec3_origin, vec3_origin, end, bs->client, MASK_PLAYERSOLID ); if ( tr.fraction < 1.f ) { vec3_t axis[3], forward, right; AngleVectors( bs->viewangles, forward, right, NULL ); forward[2] = 0; right[2] = 0; PM_ClipVelocity( forward, tr.plane.normal, forward, OVERCLIP ); PM_ClipVelocity( right, tr.plane.normal, right, OVERCLIP ); // VectorNormalize( forward ); VectorNormalize( right ); // VectorCopy( forward, axis[0] ); VectorCopy( right, axis[2] ); CrossProduct( axis[0], axis[2], axis[1] ); AxisToAngles( axis, mountedWeaponAngles ); // make sure these angles are within view limits towards the enemy if ( fabs( AngleDifference( mountedWeaponAngles[PITCH], ang[PITCH] ) ) < 15.f ) { int oldviewheight; // check for obstruction at feet oldviewheight = level.clients[bs->client].ps.viewheight; level.clients[bs->client].ps.viewheight = PRONE_VIEWHEIGHT; if ( BotVisibleFromPos( bs->origin, bs->client, BotGetOrigin( bs->enemy ), bs->enemy, qtrue ) ) { AIEnter_MP_Battle_MobileMG42( bs ); return qfalse; } } } } } // check for dangerous elements VectorCopy( bs->origin, goal.origin ); goal.areanum = bs->areanum; goal.entitynum = bs->client; if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } // BotEntityInfo( bs->enemy, &entinfo ); enemydist = VectorDistanceSquared( bs->origin, entinfo.origin ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->ignore_specialgoal_time = 0; bs->enemydeath_time = 0; bs->enemy = -1; BotDefaultNode( bs ); return qfalse; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } //if the enemy is invisible and not shooting the bot looses track easily if ( EntityIsInvisible( &entinfo ) && !EntityIsShooting( &entinfo ) ) { if ( random() < 0.2 ) { BotDefaultNode( bs ); return qfalse; } } //update the reachability area and origin if possible areanum = BotGetArea( bs->enemy ); //BotPointAreaNum(entinfo.number, entinfo.origin); if ( areanum ) { VectorCopy( entinfo.origin, bs->lastenemyorigin ); bs->lastenemyareanum = areanum; } //update the attack inventory values BotUpdateBattleInventory( bs, bs->enemy ); // get in real close to enemy flag carriers if ( BotCarryingFlag( bs->enemy ) && VectorDistanceSquared( bs->origin, bs->lastenemyorigin ) > SQR( 72 ) ) { if ( trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, bs->lastenemyareanum, bs->tfl ) ) { AIEnter_MP_Battle_Chase( bs ); return qfalse; } } //if the enemy is not visible, or is too far away if ( ( bs->weaponnum == WP_KNIFE ) || ( enemydist > SQR( BotWeaponRange( bs, bs->weaponnum ) ) ) || !BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { if ( BotWantsToChase( bs ) ) { AIEnter_MP_Battle_Chase( bs ); return qfalse; } else { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qtrue; // wait until next frame, incase this triggers a loop } } //use holdable items BotBattleUseItems( bs ); // if ( bot_grapple.integer ) { bs->tfl |= TFL_GRAPPLEHOOK; } //if in lava or slime the bot should be able to get out if ( BotInLava( bs ) ) { bs->tfl |= TFL_LAVA; } if ( BotInSlime( bs ) ) { bs->tfl |= TFL_SLIME; } // if ( BotCanAndWantsToRocketJump( bs ) ) { bs->tfl |= TFL_ROCKETJUMP; } //choose the best weapon to fight with BotChooseWeapon( bs ); if ( BotMoveWhileFiring( bs->weaponnum ) ) { //do attack movements moveresult = BotAttackMove( bs, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); } // BotAIBlocked( bs, &moveresult, qfalse ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( !BotCheckAttack( bs ) ) { if ( BotWantsToChase( bs ) ) { AIEnter_MP_Battle_Chase( bs ); return qfalse; // chase them immediately } else { BotDefaultNode( bs ); return qtrue; // prevent loop } } //if the bot wants to retreat if ( BotWantsToRetreat( bs ) ) { BotFindSpecialGoals( bs ); return qtrue; } return qtrue; } /* ================== AIEnter_MP_Battle_Chase() ================== */ void AIEnter_MP_Battle_Chase( bot_state_t *bs ) { bs->altenemy = -1; BotClearGoal( &bs->target_goal ); bs->chase_time = trap_AAS_Time(); bs->ainode = AINode_MP_Battle_Chase; bs->ainodeText = "AINode_MP_Battle_Chase"; } /* ================== AINode_MP_Battle_Chase() ================== */ int AINode_MP_Battle_Chase( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; aas_entityinfo_t entinfo; int oldenemy; float enemydist; memset( &moveresult, 0, sizeof( moveresult ) ); if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // RF, if we have a leader, go back to protecting them if ( ( bs->leader > -1 ) && !BotCarryingFlag( bs->enemy ) ) { AIEnter_MP_DefendTarget( bs ); return qfalse; } //if no enemy if ( bs->enemy < 0 ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( !BotCarryingFlag( bs->enemy ) && BotCheckEmergencyTargets( bs ) ) { return qfalse; } //if the enemy is visible BotEntityInfo( bs->enemy, &entinfo ); VectorCopy( entinfo.origin, bs->lastenemyorigin ); enemydist = VectorDistanceSquared( bs->origin, bs->lastenemyorigin ); if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { // update chase org bs->lastenemyareanum = BotPointAreaNum( bs->enemy, entinfo.origin ); bs->chase_time = trap_AAS_Time() + 5.0; // RF, if we are in the air, wait until we land (we may be climbing a ladder) if ( bs->cur_ps.groundEntityNum != ENTITYNUM_NONE || bs->cur_ps.velocity[2] < 0 ) { // get real close to flag carrier if ( ( bs->weaponnum != WP_KNIFE ) && ( ( !BotCarryingFlag( bs->enemy ) && ( enemydist < SQR( BotWeaponRange( bs, bs->weaponnum ) ) ) ) || ( enemydist < SQR( 64 ) ) ) ) { // make sure it is safe to attack if ( BotCheckAttack( bs ) ) { AIEnter_MP_Battle_Fight( bs ); return qfalse; } } else { // attack and keep moving towards them //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } } } // if ( !BotCarryingFlag( bs->enemy ) ) { int oldEnemy; //if there is another enemy oldEnemy = bs->enemy; if ( ( bs->weaponnum != WP_KNIFE ) && BotFindEnemyMP( bs, -1, qfalse ) && ( bs->enemy != oldEnemy ) ) { AIEnter_MP_Battle_Fight( bs ); return qfalse; } } //there is no last enemy area if ( ( bs->weaponnum != WP_KNIFE ) && !bs->lastenemyareanum ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // if ( bot_grapple.integer ) { bs->tfl |= TFL_GRAPPLEHOOK; } //if in lava or slime the bot should be able to get out if ( BotInLava( bs ) ) { bs->tfl |= TFL_LAVA; } if ( BotInSlime( bs ) ) { bs->tfl |= TFL_SLIME; } // if ( BotCanAndWantsToRocketJump( bs ) ) { bs->tfl |= TFL_ROCKETJUMP; } //map specific code BotMapScripts( bs ); //create the chase goal goal.entitynum = bs->enemy; goal.areanum = bs->lastenemyareanum; VectorCopy( bs->lastenemyorigin, goal.origin ); VectorSet( goal.mins, -8, -8, -8 ); VectorSet( goal.maxs, 8, 8, 8 ); // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } //if the last seen enemy spot is reached the enemy could not be found if ( ( bs->weaponnum != WP_KNIFE ) && trap_BotTouchingGoal( bs->origin, &goal ) ) { bs->chase_time = 0; } //if there's no chase time left if ( !bs->chase_time || bs->chase_time < trap_AAS_Time() - 10 ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // BotUpdateBattleInventory( bs, bs->enemy ); //initialize the movement state BotSetupForMovement( bs ); //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { bs->enemy = -1; //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); // try and find something else to do bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // BotAIBlocked( bs, &moveresult, qfalse ); // if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } else { // forget about our current enemy temporarily, so we can shoot at other while pursuing the flag carrier oldenemy = bs->enemy; if ( BotCarryingFlag( oldenemy ) ) { // check for enemies bs->enemy = -1; if ( bs->altenemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } else { bs->enemy = bs->altenemy; } if ( bs->enemy > -1 ) { bs->altenemy = bs->enemy; // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->altenemy = -1; } } else { bs->altenemy = -1; } } // if ( ( bs->altenemy < 0 || !BotCarryingFlag( oldenemy ) ) && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } // restore the enemy bs->enemy = oldenemy; } //if the weapon is used for the bot movement if ( moveresult.flags & MOVERESULT_MOVEMENTWEAPON ) { bs->weaponnum = moveresult.weapon; } //if the bot is in the area the enemy was last seen in if ( bs->areanum == bs->lastenemyareanum ) { bs->chase_time = 0; } //if the bot wants to retreat (the bot could have been damage during the chase) if ( BotWantsToRetreat( bs ) ) { AIEnter_MP_Battle_Retreat( bs ); return qtrue; } // reload? if ( ( bs->last_fire != level.time ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * GetAmmoTableData( bs->cur_ps.weapon )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { trap_EA_Reload( bs->client ); } // return qtrue; } /* ================== AIEnter_MP_Battle_Retreat() ================== */ void AIEnter_MP_Battle_Retreat( bot_state_t *bs ) { bs->ainode = AINode_MP_Battle_Retreat; bs->ainodeText = "AINode_MP_Battle_Retreat"; } /* ================== AINode_MP_Battle_Retreat() ================== */ int AINode_MP_Battle_Retreat( bot_state_t *bs ) { aas_entityinfo_t entinfo; bot_moveresult_t moveresult; int areanum; memset( &moveresult, 0, sizeof( moveresult ) ); if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } //if no enemy if ( bs->enemy < 0 ) { BotDefaultNode( bs ); return qfalse; } // BotEntityInfo( bs->enemy, &entinfo ); if ( EntityIsDead( &entinfo ) ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // if ( bot_grapple.integer ) { bs->tfl |= TFL_GRAPPLEHOOK; } //if in lava or slime the bot should be able to get out if ( BotInLava( bs ) ) { bs->tfl |= TFL_LAVA; } if ( BotInSlime( bs ) ) { bs->tfl |= TFL_SLIME; } //map specific code BotMapScripts( bs ); //update the attack inventory values BotUpdateBattleInventory( bs, bs->enemy ); //if the bot doesn't want to retreat anymore... probably picked up some nice items if ( BotWantsToChase( bs ) ) { //empty the goal stack, when chasing, only the enemy is the goal trap_BotEmptyGoalStack( bs->gs ); //go chase the enemy AIEnter_MP_Battle_Chase( bs ); return qfalse; } //update the last time the enemy was visible if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { bs->enemyvisible_time = trap_AAS_Time(); //update the reachability area and origin if possible areanum = BotPointAreaNum( entinfo.number, entinfo.origin ); if ( areanum && trap_AAS_AreaReachability( areanum ) ) { VectorCopy( entinfo.origin, bs->lastenemyorigin ); bs->lastenemyareanum = areanum; } } //if the enemy is NOT visible for 4 seconds if ( bs->enemyvisible_time < trap_AAS_Time() - 4 ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } //else if the enemy is NOT visible else if ( bs->enemyvisible_time < trap_AAS_Time() ) { //if there is another enemy if ( BotFindEnemyMP( bs, -1, qfalse ) ) { AIEnter_MP_Battle_Fight( bs ); return qfalse; } } // //use holdable items BotBattleUseItems( bs ); // // !!! RF, we should try to move somewhere safe here // //choose the best weapon to fight with BotChooseWeapon( bs ); //if the view is fixed for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEW //|MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } else if ( !( moveresult.flags & MOVERESULT_MOVEMENTVIEWSET ) && !( bs->flags & BFL_IDEALVIEWSET ) ) { BotAimAtEnemy( bs ); } //if the weapon is used for the bot movement if ( moveresult.flags & MOVERESULT_MOVEMENTWEAPON ) { bs->weaponnum = moveresult.weapon; } //attack the enemy if possible BotCheckAttack( bs ); // return qtrue; } /* ================== AIEnter_MP_Script_MoveToMarker() ================== */ void AIEnter_MP_Script_MoveToMarker( bot_state_t *bs ) { //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_Script_MoveToMarker; bs->ainodeText = "AINode_MP_Script_MoveToMarker"; } /* ================== AINode_MP_Script_MoveToMarker() ================== */ int AINode_MP_Script_MoveToMarker( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; g_serverEntity_t *marker; // goal = bs->target_goal; // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( !BotIsPOW( bs ) ) { if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } } // check for dangerous elements if ( !BotIsPOW( bs ) ) { if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } } /* // check for special class actions if (BotCheckClassActions(bs)) { return qfalse; } */ // look for health/ammo packs if ( !BotIsPOW( bs ) ) { if ( BotFindNearbyGoal( bs ) ) { AIEnter_MP_Seek_NBG( bs ); return qfalse; } } if ( !BotIsPOW( bs ) ) { if ( BotFindSpecialGoals( bs ) ) { return qfalse; } } // // should we stop pursuing this target? marker = GetServerEntity( bs->target_goal.entitynum ); if ( !marker || !marker->inuse ) { bs->ignore_specialgoal_time = 0; BotDefaultNode( bs ); return qfalse; } // VectorCopy( goal.origin, target ); if ( fabs( target[2] - bs->origin[2] ) < 80 ) { target[2] = bs->origin[2]; } // //choose the best weapon to fight with BotChooseWeapon( bs ); // //map specific code BotMapScripts( bs ); //initialize the movement state BotSetupForMovement( bs ); // memset( &moveresult, 0, sizeof( moveresult ) ); // if ( VectorDistanceSquared( bs->origin, target ) >= SQR( 12 ) ) { //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); if ( !( bs->flags & BFL_MISCFLAG ) ) { Bot_ScriptLog_Entry( bs, qfalse, NULL, "movement failure" ); bs->flags |= BFL_MISCFLAG; } return qtrue; } else { if ( bs->flags & BFL_MISCFLAG ) { Bot_ScriptLog_Entry( bs, qfalse, NULL, "resumed movement" ); bs->flags &= ~BFL_MISCFLAG; } } // set the movement type if ( bs->script.moveType == BSMT_WALKING ) { trap_EA_Walk( bs->client ); } else if ( bs->script.moveType == BSMT_CROUCHING ) { trap_EA_Crouch( bs->client ); } // BotAIBlocked( bs, &moveresult, qtrue ); } else { // Gordon: TEMP: i doubt this should be here if ( BotIsPOW( bs ) ) { BotDefaultNode( bs ); return qfalse; } } //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; // check for giving ourselves some ammo if ( bs->sess.playerType == PC_FIELDOPS && ClientNeedsAmmo( bs->client ) ) { // switch to regen and pump away bs->weaponnum = WP_AMMO; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); if ( bs->cur_ps.weapon == WP_AMMO && BotWeaponCharged( bs, WP_AMMO ) ) { trap_EA_Attack( bs->client ); } } // check for giving ourselves some health if ( bs->sess.playerType == PC_MEDIC && BotHealthScale( bs->client ) < 1.0 ) { // switch to regen and pump away bs->weaponnum = WP_MEDKIT; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); if ( bs->cur_ps.weapon == WP_MEDKIT && BotWeaponCharged( bs, WP_MEDKIT ) ) { trap_EA_Attack( bs->client ); } } } } // reload? if ( ( bs->last_fire != level.time ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * GetAmmoTableData( bs->cur_ps.weapon )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { trap_EA_Reload( bs->client ); } // return qtrue; } /* ================== AIEnter_MP_MoveToAutonomyRange() ================== */ void AIEnter_MP_MoveToAutonomyRange( bot_state_t *bs ) { vec3_t pos; // if ( !BotGetMovementAutonomyPos( bs, pos ) ) { if ( g_developer.integer ) { G_Printf( "AIEnter_MP_MoveToAutonomyRange: autonomy pos unknown\n" ); } } // make the current goal our autonomy spot BotClearGoal( &bs->target_goal ); VectorCopy( pos, bs->target_goal.origin ); bs->target_goal.areanum = BotPointAreaNum( bs->client, pos ); //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->ainode = AINode_MP_MoveToAutonomyRange; bs->ainodeText = "AINode_MP_MoveToAutonomyRange"; } /* ================== AINode_MP_MoveToAutonomyRange() ================== */ int AINode_MP_MoveToAutonomyRange( bot_state_t *bs ) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; // goal = bs->target_goal; // if ( BotIsObserver( bs ) ) { AIEnter_MP_Observer( bs ); return qfalse; } //if in the intermission if ( BotIntermission( bs ) ) { AIEnter_MP_Intermission( bs ); return qfalse; } //respawn if dead if ( BotIsDead( bs ) ) { AIEnter_MP_Respawn( bs ); return qfalse; } // check for emergency targets (flags, etc) if ( BotCheckEmergencyTargets( bs ) ) { return qfalse; } // check for dangerous elements if ( BotDangerousGoal( bs, &goal ) ) { AIEnter_MP_AvoidDanger( bs ); // avoid this danger until it passes return qfalse; } /* // check for special class actions if (BotCheckClassActions(bs)) { return qfalse; } */ // look for health/ammo packs //if (BotFindNearbyGoal( bs )) { // AIEnter_MP_Seek_NBG( bs ); // return qfalse; //} //if (BotFindSpecialGoals(bs)) { // return qfalse; //} // // should we stop pursuing this target? //ent = BotGetEntity( bs->target_goal.entitynum ); //if (!ent->inuse) { // bs->ignore_specialgoal_time = 0; // BotDefaultNode(bs); // return qfalse; //} // VectorCopy( goal.origin, target ); if ( fabs( target[2] - bs->origin[2] ) < 80 ) { target[2] = bs->origin[2]; } // //choose the best weapon to fight with BotChooseWeapon( bs ); // //map specific code BotMapScripts( bs ); //initialize the movement state BotSetupForMovement( bs ); // memset( &moveresult, 0, sizeof( moveresult ) ); // { float halfRange = ( 0.5 * BotGetMovementAutonomyRange( bs, NULL ) ); if ( VectorDistanceSquared( bs->origin, target ) <= SQR( halfRange ) ) { // go back to standing AIEnter_MP_Stand( bs ); return qfalse; } } // //try a direct movement if ( !BotDirectMoveToGoal( bs, &goal, &moveresult ) ) { //move towards the goal BotMP_MoveToGoal( bs, &moveresult, bs->ms, &goal, bs->tfl ); } //if the movement failed if ( moveresult.failure ) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach( bs->ms ); if ( !( bs->flags & BFL_MISCFLAG ) ) { Bot_ScriptLog_Entry( bs, qfalse, NULL, "movement failure" ); bs->flags |= BFL_MISCFLAG; } return qtrue; } else { if ( bs->flags & BFL_MISCFLAG ) { Bot_ScriptLog_Entry( bs, qfalse, NULL, "resumed movement" ); bs->flags &= ~BFL_MISCFLAG; } } // set the movement type if ( bs->script.moveType == BSMT_WALKING ) { trap_EA_Walk( bs->client ); } else if ( bs->script.moveType == BSMT_CROUCHING ) { trap_EA_Crouch( bs->client ); } // BotAIBlocked( bs, &moveresult, qtrue ); // //if the viewangles are used for the movement if ( moveresult.flags & ( MOVERESULT_MOVEMENTVIEWSET | MOVERESULT_MOVEMENTVIEW | MOVERESULT_SWIMVIEW ) ) { VectorCopy( moveresult.ideal_viewangles, bs->ideal_viewangles ); } //if waiting for something else if ( moveresult.flags & MOVERESULT_WAITING ) { if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } } else { // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // if ( bs->enemy < 0 && !( bs->flags & BFL_IDEALVIEWSET ) ) { if ( moveresult.flags & MOVERESULT_DIRECTMOVE ) { VectorSubtract( goal.origin, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } else if ( trap_BotMovementViewTarget( bs->ms, &goal, bs->tfl, 300, target ) ) { VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); } //FIXME: look at cluster portals? else if ( VectorLengthSquared( moveresult.movedir ) ) { vectoangles( moveresult.movedir, bs->ideal_viewangles ); } else if ( random() < bs->thinktime * 0.8 ) { BotRoamGoal( bs, target ); VectorSubtract( target, bs->origin, dir ); vectoangles( dir, bs->ideal_viewangles ); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; // check for giving ourselves some ammo if ( bs->sess.playerType == PC_FIELDOPS && ClientNeedsAmmo( bs->client ) ) { // switch to regen and pump away bs->weaponnum = WP_AMMO; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); if ( bs->cur_ps.weapon == WP_AMMO && BotWeaponCharged( bs, WP_AMMO ) ) { trap_EA_Attack( bs->client ); } } // check for giving ourselves some health if ( bs->sess.playerType == PC_MEDIC && BotHealthScale( bs->client ) < 1.0 ) { // switch to regen and pump away bs->weaponnum = WP_MEDKIT; trap_EA_SelectWeapon( bs->client, bs->weaponnum ); if ( bs->cur_ps.weapon == WP_MEDKIT && BotWeaponCharged( bs, WP_MEDKIT ) ) { trap_EA_Attack( bs->client ); } } } } // reload? if ( ( bs->last_fire != level.time ) && ( bs->cur_ps.ammoclip[BG_FindClipForWeapon( bs->cur_ps.weapon )] < (int)( 0.8 * GetAmmoTableData( bs->cur_ps.weapon )->maxclip ) ) && bs->cur_ps.ammo[BG_FindAmmoForWeapon( bs->cur_ps.weapon )] ) { trap_EA_Reload( bs->client ); } // return qtrue; } /* ================== AIEnter_MP_NavigateFromVoid() ================== */ void AIEnter_MP_NavigateFromVoid( bot_state_t *bs ) { //choose the best weapon to fight with BotChooseWeapon( bs ); bs->flags &= ~BFL_MISCFLAG; bs->last_SparseDefense = 0; bs->ainode = AINode_MP_NavigateFromVoid; bs->ainodeText = "AINode_MP_NavigateFromVoid"; } /* ================== AINode_MP_NavigateFromVoid() ================== */ int AINode_MP_NavigateFromVoid( bot_state_t *bs ) { const int checkFrames = 40; const float checkFrametime = 0.05; const int randomChecksPerSlice = 5; int i; vec3_t dir, angles; aas_clientmove_t move; qboolean success; float bestDist; vec3_t bestDir; // are we out of the void? if ( bs->areanum ) { BotDefaultNode( bs ); return qfalse; } // try to navigate out of the void // if we have a current direction, validate it if ( bs->flags & BFL_MISCFLAG ) { // we are currently heading in a direction, verify to make sure it's still valid if ( bs->last_attackShout < level.time - 250 ) { VectorCopy( bs->lastLeaderPosition, dir ); success = qfalse; if ( trap_AAS_PredictClientMovement( &move, bs->client, bs->origin, -1, qfalse, dir, vec3_origin, checkFrames, 0, checkFrametime, SE_HITGROUNDDAMAGE | SE_HITGROUNDAREA | SE_STUCK | SE_GAP, -1, qfalse ) ) { switch ( move.stopevent ) { case SE_HITGROUNDAREA: // success! success = qtrue; // keep it valid bs->last_SparseDefense = level.time + 500; break; } } // if ( !success ) { bs->flags &= ~BFL_MISCFLAG; } } } // do we need a new direction? if ( !( bs->flags & BFL_MISCFLAG ) ) { bestDist = -1.0; for ( i = 0; i < randomChecksPerSlice; i++ ) { // choose a random direction VectorClear( angles ); angles[YAW] = rand() % 360; AngleVectors( angles, dir, NULL, NULL ); // how far will this direction get us? success = qfalse; if ( trap_AAS_PredictClientMovement( &move, bs->client, bs->origin, -1, qfalse, dir, vec3_origin, checkFrames, 0, checkFrametime, SE_HITGROUNDDAMAGE | SE_HITGROUNDAREA | SE_STUCK | SE_GAP, -1, qfalse ) ) { switch ( move.stopevent ) { case SE_HITGROUNDAREA: // success! success = qtrue; break; } } // if ( success ) { bs->flags |= BFL_MISCFLAG; bs->last_attackShout = level.time; bs->last_SparseDefense = level.time + 200; VectorCopy( dir, bs->lastLeaderPosition ); break; } else { switch ( move.stopevent ) { case SE_HITGROUNDDAMAGE: case SE_GAP: // bad direction break; default: // Gordon: OPT: bleh.... if ( bestDist < 0 || VectorDistanceSquared( bs->origin, move.endpos ) < SQR( bestDist ) ) { bestDist = VectorDistance( bs->origin, move.endpos ); VectorCopy( dir, bestDir ); } } } } if ( !( bs->flags & BFL_MISCFLAG ) && ( bestDist > 0.0 ) ) { bs->last_SparseDefense = level.time + 200; VectorCopy( bestDir, bs->lastLeaderPosition ); } } // check for enemies if ( bs->enemy < 0 ) { BotFindEnemyMP( bs, -1, qfalse ); } if ( bs->enemy >= 0 ) { aas_entityinfo_t entinfo; BotEntityInfo( bs->enemy, &entinfo ); //if the enemy is dead if ( bs->enemydeath_time ) { if ( bs->enemydeath_time < trap_AAS_Time() - 0.3 ) { bs->enemydeath_time = 0; bs->enemy = -1; } } else { if ( EntityIsDead( &entinfo ) ) { bs->enemydeath_time = trap_AAS_Time(); } } // if ( bs->enemy >= 0 ) { // attack and keep moving if ( BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy, NULL ) ) { //choose the best weapon to fight with BotChooseWeapon( bs ); //aim at the enemy BotAimAtEnemy( bs ); //attack the enemy if possible if ( bs->weaponnum == bs->cur_ps.weapon ) { BotCheckAttack( bs ); } } else { bs->enemy = -1; } } } // do we have a current direction? if ( bs->last_SparseDefense > level.time ) { // continue in that direction trap_EA_Move( bs->client, bs->lastLeaderPosition, 400 ); if ( bs->enemy < 0 ) { vectoangles( bs->lastLeaderPosition, angles ); trap_EA_View( bs->client, angles ); } } return qtrue; }