#include common_scripts\utility; #include maps\_utility; #include maps\so\zm_common\_zm_utility; register_stats_for_bot_weapon( weapon, score ) { if ( !isDefined( level.bot_weapons_stats ) ) { level.bot_weapons_stats = []; } level.bot_weapons_stats[ weapon ] = score; } parse_bot_weapon_stats_from_table() { WEAPON_COLUMN = 0; SCORE_COLUMN = 1; /* row = 0; while ( true ) { weapon = fs_tablelookupcolumnforrow( "tables\bot_weapon_stats.csv", row, WEAPON_COLUMN ); if ( !isDefined( weapon ) || weapon == "" ) { break; } score = fs_tablelookupcolumnforrow( "tables\bot_weapon_stats.csv", row, SCORE_COLUMN ); if ( !isDefined( score ) || score == "" ) { row++; continue; } if ( isDefined( level.zombie_include_weapons[ weapon + "_zm" ] ) ) { register_stats_for_bot_weapon( weapon + "_zm", int( score ) ); if ( isDefined( level.zombie_include_weapons[ weapon + "_upgraded_zm" ] ) ) { register_stats_for_bot_weapon( weapon + "_upgraded_zm", int( score ) + 1 ); } } else if ( isDefined( level.zombie_include_weapons[ weapon ] ) ) { register_stats_for_bot_weapon( weapon, int( score ) ); } row++; } */ } array_validate( array ) { return isDefined( array ) && isArray( array ) && array.size > 0; } array_add( array, item ) { array[ array.size ] = item; } swap( array, index1, index2 ) { temp = array[ index1 ]; array[ index1 ] = array[ index2 ]; array[ index2 ] = temp; return array; } merge_sort( current_list, func_sort, param ) { if ( current_list.size <= 1 ) { return current_list; } left = []; right = []; middle = current_list.size / 2; for ( x = 0; x < middle; x++ ) { array_add( left, current_list[ x ] ); } for ( ; x < current_list.size; x++ ) { array_add( right, current_list[ x ] ); } left = merge_sort( left, func_sort, param ); right = merge_sort( right, func_sort, param ); //result = merge( left, right, func_sort, param ); //return result; } quickSort(array, compare_func) { return quickSortMid(array, 0, array.size -1, compare_func); } quickSortMid( array, start, end, compare_func ) { i = start; k = end; if(!IsDefined(compare_func)) compare_func = ::quicksort_compare; if (end - start >= 1) { pivot = array[start]; while (k > i) { while ( [[ compare_func ]](array[i], pivot) && i <= end && k > i) i++; while ( ![[ compare_func ]](array[k], pivot) && k >= start && k >= i) k--; if (k > i) array = swap(array, i, k); } array = swap(array, start, k); array = quickSortMid(array, start, k - 1, compare_func); array = quickSortMid(array, k + 1, end, compare_func); } else return array; return array; } quicksort_compare(left, right) { return left <= right; } push( array, val, index ) { if ( !isdefined( index ) ) { // use max free integer as index index = 0; keys = GetArrayKeys( array ); for ( i = 0; i < keys.size; i++ ) { key = keys[ i ]; if ( IsInt( key ) && ( key >= index ) ) { index = key + 1; } } } array = array_insert( array, val, index ); return array; } bot_spawn_init() { time = gettime(); if ( !isdefined( self.bot ) ) { self.bot = spawnstruct(); self.bot.threat = spawnstruct(); } self.bot.glass_origin = undefined; self.bot.ignore_entity = []; self.bot.previous_origin = self.origin; self.bot.time_ads = 0; self.bot.update_c4 = time + randomintrange( 1000, 3000 ); self.bot.update_crate = time + randomintrange( 1000, 3000 ); self.bot.update_crouch = time + randomintrange( 1000, 3000 ); self.bot.update_failsafe = time + randomintrange( 1000, 3000 ); self.bot.update_idle_lookat = time + randomintrange( 1000, 3000 ); self.bot.update_killstreak = time + randomintrange( 1000, 3000 ); self.bot.update_lookat = time + randomintrange( 1000, 3000 ); self.bot.update_objective = time + randomintrange( 1000, 3000 ); self.bot.update_objective_patrol = time + randomintrange( 1000, 3000 ); self.bot.update_patrol = time + randomintrange( 1000, 3000 ); self.bot.update_toss = time + randomintrange( 1000, 3000 ); self.bot.update_launcher = time + randomintrange( 1000, 3000 ); self.bot.update_weapon = time + randomintrange( 1000, 3000 ); self.bot.threat.entity = undefined; self.bot.threat.position = ( 0, 0, 0 ); self.bot.threat.time_first_sight = 0; self.bot.threat.time_recent_sight = 0; self.bot.threat.time_aim_interval = 0; self.bot.threat.time_aim_correct = 0; self.bot.threat.update_riotshield = 0; } bot_should_hip_fire() { enemy = self.bot.threat.entity; weapon = self getcurrentweapon(); if ( weapon == "none" ) return 0; if ( weaponisdualwield( weapon ) ) return 1; weapon_class = weaponclass( weapon ); if ( isplayer( enemy ) && weapon_class == "spread" ) return 1; distsq = distancesquared( self.origin, enemy.origin ); distcheck = 0; switch ( weapon_class ) { case "mg": distcheck = 250; break; case "smg": distcheck = 350; break; case "spread": distcheck = 400; break; case "pistol": distcheck = 200; break; case "rocketlauncher": distcheck = 0; break; case "rifle": default: distcheck = 300; break; } if ( isweaponscopeoverlay( weapon ) ) distcheck = 500; return distsq < distcheck * distcheck; } get_allies() { return getPlayers( self.team ); } get_zombies() { return getAiSpeciesArray( level.zombie_team, "all" ); } find_gaps() { } are_enemies_horded() { MINIMUM_PERCENT_TO_BE_HORDE = 0.9; DISTANCE_SQ = 120 * 120; zombies = get_zombies(); amount_in_horde = 0; max_eligible_zombies = isDefined( level.speed_change_round ) ? zombies.size - level.speed_change_num : zombies.size; expected_amount_in_horde_min = int( max_eligible_zombies * MINIMUM_PERCENT_TO_BE_HORDE ); if ( isDefined( level.speed_change_round ) ) { for ( i = 0; i < zombies.size; i++ ) { if ( zombies[ i ].zombie_move_speed == "walk" ) { continue; } if ( !isDefined( zombies[ i + 1 ] ) ) { return false; } if ( zombies[ i + 1 ].zombie_move_speed != "walk" ) { if ( distanceSquared( zombies[ i + 1 ].origin, zombies[ i ].origin ) <= DISTANCE_SQ ) { amount_in_horde++; } } if ( amount_in_horde >= expected_amount_in_horde_min ) { return true; } } } else { for ( i = 0; i < zombies.size; i++ ) { if ( !isDefined( zombies[ i + 1 ] ) ) { return false; } if ( distanceSquared( zombies[ i + 1 ].origin, zombies[ i ].origin ) <= DISTANCE_SQ ) { amount_in_horde++; } if ( amount_in_horde >= expected_amount_in_horde_min ) { return true; } } } return false; } any_enemies_in_direction( dir ) { } predict_entity_position_frames( frames ) { current_velocity = self getVelocity(); predicted_origin = self.origin; for ( i = 0; i < frames; i++ ) { predicted_origin += ( current_velocity / 20 ); } return predicted_origin; } predict_entity_position_seconds( seconds ) { current_velocity = self getVelocity(); predicted_origin = self.origin; for ( i = 0; i < seconds; i++ ) { predicted_origin += current_velocity; } return predicted_origin; } any_zombies_targeting_self() { ZOMBIE_TARGETING_DIST_SQ = 10 * 10; zombies = get_zombies(); if ( !array_validate( zombies ) ) { return false; } for ( i = 0; i < zombies.size; i++ ) { if ( isDefined( zombies[ i ].favoriteenemy ) && zombies[ i ].favoriteenemy == self ) { return true; } if ( isDefined( zombies[ i ].goal_pos ) && distanceSquared( zombies[ i ].goal_pos, self.origin ) < ZOMBIE_TARGETING_DIST_SQ ) { return true; } } return false; } bot_is_in_danger( player ) { return false; } bot_valid( player ) { if ( !isdefined( player ) ) return false; if ( !isalive( player ) ) return false; if ( !isplayer( player ) ) return false; if ( !is_true( player.pers[ "isBot" ] ) ) return false; if ( isdefined( player.is_zombie ) && player.is_zombie == 1 ) return false; if ( player.sessionstate == "spectator" ) return false; if ( player.sessionstate == "intermission" ) return false; if ( isdefined( player.intermission ) && player.intermission ) return false; if ( isdefined( level.is_player_valid_override ) ) return [[ level.is_player_valid_override ]]( player ); return true; } assign_priority_to_powerup( powerup ) { if ( !isDefined( powerup ) ) { return; } priority = 0; powerup_is_max_ammo = false; switch ( powerup.powerup_name ) { case "zombie_blood": case "insta_kill": case "nuke": priority += 2; break; case "full_ammo": powerup_is_max_ammo = true; priority += 1; break; case "double_points": case "fire_sale": case "carpenter": case "free_perk": priority += 1; break; default: priority += 0; break; } if ( powerup_is_max_ammo ) { LOW_AMMO_THRESHOLD = 0.3; for ( i = 0; i < level.players.size; i++ ) { weapons = level.players[ i ] getWeaponsListPrimaries(); for ( j = 0; j < weapons.size; j++ ) { if ( self getWeaponAmmoStock( weapons[ j ] ) <= int( weaponmaxammo( weapons[ j ] ) * LOW_AMMO_THRESHOLD ) ) { priority += 1; break; } } if ( priority > 3 ) { break; } } } if ( maps\mp\zombies\_zm_laststand::player_any_player_in_laststand() ) { switch ( powerup.powerup_name ) { case "zombie_blood": case "insta_kill": case "nuke": priority += 1; break; case "full_ammo": priority += 0; break; case "double_points": case "fire_sale": case "carpenter": case "free_perk": priority -= 1; break; default: priority += 0; break; } } if ( powerup.time_left_until_timeout < 10.0 ) { priority += 1; } if ( priority < 0 ) { priority = 0; } powerup.priority = priority; } sort_array_by_priority_field( array, item ) { if ( isDefined( item ) ) { array[ array.size ] = item; } priority_array = []; for ( i = 0; i < array.size; i++ ) { priority_array[ i ] = array[ i ].priority; } priority_array = quickSort( priority_array ); sorted_array = []; for ( i = 0; i < priority_array.size; i++ ) { for ( j = 0; j < array.size; j++ ) { if ( array[ j ].priority == priority_array[ i ] ) { sorted_array[ sorted_array.size ] = array[ j ]; } } } return sorted_array; } /* We need to calculate where the bot should go to next and update their movement constantly here If the calculations predicts death or teammates death based on current course we need recalculate next move Updating every frame(0.05) should be sufficient Key to movement code is determining gaps, and safe lines to follow Bot should try to find the nearest safe line and follow it Due to many different variables(primarily resulting from other players) we need to constantly verify if the line is safe to follow If the bot doesn't detect any danger allow them to stand still far enough away from the zombies to not draw aggro but close enough to shoot at them Questions: Can bots move and use the use button at the same time? Necessary to be able to support circle revive How do we know the bot is safe where they are currently or where they will be moving to? How do we determine gaps in zombies/terrain to slip through? */ movement_think() { while ( true ) { } } /* bot methods docs enum BotGoalPriority { GOAL_PRIORITY_UNUSED = 0x0, GOAL_PRIORITY_LOW = 0x1, GOAL_PRIORITY_NORMAL = 0x2, GOAL_PRIORITY_HIGH = 0x3, GOAL_PRIORITY_URGENT = 0x4, GOAL_PRIORITY_MAX = 0x5, }; success = bot addGoal( , float, BotGoalPriority, string ); bot cancelGoal( string ); bot atGoal( string[notify] ); bot hasGoal( string ); origin = bot getGoal( string ); bot pressUseButton( float ); bot pressAttackButton( float ); bot pressDtpButton(); bot pressAds( ); bot pressMelee(); bot throwGrenade( string, vector ); dist = bot getLookaheadDist(); dir = bot getLookAheadDir(); bot lookAt( vector ); bot clearLookat(); pos = bot predictPosition( entity, int ); success = bot sightTracePassed( entity, vector[point] ); enemies = bot getThreats( float ); //Fov value can be -1 to find all enemies instead of enemies in fov. bot botSetFailsafeNode( pathnode[node] ); player methods docs player allowStand( ); player allowCrouch( ); player allowProne( ); player allowAds( ); player allowSprint( ); player allowMelee( ); player setSpawnWeapon( string ); player isLookingAt( ); ads_amount = player playerAds(); stance = player getstance(); player setStance( string ); dot = player playerSightTrace( vector, int, int ); entity methods docs mins = entity getMins(); maxes = entity getMaxes(); absmins = entity getAdbMins(); absmaxes = entity getAbsMaxes(); eye = entity getEye(); centroid = entity getCentroid() velocity = entity getVelocity(); vector = entity getpointinbounds( float, float, float ); is_touching = entity isTouching( entity, vector[extra_boundary] ); is_touching = entity isTouchingVolume( vector, vector, vector ); dot = entity damageConeTrace( vector, entity[ent], vector[damage_angles], float[damage_amount] ); dot = entity sightConeTrace( vector, entity[ent], vector[damage_angles], float[damage_amount] ); common functions docs nodeStringTable = { "Path", "Cover Stand", "Cover Crouch", "Cover Crouch Window", "Cover Prone", "Cover Right", "Cover Left", "Cover Pillar", "Ambush", "Exposed", "Conceal Stand", "Conceal Crouch", "Conceal Prone", "Reacquire", "Balcony", "Scripted", "Begin", "End", "Turret", "Guard" } enum team_t { TEAM_FREE = 0x0, TEAM_BAD = 0x0, TEAM_ALLIES = 0x1, TEAM_AXIS = 0x2, TEAM_THREE = 0x3, TEAM_FOUR = 0x4, TEAM_FIVE = 0x5, TEAM_SIX = 0x6, TEAM_SEVEN = 0x7, TEAM_EIGHT = 0x8, TEAM_NUM_PLAYING_TEAMS = 0x9, TEAM_SPECTATOR = 0x9, TEAM_NUM_TEAMS = 0xA, TEAM_LOCALPLAYERS = 0xB, TEAM_FIRST_PLAYING_TEAM = 0x1, TEAM_LAST_PLAYING_TEAM = 0x8, }; nodespawn_t nodespawns[21] { 'node_pathnode' 'node_guard' 'node_turret' 'node_negotiation_end' 'node_negotiation_begin' 'node_scripted' 'node_balcony' 'node_reacquire' 'node_concealment_prone' 'node_concealment_crouch' 'node_concealment_stand' 'node_exposed' 'node_ambush' 'node_cover_pillar' 'node_cover_left' 'node_cover_right' 'node_cover_prone' 'node_cover_crouch_window' 'node_cover_crouch' 'node_cover_stand' } node = getNode( string, entkey ); nodes = getNodeArray( string, entkey, entkey, vector[origin], float[max_dist] ); nodes = getAnyNodeArray( vector, float ); nodes = getCoverNodeArray( vector, float ); nodes = getAllNodes(); nodes = getNodesInRadius( vector, float, float, float[max_height], nodeStringTable[type], int ); nodes = getNodesInRadiusSorted( vector, float, float, float[max_height], nodeStringTable[type], int ); node = getNearestNode( vector ); node = getVisibleNode( vector, vector, entity[ent] ); nodes = getVisibleNodes( node_ent ); visible = nodesVisible( node_ent, node_ent ); canpath = nodesCanPath( node_ent, node_ent ); canclaimnode = canClaimNode( node_ent, team_t ); setEnableNode( node_ent, [bool] ); linkNodes( node_ent, node_ent ); unLinkNodes( node_ent, node_ent ); nodesAreLinked( node_ent, node_ent ); dropnodetofloor( node_ent ); node = spawnPathNode( nodespawn_t, vector, vector, key[key1], value[val1], ... ); deletePathNode( node_ent ); occupied = isNodeOccupied( node_ent ); nodeowner = getNodeOwner( node_ent ); foundpath = findPath( vector, vector, entity[ent], bool[allow_negotiation_links], bool[allow_negotiation_hints] ); vector = vectorFromLineToPoint( vector<, vector, vector ); point = pointOnSegmentNearestToPoint( vector, vector, vector ); pos_a_is_closer = closer( vector, vector, vector ); dot = vectorDot( vector, vector ); cross = vectorCross( vector, vector ); normalized_vector = vectorNormalize( vector ); lerped_vector = vectorLerp( vector, vector, float ); combined_angles = combineAngles( vector, vector ); angles = vectorToAngles( vector ); angle = absAngleClamp180( float ); angle = absAngleClamp360( float ); point = rotatePoint( vector, vector ); trace { "fraction", "position", "entity", "normal", "surfacetype" } trace = bulletTrace( vector, vector, bool, entity, bool[modify_contents_flags1], bool[modify_contents_flags2] ); passed = bulletTracePassed( vector, vector, bool, entity, entity[ignore_ent2?], bool[check_fx_visibility] ); trace = groundTrace( vector, vector, bool, entity, bool[modify_contents_flags1], bool[modify_contents_flags2] ); passed = sightTracePassed( vector, vector, bool, entity ); player_physics_trace = playerPhysicsTrace( vector, vector ); trace = physicsTrace( vector, vector, vector[maxes], vector[end_pos], entity[ignore_ent] ); trace = worldTrace( vector, vector ); ? customs functions to add node = bot getNextNodeInPath( vector, vector, entity[ent], bool[allow_negotiation_links], bool[allow_negotiation_hints] ); bot botMovementOverride( byte, byte ); self botButtonOverride( string