1057 lines
29 KiB
Plaintext
1057 lines
29 KiB
Plaintext
#using scripts\shared\array_shared;
|
|
#using scripts\shared\math_shared;
|
|
#using scripts\shared\util_shared;
|
|
|
|
|
|
|
|
|
|
|
|
#using scripts\shared\bots\_bot;
|
|
#using scripts\shared\bots\bot_buttons;
|
|
|
|
|
|
|
|
|
|
#namespace bot_combat;
|
|
|
|
// Combat Think
|
|
//========================================
|
|
|
|
function combat_think()
|
|
{
|
|
if ( self has_threat() )
|
|
{
|
|
// Assume the 'death' notification is always going to come up for threats
|
|
if ( self threat_is_alive() )
|
|
{
|
|
self update_threat();
|
|
}
|
|
else
|
|
{
|
|
self thread [[level.botThreatDead]]();
|
|
}
|
|
}
|
|
|
|
if ( !self has_threat() && !self get_new_threat() )
|
|
{
|
|
return;
|
|
}
|
|
else if ( self has_threat() )
|
|
{
|
|
if ( !self threat_visible() || self.bot.threat.lastDistanceSq > level.botSettings.threatRadiusMaxSq )
|
|
{
|
|
self get_new_threat( level.botSettings.threatRadiusMin );
|
|
}
|
|
}
|
|
|
|
if ( self threat_visible() )
|
|
{
|
|
self thread [[level.botUpdateThreatGoal]]();
|
|
self thread [[level.botThreatEngage]]();
|
|
}
|
|
else
|
|
{
|
|
self thread [[level.botThreatLost]]();
|
|
}
|
|
}
|
|
|
|
|
|
// Default Combat Queries
|
|
//========================================
|
|
|
|
function is_alive( entity )
|
|
{
|
|
// TODO: Add killstreak checks for mp
|
|
return IsAlive( entity );
|
|
}
|
|
|
|
function get_bot_threats( maxDistance )
|
|
{
|
|
if(!isdefined(maxDistance))maxDistance=0;
|
|
|
|
return self BotGetThreats( maxDistance );
|
|
}
|
|
|
|
function get_ai_threats()
|
|
{
|
|
return GetAITeamArray( "axis" );
|
|
}
|
|
|
|
function ignore_none( entity )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
function ignore_non_sentient( entity )
|
|
{
|
|
return !IsSentient( entity );
|
|
}
|
|
|
|
// Threat CRUD
|
|
//========================================
|
|
|
|
function has_threat()
|
|
{
|
|
return ( isdefined( self.bot.threat.entity ) );
|
|
}
|
|
|
|
function threat_visible()
|
|
{
|
|
return self has_threat() && self.bot.threat.visible;
|
|
}
|
|
|
|
function threat_is_alive()
|
|
{
|
|
if ( !self has_threat() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( isdefined( level.botThreatIsAlive) )
|
|
{
|
|
return self [[level.botThreatIsAlive]]( self.bot.threat.entity );
|
|
}
|
|
|
|
return IsAlive( self.bot.threat.entity );
|
|
}
|
|
|
|
function set_threat( entity )
|
|
{
|
|
self.bot.threat.entity = entity;
|
|
self.bot.threat.aimOffset = self get_aim_offset( entity );
|
|
|
|
self update_threat( true );
|
|
}
|
|
|
|
function clear_threat()
|
|
{
|
|
self.bot.threat.entity = undefined;
|
|
self clear_threat_aim();
|
|
self BotLookForward();
|
|
/*
|
|
self.bot.threat.lastPosition = ( 0, 0, 0 );
|
|
self.bot.threat.lastVisibleTime = 0;
|
|
self.bot.threat.lastUpdateTime = 0;
|
|
*/
|
|
}
|
|
|
|
function update_threat( newThreat )
|
|
{
|
|
if ( ( isdefined( newThreat ) && newThreat ) )
|
|
{
|
|
self.bot.threat.wasVisible = false;
|
|
self clear_threat_aim();
|
|
}
|
|
else
|
|
{
|
|
self.bot.threat.wasVisible = self.bot.threat.visible;
|
|
}
|
|
|
|
velocity = self.bot.threat.entity GetVelocity();
|
|
distanceSq = DistanceSquared( self GetEye(), self.bot.threat.entity.origin );
|
|
predictionTime = (isdefined(level.botSettings.thinkInterval)?level.botSettings.thinkInterval:0.05);
|
|
predictedPosition = self.bot.threat.entity.origin + ( velocity * predictionTime );
|
|
aimPoint = predictedPosition + self.bot.threat.aimOffset;
|
|
|
|
dot = self bot::fwd_dot( aimPoint );
|
|
fov = self BotGetFov();
|
|
|
|
if ( ( isdefined( newThreat ) && newThreat ) )
|
|
{
|
|
self.bot.threat.visible = true;
|
|
}
|
|
else if ( dot < fov || !self BotSightTrace( self.bot.threat.entity ) )
|
|
{
|
|
// TODO: Be able to pass the aimPoint into the trace
|
|
self.bot.threat.visible = false;
|
|
return;
|
|
}
|
|
|
|
self.bot.threat.visible = true;
|
|
self.bot.threat.lastVisibleTime = GetTime();
|
|
self.bot.threat.lastDistanceSq = distanceSq;
|
|
self.bot.threat.lastVelocity = velocity;
|
|
self.bot.threat.lastPosition = predictedPosition;
|
|
self.bot.threat.aimPoint = aimPoint;
|
|
self.bot.threat.dot = dot;
|
|
|
|
weapon = self GetCurrentWeapon();
|
|
|
|
weaponRange = weapon_range( weapon );
|
|
self.bot.threat.inRange = distanceSq < ( weaponRange * weaponRange );
|
|
|
|
weaponRangeClose = weapon_range_close( weapon );
|
|
self.bot.threat.inCloseRange = distanceSq < ( weaponRangeClose * weaponRangeClose );
|
|
}
|
|
|
|
|
|
// Get Threats
|
|
//========================================
|
|
|
|
function get_new_threat( maxDistance )
|
|
{
|
|
// TODO: New threat Difficult delay
|
|
entity = self get_greatest_threat( maxDistance );
|
|
|
|
if ( isdefined( entity ) && entity !== self.bot.threat.entity )
|
|
{
|
|
self set_threat( entity );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function get_greatest_threat( maxDistance )
|
|
{
|
|
// TODO: factor in current threat?
|
|
// TODO: Factor in damage
|
|
|
|
threats = self [[level.botGetThreats]]( maxDistance );
|
|
|
|
if ( !isdefined( threats ) )
|
|
{
|
|
return undefined;
|
|
}
|
|
|
|
foreach( entity in threats )
|
|
{
|
|
if ( self [[level.botIgnoreThreat]]( entity ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return entity;
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
|
|
// Threat Engagement
|
|
//========================================
|
|
|
|
function engage_threat()
|
|
{
|
|
if ( !self.bot.threat.wasVisible &&
|
|
self.bot.threat.visible &&
|
|
!self IsThrowingGrenade() &&
|
|
!self FragButtonPressed() &&
|
|
!self SecondaryOffhandButtonPressed() &&
|
|
!self IsSwitchingWeapons() )
|
|
{
|
|
visibleRoll = RandomInt( 100 );
|
|
|
|
rollWeight = (isdefined(level.botSettings.lethalWeight)?level.botSettings.lethalWeight:0);
|
|
if ( visibleRoll < rollWeight &&
|
|
self.bot.threat.lastDistanceSq >= level.botSettings.lethalDistanceMinSq &&
|
|
self.bot.threat.lastDistanceSq <= level.botSettings.lethalDistanceMaxSq &&
|
|
self GetWeaponAmmoStock( self.grenadeTypePrimary ) )
|
|
{
|
|
self clear_threat_aim();
|
|
self throw_grenade( self.grenadeTypePrimary, self.bot.threat.lastPosition );
|
|
return;
|
|
}
|
|
visibleRoll -= rollWeight;
|
|
|
|
rollWeight = (isdefined(level.botSettings.tacticalWeight)?level.botSettings.tacticalWeight:0);
|
|
if ( visibleRoll >= 0 &&
|
|
visibleRoll < rollWeight &&
|
|
self.bot.threat.lastDistanceSq >= level.botSettings.tacticalDistanceMinSq &&
|
|
self.bot.threat.lastDistanceSq <= level.botSettings.tacticalDistanceMaxSq &&
|
|
self GetWeaponAmmoStock( self.grenadeTypeSecondary ) )
|
|
{
|
|
self clear_threat_aim();
|
|
self throw_grenade( self.grenadeTypeSecondary, self.bot.threat.lastPosition );
|
|
return;
|
|
}
|
|
//visbileRoll -= rollWeight;
|
|
// TODO: Fancier hero gadget stuff
|
|
|
|
// Retarget
|
|
self.bot.threat.aimOffset = self get_aim_offset( self.bot.threat.entity );
|
|
}
|
|
|
|
if ( self FragButtonPressed() )
|
|
{
|
|
self throw_grenade( self.grenadeTypePrimary, self.bot.threat.lastPosition );
|
|
return;
|
|
}
|
|
else if ( self SecondaryOffhandButtonPressed() )
|
|
{
|
|
self throw_grenade( self.grenadeTypeSecondary, self.bot.threat.lastPosition );
|
|
return;
|
|
}
|
|
|
|
self update_weapon_aim();
|
|
|
|
if ( self IsReloading() ||
|
|
self IsSwitchingWeapons() ||
|
|
self IsThrowingGrenade() ||
|
|
self FragButtonPressed() ||
|
|
self SecondaryOffhandButtonPressed() ||
|
|
self IsMeleeing() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( melee_attack() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self update_weapon_ads();
|
|
self fire_weapon();
|
|
}
|
|
|
|
// Combat Movment
|
|
//========================================
|
|
|
|
function update_threat_goal()
|
|
{
|
|
if ( self BotUnderManualControl() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self BotGoalSet() &&
|
|
( self.bot.threat.wasVisible || !self.bot.threat.visible ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
radius = get_threat_goal_radius();
|
|
radiusSq = radius * radius;
|
|
|
|
threatDistSq = Distance2DSquared( self.origin, self.bot.threat.lastPosition );
|
|
|
|
if ( threatDistSq < radiusSq || !self BotSetGoal( self.bot.threat.lastPosition, radius ) )
|
|
{
|
|
self combat_strafe();
|
|
}
|
|
}
|
|
|
|
function get_threat_goal_radius( )
|
|
{
|
|
weapon = self GetCurrentWeapon();
|
|
|
|
if ( RandomInt( 100 ) < 10 ||
|
|
weapon.weapClass == "melee" ||
|
|
( !self GetWeaponAmmoClip( weapon ) && !self GetWeaponAmmoStock( weapon ) ) )
|
|
{
|
|
return level.botSettings.meleeRange;
|
|
}
|
|
|
|
return RandomIntRange( level.botSettings.threatRadiusMin, level.botSettings.threatRadiusMax );
|
|
}
|
|
|
|
// Attack
|
|
//========================================
|
|
|
|
function fire_weapon()
|
|
{
|
|
if ( !self.bot.threat.inRange )
|
|
{
|
|
return;
|
|
}
|
|
|
|
weapon = self GetCurrentWeapon();
|
|
|
|
if ( weapon == level.weaponNone ||
|
|
!self GetWeaponAmmoClip( weapon ) ||
|
|
self.bot.threat.dot < weapon_fire_dot( weapon ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( weapon.fireType == "Single Shot" ||
|
|
weapon.fireType == "Burst" ||
|
|
weapon.fireType == "Charge Shot" )
|
|
{
|
|
if ( self AttackButtonPressed() )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
self bot::press_attack_button();
|
|
|
|
if ( weapon.isDualWield )
|
|
{
|
|
self bot::press_throw_button();
|
|
}
|
|
}
|
|
|
|
function melee_attack()
|
|
{
|
|
// TODO: Check for shotgun/ammo and armblades
|
|
|
|
if ( self.bot.threat.dot < level.botSettings.meleeDot )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( DistanceSquared( self.origin, self.bot.threat.lastPosition ) > level.botSettings.meleeRangeSq )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
self bot::tap_melee_button();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Threat Chase
|
|
//========================================
|
|
|
|
function chase_threat()
|
|
{
|
|
if ( self BotUnderManualControl() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// TODO: factor in HasPerk( "specialty_tracker" ) and threat HasPerk( "specialty_trackerjammer" )
|
|
|
|
if ( self.bot.threat.wasVisible && !self.bot.threat.visible )
|
|
{
|
|
self clear_threat_aim();
|
|
self BotSetGoal( self.bot.threat.lastPosition );
|
|
self bot::sprint_to_goal();
|
|
|
|
return;
|
|
}
|
|
|
|
if ( self.bot.threat.lastVisibleTime + (isdefined(level.botSettings.chaseThreatTime)?level.botSettings.chaseThreatTime:0) < GetTime() )
|
|
{
|
|
// Give up looking for this threat
|
|
self clear_threat();
|
|
|
|
return;
|
|
}
|
|
|
|
if ( !self BotGoalSet() )
|
|
{
|
|
self bot::navmesh_wander( self.bot.threat.lastVelocity, self.botSettings.chaseWanderMin, self.botSettings.chaseWanderMax, self.botSettings.chaseWanderSpacing, self.botSettings.chaseWanderFwdDot );
|
|
self clear_threat();
|
|
//self bot::sprint_to_goal();
|
|
}
|
|
}
|
|
|
|
// Aim
|
|
//========================================
|
|
|
|
function get_aim_offset( entity )
|
|
{
|
|
if ( IsSentient( entity ) && RandomInt( 100 ) < (isdefined(level.botSettings.headshotWeight)?level.botSettings.headshotWeight:0) )
|
|
{
|
|
return entity GetEye() - entity.origin;
|
|
}
|
|
|
|
return entity GetCentroid() - entity.origin;
|
|
}
|
|
|
|
function update_weapon_aim()
|
|
{
|
|
if ( !isdefined( self.bot.threat.aimStartTime ) )
|
|
{
|
|
self start_threat_aim();
|
|
}
|
|
|
|
aimTime = GetTime() - self.bot.threat.aimStartTime;
|
|
|
|
if ( aimTime < 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( aimTime >= self.bot.threat.aimTime || !isdefined( self.bot.threat.aimError ) )
|
|
{
|
|
self BotLookAtPoint( self.bot.threat.aimPoint );
|
|
return;
|
|
}
|
|
|
|
eyePoint = self GetEye();
|
|
threatAngles = VectorToAngles( self.bot.threat.aimPoint - eyePoint );
|
|
initialAngles = threatAngles + self.bot.threat.aimError;
|
|
|
|
currAngles = VectorLerp( initialAngles, threatAngles, aimTime / self.bot.threat.aimTime );
|
|
playerAngles = self GetPlayerAngles();
|
|
self BotSetLookAngles( AnglesToForward( currAngles ) );
|
|
}
|
|
|
|
function start_threat_aim()
|
|
{
|
|
self.bot.threat.aimStartTime = GetTime() + (isdefined(level.botSettings.aimDelay)?level.botSettings.aimDelay:0) * 1000;
|
|
self.bot.threat.aimTime = (isdefined(level.botSettings.aimTime)?level.botSettings.aimTime:0) * 1000;
|
|
|
|
pitchError = angleError( (isdefined(level.botSettings.aimErrorMinPitch)?level.botSettings.aimErrorMinPitch:0), (isdefined(level.botSettings.aimErrorMaxPitch)?level.botSettings.aimErrorMaxPitch:0) );
|
|
yawError = angleError( (isdefined(level.botSettings.aimErrorMinYaw)?level.botSettings.aimErrorMinYaw:0), (isdefined(level.botSettings.aimErrorMaxYaw)?level.botSettings.aimErrorMaxYaw:0) );
|
|
|
|
self.bot.threat.aimError = ( pitchError, yawError, 0 );
|
|
}
|
|
|
|
function angleError( angleMin, angleMax )
|
|
{
|
|
angle = angleMax - angleMin;
|
|
angle *= RandomFloatRange( -1, 1 );
|
|
|
|
if ( angle < 0 )
|
|
{
|
|
angle -= angleMin;
|
|
}
|
|
else
|
|
{
|
|
angle += angleMin;
|
|
}
|
|
|
|
return angle;
|
|
}
|
|
|
|
function clear_threat_aim()
|
|
{
|
|
if ( !isdefined( self.bot.threat.aimStartTime ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self.bot.threat.aimStartTime = undefined;
|
|
self.bot.threat.aimTime = undefined;
|
|
self.bot.threat.aimError = undefined;
|
|
}
|
|
|
|
// Pre Combat
|
|
//========================================
|
|
|
|
function bot_pre_combat()
|
|
{
|
|
if ( self has_threat() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Look for whoever is shooting at the bot
|
|
if ( isdefined( self.bot.damage.time ) && self.bot.damage.time + 1500 > GetTime() )
|
|
{
|
|
if ( self has_threat() && self.bot.damage.time > self.bot.threat.lastVisibleTime )
|
|
{
|
|
self clear_threat();
|
|
}
|
|
|
|
self bot::navmesh_wander( self.bot.damage.attackDir, level.botSettings.damageWanderMin, level.botSettings.damageWanderMax, level.botSettings.damageWanderSpacing, level.botSettings.damageWanderFwdDot );
|
|
self bot::end_sprint_to_goal();
|
|
self bot_combat::clear_damage();
|
|
}
|
|
}
|
|
|
|
// Post Combat
|
|
//========================================
|
|
|
|
function bot_post_combat()
|
|
{
|
|
|
|
}
|
|
|
|
// Weapon Stuff
|
|
//========================================
|
|
|
|
function update_weapon_ads()
|
|
{
|
|
if ( !self.bot.threat.inRange || self.bot.threat.inCloseRange )
|
|
{
|
|
return;
|
|
}
|
|
|
|
weapon = self GetCurrentWeapon();
|
|
|
|
if ( weapon == level.weaponNone ||
|
|
weapon.isDualWield ||
|
|
weapon.weapClass == "melee" ||
|
|
self GetWeaponAmmoClip( weapon ) <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self.bot.threat.dot < weapon_ads_dot( weapon ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// TODO: Set ADS time
|
|
self bot::press_ads_button();
|
|
}
|
|
|
|
function weapon_ads_dot( weapon )
|
|
{
|
|
if ( weapon.isSniperWeapon )
|
|
{
|
|
return level.botSettings.sniperAds;
|
|
}
|
|
else if ( weapon.isRocketLauncher )
|
|
{
|
|
return level.botSettings.rocketLauncherAds;
|
|
}
|
|
|
|
switch( weapon.weapClass )
|
|
{
|
|
case "mg":
|
|
return level.botSettings.mgAds;
|
|
case "smg":
|
|
return level.botSettings.smgAds;
|
|
case "spread":
|
|
return level.botSettings.spreadAds;
|
|
case "pistol":
|
|
return level.botSettings.pistolAds;
|
|
case "rifle":
|
|
return level.botSettings.rifleAds;
|
|
}
|
|
|
|
return level.botSettings.defaultAds;
|
|
}
|
|
|
|
function weapon_fire_dot( weapon )
|
|
{
|
|
if ( weapon.isSniperWeapon )
|
|
{
|
|
return level.botSettings.sniperFire;
|
|
}
|
|
else if ( weapon.isRocketLauncher )
|
|
{
|
|
return level.botSettings.rocketLauncherFire;
|
|
}
|
|
|
|
switch( weapon.weapClass )
|
|
{
|
|
case "mg":
|
|
return level.botSettings.mgFire;
|
|
case "smg":
|
|
return level.botSettings.smgFire;
|
|
case "spread":
|
|
return level.botSettings.spreadFire;
|
|
case "pistol":
|
|
return level.botSettings.pistolFire;
|
|
case "rifle":
|
|
return level.botSettings.rifleFire;
|
|
}
|
|
|
|
return level.botSettings.defaultFire;
|
|
}
|
|
|
|
function weapon_range( weapon )
|
|
{
|
|
if ( weapon.isSniperWeapon )
|
|
{
|
|
return level.botSettings.sniperRange;
|
|
}
|
|
else if ( weapon.isRocketLauncher )
|
|
{
|
|
return level.botSettings.rocketLauncherRange;
|
|
}
|
|
|
|
switch( weapon.weapClass )
|
|
{
|
|
case "mg":
|
|
return level.botSettings.mgRange;
|
|
case "smg":
|
|
return level.botSettings.smgRange;
|
|
case "spread":
|
|
return level.botSettings.spreadRange;
|
|
case "pistol":
|
|
return level.botSettings.pistolRange;
|
|
case "rifle":
|
|
return level.botSettings.rifleRange;
|
|
}
|
|
|
|
return level.botSettings.defaultRange;
|
|
}
|
|
|
|
function weapon_range_close( weapon )
|
|
{
|
|
if ( weapon.isSniperWeapon )
|
|
{
|
|
return level.botSettings.sniperRangeClose;
|
|
}
|
|
else if ( weapon.isRocketLauncher )
|
|
{
|
|
return level.botSettings.rocketLauncherRangeClose;
|
|
}
|
|
|
|
switch( weapon.weapClass )
|
|
{
|
|
case "mg":
|
|
return level.botSettings.mgRangeClose;
|
|
case "smg":
|
|
return level.botSettings.smgRangeClose;
|
|
case "spread":
|
|
return level.botSettings.spreadRangeClose;
|
|
case "pistol":
|
|
return level.botSettings.pistolRangeClose;
|
|
case "rifle":
|
|
return level.botSettings.rifleRangeClose;
|
|
}
|
|
|
|
return level.botSettings.defaultRangeClose;
|
|
}
|
|
|
|
function switch_weapon()
|
|
{
|
|
currentWeapon = self GetCurrentWeapon();
|
|
|
|
if ( self IsSwitchingWeapons() ||
|
|
currentWeapon.isHeroWeapon ||
|
|
currentWeapon.isItem )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
weapon = bot::get_ready_gadget();
|
|
|
|
if ( weapon != level.weaponNone )
|
|
{
|
|
if ( !isdefined( level.enemyEmpActive ) || !self [[level.enemyEmpActive]]() )
|
|
{
|
|
self bot::activate_hero_gadget( weapon );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
weapons = self GetWeaponsListPrimaries();
|
|
|
|
// Switch away from a 'sidearm'
|
|
if ( currentWeapon == level.weaponNone ||
|
|
currentWeapon.weapClass == "melee" ||
|
|
currentWeapon.weapClass == "rocketLauncher" ||
|
|
currentWeapon.weapClass == "pistol" )
|
|
{
|
|
foreach( weapon in weapons )
|
|
{
|
|
if ( weapon == currentWeapon )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( self GetWeaponAmmoClip( weapon ) || self GetWeaponAmmoStock( weapon ) )
|
|
{
|
|
self BotSwitchToWeapon( weapon );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
currentAmmoStock = self GetWeaponAmmoStock( currentWeapon );
|
|
if ( currentAmmoStock )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switchFrac = 0.3;
|
|
|
|
currentClipFrac = self weapon_clip_frac( currentWeapon );
|
|
if ( currentClipFrac > switchFrac )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach( weapon in weapons )
|
|
{
|
|
if ( self GetWeaponAmmoStock( weapon ) || self weapon_clip_frac( weapon ) > switchFrac )
|
|
{
|
|
self BotSwitchToWeapon( weapon );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function threat_switch_weapon()
|
|
{
|
|
currentWeapon = self GetCurrentWeapon();
|
|
|
|
if ( self IsSwitchingWeapons() ||
|
|
self GetWeaponAmmoClip( currentWeapon ) ||
|
|
currentWeapon.isItem )
|
|
{
|
|
return;
|
|
}
|
|
|
|
currentAmmoStock = self GetWeaponAmmoStock( currentWeapon );
|
|
weapons = self GetWeaponsListPrimaries();
|
|
|
|
foreach( weapon in weapons )
|
|
{
|
|
// TODO: Check if the target is a scorestreak laong with .requireLockOnToFire
|
|
if ( weapon == currentWeapon || weapon.requireLockOnToFire )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( weapon.weapClass == "melee" )
|
|
{
|
|
// if we have ammo, low chance to switch to melee weapon
|
|
if ( currentAmmoStock && RandomIntRange( 0, 100 ) < 75 )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Dont' switch if we'd have to reload the new weapon, and we can reload this one
|
|
if( !self GetWeaponAmmoClip( weapon ) && currentAmmoStock )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
weaponAmmoStock = self GetWeaponAmmoStock( weapon );
|
|
|
|
// Don't switch if neither has any ammo
|
|
if ( !currentAmmoStock && !weaponAmmoStock )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Maybe don't switch if it's not to a pistol
|
|
if ( weapon.weapClass != "pistol" && RandomIntRange( 0, 100 ) < 75 )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
self BotSwitchToWeapon( weapon );
|
|
}
|
|
}
|
|
|
|
function reload_weapon()
|
|
{
|
|
weapon = self GetCurrentWeapon();
|
|
|
|
if ( !self GetWeaponAmmoStock( weapon ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
reloadFrac = 0.5;
|
|
|
|
if ( weapon.weapClass == "mg" )
|
|
{
|
|
reloadFrac = 0.25;
|
|
}
|
|
|
|
if ( self weapon_clip_frac( weapon ) < reloadFrac )
|
|
{
|
|
self bot::tap_reload_button();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function weapon_clip_frac( weapon )
|
|
{
|
|
if ( weapon.clipSize <= 0 )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
clipAmmo = self GetWeaponAmmoClip( weapon );
|
|
|
|
return ( clipAmmo / weapon.clipSize );
|
|
}
|
|
|
|
// Grenades
|
|
//========================================
|
|
|
|
|
|
function throw_grenade( weapon, target )
|
|
{
|
|
if ( !isdefined( self.bot.threat.aimStartTime ) )
|
|
{
|
|
self aim_grenade( weapon, target );
|
|
self press_grenade_button( weapon );
|
|
return;
|
|
}
|
|
|
|
if ( self.bot.threat.aimStartTime + self.bot.threat.aimTime > GetTime() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self will_hit_target( weapon, target ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self press_grenade_button( weapon );
|
|
}
|
|
|
|
function press_grenade_button( weapon )
|
|
{
|
|
if ( weapon == self.grenadeTypePrimary )
|
|
{
|
|
self bot::press_frag_button();
|
|
}
|
|
else if ( weapon == self.grenadeTypeSecondary )
|
|
{
|
|
self bot::press_offhand_button();
|
|
}
|
|
}
|
|
|
|
function aim_grenade( weapon, target )
|
|
{
|
|
// Just look above the target some
|
|
aimPeak = target + ( 0, 0, 100 );
|
|
|
|
self.bot.threat.aimStartTime = GetTime();
|
|
|
|
self.bot.threat.aimTime = 1500;
|
|
|
|
self BotSetLookAnglesFromPoint( aimPeak );
|
|
}
|
|
|
|
function will_hit_target( weapon, target )
|
|
{
|
|
velocity = get_throw_velocity( weapon );
|
|
|
|
throwOrigin = self GetEye();
|
|
|
|
xyDist = Distance2D( throwOrigin, target );
|
|
xySpeed = Distance2D( velocity, ( 0, 0, 0 ) );
|
|
|
|
t = xyDist / xySpeed;
|
|
|
|
gravity = -GetDvarFloat( "bg_gravity" );
|
|
|
|
tHeight = throwOrigin[2] + velocity[2] * t + ( gravity * t * t * .5 );
|
|
|
|
return Abs( tHeight - target[2] ) < 20;
|
|
}
|
|
|
|
function get_throw_velocity( weapon )
|
|
{
|
|
angles = self GetPlayerAngles();
|
|
forward = AnglesToForward( angles );
|
|
|
|
// Precomputed velocity of most grenades
|
|
return forward * 928;
|
|
|
|
/* TODO: These weapon values aren't actually accessable
|
|
velocity = forward * weapon.projectileSpeed;
|
|
|
|
velocity += AnglesToUp( angles ) * weapon.projectileSpeedRelativeUp;
|
|
|
|
velocity = ( velocity[0], velocity[1], velocity[2] + weapon.projectileSpeedUp );
|
|
|
|
if ( weapon.projectileSpeedForward )
|
|
{
|
|
xyForward = VectorNormalize( ( forward[0], forward[1], 0 ) );
|
|
velocity += xyForward * weapon.projectileSpeedForward;
|
|
}
|
|
|
|
return velocity;
|
|
*/
|
|
}
|
|
|
|
function get_lethal_grenade()
|
|
{
|
|
weaponsList = self GetWeaponsList();
|
|
|
|
foreach( weapon in weaponsList )
|
|
{
|
|
if ( weapon.type == "grenade" && self GetWeaponAmmoStock( weapon ) )
|
|
{
|
|
return weapon;
|
|
}
|
|
}
|
|
|
|
return level.weaponNone;
|
|
}
|
|
|
|
|
|
// Damage
|
|
//========================================
|
|
|
|
function wait_damage_loop()
|
|
{
|
|
self endon( "death" );
|
|
level endon( "game_ended" );
|
|
|
|
while( 1 )
|
|
{
|
|
self waittill( "damage", damage, attacker, direction, point, mod, unused1, unused2, unused3, weapon, flags, inflictor );
|
|
|
|
self.bot.damage.entity = attacker;
|
|
self.bot.damage.amount = damage;
|
|
self.bot.damage.attackDir = VectorNormalize( attacker.origin - self.origin );
|
|
self.bot.damage.weapon = weapon;
|
|
self.bot.damage.mod = mod;
|
|
self.bot.damage.time = GetTime();
|
|
|
|
self thread [[level.onBotDamage]]();
|
|
}
|
|
}
|
|
|
|
function clear_damage()
|
|
{
|
|
self.bot.damage.entity = undefined;
|
|
self.bot.damage.amount = undefined;
|
|
self.bot.damage.direction = undefined;
|
|
self.bot.damage.weapon = undefined;
|
|
self.bot.damage.mod = undefined;
|
|
self.bot.damage.time = undefined;
|
|
}
|
|
|
|
|
|
// Movement
|
|
//========================================
|
|
|
|
function combat_strafe( radiusMin, radiusMax, spacing, sideDotMin, sideDotMax )
|
|
{
|
|
if(!isdefined(radiusMin))radiusMin=(isdefined(level.botSettings.strafeMin)?level.botSettings.strafeMin:0);
|
|
if(!isdefined(radiusMax))radiusMax=(isdefined(level.botSettings.strafeMax)?level.botSettings.strafeMax:0);
|
|
if(!isdefined(spacing))spacing=(isdefined(level.botSettings.strafeSpacing)?level.botSettings.strafeSpacing:0);
|
|
if(!isdefined(sideDotMin))sideDotMin=(isdefined(level.botSettings.strafeSideDotMin)?level.botSettings.strafeSideDotMin:0);
|
|
if(!isdefined(sideDotMax))sideDotMax=(isdefined(level.botSettings.strafeSideDotMax)?level.botSettings.strafeSideDotMax:0);
|
|
|
|
fwd = AnglesToForward( self.angles );
|
|
|
|
/#
|
|
//Circle( self.origin, radiusMin, ( 1, 1, 1 ), false, true, 3 * 20 );
|
|
//Circle( self.origin, radiusMax, ( 1, 1, 1 ), false, true, 3 * 20 );
|
|
//Line( self.origin, self.origin + ( fwd * radiusMax ), ( 1, 1, 1 ), 1, false, 3 * 20 );
|
|
#/
|
|
queryResult = PositionQuery_Source_Navigation( self.origin, radiusMin, radiusMax, 64, spacing, self );
|
|
|
|
best_point = undefined;
|
|
|
|
foreach ( point in queryResult.data )
|
|
{
|
|
moveDir = VectorNormalize( point.origin - self.origin );
|
|
dot = VectorDot( moveDir, fwd );
|
|
|
|
// Don't even consider points outside of the strafe cones
|
|
if ( dot >= sideDotMin && dot <= sideDotMax )
|
|
{
|
|
point.score = MapFloat( radiusMin, radiusMax, 0, 50, point.distToOrigin2D );
|
|
point.score += randomFloatRange( 0, 50 );
|
|
}
|
|
/#
|
|
//Line( point.origin, point.origin + ( 0, 0, point.score ), ( 0, 0, 1 ), 1, false, 3 * 20 );
|
|
#/
|
|
if ( !isdefined( best_point ) || point.score > best_point.score )
|
|
{
|
|
best_point = point;
|
|
}
|
|
}
|
|
|
|
if( isdefined( best_point ) )
|
|
{
|
|
/#
|
|
//Circle( best_point.origin, 16, ( 0, 1, 0 ), false, true, 3 * 20 );
|
|
#/
|
|
self BotSetGoal( best_point.origin );
|
|
self bot::end_sprint_to_goal();
|
|
}
|
|
} |