// Bot.cpp // #include "precompiled.h" #include "../Game_local.h" idCVar bot_pathdebug( "bot_pathdebug", "0", CVAR_BOOL | CVAR_CHEAT, "force the bot to path to player" ); 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", "4", CVAR_INTEGER, ""); static int BotSafeAmmoCount( const idInventory& inv, const char* ammoName ) { const int ammoNum = idWeapon::GetAmmoNumForName( ammoName ); if( ammoNum < 0 || ammoNum >= AMMO_NUMTYPES ) { return 0; } return inv.ammo[ammoNum]; } static bool BotValidGameEntityNum( int entityNum ) { return entityNum >= 0 && entityNum < gameLocal.num_entities && gameLocal.entities[entityNum] != NULL; } static bool BotDirectPathIsClear( int passEntityNum, const idVec3& from, const idVec3& to ) { trace_t trace; gameLocal.Trace( trace, from, to, CONTENTS_SOLID | CONTENTS_PLAYERCLIP, passEntityNum ); return trace.fraction >= 0.95f; } static idAngles BotTurnTowardAngles( const idAngles& currentAngles, const idAngles& desiredAngles, float maxTurn ) { idAngles result = currentAngles; for( int i = 0; i < 2; i++ ) { float delta = idMath::AngleDelta( desiredAngles[i], currentAngles[i] ); delta = idMath::ClampFloat( -maxTurn, maxTurn, delta ); result[i] = idMath::AngleMod( currentAngles[i] + delta ); } result[ROLL] = 0.0f; return result; } static idVec3 bot_stuckOrigin[MAX_CLIENTS]; static float bot_stuckTime[MAX_CLIENTS]; static bool bot_stuckInit[MAX_CLIENTS]; /* =================== rvmBot::rvmBot =================== */ rvmBot::rvmBot() { //bs.action = NULL; hasSpawned = false; //gameLocal.RegisterBot( this ); } /* =================== rvmBot::~rvmBot =================== */ rvmBot::~rvmBot() { //gameLocal.UnRegisterBot( this ); } /* ================== rvmBot::SetEnemy ================== */ void rvmBot::SetEnemy( idPlayer* player, idVec3 origin) { if( player == NULL || player == this || player->health <= 0 || player->spectating ) { return; } const bool noEnemy = bs.enemy < 0 || !BotValidGameEntityNum( bs.enemy ) || EntityIsDead( gameLocal.entities[bs.enemy] ); const bool sameEnemy = bs.enemy == player->entityNumber; if( noEnemy || sameEnemy ) { bs.enemy = player->entityNumber; bs.aggressiveAttackTime = gameLocal.SysScriptTime() + 2.0f; bs.lastenemyorigin = origin; bs.last_enemy_visible_position = origin; stateThread.SetState("state_Attacked"); } } /* ================== rvmBot::BotUpdateInventory ================== */ void rvmBot::BotUpdateInventory( void ) { bs.inventory[INVENTORY_ARMOR] = inventory.armor; bs.inventory[INVENTORY_GAUNTLET] = 1; bs.inventory[INVENTORY_SHOTGUN] = HasWeapon( weapon_shotgun ); bs.inventory[INVENTORY_MACHINEGUN] = HasWeapon( weapon_machinegun ); bs.inventory[INVENTORY_GRENADELAUNCHER] = 0; bs.inventory[INVENTORY_ROCKETLAUNCHER] = HasWeapon( weapon_rocketlauncher ); bs.inventory[INVENTORY_LIGHTNING] = 0; bs.inventory[INVENTORY_RAILGUN] = 0; bs.inventory[INVENTORY_PLASMAGUN] = HasWeapon( weapon_plasmagun ); bs.inventory[INVENTORY_BFG10K] = 0; bs.inventory[INVENTORY_GRAPPLINGHOOK] = 0; bs.inventory[INVENTORY_SHELLS] = BotSafeAmmoCount( inventory, "ammo_shells" ); bs.inventory[INVENTORY_BULLETS] = BotSafeAmmoCount( inventory, "ammo_clip" ); bs.inventory[INVENTORY_GRENADES] = 0; bs.inventory[INVENTORY_CELLS] = BotSafeAmmoCount( inventory, "ammo_cells" ); bs.inventory[INVENTORY_LIGHTNINGAMMO] = 0; bs.inventory[INVENTORY_ROCKETS] = BotSafeAmmoCount( inventory, "ammo_rockets" ); bs.inventory[INVENTORY_SLUGS] = 0; bs.inventory[INVENTORY_BFGAMMO] = 0; bs.inventory[INVENTORY_HEALTH] = health; bs.inventory[INVENTORY_TELEPORTER] = 0; bs.inventory[INVENTORY_MEDKIT] = 0; bs.inventory[INVENTORY_QUAD] = 0; bs.inventory[INVENTORY_ENVIRONMENTSUIT] = 0; bs.inventory[INVENTORY_HASTE] = 0; bs.inventory[INVENTORY_INVISIBILITY] = 0; bs.inventory[INVENTORY_REGEN] = 0; bs.inventory[INVENTORY_FLIGHT] = 0; bs.inventory[INVENTORY_REDFLAG] = 0; bs.inventory[INVENTORY_BLUEFLAG] = 0; } /* =================== rvmBot::Spawn =================== */ void rvmBot::Spawn( void ) { idStr botName; char filename[256]; int errnum; stateThread.SetOwner(this); idPlayer::Spawn(); if( gameLocal.isServer ) { weapon_machinegun = SlotForWeapon( "weapon_machinegun" ); weapon_shotgun = SlotForWeapon( "weapon_shotgun" ); weapon_plasmagun = SlotForWeapon( "weapon_plasmagun" ); weapon_rocketlauncher = SlotForWeapon( "weapon_rocketlauncher" ); WP_MACHINEGUN = weapon_machinegun; WP_SHOTGUN = weapon_shotgun; WP_PLASMAGUN = weapon_plasmagun; WP_ROCKET_LAUNCHER = weapon_rocketlauncher; botName = "dark";// spawnArgs.GetString("botname"); aas = gameLocal.GetBotNAV(); if( aas == NULL ) { gameLocal.Error( "Missing NAV\n" ); return; } // Load in the bot character. 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() ); } // Allocate the goal state. bs.gs = botGoalManager.BotAllocGoalState( entityNumber ); // Get the bot items weights file name. botCharacterStatsManager.Characteristic_String( bs.character, CHARACTERISTIC_ITEMWEIGHTS, filename, 256 ); errnum = botGoalManager.BotLoadItemWeights( bs.gs, filename ); if( errnum != BLERR_NOERROR ) { gameLocal.Error( "Failed to load bot item weights!" ); botGoalManager.BotFreeGoalState( bs.gs ); return; } //allocate a weapon state bs.ws = botWeaponInfoManager.BotAllocWeaponState(); //load the weapon weights botCharacterStatsManager.Characteristic_String( bs.character, CHARACTERISTIC_WEAPONWEIGHTS, filename, 256 ); errnum = botWeaponInfoManager.BotLoadWeaponWeights( bs.ws, filename ); if( errnum != BLERR_NOERROR ) { // trap_BotFreeGoalState(bs->gs); botWeaponInfoManager.BotFreeWeaponState( bs.ws ); return; } bs.client = entityNumber; 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"); } } /* =================== rvmBot::Think =================== */ void rvmBot::BotMoveToGoalOrigin(idVec3 goalOrigin) { idVec3 moveDir = goalOrigin - GetPhysics()->GetOrigin(); const float goalDist = moveDir.Length(); // Usercmd forward/right movement is horizontal. Feeding pitch or a view-origin // z delta into the movement vector makes the bot under-steer, drift, or push // into walls while looking up/down at a target. moveDir[2] = 0.0f; if( moveDir.LengthSqr() < Square( 4.0f ) ) { bs.botinput.dir = idVec3( 0.0f, 0.0f, 0.0f ); bs.botinput.speed = 0.0f; } else { if( bs.enemy < 0 && goalDist > 96.0f ) { if( bs.move_wander_time < Bot_Time() ) { bs.move_wander_time = Bot_Time() + idMath::FRandRange( 0.75f, 1.8f ); bs.move_wander_side = rvmBotUtil::crandom(); if( idMath::Fabs( bs.move_wander_side ) < 0.35f ) { bs.move_wander_side = bs.move_wander_side < 0.0f ? -0.35f : 0.35f; } } idVec3 forwardDir = moveDir; forwardDir.Normalize(); idVec3 sideDir( -forwardDir[1], forwardDir[0], 0.0f ); const float wanderScale = idMath::ClampFloat( 0.0f, 64.0f, goalDist * 0.18f ); idVec3 wanderedDir = moveDir + sideDir * ( wanderScale * bs.move_wander_side ); wanderedDir[2] = 0.0f; idVec3 testDir = wanderedDir; if( testDir.Normalize() > 0.0f ) { idVec3 testEnd = GetPhysics()->GetOrigin() + testDir * 56.0f; if( BotDirectPathIsClear( entityNumber, GetPhysics()->GetOrigin() + idVec3( 0.0f, 0.0f, 24.0f ), testEnd + idVec3( 0.0f, 0.0f, 24.0f ) ) ) { moveDir = wanderedDir; } else { bs.move_wander_side = -bs.move_wander_side; } } } bs.botinput.dir = moveDir; bs.botinput.dir.Normalize(); bs.botinput.speed = pm_runspeed.GetInteger(); } if( bs.enemy >= 0 ) { // Combat states set bs.viewangles through BotAimAtEnemy. Do not replace that // with a simple eye-to-eye angle here, or prediction/ground-rocket aiming is lost. bs.botinput.viewangles = bs.viewangles; } else if( moveDir.LengthSqr() > Square( 1.0f ) ) { const idAngles desiredAngles( 0.0f, moveDir.ToYaw(), 0.0f ); bs.botinput.viewangles = BotTurnTowardAngles( viewAngles, desiredAngles, 22.0f ); bs.viewangles = bs.botinput.viewangles; } else { bs.botinput.viewangles = viewAngles; bs.viewangles = viewAngles; } } /* =================== rvmBot::SpawnToPoint =================== */ void rvmBot::SpawnToPoint( const idVec3& spawn_origin, const idAngles& spawn_angles ) { idPlayer::SpawnToPoint( spawn_origin, spawn_angles ); if( gameLocal.isServer ) { bs.ltg_time = 0; stateThread.SetState("state_SeekLTG"); } } /* =================== rvmBot::StateThreadChanged =================== */ void rvmBot::StateThreadChanged(void) { // Ensure if we are switching states, pop the last goal. bs.ltg_time = 0; } /* =================== rvmBot::ServerThink =================== */ void rvmBot::ServerThink( void ) { bs.origin = GetPhysics()->GetOrigin(); bs.eye = GetEyePosition(); bs.thinktime = Bot_Time(); bs.botinput.actionflags = 0; bs.botinput.dir = idVec3( 0.0f, 0.0f, 0.0f ); bs.botinput.speed = 0.0f; bs.botinput.viewangles = viewAngles; bs.viewangles = viewAngles; BotUpdateInventory(); if( bot_pathdebug.IsModified() ) { idPlayer* localPlayer = gameLocal.GetLocalPlayer(); if( bot_pathdebug.GetBool() && localPlayer != NULL ) { bs.currentGoal.origin = localPlayer->GetPhysics()->GetOrigin(); bs.currentGoal.framenum = gameLocal.framenum; } bot_pathdebug.ClearModified(); bot_pathdebug.SetBool( false ); } stateThread.Execute(); if( BotIsDead( &bs ) || spectating || aas == NULL ) { bs.botinput.weapon = bs.weaponnum; bs.attackerEntity = NULL; 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; if( goalOrigin.LengthSqr() > Square( 1.0f ) ) { aasPath_t path; idVec3 org = bs.origin; int curAreaNum = aas->AdjustPositionAndGetArea( org ); idVec3 adjustedGoal = goalOrigin; int goalArea = aas->PointAreaNum( adjustedGoal ); if( goalArea <= 0 ) { goalArea = aas->AdjustPositionAndGetArea( adjustedGoal ); } if( bot_debug.GetBool() ) { aas->ShowWalkPath( GetOrigin(), goalArea, adjustedGoal ); aas->ShowArea( GetOrigin() ); } if( curAreaNum > 0 && goalArea > 0 && aas->WalkPathToGoal( path, curAreaNum, org, goalArea, adjustedGoal, TFL_WALK | TFL_AIR ) ) { moveGoal = path.moveGoal; haveMoveGoal = true; } else if( BotDirectPathIsClear( entityNumber, bs.eye, adjustedGoal + idVec3( 0.0f, 0.0f, 24.0f ) ) ) { moveGoal = adjustedGoal; haveMoveGoal = true; } } // If the selected route is not producing movement, invalidate the current path // and choose a small local NAV point. This prevents repeatedly pushing into the // same wall or corner when the item/chase goal is stale. if( haveMoveGoal ) { const int stuckIndex = entityNumber >= 0 && entityNumber < MAX_CLIENTS ? entityNumber : 0; if( !bot_stuckInit[stuckIndex] || ( bs.origin - bot_stuckOrigin[stuckIndex] ).LengthSqr() > Square( 8.0f ) ) { bot_stuckInit[stuckIndex] = true; bot_stuckOrigin[stuckIndex] = bs.origin; bot_stuckTime[stuckIndex] = Bot_Time(); } else if( Bot_Time() - bot_stuckTime[stuckIndex] > 1.25f ) { bs.ltg_time = 0; bs.nbg_time = 0; BotGetRandomPointNearPosition( bs.origin, bs.random_move_position, 96.0f ); bs.useRandomPosition = true; moveGoal = bs.random_move_position; bot_stuckOrigin[stuckIndex] = bs.origin; bot_stuckTime[stuckIndex] = Bot_Time(); } BotMoveToGoalOrigin( moveGoal ); } else { bs.botinput.dir = idVec3( 0.0f, 0.0f, 0.0f ); bs.botinput.speed = 0.0f; bs.botinput.viewangles = bs.viewangles; } bs.viewangles = bs.botinput.viewangles; bs.useRandomPosition = false; bs.attackerEntity = NULL; bs.botinput.weapon = bs.weaponnum; } /* ======================= rvmBot::Damage ======================= */ void rvmBot::Damage( idEntity* inflictor, idEntity* attacker, const idVec3& dir, const char* damageDefName, const float damageScale, const int location ) { idPlayer::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); idPlayer* player = attacker != NULL ? attacker->Cast() : NULL; if( health <= 0 ) { if( player != NULL ) { BotSendChatMessage( DEATH, player->netname ); } return; } if( player == NULL ) { return; } bs.attackerEntity = player; SetEnemy( player, player->GetOrigin() ); } /* ======================= rvmBot::InflictedDamageEvent ======================= */ void rvmBot::InflictedDamageEvent(idEntity* target) { idPlayer* player = target != NULL ? target->Cast() : NULL; // Don't flood the chat with death and insults. if( player != NULL && !player->IsBot() && player->health <= 0 ) { BotSendChatMessage(KILL, player->netname); } } /* ======================= rvmBot::PresenceTypeBoundingBox ======================= */ void rvmBot::PresenceTypeBoundingBox( int presencetype, idVec3& mins, idVec3& maxs ) { int index; //bounding box size for each presence type //idVec3 boxmins[3] = { {0, 0, 0}, {-15, -15, -24}, {-15, -15, -24} }; //idVec3 boxmaxs[3] = { {0, 0, 0}, { 15, 15, 32}, { 15, 15, 8} }; idVec3 boxmins[3]; idVec3 boxmaxs[3]; boxmins[0] = idVec3( 0, 0, 0 ); boxmins[1] = idVec3( -15, -15, -24 ); boxmins[2] = idVec3( -15, -15, -24 ); boxmaxs[0] = idVec3( 0, 0, 0 ); boxmaxs[1] = idVec3( 15, 15, 32 ); boxmaxs[2] = idVec3( 15, 15, 8 ); if( presencetype == PRESENCE_NORMAL ) { index = 1; } else if( presencetype == PRESENCE_CROUCH ) { index = 2; } else { //botimport.Print(PRT_FATAL, "NAV_PresenceTypeBoundingBox: unknown presence type\n"); index = 2; } mins = boxmins[index]; maxs = boxmaxs[index]; } /* =================== rvmBot::Think =================== */ void rvmBot::Think( void ) { if( !hasSpawned ) { return; } if( gameLocal.isServer ) { ServerThink(); if( bot_debug.GetBool() ) { idVec4 color; color = idVec4(1, 1, 1, 1); if (bs.enemy >= 0) color = idVec4(1, 0, 0, 1); idBounds bounds = idBounds( idVec3( -10, -10, -10 ), idVec3( 10, 10, 10 ) ); gameRenderWorld->DebugBounds( color, bounds, GetOrigin() ); idMat3 axis = viewAngles.ToMat3(); gameRenderWorld->DrawTextA(stateThread.GetState()->state.c_str(), GetOrigin(), 1.0f, color, axis); } } deltaViewAngles.Zero(); idPlayer::Think(); }