mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-17 11:00:38 +02:00
Fixes for the bots.
This commit is contained in:
+157
-59
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+693
-723
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -35,6 +35,11 @@ rvmBot::BotSendChatMessage
|
||||
====================
|
||||
*/
|
||||
void rvmBot::BotSendChatMessage(botChat_t chat, const char* targetName) {
|
||||
if( targetName == NULL )
|
||||
{
|
||||
targetName = "";
|
||||
}
|
||||
|
||||
switch (chat)
|
||||
{
|
||||
case KILL:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user