From 31a3805769de9e99b341ed84b97e605499002cdb Mon Sep 17 00:00:00 2001 From: Justin Marshall Date: Wed, 20 May 2026 09:10:15 -0700 Subject: [PATCH] Bot fixes. --- neo/doom3/game/bots/Bot.cpp | 26 +- neo/doom3/game/bots/Bot.h | 14 +- neo/doom3/game/bots/BotAI.cpp | 678 ++++++++++++--------- neo/doom3/game/bots/BotAI_Battle_Fight.cpp | 44 +- neo/doom3/game/bots/Bot_char.cpp | 3 +- neo/doom3/game/bots/Bot_goal.cpp | 215 +++---- neo/doom3/game/bots/Bot_goal.h | 4 +- neo/doom3/game/gamesys/GameTypeInfo.h | 4 +- neo/engine/framework/async/AsyncServer.cpp | 4 + 9 files changed, 571 insertions(+), 421 deletions(-) diff --git a/neo/doom3/game/bots/Bot.cpp b/neo/doom3/game/bots/Bot.cpp index 8a59659f..1a164236 100644 --- a/neo/doom3/game/bots/Bot.cpp +++ b/neo/doom3/game/bots/Bot.cpp @@ -9,7 +9,7 @@ idCVar bot_goaldist( "bot_goaldist", "20", CVAR_INTEGER | CVAR_CHEAT, "" ); idCVar bot_debugnav( "bot_debugnav", "0", CVAR_BOOL | CVAR_CHEAT, "draws navmesh paths for the bot" ); idCVar bot_showstate( "bot_showstate", "0", CVAR_BOOL | CVAR_CHEAT, "draws the bot state above the bot" ); idCVar bot_debug( "bot_debug", "0", CVAR_BOOL, "shows debug info for the bot" ); -idCVar bot_skill("bot_skill", "3", CVAR_INTEGER, ""); +idCVar bot_skill("bot_skill", "4", CVAR_INTEGER, ""); static int BotSafeAmmoCount( const idInventory& inv, const char* ammoName ) { @@ -161,7 +161,16 @@ void rvmBot::Spawn( void ) } // Load in the bot character. - bs.character = botCharacterStatsManager.BotLoadCharacterFromFile( va( "bots/%s_c.c", botName.c_str() ), 1 ); + const int skill = idMath::ClampInt( 1, 5, bot_skill.GetInteger() ); + bs.character = botCharacterStatsManager.BotLoadCharacterFromFile( va( "bots/%s_c.c", botName.c_str() ), skill ); + if( bs.character == NULL && skill != 4 ) + { + bs.character = botCharacterStatsManager.BotLoadCharacterFromFile( va( "bots/%s_c.c", botName.c_str() ), 4 ); + } + if( bs.character == NULL && skill != 1 ) + { + bs.character = botCharacterStatsManager.BotLoadCharacterFromFile( va( "bots/%s_c.c", botName.c_str() ), 1 ); + } if( !bs.character ) { gameLocal.Error( "Failed to load character file for bot %s\n", botName.c_str() ); @@ -197,10 +206,12 @@ void rvmBot::Spawn( void ) bs.entitynum = entityNumber; bs.setupcount = 4; bs.entergame_time = Bot_Time(); + bs.weaponnum = spawnArgs.GetInt( "current_weapon", "1" ); hasSpawned = true; bs.botinput.respawn = true; + bs.botinput.weapon = bs.weaponnum; bs.botinput.lastWeaponNum = -1; stateThread.SetState("state_Respawn"); @@ -316,6 +327,15 @@ void rvmBot::ServerThink( void ) return; } + if( bs.enemy >= 0 && bs.botinput.speed > 0.0f ) + { + bs.viewangles = bs.botinput.viewangles; + bs.useRandomPosition = false; + bs.attackerEntity = NULL; + bs.botinput.weapon = bs.weaponnum; + return; + } + idVec3 goalOrigin = bs.useRandomPosition ? bs.random_move_position : bs.currentGoal.origin; idVec3 moveGoal = goalOrigin; bool haveMoveGoal = false; @@ -508,4 +528,4 @@ void rvmBot::Think( void ) deltaViewAngles.Zero(); idPlayer::Think(); -} \ No newline at end of file +} diff --git a/neo/doom3/game/bots/Bot.h b/neo/doom3/game/bots/Bot.h index a2870417..99edb072 100644 --- a/neo/doom3/game/bots/Bot.h +++ b/neo/doom3/game/bots/Bot.h @@ -132,7 +132,7 @@ class rvmBotAIBotActionBase; #define ENEMY_HEIGHT 201 #define MAX_AVOIDGOALS 256 -#define MAX_GOALSTACK 8 +#define MAX_GOALSTACK 16 #define GFL_NONE 0 #define GFL_ITEM 1 @@ -347,7 +347,7 @@ struct weightconfig_t #define DEFAULT_CHARACTER "bots/default_c.c" #define MAX_AVOIDGOALS 256 -#define MAX_GOALSTACK 8 +#define MAX_GOALSTACK 16 #define GFL_NONE 0 #define GFL_ITEM 1 @@ -747,11 +747,11 @@ struct bot_state_t entitynum = 0; setupcount = 0; entergame_time = 0; - weaponnum = 0; + weaponnum = -1; lasthealth = 0; ltg_time = 0; weaponchange_time = 0; - enemy = 0; + enemy = -1; enemyvisible_time = 0; enemysuicide = 0; enemysight_time = 0; @@ -872,6 +872,10 @@ private: bool HasWeapon( int index ) { + if( index < 0 || index >= MAX_WEAPONS ) + { + return false; + } return inventory.weapons & ( 1 << index ); } private: @@ -923,4 +927,4 @@ private: idAAS* aas; }; -extern idCVar bot_skill; \ No newline at end of file +extern idCVar bot_skill; diff --git a/neo/doom3/game/bots/BotAI.cpp b/neo/doom3/game/bots/BotAI.cpp index ea9a5cc6..42ad45c2 100644 --- a/neo/doom3/game/bots/BotAI.cpp +++ b/neo/doom3/game/bots/BotAI.cpp @@ -15,72 +15,72 @@ int rvmBot::WP_ROCKET_LAUNCHER = -1; -static bool BotAIValidEntityNum( int entityNum ) { +static bool BotAIValidEntityNum(int entityNum) { return entityNum >= 0 && entityNum < gameLocal.num_entities && gameLocal.entities[entityNum] != NULL; } -static float BotAIClampFloat( float minValue, float maxValue, float value ) { - if( value < minValue ) { +static float BotAIClampFloat(float minValue, float maxValue, float value) { + if (value < minValue) { return minValue; } - if( value > maxValue ) { + if (value > maxValue) { return maxValue; } return value; } -static idVec3 BotAIEntityCenter( idEntity* ent ) { - if( ent == NULL || ent->GetPhysics() == NULL ) { - return idVec3( 0.0f, 0.0f, 0.0f ); +static idVec3 BotAIEntityCenter(idEntity* ent) { + if (ent == NULL || ent->GetPhysics() == NULL) { + return idVec3(0.0f, 0.0f, 0.0f); } return ent->GetPhysics()->GetOrigin() + ent->GetPhysics()->GetBounds().GetCenter(); } -static bool BotAIPointWithinFOV( const idVec3& eye, const idAngles& viewangles, const idVec3& point, float fov ) { - if( fov >= 359.0f ) { +static bool BotAIPointWithinFOV(const idVec3& eye, const idAngles& viewangles, const idVec3& point, float fov) { + if (fov >= 359.0f) { return true; } idVec3 dir = point - eye; - if( dir.LengthSqr() < Square( 1.0f ) ) { + if (dir.LengthSqr() < Square(1.0f)) { return true; } idAngles targetAngles = dir.ToAngles(); const float halfFov = fov * 0.5f; - const float yawDelta = idMath::Fabs( idMath::AngleDelta( targetAngles[YAW], viewangles[YAW] ) ); - const float pitchDelta = idMath::Fabs( idMath::AngleDelta( targetAngles[PITCH], viewangles[PITCH] ) ); + const float yawDelta = idMath::Fabs(idMath::AngleDelta(targetAngles[YAW], viewangles[YAW])); + const float pitchDelta = idMath::Fabs(idMath::AngleDelta(targetAngles[PITCH], viewangles[PITCH])); return yawDelta <= halfFov && pitchDelta <= halfFov; } -static bool BotAITraceCanHitEntity( int passEntityNum, int targetEntityNum, const idVec3& start, const idVec3& end, int mask ) { +static bool BotAITraceCanHitEntity(int passEntityNum, int targetEntityNum, const idVec3& start, const idVec3& end, int mask) { trace_t trace; - gameLocal.Trace( trace, start, end, mask, passEntityNum ); + gameLocal.Trace(trace, start, end, mask, passEntityNum); return trace.fraction >= 1.0f || trace.c.entityNum == targetEntityNum; } -static bool BotAIHasUsableFightWeapon( const int* inventory ) { - if( inventory == NULL ) { +static bool BotAIHasUsableFightWeapon(const int* inventory) { + if (inventory == NULL) { return false; } - return ( inventory[INVENTORY_ROCKETLAUNCHER] > 0 && inventory[INVENTORY_ROCKETS] > 0 ) || - ( inventory[INVENTORY_PLASMAGUN] > 0 && inventory[INVENTORY_CELLS] > 0 ) || - ( inventory[INVENTORY_SHOTGUN] > 0 && inventory[INVENTORY_SHELLS] > 0 ) || - ( inventory[INVENTORY_MACHINEGUN] > 0 && inventory[INVENTORY_BULLETS] > 0 ); + return (inventory[INVENTORY_ROCKETLAUNCHER] > 0 && inventory[INVENTORY_ROCKETS] > 0) || + (inventory[INVENTORY_PLASMAGUN] > 0 && inventory[INVENTORY_CELLS] > 0) || + (inventory[INVENTORY_SHOTGUN] > 0 && inventory[INVENTORY_SHELLS] > 0) || + (inventory[INVENTORY_MACHINEGUN] > 0 && inventory[INVENTORY_BULLETS] > 0); } -static bool BotAIHasPowerFightWeapon( const int* inventory ) { - if( inventory == NULL ) { +static bool BotAIHasPowerFightWeapon(const int* inventory) { + if (inventory == NULL) { return false; } - return ( inventory[INVENTORY_ROCKETLAUNCHER] > 0 && inventory[INVENTORY_ROCKETS] > 0 ) || - ( inventory[INVENTORY_PLASMAGUN] > 0 && inventory[INVENTORY_CELLS] > 0 ) || - ( inventory[INVENTORY_SHOTGUN] > 0 && inventory[INVENTORY_SHELLS] > 0 ); + return (inventory[INVENTORY_ROCKETLAUNCHER] > 0 && inventory[INVENTORY_ROCKETS] > 0) || + (inventory[INVENTORY_PLASMAGUN] > 0 && inventory[INVENTORY_CELLS] > 0) || + (inventory[INVENTORY_SHOTGUN] > 0 && inventory[INVENTORY_SHELLS] > 0); } -static bool BotAIHasOnlyPistolOrDryWeapons( const bot_state_t* bs ) { - if( bs == NULL ) { +static bool BotAIHasOnlyPistolOrDryWeapons(const bot_state_t* bs) { + if (bs == NULL) { return true; } const int* inventory = bs->inventory; @@ -88,10 +88,10 @@ static bool BotAIHasOnlyPistolOrDryWeapons( const bot_state_t* bs ) { inventory[INVENTORY_SHOTGUN] > 0 || inventory[INVENTORY_PLASMAGUN] > 0 || inventory[INVENTORY_ROCKETLAUNCHER] > 0; - if( !ownsNonPistolWeapon ) { + if (!ownsNonPistolWeapon) { return true; } - if( !BotAIHasUsableFightWeapon( inventory ) ) { + if (!BotAIHasUsableFightWeapon(inventory)) { return true; } @@ -102,44 +102,44 @@ static bool BotAIHasOnlyPistolOrDryWeapons( const bot_state_t* bs ) { return onlyMachinegun && inventory[INVENTORY_BULLETS] < 25; } -static bool BotAINeedsTacticalPickup( const bot_state_t* bs ) { - if( bs == NULL ) { +static bool BotAINeedsTacticalPickup(const bot_state_t* bs) { + if (bs == NULL) { return false; } - if( BotAIHasOnlyPistolOrDryWeapons( bs ) ) { + if (BotAIHasOnlyPistolOrDryWeapons(bs)) { return true; } - if( bs->inventory[INVENTORY_HEALTH] < 55 ) { + if (bs->inventory[INVENTORY_HEALTH] < 55) { return true; } - if( bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] <= 2 ) { + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] <= 2) { return true; } - if( bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] <= 12 ) { + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] <= 12) { return true; } - if( bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] <= 4 ) { + if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] <= 4) { return true; } return false; } -static float BotAITacticalPickupRange( const bot_state_t* bs, float requestedRange ) { +static float BotAITacticalPickupRange(const bot_state_t* bs, float requestedRange) { float range = requestedRange; - if( bs == NULL || bs->enemy < 0 ) { + if (bs == NULL || bs->enemy < 0) { return range; } - if( BotAINeedsTacticalPickup( bs ) ) { - if( range < 650.0f ) { + if (BotAINeedsTacticalPickup(bs)) { + if (range < 650.0f) { range = 650.0f; } } - else if( !BotAIHasPowerFightWeapon( bs->inventory ) ) { - if( range < 400.0f ) { + else if (!BotAIHasPowerFightWeapon(bs->inventory)) { + if (range < 400.0f) { range = 400.0f; } } - else if( range < 260.0f ) { + else if (range < 260.0f) { range = 260.0f; } return range; @@ -150,14 +150,14 @@ static float BotAITacticalPickupRange( const bot_state_t* bs, float requestedRan rvmBot::BotIsDead ========================= */ -bool rvmBot::BotIsDead( bot_state_t* bs ) +bool rvmBot::BotIsDead(bot_state_t* bs) { - if( bs == NULL || !BotAIValidEntityNum( bs->client ) ) + if (bs == NULL || !BotAIValidEntityNum(bs->client)) { return true; } - idPlayer* player = gameLocal.GetClientByNum( bs->client ); + idPlayer* player = gameLocal.GetClientByNum(bs->client); return player == NULL || player->health <= 0; } @@ -166,21 +166,21 @@ bool rvmBot::BotIsDead( bot_state_t* bs ) rvmBot::BotReachedGoal ================== */ -bool rvmBot::BotReachedGoal( bot_state_t* bs, bot_goal_t* goal ) +bool rvmBot::BotReachedGoal(bot_state_t* bs, bot_goal_t* goal) { - if( goal->flags & GFL_ITEM ) + if (goal->flags & GFL_ITEM) { //if touching the goal - if( botGoalManager.BotTouchingGoal( bs->origin, goal ) ) + if (botGoalManager.BotTouchingGoal(bs->origin, goal)) { - if( !( goal->flags & GFL_DROPPED ) ) + if (!(goal->flags & GFL_DROPPED)) { - botGoalManager.BotSetAvoidGoalTime( bs->gs, goal->number, -1 ); + botGoalManager.BotSetAvoidGoalTime(bs->gs, goal->number, -1); } return true; } //if the goal isn't there - if( botGoalManager.BotItemGoalInVisButNotVisible( bs->entitynum, bs->eye, bs->viewangles, goal ) ) + if (botGoalManager.BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { return true; } @@ -188,7 +188,7 @@ bool rvmBot::BotReachedGoal( bot_state_t* bs, bot_goal_t* goal ) else { //if touching the goal - if( botGoalManager.BotTouchingGoal( bs->origin, goal ) ) + if (botGoalManager.BotTouchingGoal(bs->origin, goal)) { return true; } @@ -201,9 +201,9 @@ bool rvmBot::BotReachedGoal( bot_state_t* bs, bot_goal_t* goal ) rvmBot::BotChooseWeapon ================== */ -void rvmBot::BotChooseWeapon( bot_state_t* bs ) +void rvmBot::BotChooseWeapon(bot_state_t* bs) { - if( bs == NULL ) + if (bs == NULL) { return; } @@ -219,46 +219,46 @@ void rvmBot::BotChooseWeapon( bot_state_t* bs ) // choose from those real slots here instead of trusting the Quake-style // weapon config numbers blindly. int practicalWeaponNum = -1; - const int enemyDist = ( bs->enemy >= 0 ) ? bs->inventory[ENEMY_HORIZONTAL_DIST] : 0; + const int enemyDist = (bs->enemy >= 0) ? bs->inventory[ENEMY_HORIZONTAL_DIST] : 0; - if( bs->enemy >= 0 && enemyDist > 0 ) + if (bs->enemy >= 0 && enemyDist > 0) { - if( enemyDist < 160 ) + if (enemyDist < 160) { // Very close: avoid splash damage unless there is no safer weapon. - if( haveShotgun ) + if (haveShotgun) { practicalWeaponNum = WP_SHOTGUN; } - else if( havePlasmaGun ) + else if (havePlasmaGun) { practicalWeaponNum = WP_PLASMAGUN; } - else if( haveMachinegun ) + else if (haveMachinegun) { practicalWeaponNum = WP_MACHINEGUN; } - else if( haveRocketLauncher ) + else if (haveRocketLauncher) { practicalWeaponNum = WP_ROCKET_LAUNCHER; } } - else if( enemyDist < 700 ) + else if (enemyDist < 700) { // Mid range: rockets are the highest value deathmatch weapon. - if( haveRocketLauncher ) + if (haveRocketLauncher) { practicalWeaponNum = WP_ROCKET_LAUNCHER; } - else if( havePlasmaGun ) + else if (havePlasmaGun) { practicalWeaponNum = WP_PLASMAGUN; } - else if( haveShotgun ) + else if (haveShotgun) { practicalWeaponNum = WP_SHOTGUN; } - else if( haveMachinegun ) + else if (haveMachinegun) { practicalWeaponNum = WP_MACHINEGUN; } @@ -267,19 +267,19 @@ void rvmBot::BotChooseWeapon( bot_state_t* bs ) { // Long range: plasma is easier to land consistently; use rockets if // plasma is unavailable. - if( havePlasmaGun ) + if (havePlasmaGun) { practicalWeaponNum = WP_PLASMAGUN; } - else if( haveRocketLauncher ) + else if (haveRocketLauncher) { practicalWeaponNum = WP_ROCKET_LAUNCHER; } - else if( haveMachinegun ) + else if (haveMachinegun) { practicalWeaponNum = WP_MACHINEGUN; } - else if( haveShotgun ) + else if (haveShotgun) { practicalWeaponNum = WP_SHOTGUN; } @@ -289,35 +289,50 @@ void rvmBot::BotChooseWeapon( bot_state_t* bs ) { // No active enemy or no distance sample yet: keep the bot on the best // generally useful weapon so it is ready when a fight starts. - if( haveRocketLauncher ) + if (haveRocketLauncher) { practicalWeaponNum = WP_ROCKET_LAUNCHER; } - else if( havePlasmaGun ) + else if (havePlasmaGun) { practicalWeaponNum = WP_PLASMAGUN; } - else if( haveShotgun ) + else if (haveShotgun) { practicalWeaponNum = WP_SHOTGUN; } - else if( haveMachinegun ) + else if (haveMachinegun) { practicalWeaponNum = WP_MACHINEGUN; } } int newweaponnum = practicalWeaponNum; - if( newweaponnum <= 0 ) + if (newweaponnum < 0) { - newweaponnum = botWeaponInfoManager.BotChooseBestFightWeapon( bs->ws, bs->inventory ); + // BotChooseBestFightWeapon returns bot weapon-config numbers. Only use + // the fuzzy result if it already matches one of the engine runtime slots; + // otherwise keep the current weapon instead of sending a bogus impulse. + const int fuzzyWeaponNum = botWeaponInfoManager.BotChooseBestFightWeapon(bs->ws, bs->inventory); + if (fuzzyWeaponNum == WP_MACHINEGUN || fuzzyWeaponNum == WP_SHOTGUN || + fuzzyWeaponNum == WP_PLASMAGUN || fuzzyWeaponNum == WP_ROCKET_LAUNCHER) + { + newweaponnum = fuzzyWeaponNum; + } } - if( newweaponnum <= 0 ) + if (newweaponnum < 0) { - newweaponnum = bs->weaponnum > 0 ? bs->weaponnum : 0; + if (bs->weaponnum >= 0) + { + newweaponnum = bs->weaponnum; + } + else + { + newweaponnum = spawnArgs.GetInt("current_weapon", "1"); + } } - if( bs->weaponnum != newweaponnum ) + if (bs->weaponnum != newweaponnum) { bs->weaponchange_time = Bot_Time(); } @@ -331,18 +346,18 @@ void rvmBot::BotChooseWeapon( bot_state_t* bs ) rvmBot::BotGetItemLongTermGoal ================== */ -int rvmBot::BotGetItemLongTermGoal( bot_state_t* bs, int tfl, bot_goal_t* goal ) +int rvmBot::BotGetItemLongTermGoal(bot_state_t* bs, int tfl, bot_goal_t* goal) { //if the bot has no goal - if( !botGoalManager.BotGetTopGoal( bs->gs, goal ) ) + if (!botGoalManager.BotGetTopGoal(bs->gs, goal)) { //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n"); bs->ltg_time = 0; } //if the bot touches the current goal - else if( BotReachedGoal( bs, goal ) ) + else if (BotReachedGoal(bs, goal)) { - BotChooseWeapon( bs ); + BotChooseWeapon(bs); bs->ltg_time = 0; } @@ -366,20 +381,20 @@ int rvmBot::BotGetItemLongTermGoal( bot_state_t* bs, int tfl, bot_goal_t* goal ) //} //if it is time to find a new long term goal - if( bs->ltg_time == 0 ) + if (bs->ltg_time == 0) { //pop the current goal from the stack - botGoalManager.BotPopGoal( bs->gs ); + botGoalManager.BotPopGoal(bs->gs); //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname))); //choose a new goal //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", Bot_Time(), bs->client); - if( botGoalManager.BotChooseLTGItem( bs->gs, bs->origin, bs->inventory, tfl ) ) + if (botGoalManager.BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) { char buf[128]; //get the goal at the top of the stack - botGoalManager.BotGetTopGoal( bs->gs, goal ); - botGoalManager.BotGoalName( goal->number, buf, sizeof( buf ) ); - common->Printf( "%1.1f: new long term goal %s\n", Bot_Time(), buf ); + botGoalManager.BotGetTopGoal(bs->gs, goal); + botGoalManager.BotGoalName(goal->number, buf, sizeof(buf)); + common->Printf("%1.1f: new long term goal %s\n", Bot_Time(), buf); bs->ltg_time = Bot_Time() + 20; bs->currentGoal.framenum = gameLocal.framenum; @@ -389,11 +404,11 @@ int rvmBot::BotGetItemLongTermGoal( bot_state_t* bs, int tfl, bot_goal_t* goal ) // //trap_BotDumpAvoidGoals(bs->gs); //reset the avoid goals and the avoid reach - botGoalManager.BotResetAvoidGoals( bs->gs ); + botGoalManager.BotResetAvoidGoals(bs->gs); //BotResetAvoidReach(bs->ms); } //get the goal at the top of the stack - if( !botGoalManager.BotGetTopGoal( bs->gs, goal ) ) + if (!botGoalManager.BotGetTopGoal(bs->gs, goal)) { return false; } @@ -410,9 +425,9 @@ int rvmBot::BotGetItemLongTermGoal( bot_state_t* bs, int tfl, bot_goal_t* goal ) rvmBot::EntityIsDead ================== */ -bool rvmBot::EntityIsDead( idEntity* entity ) +bool rvmBot::EntityIsDead(idEntity* entity) { - if( entity == NULL ) + if (entity == NULL) { return true; } @@ -428,15 +443,15 @@ BotEntityVisibleTest returns visibility in the range [0, 1] taking fog and water surfaces into account ================== */ -float rvmBot::BotEntityVisibleTest( int viewer, idVec3 eye, idAngles viewangles, float fov, int ent, bool allowHeightTest ) +float rvmBot::BotEntityVisibleTest(int viewer, idVec3 eye, idAngles viewangles, float fov, int ent, bool allowHeightTest) { - if( !BotAIValidEntityNum( viewer ) || !BotAIValidEntityNum( ent ) ) + if (!BotAIValidEntityNum(viewer) || !BotAIValidEntityNum(ent)) { return 0.0f; } idEntity* entinfo = gameLocal.entities[ent]; - if( entinfo == NULL || entinfo->GetPhysics() == NULL ) + if (entinfo == NULL || entinfo->GetPhysics() == NULL) { return 0.0f; } @@ -446,20 +461,20 @@ float rvmBot::BotEntityVisibleTest( int viewer, idVec3 eye, idAngles viewangles, idVec3 center = bounds.GetCenter(); idVec3 testPoints[3]; testPoints[0] = origin + center; - testPoints[1] = origin + idVec3( center[0], center[1], bounds[0][2] + 8.0f ); - testPoints[2] = origin + idVec3( center[0], center[1], bounds[1][2] - 4.0f ); + testPoints[1] = origin + idVec3(center[0], center[1], bounds[0][2] + 8.0f); + testPoints[2] = origin + idVec3(center[0], center[1], bounds[1][2] - 4.0f); - if( !BotAIPointWithinFOV( eye, viewangles, testPoints[0], fov ) ) + if (!BotAIPointWithinFOV(eye, viewangles, testPoints[0], fov)) { return 0.0f; } - const int pc = gameLocal.clip.PointContents( eye ); - const int infog = ( pc & CONTENTS_FOG ); - const int inwater = ( pc & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ); + const int pc = gameLocal.clip.PointContents(eye); + const int infog = (pc & CONTENTS_FOG); + const int inwater = (pc & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)); float bestvis = 0.0f; - for( int i = 0; i < 3; i++ ) + for (int i = 0; i < 3; i++) { idVec3 start = eye; idVec3 end = testPoints[i]; @@ -467,73 +482,73 @@ float rvmBot::BotEntityVisibleTest( int viewer, idVec3 eye, idAngles viewangles, int passent = viewer; int hitent = ent; - if( gameLocal.clip.PointContents( end ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) + if (gameLocal.clip.PointContents(end) & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) { - contents_mask |= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); + contents_mask |= (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER); } - if( inwater ) + if (inwater) { - if( !( contents_mask & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) + if (!(contents_mask & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER))) { passent = ent; hitent = viewer; start = end; end = eye; } - contents_mask ^= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); + contents_mask ^= (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER); } trace_t trace; - gameLocal.Trace( trace, start, end, contents_mask, passent ); - if( trace.fraction < 0.9f && allowHeightTest ) + gameLocal.Trace(trace, start, end, contents_mask, passent); + if (trace.fraction < 0.9f && allowHeightTest) { idVec3 highEnd = end; highEnd[2] += 50.0f; - gameLocal.Trace( trace, start, highEnd, contents_mask, passent ); + gameLocal.Trace(trace, start, highEnd, contents_mask, passent); } float waterfactor = 1.0f; - if( trace.c.contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) + if (trace.c.contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) { - contents_mask &= ~( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ); - gameLocal.Trace( trace, trace.endpos, end, contents_mask, passent ); + contents_mask &= ~(CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER); + gameLocal.Trace(trace, trace.endpos, end, contents_mask, passent); waterfactor = 0.5f; } - if( trace.fraction >= 1.0f || trace.c.entityNum == hitent ) + if (trace.fraction >= 1.0f || trace.c.entityNum == hitent) { float squaredfogdist = 0.0f; - const int otherinfog = ( gameLocal.clip.PointContents( end ) & CONTENTS_FOG ); + const int otherinfog = (gameLocal.clip.PointContents(end) & CONTENTS_FOG); idVec3 dir; - if( infog && otherinfog ) + if (infog && otherinfog) { dir = trace.endpos - eye; squaredfogdist = dir.LengthSqr(); } - else if( infog ) + else if (infog) { idVec3 fogStart = trace.endpos; - gameLocal.Trace( trace, fogStart, eye, CONTENTS_FOG, viewer ); + gameLocal.Trace(trace, fogStart, eye, CONTENTS_FOG, viewer); dir = eye - trace.endpos; squaredfogdist = dir.LengthSqr(); } - else if( otherinfog ) + else if (otherinfog) { idVec3 fogEnd = trace.endpos; - gameLocal.Trace( trace, eye, fogEnd, CONTENTS_FOG, viewer ); + gameLocal.Trace(trace, eye, fogEnd, CONTENTS_FOG, viewer); dir = fogEnd - trace.endpos; squaredfogdist = dir.LengthSqr(); } - float vis = 1.0f / ( ( squaredfogdist * 0.001f ) < 1.0f ? 1.0f : ( squaredfogdist * 0.001f ) ); + float vis = 1.0f / ((squaredfogdist * 0.001f) < 1.0f ? 1.0f : (squaredfogdist * 0.001f)); vis *= waterfactor; - if( vis > bestvis ) + if (vis > bestvis) { bestvis = vis; } - if( bestvis >= 0.95f ) + if (bestvis >= 0.95f) { return bestvis; } @@ -548,9 +563,9 @@ float rvmBot::BotEntityVisibleTest( int viewer, idVec3 eye, idAngles viewangles, rvmBot::BotEntityVisible ================== */ -float rvmBot::BotEntityVisible( int viewer, idVec3 eye, idAngles viewangles, float fov, int ent ) +float rvmBot::BotEntityVisible(int viewer, idVec3 eye, idAngles viewangles, float fov, int ent) { - return BotEntityVisibleTest( viewer, eye, viewangles, fov, ent, true ); + return BotEntityVisibleTest(viewer, eye, viewangles, fov, ent, true); } /* @@ -558,23 +573,23 @@ float rvmBot::BotEntityVisible( int viewer, idVec3 eye, idAngles viewangles, flo rvmBot::BotUpdateBattleInventory ================== */ -void rvmBot::BotUpdateBattleInventory( bot_state_t* bs, int enemy ) +void rvmBot::BotUpdateBattleInventory(bot_state_t* bs, int enemy) { - if( bs == NULL || !BotAIValidEntityNum( enemy ) ) + if (bs == NULL || !BotAIValidEntityNum(enemy)) { return; } idEntity* entinfo = gameLocal.entities[enemy]; - if( entinfo == NULL || entinfo->GetPhysics() == NULL ) + if (entinfo == NULL || entinfo->GetPhysics() == NULL) { return; } idVec3 dir = entinfo->GetPhysics()->GetOrigin() - bs->origin; - bs->inventory[ENEMY_HEIGHT] = ( int )dir[2]; + bs->inventory[ENEMY_HEIGHT] = (int)dir[2]; dir[2] = 0; - bs->inventory[ENEMY_HORIZONTAL_DIST] = ( int )dir.Length(); + bs->inventory[ENEMY_HORIZONTAL_DIST] = (int)dir.Length(); } /* @@ -582,81 +597,81 @@ void rvmBot::BotUpdateBattleInventory( bot_state_t* bs, int enemy ) rvmBot::BotAggression ================== */ -float rvmBot::BotAggression( bot_state_t* bs ) +float rvmBot::BotAggression(bot_state_t* bs) { //if the bot has quad - if( bs->inventory[INVENTORY_QUAD] ) + if (bs->inventory[INVENTORY_QUAD]) { //if the bot is not holding the gauntlet or the enemy is really nearby - if( bs->weaponnum != 0 || - bs->inventory[ENEMY_HORIZONTAL_DIST] < 80 ) + if (bs->weaponnum != 0 || + bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { return 70; } } //if the enemy is located way higher than the bot - if( bs->inventory[ENEMY_HEIGHT] > 200 ) + if (bs->inventory[ENEMY_HEIGHT] > 200) { return 0; } //if the bot is very low on health - if( bs->inventory[INVENTORY_HEALTH] < 60 ) + if (bs->inventory[INVENTORY_HEALTH] < 60) { return 0; } //if the bot is low on health - if( bs->inventory[INVENTORY_HEALTH] < 80 ) + if (bs->inventory[INVENTORY_HEALTH] < 80) { //if the bot has insufficient armor - if( bs->inventory[INVENTORY_ARMOR] < 40 ) + if (bs->inventory[INVENTORY_ARMOR] < 40) { return 0; } } //if the bot can use the bfg - if( bs->inventory[INVENTORY_BFG10K] > 0 && - bs->inventory[INVENTORY_BFGAMMO] > 7 ) + if (bs->inventory[INVENTORY_BFG10K] > 0 && + bs->inventory[INVENTORY_BFGAMMO] > 7) { return 100; } //if the bot can use the railgun - if( bs->inventory[INVENTORY_RAILGUN] > 0 && - bs->inventory[INVENTORY_SLUGS] > 5 ) + if (bs->inventory[INVENTORY_RAILGUN] > 0 && + bs->inventory[INVENTORY_SLUGS] > 5) { return 95; } //if the bot can use the lightning gun - if( bs->inventory[INVENTORY_LIGHTNING] > 0 && - bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50 ) + if (bs->inventory[INVENTORY_LIGHTNING] > 0 && + bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) { return 90; } //if the bot can use the rocketlauncher - if( bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && - bs->inventory[INVENTORY_ROCKETS] > 5 ) + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && + bs->inventory[INVENTORY_ROCKETS] > 5) { return 90; } //if the bot can use the plasmagun - if( bs->inventory[INVENTORY_PLASMAGUN] > 0 && - bs->inventory[INVENTORY_CELLS] > 40 ) + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && + bs->inventory[INVENTORY_CELLS] > 40) { return 85; } //if the bot can use the grenade launcher - if( bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && - bs->inventory[INVENTORY_GRENADES] > 10 ) + if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && + bs->inventory[INVENTORY_GRENADES] > 10) { return 80; } //if the bot can use the shotgun - if( bs->inventory[INVENTORY_SHOTGUN] > 0 && - bs->inventory[INVENTORY_SHELLS] > 10 ) + if (bs->inventory[INVENTORY_SHOTGUN] > 0 && + bs->inventory[INVENTORY_SHELLS] > 10) { return 50; } - if( bs->inventory[INVENTORY_BULLETS] > 0 ) + if (bs->inventory[INVENTORY_BULLETS] > 0) { return 60; } @@ -671,9 +686,9 @@ float rvmBot::BotAggression( bot_state_t* bs ) rvmBot::BotWantsToRetreat ================== */ -int rvmBot::BotWantsToRetreat( bot_state_t* bs ) +int rvmBot::BotWantsToRetreat(bot_state_t* bs) { - if( BotAggression( bs ) < 50 ) + if (BotAggression(bs) < 50) { return true; } @@ -685,18 +700,18 @@ int rvmBot::BotWantsToRetreat( bot_state_t* bs ) rvmBot::BotBattleUseItems ================== */ -void rvmBot::BotBattleUseItems( bot_state_t* bs ) +void rvmBot::BotBattleUseItems(bot_state_t* bs) { - if( bs->inventory[INVENTORY_HEALTH] < 40 ) + if (bs->inventory[INVENTORY_HEALTH] < 40) { - if( bs->inventory[INVENTORY_TELEPORTER] > 0 ) + if (bs->inventory[INVENTORY_TELEPORTER] > 0) { bs->botinput.actionflags |= ACTION_USE; } } - if( bs->inventory[INVENTORY_HEALTH] < 60 ) + if (bs->inventory[INVENTORY_HEALTH] < 60) { - if( bs->inventory[INVENTORY_MEDKIT] > 0 ) + if (bs->inventory[INVENTORY_MEDKIT] > 0) { //trap_EA_Use(bs->client); bs->botinput.actionflags |= ACTION_USE; @@ -709,17 +724,17 @@ void rvmBot::BotBattleUseItems( bot_state_t* bs ) rvmBot::BotFindEnemy ================== */ -int rvmBot::BotFindEnemy( bot_state_t* bs, int curenemy ) +int rvmBot::BotFindEnemy(bot_state_t* bs, int curenemy) { - if( bs == NULL || !BotAIValidEntityNum( bs->client ) ) + if (bs == NULL || !BotAIValidEntityNum(bs->client)) { return false; } - float alertness = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_ALERTNESS, 0, 1 ); - float easyfragger = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1 ); + float alertness = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1); + float easyfragger = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1); idPlayer* clientEnt = gameLocal.entities[bs->client]->Cast(); - if( clientEnt == NULL ) + if (clientEnt == NULL) { return false; } @@ -728,29 +743,29 @@ int rvmBot::BotFindEnemy( bot_state_t* bs, int curenemy ) bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; float cursquaredist = 0.0f; - if( curenemy >= 0 && BotAIValidEntityNum( curenemy ) ) + if (curenemy >= 0 && BotAIValidEntityNum(curenemy)) { idPlayer* curenemyinfo = gameLocal.entities[curenemy]->Cast(); - if( curenemyinfo != NULL ) + if (curenemyinfo != NULL) { idVec3 dir = curenemyinfo->GetPhysics()->GetOrigin() - bs->origin; cursquaredist = dir.LengthSqr(); } } - for( int i = 0; i < MAX_CLIENTS && i < gameLocal.num_entities; i++ ) + for (int i = 0; i < MAX_CLIENTS && i < gameLocal.num_entities; i++) { - if( i == bs->client || i == curenemy || !BotAIValidEntityNum( i ) ) + if (i == bs->client || i == curenemy || !BotAIValidEntityNum(i)) { continue; } idPlayer* entinfo = gameLocal.entities[i]->Cast(); - if( entinfo == NULL ) + if (entinfo == NULL) { continue; } - if( EntityIsDead( entinfo ) || i == bs->entitynum || entinfo->spectating ) + if (EntityIsDead(entinfo) || i == bs->entitynum || entinfo->spectating) { continue; } @@ -759,32 +774,32 @@ int rvmBot::BotFindEnemy( bot_state_t* bs, int curenemy ) idVec3 dir = potentialTargetOrigin - bs->origin; float squaredist = dir.LengthSqr(); - if( curenemy >= 0 && cursquaredist > 0.0f && squaredist > cursquaredist ) + if (curenemy >= 0 && cursquaredist > 0.0f && squaredist > cursquaredist) { continue; } - if( squaredist > Square( 900.0f + alertness * 4000.0f ) ) + if (squaredist > Square(900.0f + alertness * 4000.0f)) { continue; } float fov; - if( curenemy < 0 && ( healthdecrease || entinfo->IsShooting() ) ) + if (curenemy < 0 && (healthdecrease || entinfo->IsShooting())) { fov = 360.0f; } else { - const float clampedDist = squaredist > Square( 810.0f ) ? Square( 810.0f ) : squaredist; - fov = 180.0f - ( 90.0f - clampedDist / ( 810.0f * 9.0f ) ); + const float clampedDist = squaredist > Square(810.0f) ? Square(810.0f) : squaredist; + fov = 180.0f - (90.0f - clampedDist / (810.0f * 9.0f)); } float vis = 1.0f; - if( bs->attackerEntity != entinfo ) + if (bs->attackerEntity != entinfo) { - vis = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, fov, i ); - if( vis <= 0.0f ) + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, fov, i); + if (vis <= 0.0f) { continue; } @@ -792,12 +807,12 @@ int rvmBot::BotFindEnemy( bot_state_t* bs, int curenemy ) // If the bot can avoid a distant, unaware enemy and does not want a fight, // do not switch away from the current goal. - if( curenemy < 0 && squaredist > Square( 100.0f ) && !healthdecrease && !entinfo->IsShooting() ) + if (curenemy < 0 && squaredist > Square(100.0f) && !healthdecrease && !entinfo->IsShooting()) { - if( !entinfo->CheckFOV( clientEnt->GetPhysics()->GetOrigin() ) ) + if (!entinfo->CheckFOV(clientEnt->GetPhysics()->GetOrigin())) { - BotUpdateBattleInventory( bs, i ); - if( BotWantsToRetreat( bs ) && easyfragger < 0.5f ) + BotUpdateBattleInventory(bs, i); + if (BotWantsToRetreat(bs) && easyfragger < 0.5f) { continue; } @@ -824,7 +839,7 @@ int rvmBot::BotFindEnemy( bot_state_t* bs, int curenemy ) rvmBot::BotMoveToGoal ================== */ -void rvmBot::BotMoveToGoal( bot_state_t* bs, bot_goal_t* goal ) +void rvmBot::BotMoveToGoal(bot_state_t* bs, bot_goal_t* goal) { bs->currentGoal = *goal; bs->currentGoal.framenum = gameLocal.framenum; @@ -835,61 +850,83 @@ void rvmBot::BotMoveToGoal( bot_state_t* bs, bot_goal_t* goal ) rvmBot::BotAimAtEnemy ================== */ -void rvmBot::BotAimAtEnemy( bot_state_t* bs ) +void rvmBot::BotAimAtEnemy(bot_state_t* bs) { - if( bs == NULL || bs->enemy < 0 || !BotAIValidEntityNum( bs->enemy ) || !BotAIValidEntityNum( bs->entitynum ) ) + if (bs == NULL || bs->enemy < 0 || !BotAIValidEntityNum(bs->enemy) || !BotAIValidEntityNum(bs->entitynum)) { return; } idPlayer* self = gameLocal.entities[bs->entitynum]->Cast(); idPlayer* entinfo = gameLocal.entities[bs->enemy]->Cast(); - if( self == NULL || entinfo == NULL || entinfo->GetPhysics() == NULL ) + if (self == NULL || entinfo == NULL || entinfo->GetPhysics() == NULL) { return; } - float aim_skill = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1 ); - float aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1 ); + float aim_skill = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1); + float aim_accuracy = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); - if( aim_skill > 0.95f ) + if (aim_skill > 0.95f) { - const float reactiontime = 0.5f * botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1 ); - if( bs->enemysight_time > Bot_Time() - reactiontime || bs->teleport_time > Bot_Time() - reactiontime ) + const float reactiontime = 0.5f * botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); + if (bs->enemysight_time > Bot_Time() - reactiontime || bs->teleport_time > Bot_Time() - reactiontime) { return; } } weaponinfo_t wi; - botWeaponInfoManager.BotGetWeaponInfo( bs->ws, bs->weaponnum, &wi ); + botWeaponInfoManager.BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); - if( wi.number == WP_MACHINEGUN ) + const bool selectedMachinegun = bs->weaponnum == WP_MACHINEGUN; + const bool selectedShotgun = bs->weaponnum == WP_SHOTGUN; + const bool selectedPlasmaGun = bs->weaponnum == WP_PLASMAGUN; + const bool selectedRocketLauncher = bs->weaponnum == WP_ROCKET_LAUNCHER; + const int selectedWeaponInventory = selectedMachinegun ? INVENTORY_MACHINEGUN : + selectedShotgun ? INVENTORY_SHOTGUN : + selectedPlasmaGun ? INVENTORY_PLASMAGUN : + selectedRocketLauncher ? INVENTORY_ROCKETLAUNCHER : -1; + if (selectedWeaponInventory >= 0 && wi.weaponindex != selectedWeaponInventory) { - aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1 ); + for (int weaponInfoNum = 0; weaponInfoNum < BOT_MAX_WEAPONS; weaponInfoNum++) + { + weaponinfo_t candidateWi; + botWeaponInfoManager.BotGetWeaponInfo(bs->ws, weaponInfoNum, &candidateWi); + if (candidateWi.number == weaponInfoNum && candidateWi.weaponindex == selectedWeaponInventory) + { + wi = candidateWi; + break; + } + } } - else if( wi.number == WP_SHOTGUN ) - { - aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1 ); - } - else if( wi.number == WP_ROCKET_LAUNCHER ) - { - aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1 ); - aim_skill = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1 ); - } - else if( wi.number == WP_PLASMAGUN ) - { - aim_accuracy = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1 ); - aim_skill = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1 ); - } - aim_accuracy = BotAIClampFloat( 0.0001f, 1.0f, aim_accuracy ); - aim_skill = BotAIClampFloat( 0.0f, 1.0f, aim_skill ); - const float enemyvisible = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360.0f, bs->enemy ); + if (selectedMachinegun) + { + aim_accuracy = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1); + } + else if (selectedShotgun) + { + aim_accuracy = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1); + } + else if (selectedRocketLauncher) + { + aim_accuracy = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1); + aim_skill = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1); + } + else if (selectedPlasmaGun) + { + aim_accuracy = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1); + aim_skill = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1); + } + aim_accuracy = BotAIClampFloat(0.0001f, 1.0f, aim_accuracy); + aim_skill = BotAIClampFloat(0.0f, 1.0f, aim_skill); + + const float enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360.0f, bs->enemy); idVec3 bestorigin; idVec3 start = bs->eye; - if( enemyvisible > 0.0f ) + if (enemyvisible > 0.0f) { bs->enemyvisible_time = Bot_Time(); bs->lastenemyorigin = entinfo->GetPhysics()->GetOrigin(); @@ -902,20 +939,20 @@ void rvmBot::BotAimAtEnemy( bot_state_t* bs ) idBounds enemyBounds = entinfo->GetPhysics()->GetBounds(); const float enemyMinZ = enemyBounds[0][2]; const float enemyMaxZ = enemyBounds[1][2]; - const float enemyCenterZ = ( enemyMinZ + enemyMaxZ ) * 0.5f; - const float enemyChestZ = enemyMinZ + ( enemyMaxZ - enemyMinZ ) * 0.65f; + const float enemyCenterZ = (enemyMinZ + enemyMaxZ) * 0.5f; + const float enemyChestZ = enemyMinZ + (enemyMaxZ - enemyMinZ) * 0.65f; idVec3 candidates[5]; - candidates[0] = enemyOrigin + idVec3( 0.0f, 0.0f, enemyChestZ ); - candidates[1] = enemyOrigin + idVec3( 0.0f, 0.0f, enemyCenterZ ); + candidates[0] = enemyOrigin + idVec3(0.0f, 0.0f, enemyChestZ); + candidates[1] = enemyOrigin + idVec3(0.0f, 0.0f, enemyCenterZ); candidates[2] = entinfo->GetEyePosition(); - candidates[3] = enemyOrigin + idVec3( 0.0f, 0.0f, enemyMinZ + 12.0f ); - candidates[4] = BotAIEntityCenter( entinfo ); + candidates[3] = enemyOrigin + idVec3(0.0f, 0.0f, enemyMinZ + 12.0f); + candidates[4] = BotAIEntityCenter(entinfo); - bestorigin = ( wi.proj.damagetype & DAMAGETYPE_RADIAL ) ? candidates[1] : candidates[0]; - for( int i = 0; i < 5; i++ ) + bestorigin = (wi.proj.damagetype & DAMAGETYPE_RADIAL) ? candidates[1] : candidates[0]; + for (int i = 0; i < 5; i++) { - if( BotAITraceCanHitEntity( bs->entitynum, bs->enemy, start, candidates[i], MASK_SHOT ) ) + if (BotAITraceCanHitEntity(bs->entitynum, bs->enemy, start, candidates[i], MASK_SHOT)) { bestorigin = candidates[i]; break; @@ -924,35 +961,35 @@ void rvmBot::BotAimAtEnemy( bot_state_t* bs ) // Linear lead for projectile weapons. The old code copied an uninitialized // vector backwards and then changed the movement goal instead of changing aim. - if( wi.speed > 1.0f ) + if (wi.speed > 1.0f) { idVec3 toTarget = bestorigin - start; float leadTime = toTarget.Length() / wi.speed; - leadTime = BotAIClampFloat( 0.0f, 1.25f, leadTime ); + leadTime = BotAIClampFloat(0.0f, 1.25f, leadTime); idVec3 enemyVelocity = entinfo->GetPhysics()->GetLinearVelocity(); // Horizontal lead matters most. Clamp vertical lead so stairs/elevators do // not make projectile aim sail over or under the body. - enemyVelocity[2] = BotAIClampFloat( -120.0f, 120.0f, enemyVelocity[2] ); - bestorigin += enemyVelocity * ( leadTime * aim_skill ); + enemyVelocity[2] = BotAIClampFloat(-120.0f, 120.0f, enemyVelocity[2]); + bestorigin += enemyVelocity * (leadTime * aim_skill); } // Rockets are usually stronger when aimed at the floor near the enemy, but // only do this if the impact point is visible and not dangerously close. - if( aim_skill > 0.6f && ( wi.proj.damagetype & DAMAGETYPE_RADIAL ) ) + if (aim_skill > 0.6f && (wi.proj.damagetype & DAMAGETYPE_RADIAL)) { idVec3 end = entinfo->GetPhysics()->GetOrigin(); end[2] -= 96.0f; trace_t trace; - gameLocal.Trace( trace, entinfo->GetPhysics()->GetOrigin(), end, MASK_SHOT, bs->enemy ); + gameLocal.Trace(trace, entinfo->GetPhysics()->GetOrigin(), end, MASK_SHOT, bs->enemy); idVec3 groundtarget = trace.endpos; groundtarget[2] -= 4.0f; - gameLocal.Trace( trace, start, groundtarget, MASK_SHOT, bs->entitynum ); - if( idMath::Fabs( trace.endpos[2] - groundtarget[2] ) < 50.0f ) + gameLocal.Trace(trace, start, groundtarget, MASK_SHOT, bs->entitynum); + if (idMath::Fabs(trace.endpos[2] - groundtarget[2]) < 50.0f) { idVec3 fromBot = trace.endpos - start; idVec3 fromEnemy = trace.endpos - entinfo->GetPhysics()->GetOrigin(); - if( fromBot.LengthSqr() > Square( 100.0f ) && fromEnemy.LengthSqr() < Square( 160.0f ) ) + if (fromBot.LengthSqr() > Square(100.0f) && fromEnemy.LengthSqr() < Square(160.0f)) { bestorigin = trace.endpos; } @@ -967,7 +1004,7 @@ void rvmBot::BotAimAtEnemy( bot_state_t* bs ) else { bestorigin = bs->lastenemyorigin; - if( bestorigin.LengthSqr() < Square( 1.0f ) ) + if (bestorigin.LengthSqr() < Square(1.0f)) { bestorigin = entinfo->GetPhysics()->GetOrigin(); } @@ -975,8 +1012,8 @@ void rvmBot::BotAimAtEnemy( bot_state_t* bs ) } trace_t trace; - gameLocal.Trace( trace, bs->eye, bestorigin, MASK_SHOT, bs->entitynum ); - if( enemyvisible > 0.0f && ( trace.fraction >= 1.0f || trace.c.entityNum == bs->enemy ) ) + gameLocal.Trace(trace, bs->eye, bestorigin, MASK_SHOT, bs->entitynum); + if (enemyvisible > 0.0f && (trace.fraction >= 1.0f || trace.c.entityNum == bs->enemy)) { bs->aimtarget = bestorigin; } @@ -986,36 +1023,36 @@ void rvmBot::BotAimAtEnemy( bot_state_t* bs ) } idVec3 dir = bestorigin - bs->eye; - if( dir.LengthSqr() < Square( 1.0f ) ) + if (dir.LengthSqr() < Square(1.0f)) { return; } - if( wi.number == WP_MACHINEGUN || wi.number == WP_SHOTGUN ) + if (selectedMachinegun || selectedShotgun) { float dist = dir.Length(); - if( dist > 150.0f ) + if (dist > 150.0f) { dist = 150.0f; } aim_accuracy *= 0.6f + dist / 150.0f * 0.4f; } - if( aim_accuracy < 0.8f ) + if (aim_accuracy < 0.8f) { dir.Normalize(); const float miss = 1.0f - aim_accuracy; - for( int i = 0; i < 3; i++ ) + for (int i = 0; i < 3; i++) { dir[i] += 0.12f * rvmBotUtil::crandom() * miss; } } bs->viewangles = dir.ToAngles(); - bs->viewangles[PITCH] += 2.0f * wi.vspread * rvmBotUtil::crandom() * ( 1.0f - aim_accuracy ); - bs->viewangles[PITCH] = idMath::AngleMod( bs->viewangles[PITCH] ); - bs->viewangles[YAW] += 2.0f * wi.hspread * rvmBotUtil::crandom() * ( 1.0f - aim_accuracy ); - bs->viewangles[YAW] = idMath::AngleMod( bs->viewangles[YAW] ); + bs->viewangles[PITCH] += 2.0f * wi.vspread * rvmBotUtil::crandom() * (1.0f - aim_accuracy); + bs->viewangles[PITCH] = idMath::AngleMod(bs->viewangles[PITCH]); + bs->viewangles[YAW] += 2.0f * wi.hspread * rvmBotUtil::crandom() * (1.0f - aim_accuracy); + bs->viewangles[YAW] = idMath::AngleMod(bs->viewangles[YAW]); bs->botinput.viewangles = bs->viewangles; } @@ -1025,9 +1062,9 @@ void rvmBot::BotAimAtEnemy( bot_state_t* bs ) rvmBot::BotWantsToChase ================== */ -bool rvmBot::BotWantsToChase( bot_state_t* bs ) +bool rvmBot::BotWantsToChase(bot_state_t* bs) { - if( BotAggression( bs ) > 50 ) + if (BotAggression(bs) > 50) { return true; } @@ -1041,9 +1078,9 @@ bool rvmBot::BotWantsToChase( bot_state_t* bs ) rvmBot::BotNearbyGoal ================== */ -int rvmBot::BotNearbyGoal( bot_state_t* bs, int tfl, bot_goal_t* ltg, float range ) +int rvmBot::BotNearbyGoal(bot_state_t* bs, int tfl, bot_goal_t* ltg, float range) { - if( bs == NULL ) + if (bs == NULL) { return false; } @@ -1053,9 +1090,9 @@ int rvmBot::BotNearbyGoal( bot_state_t* bs, int tfl, bot_goal_t* ltg, float rang // a nearby weapon/ammo pickup. Expand the NBG budget only when combat inventory // makes the pickup tactically useful; otherwise keep it as a short opportunistic // detour. - range = BotAITacticalPickupRange( bs, range ); + range = BotAITacticalPickupRange(bs, range); - return botGoalManager.BotChooseNBGItem( bs->gs, bs->origin, bs->inventory, tfl, ltg, range ); + return botGoalManager.BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range); } @@ -1098,7 +1135,7 @@ void rvmBot::BotGetRandomPointNearPosition(const idVec3 point, idVec3& randomPoi } aasPath_t path; - if( aas->WalkPathToGoal( path, baseAreaNum, basePoint, testAreaNum, testPoint, TFL_WALK | TFL_AIR ) ) + if (aas->WalkPathToGoal(path, baseAreaNum, basePoint, testAreaNum, testPoint, TFL_WALK | TFL_AIR)) { aas->PushPointIntoAreaNum(testAreaNum, testPoint); randomPoint = testPoint; @@ -1115,39 +1152,39 @@ void rvmBot::BotGetRandomPointNearPosition(const idVec3 point, idVec3& randomPoi rvmBot::BotMoveInRandomDirection ======================= */ -int rvmBot::BotMoveInRandomDirection( bot_state_t* bs ) +int rvmBot::BotMoveInRandomDirection(bot_state_t* bs) { - if( bs == NULL || !BotAIValidEntityNum( bs->client ) ) + if (bs == NULL || !BotAIValidEntityNum(bs->client)) { return 0; } rvmBot* ent = gameLocal.entities[bs->client]->Cast(); - if( ent == NULL ) + if (ent == NULL) { return 0; } - float dist = idMath::Distance( ent->GetPhysics()->GetOrigin(), bs->random_move_position ); - if( bs->random_move_position.LengthSqr() < Square( 1.0f ) || !ent->PointVisible( bs->random_move_position ) ) + float dist = idMath::Distance(ent->GetPhysics()->GetOrigin(), bs->random_move_position); + if (bs->random_move_position.LengthSqr() < Square(1.0f) || !ent->PointVisible(bs->random_move_position)) { dist = 0.0f; } - if( dist < 25.0f ) + if (dist < 25.0f) { - BotGetRandomPointNearPosition( ent->GetPhysics()->GetOrigin(), bs->random_move_position, 80.0f ); + BotGetRandomPointNearPosition(ent->GetPhysics()->GetOrigin(), bs->random_move_position, 80.0f); } idVec3 dir = bs->random_move_position - ent->GetPhysics()->GetOrigin(); dir[2] = 0.0f; - if( dir.LengthSqr() > Square( 1.0f ) ) + if (dir.LengthSqr() > Square(1.0f)) { dir.Normalize(); } else { - dir = idVec3( 0.0f, 0.0f, 0.0f ); + dir = idVec3(0.0f, 0.0f, 0.0f); } bs->botinput.dir = dir; @@ -1169,23 +1206,23 @@ void rvmBot::MoveToCoverPoint(void) aasObstacle_t obstacles[10]; idVec3 origin = GetOrigin(); idAAS* aas = gameLocal.GetBotAAS(); - if( aas == NULL ) + if (aas == NULL) { return; } areaNum = aas->PointReachableAreaNum(origin, aas->DefaultSearchBounds(), (AREA_REACHABLE_WALK | AREA_REACHABLE_FLY)); - if( areaNum <= 0 ) + if (areaNum <= 0) { return; } idPlayer* localPlayer = gameLocal.GetLocalPlayer(); - if( localPlayer == NULL ) + if (localPlayer == NULL) { return; } const int targetAreaNum = aas->PointAreaNum(localPlayer->GetOrigin()); - if( targetAreaNum <= 0 ) + if (targetAreaNum <= 0) { return; } @@ -1207,9 +1244,9 @@ void rvmBot::MoveToCoverPoint(void) rvmBot::BotCheckAttack ================== */ -void rvmBot::BotCheckAttack( bot_state_t* bs ) +void rvmBot::BotCheckAttack(bot_state_t* bs) { - if( bs == NULL || bs->enemy < 0 || !BotAIValidEntityNum( bs->enemy ) || !BotAIValidEntityNum( bs->client ) ) + if (bs == NULL || bs->enemy < 0 || !BotAIValidEntityNum(bs->enemy) || !BotAIValidEntityNum(bs->client)) { return; } @@ -1217,39 +1254,39 @@ void rvmBot::BotCheckAttack( bot_state_t* bs ) const int attackentity = bs->enemy; idEntity* entinfo = gameLocal.entities[attackentity]; idPlayer* self = gameLocal.entities[bs->client]->Cast(); - if( entinfo == NULL || entinfo->GetPhysics() == NULL || self == NULL ) + if (entinfo == NULL || entinfo->GetPhysics() == NULL || self == NULL) { return; } // Make sure battle states that enter through "attacked" or chase still have // a real weapon selected before the fire decision is made. - if( bs->weaponnum <= 0 ) + if (bs->weaponnum < 0) { - BotChooseWeapon( bs ); + BotChooseWeapon(bs); } - const float reactiontime = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1 ); - if( bs->enemysight_time > Bot_Time() - reactiontime || bs->teleport_time > Bot_Time() - reactiontime ) + const float reactiontime = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); + if (bs->enemysight_time > Bot_Time() - reactiontime || bs->teleport_time > Bot_Time() - reactiontime) { return; } - if( bs->weaponchange_time > Bot_Time() - 0.1f ) + if (bs->weaponchange_time > Bot_Time() - 0.1f) { return; } - if( bs->firethrottlewait_time > Bot_Time() ) + if (bs->firethrottlewait_time > Bot_Time()) { return; } - float firethrottle = botCharacterStatsManager.Characteristic_BFloat( bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1 ); - firethrottle = BotAIClampFloat( 0.0f, 1.0f, firethrottle ); - if( bs->firethrottleshoot_time < Bot_Time() ) + float firethrottle = botCharacterStatsManager.Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1); + firethrottle = BotAIClampFloat(0.0f, 1.0f, firethrottle); + if (bs->firethrottleshoot_time < Bot_Time()) { - if( rvmBotUtil::random() > firethrottle ) + if (rvmBotUtil::random() > firethrottle) { bs->firethrottlewait_time = Bot_Time() + firethrottle; bs->firethrottleshoot_time = 0.0f; @@ -1262,14 +1299,44 @@ void rvmBot::BotCheckAttack( bot_state_t* bs ) } weaponinfo_t wi; - botWeaponInfoManager.BotGetWeaponInfo( bs->ws, bs->weaponnum, &wi ); + botWeaponInfoManager.BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + + const bool selectedMachinegun = bs->weaponnum == WP_MACHINEGUN; + const bool selectedShotgun = bs->weaponnum == WP_SHOTGUN; + const bool selectedPlasmaGun = bs->weaponnum == WP_PLASMAGUN; + const bool selectedRocketLauncher = bs->weaponnum == WP_ROCKET_LAUNCHER; + const int selectedWeaponInventory = selectedMachinegun ? INVENTORY_MACHINEGUN : + selectedShotgun ? INVENTORY_SHOTGUN : + selectedPlasmaGun ? INVENTORY_PLASMAGUN : + selectedRocketLauncher ? INVENTORY_ROCKETLAUNCHER : -1; + if (selectedWeaponInventory >= 0 && wi.weaponindex != selectedWeaponInventory) + { + for (int weaponInfoNum = 0; weaponInfoNum < BOT_MAX_WEAPONS; weaponInfoNum++) + { + weaponinfo_t candidateWi; + botWeaponInfoManager.BotGetWeaponInfo(bs->ws, weaponInfoNum, &candidateWi); + if (candidateWi.number == weaponInfoNum && candidateWi.weaponindex == selectedWeaponInventory) + { + wi = candidateWi; + break; + } + } + } + + // Hard visibility gate: movement/chase states may aim at last known position, + // but they should not press attack unless the enemy is currently visible. + if (BotEntityVisibleTest(bs->entitynum, bs->eye, bs->viewangles, 360.0f, attackentity, false) <= 0.0f) + { + bs->flags &= ~BFL_ATTACKED; + return; + } idVec3 dir = bs->aimtarget - bs->eye; - if( dir.LengthSqr() < Square( 1.0f ) ) + if (dir.LengthSqr() < Square(1.0f)) { - bs->aimtarget = BotAIEntityCenter( entinfo ); + bs->aimtarget = BotAIEntityCenter(entinfo); dir = bs->aimtarget - bs->eye; - if( dir.LengthSqr() < Square( 1.0f ) ) + if (dir.LengthSqr() < Square(1.0f)) { return; } @@ -1281,44 +1348,43 @@ void rvmBot::BotCheckAttack( bot_state_t* bs ) // smoothed the actual usercmd view, so the bot often shot several frames early. const float distToAim = dir.Length(); const float idealAttackFov = distToAim < 100.0f ? 140.0f : 90.0f; - if( !BotAIPointWithinFOV( bs->eye, bs->viewangles, bs->aimtarget, idealAttackFov ) ) + if (!BotAIPointWithinFOV(bs->eye, bs->viewangles, bs->aimtarget, idealAttackFov)) { return; } float actualAttackFov = 14.0f; - if( distToAim < 120.0f ) + if (distToAim < 120.0f) { actualAttackFov = 45.0f; } - else if( wi.number == WP_SHOTGUN ) + else if (selectedShotgun) { actualAttackFov = 32.0f; } - else if( wi.proj.damagetype & DAMAGETYPE_RADIAL ) + else if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { actualAttackFov = 24.0f; } - else if( wi.speed > 1.0f ) + else if (wi.speed > 1.0f) { actualAttackFov = 18.0f; } - if( !BotAIPointWithinFOV( bs->eye, viewAngles, bs->aimtarget, actualAttackFov ) ) + if (!BotAIPointWithinFOV(bs->eye, viewAngles, bs->aimtarget, actualAttackFov)) { return; } trace_t sightTrace; - gameLocal.Trace( sightTrace, bs->eye, bs->aimtarget, MASK_SHOT, bs->entitynum ); + gameLocal.Trace(sightTrace, bs->eye, bs->aimtarget, MASK_SHOT, bs->entitynum); const bool eyeTraceClear = sightTrace.fraction >= 1.0f || sightTrace.c.entityNum == attackentity; - const bool enemyVisible = BotEntityVisibleTest( bs->entitynum, bs->eye, bs->viewangles, 360.0f, attackentity, false ) > 0.0f; - if( !eyeTraceClear && !enemyVisible ) + if (!eyeTraceClear && !(wi.proj.damagetype & DAMAGETYPE_RADIAL)) { return; } idVec3 forward, right, start, end; - bs->viewangles.ToVectors( &forward, &right, NULL ); + bs->viewangles.ToVectors(&forward, &right, NULL); start = bs->eye; start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; @@ -1327,35 +1393,43 @@ void rvmBot::BotCheckAttack( bot_state_t* bs ) start = start - forward * 12.0f; trace_t weaponTrace; - gameLocal.Trace( weaponTrace, start, end, MASK_SHOT, bs->entitynum ); + gameLocal.Trace(weaponTrace, start, end, MASK_SHOT, bs->entitynum); // Do not shoot through another client. Team checks can be added here later; // for now this prevents the regression fix from blindly firing through bodies. - if( weaponTrace.c.entityNum >= 0 && weaponTrace.c.entityNum < MAX_CLIENTS && weaponTrace.c.entityNum != attackentity ) + if (weaponTrace.c.entityNum >= 0 && weaponTrace.c.entityNum < MAX_CLIENTS && weaponTrace.c.entityNum != attackentity) { return; } - if( weaponTrace.c.entityNum != attackentity ) + if (weaponTrace.c.entityNum != attackentity) { - if( wi.proj.damagetype & DAMAGETYPE_RADIAL ) + if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { - // For splash weapons, only suppress shots that would explode close enough - // to hurt the bot. A ground/wall impact near the enemy is intentional. - if( weaponTrace.fraction < 1.0f && weaponTrace.fraction * 1000.0f < wi.proj.radius + 24.0f ) + // Splash shots are allowed, but only when the visible impact/aim point is + // close enough to the currently visible enemy to plausibly damage them. + if (weaponTrace.fraction < 1.0f && weaponTrace.fraction * 1000.0f < wi.proj.radius + 24.0f) + { + return; + } + + const float splashRadius = wi.proj.radius > 1.0f ? wi.proj.radius : 96.0f; + const idVec3 splashPoint = weaponTrace.fraction < 1.0f ? weaponTrace.endpos : bs->aimtarget; + const idVec3 enemyCenter = BotAIEntityCenter(entinfo); + if ((splashPoint - enemyCenter).LengthSqr() > Square(splashRadius + 96.0f)) { return; } } - else if( !eyeTraceClear ) + else if (!eyeTraceClear) { return; } } - if( wi.flags & WFL_FIRERELEASED ) + if (wi.flags & WFL_FIRERELEASED) { - if( bs->flags & BFL_ATTACKED ) + if (bs->flags & BFL_ATTACKED) { bs->botinput.actionflags |= ACTION_ATTACK; bs->flags &= ~BFL_ATTACKED; diff --git a/neo/doom3/game/bots/BotAI_Battle_Fight.cpp b/neo/doom3/game/bots/BotAI_Battle_Fight.cpp index 322740a4..f2694d9a 100644 --- a/neo/doom3/game/bots/BotAI_Battle_Fight.cpp +++ b/neo/doom3/game/bots/BotAI_Battle_Fight.cpp @@ -113,8 +113,48 @@ stateResult_t rvmBot::state_BattleFight(stateParms_t* parms) } } - // Move randomly around our AAS area only when there is no useful nearby pickup. - BotMoveInRandomDirection(&bs); + // Fight like a deathmatch bot: keep pressure on the enemy while changing + // strafe direction often enough to be hard to track. + if( bs.attackstrafe_time < Bot_Time() ) + { + bs.attackstrafe_time = Bot_Time() + idMath::FRandRange( 0.65f, 1.35f ); + if( rvmBotUtil::random() < 0.5f ) + { + bs.flags ^= BFL_STRAFERIGHT; + } + } + + idVec3 toEnemy = target - bs.origin; + toEnemy[2] = 0.0f; + const float enemyDist = toEnemy.Length(); + if( enemyDist > 1.0f ) + { + toEnemy /= enemyDist; + idVec3 strafeDir = ( bs.flags & BFL_STRAFERIGHT ) ? idVec3( -toEnemy[1], toEnemy[0], 0.0f ) : idVec3( toEnemy[1], -toEnemy[0], 0.0f ); + idVec3 moveDir = strafeDir; + + if( enemyDist > 520.0f ) + { + moveDir += toEnemy * 0.65f; + } + else if( enemyDist < 170.0f ) + { + moveDir -= toEnemy * 0.75f; + } + + moveDir.Normalize(); + bs.botinput.dir = moveDir; + bs.botinput.speed = pm_runspeed.GetInteger(); + + if( enemyDist < 420.0f && rvmBotUtil::random() < 0.025f ) + { + bs.botinput.actionflags |= ACTION_JUMP; + } + } + else + { + BotMoveInRandomDirection(&bs); + } //aim at the enemy BotAimAtEnemy( &bs ); diff --git a/neo/doom3/game/bots/Bot_char.cpp b/neo/doom3/game/bots/Bot_char.cpp index 53300a92..483a0e76 100644 --- a/neo/doom3/game/bots/Bot_char.cpp +++ b/neo/doom3/game/bots/Bot_char.cpp @@ -259,8 +259,7 @@ int idBotCharacterStatsManager::CheckCharacteristicIndex( bot_character_t* ch, i { if( ch == NULL ) { - gameLocal.Error( "NULL bot character -" ); + gameLocal.Error( "NULL bot character" ); return false; } if( index < 0 || index >= MAX_CHARACTERISTICS ) diff --git a/neo/doom3/game/bots/Bot_goal.cpp b/neo/doom3/game/bots/Bot_goal.cpp index f322187d..241620aa 100644 --- a/neo/doom3/game/bots/Bot_goal.cpp +++ b/neo/doom3/game/bots/Bot_goal.cpp @@ -398,49 +398,50 @@ itemconfig_t* idBotGoalManager::LoadItemConfig( char* filename ) idBotGoalManager::ItemWeightIndex ======================== */ -/* -================ -Bot_ItemWeightAlias - -Some itemconfig entries come from Quake 3 / Team Arena style bot data, but the -Doom 3 weight config usually does not contain every one of those classnames. - -The item weight index must still point at a valid fuzzy weight, so we alias -unsupported / missing items to the closest useful existing Doom 3 weight. -================ -*/ -static const char* Bot_ItemWeightAlias(const char* classname) { - if (!classname || !classname[0]) { +static const char* Bot_ItemWeightAlias( const char* classname ) +{ + if( classname == NULL || classname[0] == '\0' ) + { return NULL; } - struct itemWeightAlias_t { + struct itemWeightAlias_t + { const char* missingName; const char* fallbackName; }; - static const itemWeightAlias_t aliases[] = { - // Missing weapon names commonly seen in Q3 bot item configs. - { "weapon_fists", "weapon_pistol" }, - { "weapon_grapplinghook", "weapon_chainsaw" }, - { "weapon_grenadelauncher", "weapon_rocketlauncher" }, - { "weapon_lightning", "weapon_plasmagun" }, - { "weapon_machinegun", "weapon_machinegun" }, - { "weapon_railgun", "weapon_rocketlauncher" }, - { "weapon_nailgun", "weapon_chaingun" }, - { "weapon_prox_launcher", "weapon_rocketlauncher" }, + static const itemWeightAlias_t aliases[] = + { + { "weapon_machinegun_mp", "weapon_machinegun" }, + { "weapon_shotgun_mp", "weapon_shotgun" }, + { "weapon_rocketlauncher_mp", "weapon_rocketlauncher" }, + { "weapon_plasmagun_mp", "weapon_plasmagun" }, + { "weapon_bfg_mp", "weapon_bfg" }, + { "weapon_grenadelauncher", "weapon_rocketlauncher" }, + { "weapon_grenadelauncher_mp", "weapon_rocketlauncher" }, + { "weapon_lightning", "weapon_plasmagun" }, + { "weapon_lightning_mp", "weapon_plasmagun" }, + { "weapon_railgun", "weapon_rocketlauncher" }, + { "weapon_railgun_mp", "weapon_rocketlauncher" }, + { "weapon_fists", "weapon_pistol" }, + { "weapon_grapplinghook", "weapon_chainsaw" }, + { "weapon_nailgun", "weapon_chaingun" }, + { "weapon_nailgun_mp", "weapon_chaingun" }, + { "weapon_prox_launcher", "weapon_rocketlauncher" }, + { "weapon_prox_launcher_mp", "weapon_rocketlauncher" }, - // Team Arena / CTF objective entities. These are not normal pickup goals. - // Give them a valid low-risk fallback so the index is never invalid. - { "team_redobelisk", "item_armor_shard" }, - { "team_blueobelisk", "item_armor_shard" }, - { "team_neutralobelisk", "item_armor_shard" }, + { "team_redobelisk", "item_armor_shard" }, + { "team_blueobelisk", "item_armor_shard" }, + { "team_neutralobelisk", "item_armor_shard" }, { NULL, NULL } }; - for (int i = 0; aliases[i].missingName != NULL; i++) { - if (idStr::Icmp(classname, aliases[i].missingName) == 0) { + for( int i = 0; aliases[i].missingName != NULL; i++ ) + { + if( idStr::Icmp( classname, aliases[i].missingName ) == 0 ) + { return aliases[i].fallbackName; } } @@ -448,81 +449,84 @@ static const char* Bot_ItemWeightAlias(const char* classname) { return NULL; } -/* -================ -idBotGoalManager::ItemWeightIndex -================ -*/ -int* idBotGoalManager::ItemWeightIndex(weightconfig_t* iwc, itemconfig_t* ic) { - int* index; - int i; - - if (iwc == NULL || ic == NULL || ic->numiteminfo <= 0) { - return NULL; +static bool Bot_ItemClassnamesMatch( const char* entityClassname, const char* itemClassname ) +{ + if( entityClassname == NULL || itemClassname == NULL ) + { + return false; + } + if( idStr::Icmp( entityClassname, itemClassname ) == 0 ) + { + return true; } - // initialize item weight index - index = new int[ic->numiteminfo]; // jmarshall: Get this off of the heap! - - for (i = 0; i < ic->numiteminfo; i++) { - const char* classname = ic->iteminfo[i].classname.c_str(); - - index[i] = botFuzzyWeightManager.FindFuzzyWeight(iwc, (char*)classname); - - if (index[i] < 0) { - const char* aliasName = Bot_ItemWeightAlias(classname); - - if (aliasName != NULL) { - index[i] = botFuzzyWeightManager.FindFuzzyWeight(iwc, (char*)aliasName); - - if (index[i] >= 0) { - common->Printf( - "Bot item weight alias: item info %d \"%s\" using \"%s\"\n", - i, - classname, - aliasName - ); - continue; - } - } - - // Last-resort fallback. This keeps the bot goal system from storing - // invalid fuzzy weight indexes even if the expected alias is missing. - static const char* safeFallbacks[] = { - "item_armor_shard", - "item_armor", - "ammo_clip", - "weapon_pistol", - "weapon_shotgun", - "weapon_machinegun", - NULL - }; - - for (int j = 0; safeFallbacks[j] != NULL; j++) { - index[i] = botFuzzyWeightManager.FindFuzzyWeight(iwc, (char*)safeFallbacks[j]); - if (index[i] >= 0) { - common->Printf( - "Bot item weight fallback: item info %d \"%s\" using \"%s\"\n", - i, - classname, - safeFallbacks[j] - ); - break; - } - } - - if (index[i] < 0) { - common->Warning( - "item info %d \"%s\" has no fuzzy weight and no fallback weight\r\n", - i, - classname - ); - } + idStr normalizedItem = itemClassname; + if( idStr::Icmp( normalizedItem.Right( 3 ).c_str(), "_mp" ) == 0 ) + { + normalizedItem.StripTrailing( "_mp" ); + if( idStr::Icmp( entityClassname, normalizedItem.c_str() ) == 0 ) + { + return true; } } + const char* aliasName = Bot_ItemWeightAlias( itemClassname ); + return aliasName != NULL && idStr::Icmp( entityClassname, aliasName ) == 0; +} + +int* idBotGoalManager::ItemWeightIndex( weightconfig_t* iwc, itemconfig_t* ic ) +{ + int* index; + + if( iwc == NULL || ic == NULL || ic->numiteminfo <= 0 ) + { + return NULL; + } + + //initialize item weight index + index = new int[ic->numiteminfo]; // jmarshall: Get this off of the heap! + + for( int i = 0; i < ic->numiteminfo; i++ ) + { + const char* classname = ic->iteminfo[i].classname.c_str(); + index[i] = botFuzzyWeightManager.FindFuzzyWeight( iwc, ( char* )classname ); + if( index[i] < 0 ) + { + const char* aliasName = Bot_ItemWeightAlias( classname ); + if( aliasName != NULL ) + { + index[i] = botFuzzyWeightManager.FindFuzzyWeight( iwc, ( char* )aliasName ); + } + } + if( index[i] < 0 ) + { + static const char* safeFallbacks[] = + { + "item_armor_shard", + "item_armor", + "ammo_clip", + "weapon_machinegun", + "weapon_shotgun", + NULL + }; + + for( int j = 0; safeFallbacks[j] != NULL; j++ ) + { + index[i] = botFuzzyWeightManager.FindFuzzyWeight( iwc, ( char* )safeFallbacks[j] ); + if( index[i] >= 0 ) + { + break; + } + } + } + if( index[i] < 0 ) + { + common->Warning( "item info %d \"%s\" has no fuzzy weight\r\n", i, classname ); + } + } return index; } + /* ======================== idBotGoalManager::InitLevelItemHeap @@ -552,7 +556,7 @@ levelitem_t* idBotGoalManager::AllocLevelItem( void ) li = freelevelitems; if( !li ) { - gameLocal.Error( "out of level items\n" ); + gameLocal.Warning( "out of bot level items, increase MAX_BOT_LEVEL_ITEMS\n" ); return NULL; } @@ -751,7 +755,7 @@ void idBotGoalManager::InitLevelItems( void ) for( i = 0; i < ic->numiteminfo; i++ ) { - if( !strcmp( classname, ic->iteminfo[i].classname ) ) + if( Bot_ItemClassnamesMatch( classname.c_str(), ic->iteminfo[i].classname.c_str() ) ) { break; } @@ -1517,11 +1521,16 @@ void idBotGoalManager::BotPushGoal( int goalstate, bot_goal_t* goal ) { return; } + if( gs->goalstacktop > 0 && gs->goalstack[gs->goalstacktop].number == goal->number ) + { + gs->goalstack[gs->goalstacktop] = *goal; + return; + } if( gs->goalstacktop >= MAX_GOALSTACK - 1 ) { - gameLocal.Error( "goal heap overflow\n" ); + gameLocal.Warning( "bot goal stack overflow, clearing stale goals\n" ); BotDumpGoalStack( goalstate ); - return; + gs->goalstacktop = 0; } gs->goalstacktop++; gs->goalstack[gs->goalstacktop] = *goal; diff --git a/neo/doom3/game/bots/Bot_goal.h b/neo/doom3/game/bots/Bot_goal.h index 16263e26..f93fe18e 100644 --- a/neo/doom3/game/bots/Bot_goal.h +++ b/neo/doom3/game/bots/Bot_goal.h @@ -3,7 +3,7 @@ #define MAX_BOT_ITEM_INFOS 2 #define MAX_BOT_ITEM_INFO 256 -#define MAX_BOT_LEVEL_ITEMS 256 +#define MAX_BOT_LEVEL_ITEMS 2048 //#define DEBUG_AI_GOAL #ifdef RANDOMIZE @@ -339,4 +339,4 @@ private: idList campspots; }; -extern idBotGoalManager botGoalManager; \ No newline at end of file +extern idBotGoalManager botGoalManager; diff --git a/neo/doom3/game/gamesys/GameTypeInfo.h b/neo/doom3/game/gamesys/GameTypeInfo.h index 9828f263..cb65211d 100644 --- a/neo/doom3/game/gamesys/GameTypeInfo.h +++ b/neo/doom3/game/gamesys/GameTypeInfo.h @@ -8424,7 +8424,7 @@ static classVariableInfo_t bot_goalstate_t_typeInfo[] = { { "int *", "itemweightindex", (intptr_t)(&((bot_goalstate_t *)0)->itemweightindex), sizeof( ((bot_goalstate_t *)0)->itemweightindex ) }, { "int", "client", (intptr_t)(&((bot_goalstate_t *)0)->client), sizeof( ((bot_goalstate_t *)0)->client ) }, { "int", "lastreachabilityarea", (intptr_t)(&((bot_goalstate_t *)0)->lastreachabilityarea), sizeof( ((bot_goalstate_t *)0)->lastreachabilityarea ) }, - { "bot_goal_t[8]", "goalstack", (intptr_t)(&((bot_goalstate_t *)0)->goalstack), sizeof( ((bot_goalstate_t *)0)->goalstack ) }, + { "bot_goal_t[16]", "goalstack", (intptr_t)(&((bot_goalstate_t *)0)->goalstack), sizeof( ((bot_goalstate_t *)0)->goalstack ) }, { "int", "goalstacktop", (intptr_t)(&((bot_goalstate_t *)0)->goalstacktop), sizeof( ((bot_goalstate_t *)0)->goalstacktop ) }, { "int[256]", "avoidgoals", (intptr_t)(&((bot_goalstate_t *)0)->avoidgoals), sizeof( ((bot_goalstate_t *)0)->avoidgoals ) }, { "float[256]", "avoidgoaltimes", (intptr_t)(&((bot_goalstate_t *)0)->avoidgoaltimes), sizeof( ((bot_goalstate_t *)0)->avoidgoaltimes ) }, @@ -8435,7 +8435,7 @@ static classVariableInfo_t idBotGoalManager_typeInfo[] = { { ": bot_goalstate_t[33]", "botgoalstates", (intptr_t)(&((idBotGoalManager *)0)->botgoalstates), sizeof( ((idBotGoalManager *)0)->botgoalstates ) }, { "itemconfig_t", "itemconfiglocal", (intptr_t)(&((idBotGoalManager *)0)->itemconfiglocal), sizeof( ((idBotGoalManager *)0)->itemconfiglocal ) }, { "itemconfig_t *", "itemconfig", (intptr_t)(&((idBotGoalManager *)0)->itemconfig), sizeof( ((idBotGoalManager *)0)->itemconfig ) }, - { "levelitem_t[256]", "levelitemheap", (intptr_t)(&((idBotGoalManager *)0)->levelitemheap), sizeof( ((idBotGoalManager *)0)->levelitemheap ) }, + { "levelitem_t[2048]", "levelitemheap", (intptr_t)(&((idBotGoalManager *)0)->levelitemheap), sizeof( ((idBotGoalManager *)0)->levelitemheap ) }, { "levelitem_t *", "freelevelitems", (intptr_t)(&((idBotGoalManager *)0)->freelevelitems), sizeof( ((idBotGoalManager *)0)->freelevelitems ) }, { "levelitem_t *", "levelitems", (intptr_t)(&((idBotGoalManager *)0)->levelitems), sizeof( ((idBotGoalManager *)0)->levelitems ) }, { "int", "numlevelitems", (intptr_t)(&((idBotGoalManager *)0)->numlevelitems), sizeof( ((idBotGoalManager *)0)->numlevelitems ) }, diff --git a/neo/engine/framework/async/AsyncServer.cpp b/neo/engine/framework/async/AsyncServer.cpp index fd5965e2..2fbd7d90 100644 --- a/neo/engine/framework/async/AsyncServer.cpp +++ b/neo/engine/framework/async/AsyncServer.cpp @@ -2705,6 +2705,10 @@ void idAsyncServer::RunFrame( void ) { continue; } + if (client.channel.GetRemoteAddress().type == NA_BOT) { + continue; + } + // send additional message fragments if the last message was too large to send at once if ( client.channel.UnsentFragmentsLeft() ) { client.channel.SendNextFragment( serverPort, serverTime );