Fixes for the bots.

This commit is contained in:
Justin Marshall
2026-05-19 19:43:23 -07:00
parent af56d4bfeb
commit ed6b826d23
15 changed files with 1419 additions and 1103 deletions
+157 -59
View File
@@ -11,6 +11,29 @@ idCVar bot_showstate( "bot_showstate", "0", CVAR_BOOL | CVAR_CHEAT, "draws the b
idCVar bot_debug( "bot_debug", "0", CVAR_BOOL, "shows debug info for the bot" );
idCVar bot_skill("bot_skill", "3", 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 idVec3 bot_stuckOrigin[MAX_CLIENTS];
static float bot_stuckTime[MAX_CLIENTS];
static bool bot_stuckInit[MAX_CLIENTS];
CLASS_DECLARATION( idPlayer, rvmBot )
END_CLASS
@@ -43,12 +66,20 @@ rvmBot::SetEnemy
*/
void rvmBot::SetEnemy( idPlayer* player, idVec3 origin)
{
if(bs.enemy == -1)
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.action = &botAIBattleRetreat;
bs.last_enemy_visible_position = origin;
stateThread.SetState("state_Attacked");
}
}
@@ -71,12 +102,12 @@ void rvmBot::BotUpdateInventory( void )
bs.inventory[INVENTORY_PLASMAGUN] = HasWeapon( weapon_plasmagun );
bs.inventory[INVENTORY_BFG10K] = 0;
bs.inventory[INVENTORY_GRAPPLINGHOOK] = 0;
bs.inventory[INVENTORY_SHELLS] = inventory.ammo[idWeapon::GetAmmoNumForName( "ammo_shells" )];
bs.inventory[INVENTORY_BULLETS] = inventory.ammo[idWeapon::GetAmmoNumForName( "ammo_clip" )];
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] = inventory.ammo[idWeapon::GetAmmoNumForName( "ammo_cells" )];
bs.inventory[INVENTORY_CELLS] = BotSafeAmmoCount( inventory, "ammo_cells" );
bs.inventory[INVENTORY_LIGHTNINGAMMO] = 0;
bs.inventory[INVENTORY_ROCKETS] = inventory.ammo[idWeapon::GetAmmoNumForName( "ammo_rockets" )];
bs.inventory[INVENTORY_ROCKETS] = BotSafeAmmoCount( inventory, "ammo_rockets" );
bs.inventory[INVENTORY_SLUGS] = 0;
bs.inventory[INVENTORY_BFGAMMO] = 0;
bs.inventory[INVENTORY_HEALTH] = health;
@@ -170,6 +201,7 @@ void rvmBot::Spawn( void )
hasSpawned = true;
bs.botinput.respawn = true;
bs.botinput.lastWeaponNum = -1;
stateThread.SetState("state_Respawn");
}
@@ -182,22 +214,41 @@ rvmBot::Think
*/
void rvmBot::BotMoveToGoalOrigin(idVec3 goalOrigin)
{
bs.botinput.dir = (goalOrigin - firstPersonViewOrigin);
idAngles desiredAngles = bs.botinput.dir.ToAngles();
idVec3 moveDir = goalOrigin - GetPhysics()->GetOrigin();
// 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
{
bs.botinput.dir = moveDir;
bs.botinput.dir.Normalize();
bs.botinput.speed = pm_runspeed.GetInteger();
}
if( bs.enemy >= 0 )
{
idPlayer* enemy = gameLocal.entities[bs.enemy]->Cast<idPlayer>();
if( enemy )
{
desiredAngles = (enemy->firstPersonViewOrigin - firstPersonViewOrigin).ToAngles();
}
// 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 ) )
{
bs.botinput.viewangles = idAngles( 0.0f, moveDir.ToYaw(), 0.0f );
bs.viewangles = bs.botinput.viewangles;
}
else
{
bs.botinput.viewangles = viewAngles;
bs.viewangles = viewAngles;
}
bs.botinput.viewangles = desiredAngles;
bs.botinput.speed = pm_runspeed.GetInteger();
bs.botinput.dir.Normalize();
}
/*
@@ -236,14 +287,19 @@ void rvmBot::ServerThink( void )
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() )
{
if( bot_pathdebug.GetBool() )
idPlayer* localPlayer = gameLocal.GetLocalPlayer();
if( bot_pathdebug.GetBool() && localPlayer != NULL )
{
bs.currentGoal.origin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin();
bs.currentGoal.origin = localPlayer->GetPhysics()->GetOrigin();
bs.currentGoal.framenum = gameLocal.framenum;
}
@@ -253,43 +309,83 @@ void rvmBot::ServerThink( void )
stateThread.Execute();
// If we are moving along a set of waypoints, let's move along.
aasPath_t path;
//int myArea = aas->PointAreaNum(GetOrigin());
int goalArea = aas->PointAreaNum( bs.currentGoal.origin );
idVec3 org = bs.origin;
int curAreaNum = aas->AdjustPositionAndGetArea( org );
if( bot_debug.GetBool() )
if( BotIsDead( &bs ) || spectating || aas == NULL )
{
if (bs.useRandomPosition)
{
aas->ShowWalkPath(GetOrigin(), goalArea, bs.random_move_position);
}
else
{
aas->ShowWalkPath(GetOrigin(), goalArea, bs.currentGoal.origin);
}
aas->ShowArea( GetOrigin() );
bs.botinput.weapon = bs.weaponnum;
bs.attackerEntity = NULL;
return;
}
if (bs.useRandomPosition)
idVec3 goalOrigin = bs.useRandomPosition ? bs.random_move_position : bs.currentGoal.origin;
idVec3 moveGoal = goalOrigin;
bool haveMoveGoal = false;
if( goalOrigin.LengthSqr() > Square( 1.0f ) )
{
aas->WalkPathToGoal(path, curAreaNum, org, goalArea, bs.random_move_position, TFL_WALK | TFL_AIR);
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 AAS 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
{
aas->WalkPathToGoal(path, curAreaNum, org, goalArea, bs.currentGoal.origin, TFL_WALK | TFL_AIR);
bs.botinput.dir = idVec3( 0.0f, 0.0f, 0.0f );
bs.botinput.speed = 0.0f;
bs.botinput.viewangles = bs.viewangles;
}
idVec3 moveGoal = path.moveGoal;
BotMoveToGoalOrigin(path.moveGoal);
bs.viewangles = bs.botinput.viewangles;
bs.useRandomPosition = false;
bs.attackerEntity = NULL; // Has to be consumed immedaitly.
bs.attackerEntity = NULL;
bs.botinput.weapon = bs.weaponnum;
}
@@ -302,21 +398,23 @@ void rvmBot::Damage( idEntity* inflictor, idEntity* attacker, const idVec3& dir,
{
idPlayer::Damage( inflictor, attacker, dir, damageDefName, damageScale, location );
idPlayer* player = attacker->Cast<idPlayer>();
if (health <= 0)
{
if (player)
idPlayer* player = attacker != NULL ? attacker->Cast<idPlayer>() : NULL;
if( health <= 0 )
{
if( player != NULL )
{
BotSendChatMessage(DEATH, player->netname );
BotSendChatMessage( DEATH, player->netname );
}
}
if (attacker == NULL) {
return;
}
//bs.attackerEntity = attacker;
SetEnemy(player, attacker->GetOrigin());
if( player == NULL )
{
return;
}
bs.attackerEntity = player;
SetEnemy( player, player->GetOrigin() );
}
/*
@@ -325,10 +423,10 @@ rvmBot::InflictedDamageEvent
=======================
*/
void rvmBot::InflictedDamageEvent(idEntity* target) {
idPlayer* player = target->Cast<idPlayer>();
idPlayer* player = target != NULL ? target->Cast<idPlayer>() : NULL;
// Don't flood the chat with death and insults.
if (!player->IsBot() && player->health <= 0)
if( player != NULL && !player->IsBot() && player->health <= 0 )
{
BotSendChatMessage(KILL, player->netname);
}
+1
View File
@@ -811,6 +811,7 @@ struct bot_state_t
float enemysight_time; //time before reacting to enemy
float enemydeath_time; //time the enemy died
float aggressiveAttackTime;
float enemyposition_time;
idVec3 origin;
idVec3 aimtarget;
idVec3 random_move_position;
File diff suppressed because it is too large Load Diff
+33 -1
View File
@@ -18,6 +18,38 @@ stateResult_t rvmBot::state_Attacked(stateParms_t* parms) {
return SRESULT_DONE_FRAME;
}
if( bs.enemy < 0 || bs.enemy >= gameLocal.num_entities || gameLocal.entities[bs.enemy] == NULL )
{
stateThread.SetState("state_SeekLTG");
return SRESULT_DONE_FRAME;
}
// Update combat distance before weapon selection so the bot can pick
// shotgun/rocket/plasma appropriately instead of using stale inventory
// values from the previous state.
BotUpdateBattleInventory( &bs, bs.enemy );
BotChooseWeapon( &bs );
// If we were ambushed while under-armed, do not stand and trade pistol shots.
// Use the existing BattleNBG state so the bot can keep aiming/firing while it
// moves through a nearby weapon, ammo, health, or armor pickup.
if( bs.check_time < Bot_Time() )
{
bs.check_time = Bot_Time() + 0.35f;
bot_goal_t pickupAnchor;
pickupAnchor.Reset();
pickupAnchor.entitynum = bs.enemy;
pickupAnchor.origin = bs.lastenemyorigin;
pickupAnchor.mins = idVec3( -8.0f, -8.0f, -8.0f );
pickupAnchor.maxs = idVec3( 8.0f, 8.0f, 8.0f );
if( BotNearbyGoal( &bs, 0, &pickupAnchor, 300.0f ) )
{
bs.nbg_time = Bot_Time() + 18.0f;
stateThread.SetState("state_BattleNBG");
return SRESULT_DONE_FRAME;
}
}
if (gameLocal.SysScriptTime() > bs.aggressiveAttackTime || bs.weaponnum == 0) {
stateThread.SetState("state_Retreat");
return SRESULT_DONE;
@@ -47,4 +79,4 @@ stateResult_t rvmBot::state_Attacked(stateParms_t* parms) {
BotCheckAttack(&bs);
return SRESULT_WAIT;
}
}
+20 -11
View File
@@ -36,8 +36,18 @@ stateResult_t rvmBot::state_Chase(stateParms_t* parms)
return SRESULT_DONE_FRAME;
}
if( parms->stage == 0 )
{
if( bs.chase_time <= Bot_Time() )
{
bs.chase_time = Bot_Time() + 10.0f;
}
parms->stage = 1;
}
//if no enemy
if( bs.enemy < 0 )
if( bs.enemy < 0 || bs.enemy >= gameLocal.num_entities || gameLocal.entities[bs.enemy] == NULL ||
bs.client < 0 || bs.client >= gameLocal.num_entities || gameLocal.entities[bs.client] == NULL )
{
stateThread.SetState("state_SeekLTG");
return SRESULT_DONE_FRAME;
@@ -92,7 +102,7 @@ stateResult_t rvmBot::state_Chase(stateParms_t* parms)
// if for some reason we don't have line of sight to it, switch to LTG.
trace_t trace;
gameLocal.Trace( trace, bs.last_enemy_visible_position, gameLocal.entities[bs.client]->GetOrigin(), CONTENTS_SOLID, 0 );
gameLocal.Trace( trace, bs.last_enemy_visible_position, gameLocal.entities[bs.client]->GetOrigin(), CONTENTS_SOLID, bs.entitynum );
if( trace.fraction <= 0.9f )
{
@@ -111,30 +121,29 @@ stateResult_t rvmBot::state_Chase(stateParms_t* parms)
}
//if there's no chase time left
if( !bs.chase_time || bs.chase_time < Bot_Time() - 10 )
if( !bs.chase_time || bs.chase_time < Bot_Time() )
{
//AIEnter_Seek_LTG(bs, "battle chase: time out");
stateThread.SetState("state_SeekLTG");
return SRESULT_DONE_FRAME;
}
//check for nearby goals periodicly
//check for nearby goals periodically. Chasing should still route through a
// nearby weapon/ammo pickup instead of running past it, especially when the bot
// only has pistol/machinegun or is dry on ammo.
if( bs.check_time < Bot_Time() )
{
bs.check_time = Bot_Time() + 1;
range = 150;
//
bs.check_time = Bot_Time() + 0.5f;
range = 260.0f;
if( BotNearbyGoal( &bs, 0, &goal, range ) )
{
//the bot gets 5 seconds to pick up the nearby goal item
bs.nbg_time = Bot_Time() + 0.1 * range + 1;
//BotResetLastAvoidReach(bs.ms);
//AIEnter_Battle_NBG(bs, "battle chase: nbg");
bs.nbg_time = Bot_Time() + 18.0f;
stateThread.SetState("state_BattleNBG");
return SRESULT_DONE_FRAME;
}
}
BotUpdateBattleInventory( &bs, bs.enemy );
BotChooseWeapon( &bs );
////initialize the movement state
//BotSetupForMovement(bs);
+43 -75
View File
@@ -12,7 +12,6 @@ rvmBot::state_BattleFight
*/
stateResult_t rvmBot::state_BattleFight(stateParms_t* parms)
{
int areanum;
idVec3 target;
idPlayer* entinfo;
@@ -30,14 +29,18 @@ stateResult_t rvmBot::state_BattleFight(stateParms_t* parms)
}
//if no enemy
if( bs.enemy < 0 )
if( bs.enemy < 0 || bs.enemy >= gameLocal.num_entities || gameLocal.entities[bs.enemy] == NULL )
{
stateThread.SetState("state_SeekLTG");
return SRESULT_DONE_FRAME;
}
//BotEntityInfo(bs.enemy, &entinfo);
entinfo = gameLocal.entities[bs.enemy]->Cast<idPlayer>();// &g_entities[bs.enemy];
entinfo = gameLocal.entities[bs.enemy]->Cast<idPlayer>();
if( entinfo == NULL )
{
stateThread.SetState("state_SeekLTG");
return SRESULT_DONE_FRAME;
}
//if the enemy is dead
if( bs.enemydeath_time )
@@ -45,106 +48,72 @@ stateResult_t rvmBot::state_BattleFight(stateParms_t* parms)
if( bs.enemydeath_time < Bot_Time() - 1.0 )
{
bs.enemydeath_time = 0;
if( bs.enemysuicide )
{
// jmarshall - bot chat
//BotChat_EnemySuicide(bs);
// jmarshall end
}
// jmarshall - bot chat stand
// if (bs.lastkilledplayer == bs.enemy && BotChat_Kill(bs)) {
// bs.stand_time = FloatTime() + BotChatTime(bs);
// AIEnter_Stand(bs, "battle fight: enemy dead");
// }
// else {
bs.ltg_time = 0;
//AIEnter_Seek_LTG(bs, "battle fight: enemy dead");
stateThread.SetState("state_SeekLTG");
// }
// jmarshall end
return SRESULT_DONE_FRAME;
}
}
else
else if( EntityIsDead( entinfo ) )
{
if( EntityIsDead( entinfo ) )
{
bs.enemydeath_time = Bot_Time();
}
bs.enemydeath_time = Bot_Time();
}
// jmarshall - isinvisible
//if the enemy is invisible and not shooting the bot looses track easily
//if (entinfo->IsInvisible() && !entinfo->IsShooting()) {
// if (rvmBotUtil::random() < 0.2) {
// stateThread.SetState("state_SeekLTG");
// return;
// }
//}
//
target = entinfo->GetOrigin();
//update the reachability area and origin if possible
//areanum = BotPointAreaNum(target);
//if (areanum && trap_AAS_AreaReachability(areanum)) {
// VectorCopy(target, bs.lastenemyorigin);
// bs.lastenemyareanum = areanum;
//}
// bs.lastenemyareanum = areanum;
target = entinfo->GetOrigin();
//update the attack inventory values
BotUpdateBattleInventory( &bs, bs.enemy );
//if the bot's health decreased
// jmarshall - bot chat
//if (bs.lastframe_health > bs.inventory[INVENTORY_HEALTH]) {
// if (BotChat_HitNoDeath(bs)) {
// bs.stand_time = FloatTime() + BotChatTime(bs);
// AIEnter_Stand(bs, "battle fight: chat health decreased");
// return qfalse;
// }
//}
//if the bot hit someone
//if (bs.cur_ps.persistant[PERS_HITS] > bs.lasthitcount) {
// if (BotChat_HitNoKill(bs)) {
// bs.stand_time = FloatTime() + BotChatTime(bs);
// AIEnter_Stand(bs, "battle fight: chat hit someone");
// return qfalse;
// }
//}
// jmarshall end
//if the enemy is not visible
if( !BotEntityVisible( bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy ) )
{
if( BotWantsToChase( &bs ) )
{
//AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight");
bs.chase_time = Bot_Time() + 10.0f;
stateThread.SetState("state_Chase");
return SRESULT_DONE_FRAME;
}
else
{
//AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight");
stateThread.SetState("state_SeekLTG");
return SRESULT_DONE_FRAME;
}
}
bs.enemyvisible_time = Bot_Time();
bs.lastenemyorigin = target;
bs.last_enemy_visible_position = target;
//use holdable items
BotBattleUseItems( &bs );
//
//bs.tfl = TFL_DEFAULT;
//if (bot_grapple.integer) bs.tfl |= TFL_GRAPPLEHOOK;
////if in lava or slime the bot should be able to get out
//if (BotInLavaOrSlime(bs)) bs.tfl |= TFL_LAVA | TFL_SLIME;
////
//if (BotCanAndWantsToRocketJump(bs)) {
// bs.tfl |= TFL_ROCKETJUMP;
//}
//choose the best weapon to fight with
BotChooseWeapon( &bs );
// Move randomly around our AAS area.
// Opportunistic combat pickup: keep fighting, but route through a nearby item
// instead of throwing the current goal away and random-strafing forever. The
// NBG selector itself expands this range when the bot is stuck on pistol/dry
// ammo, and otherwise keeps the detour short.
if( bs.check_time < Bot_Time() )
{
bs.check_time = Bot_Time() + 0.45f;
bot_goal_t pickupAnchor;
pickupAnchor.Reset();
pickupAnchor.entitynum = bs.enemy;
pickupAnchor.origin = target;
pickupAnchor.mins = idVec3( -8.0f, -8.0f, -8.0f );
pickupAnchor.maxs = idVec3( 8.0f, 8.0f, 8.0f );
const float pickupRange = 260.0f;
if( BotNearbyGoal( &bs, 0, &pickupAnchor, pickupRange ) )
{
bs.nbg_time = Bot_Time() + 18.0f;
stateThread.SetState("state_BattleNBG");
return SRESULT_DONE_FRAME;
}
}
// Move randomly around our AAS area only when there is no useful nearby pickup.
BotMoveInRandomDirection(&bs);
//aim at the enemy
@@ -158,11 +127,10 @@ stateResult_t rvmBot::state_BattleFight(stateParms_t* parms)
{
if( BotWantsToRetreat( &bs ) )
{
//AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat");
stateThread.SetState("state_Retreat");
return SRESULT_DONE_FRAME;
}
}
return SRESULT_WAIT;
}
}
+12 -97
View File
@@ -12,23 +12,9 @@ rvmBot::state_BattleNBG
*/
stateResult_t rvmBot::state_BattleNBG(stateParms_t* parms)
{
int areanum;
bot_goal_t goal;
//aas_entityinfo_t entinfo;
idEntity* entinfo;
//bot_moveresult_t moveresult;
float attack_skill;
idVec3 target, dir;
//if (BotIsObserver(bs)) {
// AIEnter_Observer(bs, "battle nbg: observer");
// return qfalse;
//}
////if in the intermission
//if (BotIntermission(bs)) {
// AIEnter_Intermission(bs, "battle nbg: intermission");
// return qfalse;
//}
idPlayer* entinfo;
idVec3 target;
// respawn if dead.
if( BotIsDead( &bs ) )
@@ -38,56 +24,31 @@ stateResult_t rvmBot::state_BattleNBG(stateParms_t* parms)
}
// if no enemy.
if( bs.enemy < 0 )
if( bs.enemy < 0 || bs.enemy >= gameLocal.num_entities || gameLocal.entities[bs.enemy] == NULL )
{
stateThread.SetState("state_SeekLTG");
return SRESULT_DONE_FRAME;
}
//BotEntityInfo(bs.enemy, &entinfo);
entinfo = gameLocal.entities[bs.enemy]->Cast<idPlayer>();
if( entinfo == NULL )
{
stateThread.SetState("state_SeekLTG");
return SRESULT_DONE_FRAME;
}
if( entinfo->health <= 0 )
{
//AIEnter_Seek_NBG(bs, "battle nbg: enemy dead");
stateThread.SetState("state_SeekNBG");
return SRESULT_DONE_FRAME;
}
//bs.tfl = TFL_DEFAULT;
//if (bot_grapple.integer) bs.tfl |= TFL_GRAPPLEHOOK;
////if in lava or slime the bot should be able to get out
//if (BotInLavaOrSlime(bs)) bs.tfl |= TFL_LAVA | TFL_SLIME;
////
//if (BotCanAndWantsToRocketJump(bs)) {
// bs.tfl |= TFL_ROCKETJUMP;
//}
////map specific code
//BotMapScripts(bs);
//update the last time the enemy was visible
if( BotEntityVisible( bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy ) )
{
bs.enemyvisible_time = Bot_Time();
//VectorCopy(entinfo->GetOrigin(), target);
target = entinfo->GetOrigin();
// if not a player enemy
if( bs.enemy >= MAX_CLIENTS )
{
#ifdef MISSIONPACK
// if attacking an obelisk
if( bs.enemy == redobelisk.entitynum ||
bs.enemy == blueobelisk.entitynum )
{
target[2] += 16;
}
#endif
}
//update the reachability area and origin if possible
//areanum = BotPointAreaNum(target);
//if (areanum && trap_AAS_AreaReachability(areanum)) {
VectorCopy( target, bs.lastenemyorigin );
// bs.lastenemyareanum = areanum;
//}
bs.lastenemyorigin = target;
bs.last_enemy_visible_position = target;
}
//if the bot has no goal or touches the current goal
@@ -99,20 +60,16 @@ stateResult_t rvmBot::state_BattleNBG(stateParms_t* parms)
{
bs.nbg_time = 0;
}
//
if( bs.nbg_time < Bot_Time() )
{
//pop the current goal from the stack
botGoalManager.BotPopGoal( bs.gs );
//if the bot still has a goal
if( botGoalManager.BotGetTopGoal( bs.gs, &goal ) )
{
//AIEnter_Battle_Retreat(bs, "battle nbg: time out");
stateThread.SetState("state_Retreat");
}
else
{
//AIEnter_Battle_Fight(bs, "battle nbg: time out");
stateThread.SetState("state_BattleFight");
}
@@ -122,57 +79,15 @@ stateResult_t rvmBot::state_BattleNBG(stateParms_t* parms)
//move towards the goal
BotMoveToGoal( &bs, &goal );
//initialize the movement state
//BotSetupForMovement(bs);
////move towards the goal
//trap_BotMoveToGoal(&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);
// bs.nbg_time = 0;
//}
////
//BotAIBlocked(bs, &moveresult, qfalse);
//update the attack inventory values
BotUpdateBattleInventory( &bs, bs.enemy );
//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)) {
// attack_skill = trap_Characteristic_BFloat(bs.character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
// //if the bot is skilled anough and the enemy is visible
// if (attack_skill > 0.3) {
// //&& BotEntityVisible(bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy)
// BotAimAtEnemy(bs);
// }
// else {
// 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 (attack_skill > 0.3) {
//&& BotEntityVisible(bs.entitynum, bs.eye, bs.viewangles, 360, bs.enemy)
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 SRESULT_WAIT;
}
}
+27 -9
View File
@@ -26,7 +26,7 @@ stateResult_t rvmBot::state_Retreat(stateParms_t* parms)
}
// if no enemy.
if( bs.enemy < 0 )
if( bs.enemy < 0 || bs.enemy >= gameLocal.num_entities || gameLocal.entities[bs.enemy] == NULL )
{
stateThread.SetState("state_SeekLTG");
return SRESULT_DONE_FRAME;
@@ -57,6 +57,27 @@ stateResult_t rvmBot::state_Retreat(stateParms_t* parms)
//update the attack inventory values
BotUpdateBattleInventory( &bs, bs.enemy );
BotChooseWeapon( &bs );
// Before switching back to chase, opportunistically grab a nearby tactical
// pickup. This keeps the bot from running past a rocket launcher, shells, or
// health while it is already committed to a fight.
if( bs.check_time < Bot_Time() )
{
bs.check_time = Bot_Time() + 0.5f;
bot_goal_t pickupAnchor;
pickupAnchor.Reset();
pickupAnchor.entitynum = bs.enemy;
pickupAnchor.origin = entinfo->GetOrigin();
pickupAnchor.mins = idVec3( -8.0f, -8.0f, -8.0f );
pickupAnchor.maxs = idVec3( 8.0f, 8.0f, 8.0f );
if( BotNearbyGoal( &bs, 0, &pickupAnchor, 260.0f ) )
{
bs.nbg_time = Bot_Time() + 18.0f;
stateThread.SetState("state_BattleNBG");
return SRESULT_DONE_FRAME;
}
}
//if the bot doesn't want to retreat anymore... probably picked up some nice items
if( BotWantsToChase( &bs ) )
@@ -76,6 +97,7 @@ stateResult_t rvmBot::state_Retreat(stateParms_t* parms)
bs.enemyvisible_time = Bot_Time();
target = entinfo->GetOrigin();
bs.lastenemyorigin = target;
bs.last_enemy_visible_position = target;
}
//if the enemy is NOT visible for 4 seconds
@@ -108,19 +130,15 @@ stateResult_t rvmBot::state_Retreat(stateParms_t* parms)
return SRESULT_DONE_FRAME;
}
//check for nearby goals periodicly
//check for nearby goals periodically while retreating along the long-term item path
if( bs.check_time < Bot_Time() )
{
bs.check_time = Bot_Time() + 1;
range = 150;
bs.check_time = Bot_Time() + 0.5f;
range = 260.0f;
//
if( BotNearbyGoal( &bs, 0, &goal, range ) )
{
//trap_BotResetLastAvoidReach(bs.ms);
//time the bot gets to pick up the nearby goal item
bs.nbg_time = Bot_Time() + range / 100 + 1;
//AIEnter_Battle_NBG(bs, "battle retreat: nbg");
bs.nbg_time = Bot_Time() + 18.0f;
stateThread.SetState("state_BattleNBG");
return SRESULT_DONE_FRAME;
}
+52 -59
View File
@@ -16,65 +16,47 @@ void rvmBot::BotInputToUserCommand(bot_input_t* bi, usercmd_t* ucmd, int time)
{
idVec3 forward, right;
short temp;
int j;
if( bi == NULL || ucmd == NULL )
{
return;
}
//clear the whole structure
// memset(ucmd, 0, sizeof(usercmd_t));
//
//common->Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed);
//the duration for the user command in milli seconds
//
if (bi->actionflags & ACTION_DELAYEDJUMP)
{
bi->actionflags |= ACTION_JUMP;
bi->actionflags &= ~ACTION_DELAYEDJUMP;
}
//set the buttons
if (bi->actionflags & ACTION_RESPAWN)
{
ucmd->buttons = BUTTON_ATTACK;
ucmd->buttons |= BUTTON_ATTACK;
}
if (bi->actionflags & ACTION_ATTACK)
{
ucmd->buttons |= BUTTON_ATTACK;
}
//if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK;
//if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE;
//if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE;
if (bi->actionflags & ACTION_WALK)
{
ucmd->buttons |= BUTTON_RUN;
}
//if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE;
//if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE;
//if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG;
//if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE;
//if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL;
//if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME;
//
ucmd->impulse |= bi->weapon;
ucmd->impulse = 0;
if (bi->lastWeaponNum != bi->weapon)
{
//ucmd->flags = UCF_IMPULSE_SEQUENCE;
ucmd->impulse = bi->weapon;
bi->lastWeaponNum = bi->weapon;
}
else
{
//ucmd->flags = 0;
}
idAngles botViewAngles = viewAngles;
// Slow smoothing made bots fire while their usercmd view was still several
// frames behind the computed aim, which is very noticeable when looking down
// stairs. Turn briskly in general and snap on the frames where the bot is
// actually firing.
const float turnScale = ( bi->actionflags & ACTION_ATTACK ) ? 1.0f : 0.35f;
for (int i = 0; i < 3; i++)
{
int i;
float move;
float angMod = (1.0f / 12.0f);
for (i = 0; i < 3; i++) {
move = idMath::AngleDelta(bi->viewangles[i], botViewAngles[i]);
botViewAngles[i] += (move * angMod);
}
const float move = idMath::AngleDelta( bi->viewangles[i], botViewAngles[i] );
botViewAngles[i] += move * turnScale;
}
//set the view angles
@@ -83,46 +65,57 @@ void rvmBot::BotInputToUserCommand(bot_input_t* bi, usercmd_t* ucmd, int time)
ucmd->angles[1] = ANGLE2SHORT(botViewAngles[1] - deltaViewAngles[1]);
ucmd->angles[2] = ANGLE2SHORT(botViewAngles[2] - deltaViewAngles[2]);
bi->viewangles.ToVectors(&forward, &right, NULL);
// Movement commands are relative to the view angles that are actually put into
// this usercmd, not the unsmoothed desired angles. Also keep movement on the
// horizontal plane so looking up/down at an enemy does not change speed.
idAngles moveAngles = botViewAngles;
moveAngles[PITCH] = 0.0f;
moveAngles[ROLL] = 0.0f;
moveAngles.ToVectors(&forward, &right, NULL);
//bot input speed is in the range [0, 400]
bi->speed = bi->speed * 127 / 400;
//set the view independent movement
ucmd->forwardmove = idMath::ClampChar(DotProduct(forward, bi->dir) * bi->speed);
ucmd->rightmove = idMath::ClampChar(DotProduct(right, bi->dir) * bi->speed);
//ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed;
idVec3 moveDir = bi->dir;
moveDir[2] = 0.0f;
if( moveDir.LengthSqr() > Square( 0.001f ) )
{
moveDir.Normalize();
}
else
{
moveDir = idVec3( 0.0f, 0.0f, 0.0f );
}
float scaledSpeed = bi->speed;
if( scaledSpeed < 0.0f )
{
scaledSpeed = 0.0f;
}
else if( scaledSpeed > 400.0f )
{
scaledSpeed = 400.0f;
}
scaledSpeed = scaledSpeed * 127.0f / 400.0f;
ucmd->forwardmove = idMath::ClampChar( DotProduct(forward, moveDir) * scaledSpeed );
ucmd->rightmove = idMath::ClampChar( DotProduct(right, moveDir) * scaledSpeed );
//normal keyboard movement
if (bi->actionflags & ACTION_MOVEFORWARD)
{
ucmd->forwardmove += 127;
ucmd->forwardmove = idMath::ClampChar( ucmd->forwardmove + 127 );
}
if (bi->actionflags & ACTION_MOVEBACK)
{
ucmd->forwardmove -= 127;
ucmd->forwardmove = idMath::ClampChar( ucmd->forwardmove - 127 );
}
if (bi->actionflags & ACTION_MOVELEFT)
{
ucmd->rightmove -= 127;
ucmd->rightmove = idMath::ClampChar( ucmd->rightmove - 127 );
}
if (bi->actionflags & ACTION_MOVERIGHT)
{
ucmd->rightmove += 127;
ucmd->rightmove = idMath::ClampChar( ucmd->rightmove + 127 );
}
//jump/moveup
//if (bi->actionflags & ACTION_JUMP)
// ucmd->buttons |= BUTTON_JUMP;
// ucmd->upmove += 127;
//
////crouch/movedown
//if (bi->actionflags & ACTION_CROUCH)
// ucmd->upmove -= 127;
//
//Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove);
//Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime);
if( bi->respawn )
{
ucmd->buttons |= BUTTON_ATTACK;
+16 -4
View File
@@ -59,7 +59,6 @@ bot_character_t* idBotCharacterStatsManager::AllocBotCharacter( void )
{
memset( &ch->c[0], 0, MAX_CHARACTERISTICS * sizeof( bot_characteristic_t ) );
ch->filename = "";
ch->inUse = false;
ch->skill = 0;
}
else
@@ -70,6 +69,8 @@ bot_character_t* idBotCharacterStatsManager::AllocBotCharacter( void )
ch->inUse = true;
return ch;
}
@@ -157,7 +158,7 @@ bot_character_t* idBotCharacterStatsManager::BotLoadCharacterFromFile( const cha
}
index = token.GetIntValue();
if( index < 0 || index > MAX_CHARACTERISTICS )
if( index < 0 || index >= MAX_CHARACTERISTICS )
{
FreeCharacterFile( ch );
parser.Error( "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS );
@@ -256,6 +257,12 @@ idBotCharacterStatsManager::CheckCharacteristicIndex
*/
int idBotCharacterStatsManager::CheckCharacteristicIndex( bot_character_t* ch, int index )
{
if( ch == NULL )
{
gameLocal.Error( "NULL bot character
" );
return false;
}
if( index < 0 || index >= MAX_CHARACTERISTICS )
{
gameLocal.Error( "characteristic %d does not exist\n", index );
@@ -309,6 +316,12 @@ Characteristic_String
*/
void idBotCharacterStatsManager::Characteristic_String( bot_character_t* ch, int index, char* buf, int size )
{
if( buf == NULL || size <= 0 )
{
return;
}
buf[0] = '\0';
//check if the index is in range
if( !CheckCharacteristicIndex( ch, index ) )
{
@@ -318,8 +331,7 @@ void idBotCharacterStatsManager::Characteristic_String( bot_character_t* ch, int
//an integer will be converted to a float
if( ch->c[index].type == CT_STRING )
{
strcpy( buf, ch->c[index].value.string );
buf[size - 1] = '\0';
idStr::Copynz( buf, ch->c[index].value.string, size );
return;
}
gameLocal.Error( "characteristic %d is not a string\n", index );
+5
View File
@@ -35,6 +35,11 @@ rvmBot::BotSendChatMessage
====================
*/
void rvmBot::BotSendChatMessage(botChat_t chat, const char* targetName) {
if( targetName == NULL )
{
targetName = "";
}
switch (chat)
{
case KILL:
+318 -33
View File
@@ -9,6 +9,143 @@ idCVar bot_itemsfile( "bot_itemsfile", "items.c", CVAR_CHEAT, "" );
idBotGoalManager botGoalManager;
static bool BotGoalItemNameContains( const iteminfo_t* itemInfo, const char* needle ) {
if( itemInfo == NULL || needle == NULL ) {
return false;
}
return strstr( itemInfo->classname.c_str(), needle ) != NULL || strstr( itemInfo->name.c_str(), needle ) != NULL;
}
static bool BotGoalHasUsableWeapon( 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 );
}
static bool BotGoalHasPowerWeapon( 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 );
}
static bool BotGoalHasOnlyPistolOrDryWeapons( const int* inventory ) {
if( inventory == NULL ) {
return true;
}
const bool ownsNonPistolWeapon = inventory[INVENTORY_MACHINEGUN] > 0 ||
inventory[INVENTORY_SHOTGUN] > 0 ||
inventory[INVENTORY_PLASMAGUN] > 0 ||
inventory[INVENTORY_ROCKETLAUNCHER] > 0;
if( !ownsNonPistolWeapon ) {
return true;
}
if( !BotGoalHasUsableWeapon( inventory ) ) {
return true;
}
const bool onlyMachinegun = inventory[INVENTORY_MACHINEGUN] > 0 &&
inventory[INVENTORY_SHOTGUN] <= 0 &&
inventory[INVENTORY_PLASMAGUN] <= 0 &&
inventory[INVENTORY_ROCKETLAUNCHER] <= 0;
return onlyMachinegun && inventory[INVENTORY_BULLETS] < 25;
}
static float BotGoalCombatItemBias( const iteminfo_t* itemInfo, const int* inventory ) {
if( itemInfo == NULL || inventory == NULL ) {
return 0.0f;
}
const bool desperate = BotGoalHasOnlyPistolOrDryWeapons( inventory );
const bool lacksPowerWeapon = !BotGoalHasPowerWeapon( inventory );
float bias = 0.0f;
// Weapons: route through these during a fight, especially if the bot is stuck
// on pistol/machinegun or is out of usable ammo.
if( BotGoalItemNameContains( itemInfo, "weapon_rocketlauncher" ) || BotGoalItemNameContains( itemInfo, "rocketlauncher" ) ) {
if( inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || desperate ) {
bias += desperate ? 9000.0f : 4200.0f;
}
else if( inventory[INVENTORY_ROCKETS] <= 3 ) {
bias += 2200.0f;
}
}
else if( BotGoalItemNameContains( itemInfo, "weapon_plasmagun" ) || BotGoalItemNameContains( itemInfo, "plasmagun" ) ) {
if( inventory[INVENTORY_PLASMAGUN] <= 0 || desperate ) {
bias += desperate ? 8200.0f : 3800.0f;
}
else if( inventory[INVENTORY_CELLS] <= 18 ) {
bias += 1800.0f;
}
}
else if( BotGoalItemNameContains( itemInfo, "weapon_shotgun" ) || BotGoalItemNameContains( itemInfo, "shotgun" ) ) {
if( inventory[INVENTORY_SHOTGUN] <= 0 || desperate || lacksPowerWeapon ) {
bias += desperate ? 7600.0f : 3200.0f;
}
else if( inventory[INVENTORY_SHELLS] <= 6 ) {
bias += 1600.0f;
}
}
else if( BotGoalItemNameContains( itemInfo, "weapon_machinegun" ) || BotGoalItemNameContains( itemInfo, "machinegun" ) ) {
if( inventory[INVENTORY_MACHINEGUN] <= 0 || desperate ) {
bias += desperate ? 6200.0f : 1800.0f;
}
else if( inventory[INVENTORY_BULLETS] <= 30 ) {
bias += 1200.0f;
}
}
else if( BotGoalItemNameContains( itemInfo, "ammo_rockets" ) || BotGoalItemNameContains( itemInfo, "rocket_ammo" ) ) {
if( inventory[INVENTORY_ROCKETLAUNCHER] > 0 && inventory[INVENTORY_ROCKETS] <= 5 ) {
bias += inventory[INVENTORY_ROCKETS] <= 1 ? 4200.0f : 2400.0f;
}
}
else if( BotGoalItemNameContains( itemInfo, "ammo_cells" ) || BotGoalItemNameContains( itemInfo, "cells" ) ) {
if( inventory[INVENTORY_PLASMAGUN] > 0 && inventory[INVENTORY_CELLS] <= 25 ) {
bias += inventory[INVENTORY_CELLS] <= 8 ? 3600.0f : 2000.0f;
}
}
else if( BotGoalItemNameContains( itemInfo, "ammo_shells" ) || BotGoalItemNameContains( itemInfo, "shells" ) ) {
if( inventory[INVENTORY_SHOTGUN] > 0 && inventory[INVENTORY_SHELLS] <= 10 ) {
bias += inventory[INVENTORY_SHELLS] <= 3 ? 3200.0f : 1700.0f;
}
}
else if( BotGoalItemNameContains( itemInfo, "ammo_clip" ) || BotGoalItemNameContains( itemInfo, "bullets" ) ) {
if( inventory[INVENTORY_MACHINEGUN] > 0 && inventory[INVENTORY_BULLETS] <= 45 ) {
bias += inventory[INVENTORY_BULLETS] <= 10 ? 2800.0f : 1300.0f;
}
}
// Health/armor should also interrupt a fight, but only when it is tactically
// worth the detour. This still lets fuzzy item weights handle normal roaming.
if( BotGoalItemNameContains( itemInfo, "health" ) ) {
if( inventory[INVENTORY_HEALTH] < 35 ) {
bias += 5200.0f;
}
else if( inventory[INVENTORY_HEALTH] < 65 ) {
bias += 2600.0f;
}
else if( inventory[INVENTORY_HEALTH] < 95 ) {
bias += 900.0f;
}
}
if( BotGoalItemNameContains( itemInfo, "armor" ) ) {
if( inventory[INVENTORY_ARMOR] < 25 ) {
bias += 2400.0f;
}
else if( inventory[INVENTORY_ARMOR] < 75 ) {
bias += 1100.0f;
}
}
return bias;
}
/*
==================
idBotGoalManager::idBotGoalManager
@@ -261,24 +398,131 @@ itemconfig_t* idBotGoalManager::LoadItemConfig( char* filename )
idBotGoalManager::ItemWeightIndex
========================
*/
int* idBotGoalManager::ItemWeightIndex( weightconfig_t* iwc, itemconfig_t* ic )
{
int* index, i;
/*
================
Bot_ItemWeightAlias
//initialize item weight index
index = new int[( sizeof( int ) * ic->numiteminfo )]; // jmarshall: Get this off of the heap!
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.
for( i = 0; i < ic->numiteminfo; i++ )
{
index[i] = botFuzzyWeightManager.FindFuzzyWeight( iwc, ( char* )ic->iteminfo[i].classname.c_str() );
if( index[i] < 0 )
{
common->Warning( "item info %d \"%s\" has no fuzzy weight\r\n", i, ic->iteminfo[i].classname.c_str() );
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]) {
return NULL;
}
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" },
// 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" },
{ NULL, NULL }
};
for (int i = 0; aliases[i].missingName != NULL; i++) {
if (idStr::Icmp(classname, aliases[i].missingName) == 0) {
return aliases[i].fallbackName;
}
}
return index;
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;
}
// 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
);
}
}
}
return index;
}
/*
========================
idBotGoalManager::InitLevelItemHeap
@@ -490,6 +734,11 @@ void idBotGoalManager::InitLevelItems( void )
//for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent))
for( int idx = 0; idx < gameLocal.num_entities; idx++ )
{
if( gameLocal.entities[idx] == NULL )
{
continue;
}
idItem* ent = gameLocal.entities[idx]->Cast<idItem>();
if( ent == nullptr )
@@ -606,6 +855,12 @@ void idBotGoalManager::BotGoalName( int number, char* name, int size )
{
levelitem_t* li;
if( name == NULL || size <= 0 )
{
return;
}
name[0] = '\0';
if( !itemconfig )
{
return;
@@ -615,8 +870,7 @@ void idBotGoalManager::BotGoalName( int number, char* name, int size )
{
if( li->number == number )
{
name[size - 1] = '\0';
strcpy( name, itemconfig->iteminfo[li->iteminfo].name );
idStr::Copynz( name, itemconfig->iteminfo[li->iteminfo].name, size );
return;
}
}
@@ -858,7 +1112,7 @@ int idBotGoalManager::BotGetLevelItemGoal( int index, char* name, bot_goal_t* go
{
// goal->areanum = li->goalareanum;
goal->origin = li->goalorigin;
goal->entitynum = li->item->entityNumber;
goal->entitynum = li->item != NULL ? li->item->entityNumber : 0;
goal->mins = itemconfig->iteminfo[li->iteminfo].mins;
goal->maxs = itemconfig->iteminfo[li->iteminfo].maxs;
goal->number = li->number;
@@ -953,9 +1207,14 @@ void idBotGoalManager::BotFindEntityForLevelItem( levelitem_t* li )
}
for( int idx = 0; idx < gameLocal.num_entities; idx++ )
{
if( gameLocal.entities[idx] == NULL )
{
continue;
}
idItem* ent = gameLocal.entities[idx]->Cast<idItem>();
if( ent == NULL )
if( ent == nullptr )
{
continue;
}
@@ -1032,6 +1291,11 @@ void idBotGoalManager::UpdateEntityItems( void )
//
for( int idx = 0; idx < gameLocal.num_entities; idx++ )
{
if( gameLocal.entities[idx] == NULL )
{
continue;
}
idItem* ent = gameLocal.entities[idx]->Cast<idItem>();
if( ent == nullptr )
@@ -1430,17 +1694,25 @@ int idBotGoalManager::BotChooseLTGItem( int goalstate, idVec3 origin, int* inven
}
//get the fuzzy weight function for this item
iteminfo = &ic->iteminfo[li->iteminfo];
const float tacticalBias = BotGoalCombatItemBias( iteminfo, inventory );
weightnum = gs->itemweightindex[iteminfo->number];
if( weightnum < 0 )
{
continue;
if( tacticalBias <= 0.0f )
{
continue;
}
weight = 0.0f;
}
else
{
#ifdef UNDECIDEDFUZZY
weight = FuzzyWeightUndecided( inventory, gs->itemweightconfig, weightnum );
weight = FuzzyWeightUndecided( inventory, gs->itemweightconfig, weightnum );
#else
weight = botFuzzyWeightManager.FuzzyWeight( inventory, gs->itemweightconfig, weightnum );
weight = botFuzzyWeightManager.FuzzyWeight( inventory, gs->itemweightconfig, weightnum );
#endif //UNDECIDEDFUZZY
}
weight += tacticalBias;
#ifdef DROPPEDWEIGHT
//HACK: to make dropped items more attractive
if( li->timeout )
@@ -1517,7 +1789,7 @@ int idBotGoalManager::BotChooseLTGItem( int goalstate, idVec3 origin, int* inven
VectorCopy( iteminfo->mins, goal.mins );
VectorCopy( iteminfo->maxs, goal.maxs );
// goal.areanum = bestitem->goalareanum;
goal.entitynum = bestitem->item->entityNumber;
goal.entitynum = bestitem->item != NULL ? bestitem->item->entityNumber : 0;
goal.number = bestitem->number;
goal.flags = GFL_ITEM;
if( bestitem->timeout )
@@ -1656,17 +1928,25 @@ int idBotGoalManager::BotChooseNBGItem( int goalstate, idVec3 origin, int* inven
}
//get the fuzzy weight function for this item
iteminfo = &ic->iteminfo[li->iteminfo];
const float tacticalBias = BotGoalCombatItemBias( iteminfo, inventory );
weightnum = gs->itemweightindex[iteminfo->number];
if( weightnum < 0 )
{
continue;
if( tacticalBias <= 0.0f )
{
continue;
}
weight = 0.0f;
}
//
else
{
#ifdef UNDECIDEDFUZZY
weight = FuzzyWeightUndecided( inventory, gs->itemweightconfig, weightnum );
weight = FuzzyWeightUndecided( inventory, gs->itemweightconfig, weightnum );
#else
weight = botFuzzyWeightManager.FuzzyWeight( inventory, gs->itemweightconfig, weightnum );
weight = botFuzzyWeightManager.FuzzyWeight( inventory, gs->itemweightconfig, weightnum );
#endif //UNDECIDEDFUZZY
}
weight += tacticalBias;
#ifdef DROPPEDWEIGHT
//HACK: to make dropped items more attractive
if( li->timeout )
@@ -1709,8 +1989,11 @@ int idBotGoalManager::BotChooseNBGItem( int goalstate, idVec3 origin, int* inven
t = gameLocal.TravelTimeToGoal( li->goalorigin, ltg->origin );
// jmarshall end
} //end if
//if the travel back is possible and doesn't take too long
if( t <= ltg_time )
// For ordinary detours, keep the original "on the way" check. For tactical
// combat pickups such as a missing weapon, ammo, or low-health pickup, allow
// a short off-route move because surviving the fight is more important than
// staying glued to the enemy/chase goal.
if( tacticalBias > 0.0f || t <= ltg_time )
{
bestweight = weight;
bestitem = li;
@@ -1730,7 +2013,7 @@ int idBotGoalManager::BotChooseNBGItem( int goalstate, idVec3 origin, int* inven
VectorCopy( iteminfo->mins, goal.mins );
VectorCopy( iteminfo->maxs, goal.maxs );
// goal.areanum = bestitem->goalareanum;
goal.entitynum = bestitem->item->entityNumber;
goal.entitynum = bestitem->item != NULL ? bestitem->item->entityNumber : 0;
goal.number = bestitem->number;
goal.flags = GFL_ITEM;
if( bestitem->timeout )
@@ -1818,7 +2101,7 @@ int idBotGoalManager::BotItemGoalInVisButNotVisible( int viewer, idVec3 eye, idA
return false;
}
VectorAdd( goal->mins, goal->mins, middle );
VectorAdd( goal->mins, goal->maxs, middle );
VectorScale( middle, 0.5, middle );
VectorAdd( goal->origin, middle, middle );
@@ -1926,11 +2209,13 @@ void idBotGoalManager::BotFreeItemWeights( int goalstate )
if( gs->itemweightconfig )
{
botFuzzyWeightManager.FreeWeightConfig( gs->itemweightconfig );
gs->itemweightconfig = NULL;
}
if( gs->itemweightindex )
{
delete[] gs->itemweightindex;
gs->itemweightindex = NULL;
}
// jmarshall - eval
//if (gs->itemweightindex)
// FreeMemory(gs->itemweightindex);
// jmarshall end
}
/*
+19 -10
View File
@@ -17,7 +17,7 @@ idBotWeaponInfoManager::BotValidWeaponNumber
*/
int idBotWeaponInfoManager::BotValidWeaponNumber( int weaponnum )
{
if( weaponnum <= 0 || weaponnum > BOT_MAX_WEAPONS )
if( weaponnum < 0 || weaponnum >= BOT_MAX_WEAPONS )
{
gameLocal.Error( "weapon number out of range\n" );
return false;
@@ -35,6 +35,7 @@ bot_weaponstate_t* idBotWeaponInfoManager::BotWeaponStateFromHandle( int handle
if( handle <= 0 || handle > MAX_CLIENTS )
{
gameLocal.Error( "move state handle %d out of range\n", handle );
return NULL;
}
return &botweaponstates[handle];
@@ -374,19 +375,27 @@ idBotWeaponInfoManager::BotGetWeaponInfo
*/
void idBotWeaponInfoManager::BotGetWeaponInfo( int weaponstate, int weapon, weaponinfo_t* weaponinfo )
{
bot_weaponstate_t* ws;
if( !BotValidWeaponNumber( weapon ) )
{
return;
}
ws = BotWeaponStateFromHandle( weaponstate );
if( !ws )
if( weaponinfo == NULL )
{
return;
}
*weaponinfo = this->weaponinfo[weapon];
if( BotValidWeaponNumber( weapon ) && this->weaponinfo[weapon].valid )
{
*weaponinfo = this->weaponinfo[weapon];
return;
}
// Fallback: return the first loaded weapon so callers never keep an
// uninitialized weaponinfo_t when a bot has not picked a valid weapon yet.
for( int i = 0; i < BOT_MAX_WEAPONS; i++ )
{
if( this->weaponinfo[i].valid )
{
*weaponinfo = this->weaponinfo[i];
return;
}
}
}
/*
+22 -22
View File
@@ -48,14 +48,15 @@ bool idBotFuzzyWeightManager::ReadValue( idParser& source, float* value )
{
idToken token;
if( !source.ReadToken( &token ) )
if( value == NULL || !source.ReadToken( &token ) )
{
return false;
}
bool negative = false;
if( token == "-" )
{
source.Warning( "negative value set to zero\n" );
negative = true;
if( !source.ExpectTokenType( TT_NUMBER, 0, &token ) )
{
return false;
@@ -68,8 +69,7 @@ bool idBotFuzzyWeightManager::ReadValue( idParser& source, float* value )
return false;
}
*value = token.GetFloatValue();
*value = negative ? -token.GetFloatValue() : token.GetFloatValue();
return true;
}
@@ -382,16 +382,17 @@ weightconfig_t* idBotFuzzyWeightManager::ReadWeightConfig( char* filename )
if( config->inUse )
{
if( config->filename == filename )
{
return config;
}
continue;
}
if( config->filename == filename )
if( avail == -1 )
{
return config;
avail = n;
}
avail = n;
break;
}
if( avail == -1 )
@@ -570,10 +571,10 @@ float idBotFuzzyWeightManager::FuzzyWeight_r( int* inventory, fuzzyseperator_t*
}
//the scale factor
scale = ( inventory[fs->index] - fs->value ) / ( fs->next->value - fs->value );
scale = ( float )( inventory[fs->index] - fs->value ) / ( float )( fs->next->value - fs->value );
//scale between the two weights
return scale * w1 + ( 1 - scale ) * w2;
return ( 1 - scale ) * w1 + scale * w2;
}
return FuzzyWeight_r( inventory, fs->next );
}
@@ -625,10 +626,10 @@ float idBotFuzzyWeightManager::FuzzyWeightUndecided_r( int* inventory, fuzzysepe
}
//the scale factor
scale = ( inventory[fs->index] - fs->value ) / ( fs->next->value - fs->value );
scale = ( float )( inventory[fs->index] - fs->value ) / ( float )( fs->next->value - fs->value );
//scale between the two weights
return scale * w1 + ( 1 - scale ) * w2;
return ( 1 - scale ) * w1 + scale * w2;
}
return FuzzyWeightUndecided_r( inventory, fs->next );
}
@@ -722,7 +723,7 @@ void idBotFuzzyWeightManager::ScaleFuzzySeperator_r( fuzzyseperator_t* fs, float
}
else if( fs->type == WT_BALANCE )
{
fs->weight = ( fs->maxweight + fs->minweight ) * scale;
fs->weight = fs->minweight + ( fs->maxweight - fs->minweight ) * scale;
//get the weight between bounds
if( fs->weight < fs->minweight )
@@ -832,7 +833,7 @@ int idBotFuzzyWeightManager::InterbreedFuzzySeperator_r( fuzzyseperator_t* fs1,
gameLocal.Error( "cannot interbreed weight configs, unequal child\n" );
return false;
}
if( !InterbreedFuzzySeperator_r( fs2->child, fs2->child, fsout->child ) )
if( !InterbreedFuzzySeperator_r( fs1->child, fs2->child, fsout->child ) )
{
return false;
}
@@ -849,7 +850,7 @@ int idBotFuzzyWeightManager::InterbreedFuzzySeperator_r( fuzzyseperator_t* fs1,
{
fsout->maxweight = fsout->weight;
}
if( fsout->weight > fsout->minweight )
if( fsout->weight < fsout->minweight )
{
fsout->minweight = fsout->weight;
}
@@ -905,11 +906,10 @@ void idBotFuzzyWeightManager::BotShutdownWeights( void )
for( i = 0; i < MAX_WEIGHT_FILES; i++ )
{
weightFileList[i].inUse = false;
//if (weightFileList[i])
//{
// FreeWeightConfig2(weightFileList[i]);
// weightFileList[i] = NULL;
//}
if( weightFileList[i].inUse )
{
FreeWeightConfig2( &weightFileList[i] );
}
weightFileList[i].Reset();
}
}
+1
View File
@@ -8476,6 +8476,7 @@ static classVariableInfo_t bot_state_t_typeInfo[] = {
{ "float", "enemysight_time", (intptr_t)(&((bot_state_t *)0)->enemysight_time), sizeof( ((bot_state_t *)0)->enemysight_time ) },
{ "float", "enemydeath_time", (intptr_t)(&((bot_state_t *)0)->enemydeath_time), sizeof( ((bot_state_t *)0)->enemydeath_time ) },
{ "float", "aggressiveAttackTime", (intptr_t)(&((bot_state_t *)0)->aggressiveAttackTime), sizeof( ((bot_state_t *)0)->aggressiveAttackTime ) },
{ "float", "enemyposition_time", (intptr_t)(&((bot_state_t *)0)->enemyposition_time), sizeof( ((bot_state_t *)0)->enemyposition_time ) },
{ "idVec3", "origin", (intptr_t)(&((bot_state_t *)0)->origin), sizeof( ((bot_state_t *)0)->origin ) },
{ "idVec3", "aimtarget", (intptr_t)(&((bot_state_t *)0)->aimtarget), sizeof( ((bot_state_t *)0)->aimtarget ) },
{ "idVec3", "random_move_position", (intptr_t)(&((bot_state_t *)0)->random_move_position), sizeof( ((bot_state_t *)0)->random_move_position ) },