/*
===========================================================================
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;
}