1 #using scripts\shared\array_shared;
2 #using scripts\shared\math_shared;
3 #using scripts\shared\util_shared;
5 #insert scripts\shared\shared.gsh;
6 #insert scripts\shared\bots\_bot.gsh;
7 #insert scripts\shared\abilities\_ability_util.gsh;
9 #using scripts\shared\bots\_bot;
10 #using scripts\shared\bots\bot_buttons;
12 #define EXPLOSION_RADIUS_FRAG 256
13 #define EXPLOSION_RADIUS_FLASH 650
15 #namespace bot_combat;
31 self thread [[level.botThreatDead]]();
41 if ( !
self threat_visible() ||
self.bot.threat.lastDistanceSq > level.botSettings.threatRadiusMaxSq )
49 self thread [[level.botUpdateThreatGoal]]();
50 self thread [[level.botThreatEngage]]();
54 self thread [[level.botThreatLost]]();
65 return IsAlive( entity );
72 return self BotGetThreats( maxDistance );
77 return GetAITeamArray(
"axis" );
87 return !IsSentient( entity );
95 return ( isdefined(
self.bot.threat.entity ) );
100 return self has_threat() &&
self.bot.threat.visible;
110 if ( isdefined( level.botThreatIsAlive) )
112 return self [[level.botThreatIsAlive]](
self.bot.threat.entity );
115 return IsAlive(
self.bot.threat.entity );
120 self.bot.threat.entity = entity;
128 self.bot.threat.entity = undefined;
130 self BotLookForward();
142 self.bot.threat.wasVisible =
false;
147 self.bot.threat.wasVisible =
self.bot.threat.visible;
150 velocity =
self.bot.threat.entity GetVelocity();
151 distanceSq = DistanceSquared(
self GetEye(),
self.bot.threat.entity.origin );
152 predictionTime =
VAL( level.botSettings.thinkInterval, 0.05 );
153 predictedPosition =
self.bot.threat.entity.origin + ( velocity * predictionTime );
154 aimPoint = predictedPosition +
self.bot.threat.aimOffset;
157 fov =
self BotGetFov();
161 self.bot.threat.visible =
true;
163 else if ( dot < fov || !
self BotSightTrace(
self.bot.threat.entity ) )
166 self.bot.threat.visible =
false;
170 self.bot.threat.visible =
true;
171 self.bot.threat.lastVisibleTime = GetTime();
172 self.bot.threat.lastDistanceSq = distanceSq;
173 self.bot.threat.lastVelocity = velocity;
174 self.bot.threat.lastPosition = predictedPosition;
175 self.bot.threat.aimPoint = aimPoint;
176 self.bot.threat.dot = dot;
178 weapon =
self GetCurrentWeapon();
181 self.bot.threat.inRange = distanceSq < ( weaponRange * weaponRange );
184 self.bot.threat.inCloseRange = distanceSq < ( weaponRangeClose * weaponRangeClose );
196 if ( isdefined( entity ) && entity !==
self.bot.threat.entity )
210 threats =
self [[level.botGetThreats]]( maxDistance );
212 if ( !isdefined( threats ) )
217 foreach( entity
in threats )
219 if (
self [[level.botIgnoreThreat]]( entity ) )
236 if ( !
self.bot.threat.wasVisible &&
237 self.bot.threat.visible &&
238 !
self IsThrowingGrenade() &&
239 !
self FragButtonPressed() &&
240 !
self SecondaryOffhandButtonPressed() &&
241 !
self IsSwitchingWeapons() )
243 visibleRoll = RandomInt( 100 );
245 rollWeight =
VAL( level.botSettings.lethalWeight, 0 );
246 if ( visibleRoll < rollWeight &&
247 self.bot.threat.lastDistanceSq >= level.botSettings.lethalDistanceMinSq &&
248 self.bot.threat.lastDistanceSq <= level.botSettings.lethalDistanceMaxSq &&
249 self GetWeaponAmmoStock(
self.grenadeTypePrimary ) )
252 self throw_grenade(
self.grenadeTypePrimary,
self.bot.threat.lastPosition );
255 visibleRoll -= rollWeight;
257 rollWeight =
VAL( level.botSettings.tacticalWeight, 0 );
258 if ( visibleRoll >= 0 &&
259 visibleRoll < rollWeight &&
260 self.bot.threat.lastDistanceSq >= level.botSettings.tacticalDistanceMinSq &&
261 self.bot.threat.lastDistanceSq <= level.botSettings.tacticalDistanceMaxSq &&
262 self GetWeaponAmmoStock(
self.grenadeTypeSecondary ) )
265 self throw_grenade(
self.grenadeTypeSecondary,
self.bot.threat.lastPosition );
272 self.bot.threat.aimOffset =
self get_aim_offset(
self.bot.threat.entity );
275 if (
self FragButtonPressed() )
277 self throw_grenade(
self.grenadeTypePrimary,
self.bot.threat.lastPosition );
280 else if (
self SecondaryOffhandButtonPressed() )
282 self throw_grenade(
self.grenadeTypeSecondary,
self.bot.threat.lastPosition );
288 if (
self IsReloading() ||
289 self IsSwitchingWeapons() ||
290 self IsThrowingGrenade() ||
291 self FragButtonPressed() ||
292 self SecondaryOffhandButtonPressed() ||
312 if (
self BotUnderManualControl() )
317 if (
self BotGoalSet() &&
318 (
self.bot.threat.wasVisible || !
self.bot.threat.visible ) )
324 radiusSq = radius * radius;
326 threatDistSq = Distance2DSquared(
self.origin,
self.bot.threat.lastPosition );
328 if ( threatDistSq < radiusSq || !
self BotSetGoal(
self.bot.threat.lastPosition, radius ) )
336 weapon =
self GetCurrentWeapon();
338 if ( RandomInt( 100 ) < 10 ||
339 weapon.weapClass ==
"melee" ||
340 ( !
self GetWeaponAmmoClip( weapon ) && !
self GetWeaponAmmoStock( weapon ) ) )
342 return level.botSettings.meleeRange;
345 return RandomIntRange( level.botSettings.threatRadiusMin, level.botSettings.threatRadiusMax );
353 if ( !
self.bot.threat.inRange )
358 weapon =
self GetCurrentWeapon();
360 if ( weapon == level.weaponNone ||
361 !
self GetWeaponAmmoClip( weapon ) ||
367 if ( weapon.fireType ==
"Single Shot" ||
368 weapon.fireType ==
"Burst" ||
369 weapon.fireType ==
"Charge Shot" )
371 if (
self AttackButtonPressed() )
379 if ( weapon.isDualWield )
389 if (
self.bot.threat.dot < level.botSettings.meleeDot )
394 if ( DistanceSquared(
self.origin,
self.bot.threat.lastPosition ) > level.botSettings.meleeRangeSq )
410 if (
self BotUnderManualControl() )
417 if (
self.bot.threat.wasVisible && !
self.bot.threat.visible )
420 self BotSetGoal(
self.bot.threat.lastPosition );
426 if (
self.bot.threat.lastVisibleTime +
VAL( level.botSettings.chaseThreatTime, 0 ) < GetTime() )
434 if ( !
self BotGoalSet() )
436 self bot::navmesh_wander(
self.bot.threat.lastVelocity,
self.botSettings.chaseWanderMin,
self.botSettings.chaseWanderMax,
self.botSettings.chaseWanderSpacing,
self.botSettings.chaseWanderFwdDot );
447 if ( IsSentient( entity ) && RandomInt( 100 ) <
VAL( level.botSettings.headshotWeight, 0 ) )
449 return entity GetEye() - entity.origin;
452 return entity GetCentroid() - entity.origin;
457 if ( !isdefined(
self.bot.threat.aimStartTime ) )
462 aimTime = GetTime() -
self.bot.threat.aimStartTime;
469 if ( aimTime >=
self.bot.threat.aimTime || !isdefined(
self.bot.threat.aimError ) )
471 self BotLookAtPoint(
self.bot.threat.aimPoint );
475 eyePoint =
self GetEye();
476 threatAngles = VectorToAngles(
self.bot.threat.aimPoint - eyePoint );
477 initialAngles = threatAngles +
self.bot.threat.aimError;
479 currAngles = VectorLerp( initialAngles, threatAngles, aimTime /
self.bot.threat.aimTime );
480 playerAngles =
self GetPlayerAngles();
481 self BotSetLookAngles( AnglesToForward( currAngles ) );
486 self.bot.threat.aimStartTime = GetTime() +
VAL( level.botSettings.aimDelay, 0 ) * 1000;
487 self.bot.threat.aimTime =
VAL( level.botSettings.aimTime, 0 ) * 1000;
489 pitchError =
angleError(
VAL( level.botSettings.aimErrorMinPitch, 0 ),
VAL( level.botSettings.aimErrorMaxPitch, 0 ) );
490 yawError =
angleError(
VAL( level.botSettings.aimErrorMinYaw, 0 ),
VAL( level.botSettings.aimErrorMaxYaw, 0 ) );
492 self.bot.threat.aimError = ( pitchError, yawError, 0 );
497 angle = angleMax - angleMin;
498 angle *= RandomFloatRange( -1, 1 );
514 if ( !isdefined(
self.bot.threat.aimStartTime ) )
519 self.bot.threat.aimStartTime = undefined;
520 self.bot.threat.aimTime = undefined;
521 self.bot.threat.aimError = undefined;
535 if ( isdefined(
self.bot.damage.time ) &&
self.bot.damage.time + 1500 > GetTime() )
537 if (
self has_threat() &&
self.bot.damage.time >
self.bot.threat.lastVisibleTime )
542 self bot::navmesh_wander(
self.bot.damage.attackDir, level.botSettings.damageWanderMin, level.botSettings.damageWanderMax, level.botSettings.damageWanderSpacing, level.botSettings.damageWanderFwdDot );
561 if ( !
self.bot.threat.inRange ||
self.bot.threat.inCloseRange )
566 weapon =
self GetCurrentWeapon();
568 if ( weapon == level.weaponNone ||
569 weapon.isDualWield ||
570 weapon.weapClass ==
"melee" ||
571 self GetWeaponAmmoClip( weapon ) <= 0 )
587 if ( weapon.isSniperWeapon )
589 return level.botSettings.sniperAds;
591 else if ( weapon.isRocketLauncher )
593 return level.botSettings.rocketLauncherAds;
596 switch( weapon.weapClass )
599 return level.botSettings.mgAds;
601 return level.botSettings.smgAds;
603 return level.botSettings.spreadAds;
605 return level.botSettings.pistolAds;
607 return level.botSettings.rifleAds;
610 return level.botSettings.defaultAds;
615 if ( weapon.isSniperWeapon )
617 return level.botSettings.sniperFire;
619 else if ( weapon.isRocketLauncher )
621 return level.botSettings.rocketLauncherFire;
624 switch( weapon.weapClass )
627 return level.botSettings.mgFire;
629 return level.botSettings.smgFire;
631 return level.botSettings.spreadFire;
633 return level.botSettings.pistolFire;
635 return level.botSettings.rifleFire;
638 return level.botSettings.defaultFire;
643 if ( weapon.isSniperWeapon )
645 return level.botSettings.sniperRange;
647 else if ( weapon.isRocketLauncher )
649 return level.botSettings.rocketLauncherRange;
652 switch( weapon.weapClass )
655 return level.botSettings.mgRange;
657 return level.botSettings.smgRange;
659 return level.botSettings.spreadRange;
661 return level.botSettings.pistolRange;
663 return level.botSettings.rifleRange;
666 return level.botSettings.defaultRange;
671 if ( weapon.isSniperWeapon )
673 return level.botSettings.sniperRangeClose;
675 else if ( weapon.isRocketLauncher )
677 return level.botSettings.rocketLauncherRangeClose;
680 switch( weapon.weapClass )
683 return level.botSettings.mgRangeClose;
685 return level.botSettings.smgRangeClose;
687 return level.botSettings.spreadRangeClose;
689 return level.botSettings.pistolRangeClose;
691 return level.botSettings.rifleRangeClose;
694 return level.botSettings.defaultRangeClose;
699 currentWeapon =
self GetCurrentWeapon();
701 if (
self IsSwitchingWeapons() ||
702 currentWeapon.isHeroWeapon ||
703 currentWeapon.isItem )
710 if ( weapon != level.weaponNone )
712 if ( !isdefined( level.enemyEmpActive ) || !
self [[level.enemyEmpActive]]() )
719 weapons =
self GetWeaponsListPrimaries();
722 if ( currentWeapon == level.weaponNone ||
723 currentWeapon.weapClass ==
"melee" ||
724 currentWeapon.weapClass ==
"rocketLauncher" ||
725 currentWeapon.weapClass ==
"pistol" )
727 foreach( weapon
in weapons )
729 if ( weapon == currentWeapon )
734 if (
self GetWeaponAmmoClip( weapon ) ||
self GetWeaponAmmoStock( weapon ) )
736 self BotSwitchToWeapon( weapon );
744 currentAmmoStock =
self GetWeaponAmmoStock( currentWeapon );
745 if ( currentAmmoStock )
753 if ( currentClipFrac > switchFrac )
758 foreach( weapon
in weapons )
760 if (
self GetWeaponAmmoStock( weapon ) ||
self weapon_clip_frac( weapon ) > switchFrac )
762 self BotSwitchToWeapon( weapon );
772 currentWeapon =
self GetCurrentWeapon();
774 if (
self IsSwitchingWeapons() ||
775 self GetWeaponAmmoClip( currentWeapon ) ||
776 currentWeapon.isItem )
781 currentAmmoStock =
self GetWeaponAmmoStock( currentWeapon );
782 weapons =
self GetWeaponsListPrimaries();
784 foreach( weapon
in weapons )
787 if ( weapon == currentWeapon || weapon.requireLockOnToFire )
792 if ( weapon.weapClass ==
"melee" )
795 if ( currentAmmoStock && RandomIntRange( 0, 100 ) < 75 )
803 if( !
self GetWeaponAmmoClip( weapon ) && currentAmmoStock )
808 weaponAmmoStock =
self GetWeaponAmmoStock( weapon );
811 if ( !currentAmmoStock && !weaponAmmoStock )
817 if ( weapon.weapClass !=
"pistol" && RandomIntRange( 0, 100 ) < 75 )
823 self BotSwitchToWeapon( weapon );
829 weapon =
self GetCurrentWeapon();
831 if ( !
self GetWeaponAmmoStock( weapon ) )
838 if ( weapon.weapClass ==
"mg" )
854 if ( weapon.clipSize <= 0 )
859 clipAmmo =
self GetWeaponAmmoClip( weapon );
861 return ( clipAmmo / weapon.clipSize );
870 if ( !isdefined(
self.bot.threat.aimStartTime ) )
877 if (
self.bot.threat.aimStartTime +
self.bot.threat.aimTime > GetTime() )
892 if ( weapon ==
self.grenadeTypePrimary )
896 else if ( weapon ==
self.grenadeTypeSecondary )
905 aimPeak = target + ( 0, 0, 100 );
907 self.bot.threat.aimStartTime = GetTime();
909 self.bot.threat.aimTime = 1500;
911 self BotSetLookAnglesFromPoint( aimPeak );
918 throwOrigin =
self GetEye();
920 xyDist = Distance2D( throwOrigin, target );
921 xySpeed = Distance2D( velocity, ( 0, 0, 0 ) );
923 t = xyDist / xySpeed;
925 gravity = -GetDvarFloat(
"bg_gravity" );
927 tHeight = throwOrigin[2] + velocity[2] * t + ( gravity * t * t * .5 );
929 return Abs( tHeight - target[2] ) < 20;
934 angles =
self GetPlayerAngles();
935 forward = AnglesToForward( angles );
938 return forward * 928;
959 weaponsList =
self GetWeaponsList();
961 foreach( weapon
in weaponsList )
963 if ( weapon.type ==
"grenade" &&
self GetWeaponAmmoStock( weapon ) )
969 return level.weaponNone;
978 self endon(
"death" );
979 level endon(
"game_ended" );
983 self waittill(
"damage",
damage, attacker, direction, point, mod, unused1, unused2, unused3, weapon, flags, inflictor );
985 self.bot.damage.entity = attacker;
986 self.bot.damage.amount =
damage;
987 self.bot.damage.attackDir = VectorNormalize( attacker.origin -
self.origin );
988 self.bot.damage.weapon = weapon;
989 self.bot.damage.mod = mod;
990 self.bot.damage.time = GetTime();
992 self thread [[level.onBotDamage]]();
998 self.bot.damage.entity = undefined;
999 self.bot.damage.amount = undefined;
1000 self.bot.damage.direction = undefined;
1001 self.bot.damage.weapon = undefined;
1002 self.bot.damage.mod = undefined;
1003 self.bot.damage.time = undefined;
1012 DEFAULT( radiusMin,
VAL( level.botSettings.strafeMin, 0 ) );
1013 DEFAULT( radiusMax,
VAL( level.botSettings.strafeMax, 0 ) );
1014 DEFAULT( spacing,
VAL( level.botSettings.strafeSpacing, 0 ) );
1015 DEFAULT( sideDotMin,
VAL( level.botSettings.strafeSideDotMin, 0 ) );
1016 DEFAULT( sideDotMax,
VAL( level.botSettings.strafeSideDotMax, 0 ) );
1018 fwd = AnglesToForward(
self.angles );
1020 queryResult = PositionQuery_Source_Navigation(
self.origin, radiusMin, radiusMax, 64, spacing,
self );
1022 best_point = undefined;
1024 foreach ( point
in queryResult.data )
1026 moveDir = VectorNormalize( point.origin -
self.origin );
1027 dot = VectorDot( moveDir, fwd );
1030 if ( dot >= sideDotMin && dot <= sideDotMax )
1032 point.score = MapFloat( radiusMin, radiusMax, 0, 50, point.distToOrigin2D );
1033 point.score += randomFloatRange( 0, 50 );
1035 if ( !isdefined( best_point ) || point.score > best_point.score )
1041 if( isdefined( best_point ) )
1043 self BotSetGoal( best_point.origin );