1818 lines
64 KiB
Plaintext
1818 lines
64 KiB
Plaintext
|
|
#include common_scripts\utility;
|
|
#include maps\mp\_utility;
|
|
#include maps\mp\bots\_bots_ks;
|
|
|
|
SCR_CONST_MAX_CUSTOM_DEFAULT_LOADOUTS = 6; // See recipes.raw: CustomGameClass defaultClasses[ PlayerGroup ][ 6 ];
|
|
SCR_CONST_BOT_CHANGE_LOADOUT_CHANCE = 0.1;
|
|
|
|
init()
|
|
{
|
|
// Initialize tables of loadouts per personality and per difficulty
|
|
init_template_table();
|
|
init_class_table();
|
|
init_bot_attachmenttable(); // needs to be before init_bot_weap_statstable
|
|
init_bot_weap_statstable();
|
|
init_bot_reticletable();
|
|
init_bot_camotable();
|
|
|
|
level.bot_min_rank_for_item = [];
|
|
|
|
// Needed to ensure we're not trying to set bot loadouts before they have been parsed
|
|
level.bot_loadouts_initialized = true;
|
|
}
|
|
|
|
init_class_table()
|
|
{
|
|
filename = "mp/botClassTable.csv";
|
|
|
|
level.botLoadoutSets = [];
|
|
fieldArray = bot_loadout_fields();
|
|
column = 0;
|
|
|
|
for(;;)
|
|
{
|
|
column++;
|
|
|
|
strPers = tableLookup( filename, 0, "botPersonalities", column );
|
|
strDiff = tableLookup( filename, 0, "botDifficulties", column );
|
|
|
|
if ( !isDefined( strPers ) || (strPers == "") )
|
|
break;
|
|
|
|
if ( !isDefined( strDiff ) || (strDiff == "") )
|
|
break;
|
|
|
|
loadoutValues = [];
|
|
foreach ( field in fieldArray )
|
|
{
|
|
loadoutValues[field] = tableLookup( filename, 0, field, column );
|
|
/#
|
|
assert_field_valid( field, loadoutValues[field], filename, loadoutValues );
|
|
#/
|
|
}
|
|
|
|
personalities = StrTok( strPers, "| " );
|
|
difficulties = StrTok( strDiff, "| " );
|
|
|
|
foreach ( personality in personalities )
|
|
{
|
|
foreach ( difficulty in difficulties )
|
|
{
|
|
loadoutSet = bot_loadout_set( personality, difficulty, true );
|
|
loadout = SpawnStruct();
|
|
loadout.loadoutValues = loadoutValues;
|
|
loadoutSet.loadouts[loadoutSet.loadouts.size] = loadout;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
init_template_table()
|
|
{
|
|
filename = "mp/botTemplateTable.csv";
|
|
|
|
level.botLoadoutTemplates = [];
|
|
fieldArray = bot_loadout_fields();
|
|
column = 0;
|
|
|
|
for(;;)
|
|
{
|
|
column++;
|
|
|
|
strTemplateName = tableLookup( filename, 0, "template_", column );
|
|
|
|
if ( !isDefined( strTemplateName ) || (strTemplateName == "") )
|
|
break;
|
|
|
|
strTemplateLookupName = "template_" + strTemplateName;
|
|
|
|
level.botLoadoutTemplates[strTemplateLookupName] = [];
|
|
|
|
foreach ( field in fieldArray )
|
|
{
|
|
entry = tableLookup( filename, 0, field, column );
|
|
if ( IsDefined( entry ) && entry != "" )
|
|
{
|
|
level.botLoadoutTemplates[strTemplateLookupName][field] = entry;
|
|
/#
|
|
assert_field_valid( field, entry, filename );
|
|
#/
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bot_custom_classes_allowed()
|
|
{
|
|
if ( IsUsingMatchRulesData() && !GetMatchRulesData( "commonOption", "allowCustomClasses" ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
///ScriptDocBegin
|
|
"Name: bot_loadout_item_allowed( <type>, <item>, <perkClass> )"
|
|
"Summary: Checks if a loadout item is restricted in the current match rules (returns true if not restricted)"
|
|
"MandatoryArg: <type> : The type of item ("weapon", "attachment", "killstreak", "perk")"
|
|
"MandatoryArg: <item> : The specific item"
|
|
"OptionalArg: <perkClass> : If type is perk, then the relevant PerkClass enum from recipes.def"
|
|
"Example: bot_loadout_item_allowed( "weapon", "iw6_fads", undefined );"
|
|
///ScriptDocEnd
|
|
============
|
|
*/
|
|
bot_loadout_item_allowed( type, item, perkClass )
|
|
{
|
|
if ( !IsUsingMatchRulesData() )
|
|
return true;
|
|
|
|
if ( !bot_custom_classes_allowed() )
|
|
return false;
|
|
|
|
if ( item == "specialty_null" )
|
|
return true;
|
|
|
|
if ( item == "none" )
|
|
return true;
|
|
|
|
itemRuleName = type + "Restricted";
|
|
classRuleName = type + "ClassRestricted";
|
|
class = "";
|
|
|
|
switch ( type )
|
|
{
|
|
case "weapon":
|
|
class = getWeaponClass( item );
|
|
break;
|
|
case "attachment":
|
|
class = getAttachmentType( item );
|
|
break;
|
|
case "killstreak":
|
|
if ( GetMatchRulesData( "commonOption", "allStreaksRestricted" ) )
|
|
return false;
|
|
break;
|
|
case "module":
|
|
class = maps\mp\killstreaks\_killstreaks::getStreakModuleBaseKillstreak( item );
|
|
break;
|
|
case "perk":
|
|
class = perkClass;
|
|
break;
|
|
default:
|
|
AssertEx( 0, "bot_loadout_item_allowed() - Unsupported loadout type '" + type + "'" );
|
|
return false;
|
|
}
|
|
|
|
if ( GetMatchRulesData( "commonOption", itemRuleName, item ) )
|
|
return false;
|
|
|
|
if ( ( class != "" ) && GetMatchRulesData( "commonOption", classRuleName, class ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bot_loadout_choose_fallback_primary( loadoutValueArray )
|
|
{
|
|
weapon = "none";
|
|
|
|
// First try to pick primary weapon randomly from all difficulties with my personality
|
|
difficulties = [ "veteran", "hardened", "regular", "recruit" ];
|
|
difficulties = array_randomize( difficulties );
|
|
foreach( difficulty in difficulties )
|
|
{
|
|
weapon = bot_loadout_choose_from_statstable( "weap_statstable", loadoutValueArray, "loadoutPrimary", self.personality, difficulty );
|
|
if ( weapon != "none" )
|
|
return weapon;
|
|
}
|
|
|
|
// Then try to pick primary weapon randomly from all personalities and difficulties
|
|
if ( IsDefined( level.bot_personality_list ) )
|
|
{
|
|
personalities = array_randomize( level.bot_personality_list );
|
|
foreach( personality in personalities )
|
|
{
|
|
foreach( difficulty in difficulties )
|
|
{
|
|
weapon = bot_loadout_choose_from_statstable( "weap_statstable", loadoutValueArray, "loadoutPrimary", personality, difficulty );
|
|
if ( weapon != "none" )
|
|
{
|
|
self.bot_fallback_personality = personality;
|
|
return weapon;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Still no primary weapon found, pick one randomly from custom default classes
|
|
if ( IsUsingMatchRulesData() )
|
|
{
|
|
validCount = 0.0;
|
|
index = 0;
|
|
defaultClassWeapon = "none";
|
|
while( index < SCR_CONST_MAX_CUSTOM_DEFAULT_LOADOUTS )
|
|
{
|
|
if ( getMatchRulesData( "defaultClasses", self bot_loadout_team(), index, "class", "inUse" ) )
|
|
{
|
|
weapon = self bot_loadout_choose_from_custom_default_class( index, "loadoutPrimary" );
|
|
if ( weapon != "none" )
|
|
{
|
|
validCount = validCount + 1.0;
|
|
if ( RandomFloat( 1.0 ) >= 1.0 / validCount )
|
|
defaultClassWeapon = weapon;
|
|
}
|
|
}
|
|
index++;
|
|
}
|
|
|
|
if ( defaultClassWeapon != "none" )
|
|
{
|
|
self.bot_fallback_personality = "weapon";
|
|
return defaultClassWeapon;
|
|
}
|
|
}
|
|
|
|
// Still no primary weapon found, pick level.bot_fallback_weapon (knife)
|
|
self.bot_fallback_personality = "weapon";
|
|
return level.bot_fallback_weapon;
|
|
}
|
|
|
|
bot_loadout_team() // self = bot
|
|
{
|
|
if ( !IsDefined( level.teambased ) || !level.teambased )
|
|
return "allies";
|
|
|
|
return self maps\mp\bots\_bots::bot_get_player_team();
|
|
}
|
|
|
|
bot_default_class_random() // self = bot
|
|
{
|
|
assert( IsBot( self ) );
|
|
|
|
// By default we choose from the standard default class name strings
|
|
selections = [ "class1", "class2", "class3", "class4", "class5" ];
|
|
|
|
// Unless its a private match with custom default classes in which case we selectively replace items in the array with an int
|
|
if ( IsUsingMatchRulesData() )
|
|
{
|
|
for ( i = 0; i < selections.size; i++ )
|
|
{
|
|
if ( getMatchRulesData( "defaultClasses", self bot_loadout_team(), i, "class", "inUse" ) )
|
|
selections[i] = i;
|
|
}
|
|
}
|
|
|
|
classChoice = random( selections );
|
|
loadoutValues = [];
|
|
|
|
foreach ( loadoutValueName in level.bot_loadout_fields )
|
|
{
|
|
if ( IsString( classChoice ) )
|
|
loadoutValues[loadoutValueName] = self bot_loadout_choose_from_default_class( classChoice, loadoutValueName );
|
|
else
|
|
loadoutValues[loadoutValueName] = self bot_loadout_choose_from_custom_default_class( classChoice, loadoutValueName );
|
|
}
|
|
|
|
return loadoutValues;
|
|
}
|
|
|
|
bot_pick_personality_from_weapon( weapon ) // self = bot player
|
|
{
|
|
if ( IsDefined( weapon ) )
|
|
{
|
|
weapPers = level.bot_weap_personality[weapon];
|
|
if ( IsDefined( weapPers ) )
|
|
{
|
|
personalityChoices = StrTok( weapPers, "| " );
|
|
if ( personalityChoices.size > 0 )
|
|
self maps\mp\bots\_bots_util::bot_set_personality( random( personalityChoices ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
bot_loadout_set_has_wildcard( loadout_fields, wildcard )
|
|
{
|
|
if ( loadout_fields["loadoutWildcard1"] == wildcard)
|
|
return true;
|
|
if ( loadout_fields["loadoutWildcard2"] == wildcard)
|
|
return true;
|
|
if ( loadout_fields["loadoutWildcard3"] == wildcard)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/#
|
|
assert_field_valid( field, value, filename, all_previous_fields )
|
|
{
|
|
// Make sure we actually found this value in the classtable
|
|
if ( field != "" )
|
|
{
|
|
AssertEx( value != "", "Field " + field + " not defined in " + filename );
|
|
}
|
|
|
|
// Make sure the killstreaks loaded are valid in general for bots
|
|
if ( field == "loadoutStreak1" || field == "loadoutStreak2" || field == "loadoutStreak3" )
|
|
{
|
|
killstreaks = StrTok( value, "| " );
|
|
foreach( streak in killstreaks )
|
|
{
|
|
if ( streak != "none" && GetSubStr( streak, 0, 9 ) != "template_" && GetSubStr( streak, 0, 5 ) != "class" )
|
|
assert_streak_valid_for_bots_in_general(streak);
|
|
}
|
|
}
|
|
|
|
if ( field == "loadoutOffhand" && !IsSubStr( value, "template_" ) && !IsSubStr( value, "classtable_" ) )
|
|
{
|
|
has_dual_lethals = false;
|
|
if ( IsDefined(all_previous_fields) )
|
|
has_dual_lethals = bot_loadout_set_has_wildcard(all_previous_fields, "specialty_wildcard_duallethals");
|
|
|
|
exo_abilities = StrTok( value, "|" );
|
|
foreach( ability in exo_abilities )
|
|
AssertEx( maps\mp\gametypes\_class::isValidOffhand( ability, has_dual_lethals ), "'" + ability + "' not valid for '" + field + "' in " + filename );
|
|
}
|
|
|
|
if ( field == "loadoutOffhand2" && value != "specialty_null" )
|
|
{
|
|
AssertMsg( "Field 'loadOffhand2' is deprecated(?) and should not be used for bots. Value was '" + value + "' in " + filename );
|
|
}
|
|
|
|
if ( IsSubStr( field, "Reticle" ) )
|
|
{
|
|
reticles = StrTok( value, "|" );
|
|
foreach( reticle in reticles )
|
|
AssertEx( reticle == "reticletable" || maps\mp\gametypes\_class::isValidReticle( reticle, true ), "'" + reticle + "' not valid for '" + field + "' in " + filename );
|
|
}
|
|
|
|
if ( IsSubStr( field, "Camo" ) )
|
|
{
|
|
camos = StrTok( value, "|" );
|
|
foreach( camo in camos )
|
|
AssertEx( camo == "camotable" || maps\mp\gametypes\_class::isValidCamo( camo ), "'" + camo + "' not valid for '" + field + "' in " + filename );
|
|
}
|
|
|
|
if ( IsSubStr( field, "Equipment" ) && !IsSubStr( value, "template_" ) && !IsSubStr( value, "classtable_" ) )
|
|
{
|
|
has_dual_tacticals = false;
|
|
if ( IsDefined(all_previous_fields) )
|
|
has_dual_tacticals = bot_loadout_set_has_wildcard(all_previous_fields, "specialty_wildcard_dualtacticals");
|
|
|
|
equipments = StrTok( value, "|" );
|
|
foreach( equipment in equipments )
|
|
AssertEx( maps\mp\gametypes\_class::isValidEquipment( equipment, has_dual_tacticals ), "'" + equipment + "' not valid for '" + field + "' in " + filename );
|
|
}
|
|
|
|
if ( IsSubStr( field, "Perk" ) && !IsSubStr( value, "classtable_" ) && value != "specialty_null" && IsDefined(all_previous_fields) )
|
|
{
|
|
perk_num = int( GetSubStr( field, 11 ) );
|
|
|
|
wildcard_required = "";
|
|
if ( perk_num == 2 )
|
|
wildcard_required = "specialty_wildcard_perkslot1";
|
|
else if ( perk_num == 4 )
|
|
wildcard_required = "specialty_wildcard_perkslot2";
|
|
else if ( perk_num == 6 )
|
|
wildcard_required = "specialty_wildcard_perkslot3";
|
|
|
|
if ( wildcard_required != "" )
|
|
AssertEx( bot_loadout_set_has_wildcard(all_previous_fields, wildcard_required), "'" + value + "' not valid for '" + field + "' in " + filename );
|
|
}
|
|
}
|
|
#/
|
|
|
|
bot_loadout_fields()
|
|
{
|
|
filename = "mp/botClassTable.csv";
|
|
if ( !IsDefined(level.bot_loadout_fields) )
|
|
{
|
|
level.bot_loadout_fields = [];
|
|
|
|
// Start with the 3 wildcards since they have to be first (because they can modify other slots)
|
|
level.bot_loadout_fields[0] = "loadoutWildcard1";
|
|
level.bot_loadout_fields[1] = "loadoutWildcard2";
|
|
level.bot_loadout_fields[2] = "loadoutWildcard3";
|
|
|
|
row = 2;
|
|
while(1)
|
|
{
|
|
rowHeading = TableLookupByRow( filename, row, 0 );
|
|
if ( rowHeading == "" )
|
|
break;
|
|
if ( !IsSubStr( rowHeading, "Wildcard" ) )
|
|
level.bot_loadout_fields[level.bot_loadout_fields.size] = rowHeading;
|
|
|
|
row++;
|
|
}
|
|
}
|
|
|
|
return level.bot_loadout_fields;
|
|
}
|
|
|
|
bot_loadout_set( personality, difficulty, createIfNeeded )
|
|
{
|
|
setName = difficulty + "_" + personality;
|
|
|
|
if ( !isDefined( level.botLoadoutSets ) )
|
|
level.botLoadoutSets = [];
|
|
|
|
if ( !isDefined( level.botLoadoutSets[setName] ) && createIfNeeded )
|
|
{
|
|
level.botLoadoutSets[setName] = SpawnStruct();
|
|
level.botLoadoutSets[setName].loadouts = [];
|
|
}
|
|
|
|
if ( isDefined( level.botLoadoutSets[setName] ) )
|
|
return level.botLoadoutSets[setName];
|
|
}
|
|
|
|
bot_loadout_pick( personality, difficulty )
|
|
{
|
|
loadoutSet = bot_loadout_set( personality, difficulty, false );
|
|
if ( IsDefined( loadoutSet ) && isDefined( loadoutSet.loadouts ) && loadoutSet.loadouts.size > 0 )
|
|
{
|
|
loadoutChoice = RandomInt( loadoutSet.loadouts.size );
|
|
return loadoutSet.loadouts[loadoutChoice].loadoutValues;
|
|
}
|
|
}
|
|
|
|
bot_validate_weapon( weaponName, attachment1, attachment2, attachment3 )
|
|
{
|
|
attachment_array = [];
|
|
|
|
attachment_start_num = 0;
|
|
if ( IsDefined( level.bot_weap_built_in_attachments[weaponName] ) && level.bot_weap_built_in_attachments[weaponName] != "none" )
|
|
{
|
|
attachment_array[attachment_array.size] = level.bot_weap_built_in_attachments[weaponName];
|
|
attachment_start_num++; // Don't do normal verification for this attachment since it is built in, but still check it for allowable attachment combos
|
|
}
|
|
|
|
if ( IsDefined( attachment1 ) && attachment1 != "none" )
|
|
attachment_array[ attachment_array.size ] = attachment1;
|
|
|
|
if ( IsDefined( attachment2 ) && attachment2 != "none" )
|
|
attachment_array[ attachment_array.size ] = attachment2;
|
|
|
|
if ( IsDefined( attachment3 ) && attachment3 != "none" )
|
|
attachment_array[attachment_array.size] = attachment3;
|
|
|
|
validAttachments = getWeaponAttachmentArrayFromStats( weaponName );
|
|
|
|
for( i = attachment_start_num; i < attachment_array.size; i++ )
|
|
{
|
|
Assert( IsDefined(attachment_array[i]) && attachment_array[i] != "none" );
|
|
|
|
base_attachment = attachmentMap_toBase( attachment_array[i] );
|
|
if ( base_attachment != attachment_array[i] )
|
|
{
|
|
AssertMsg( "Bots should only pick base attachments, i.e. " + base_attachment + " not " + attachment_array[i] );
|
|
return false;
|
|
}
|
|
|
|
if ( !bot_loadout_item_allowed( "attachment", attachment_array[i], undefined ) )
|
|
return false;
|
|
|
|
if ( !array_contains( validAttachments, attachment_array[i] ) )
|
|
return false;
|
|
|
|
num_attachment_duplicates = 0;
|
|
for( j = i-1; j >= 0; j-- )
|
|
{
|
|
if ( attachment_array[i] == attachment_array[j] )
|
|
{
|
|
// Found the same attachment, check if that is allowed
|
|
|
|
num_attachment_duplicates++;
|
|
if ( num_attachment_duplicates == 1 )
|
|
{
|
|
// Found one duplicate attachment, check if that is not allowed
|
|
if ( !IsDefined(level.allowable_double_attachments[attachment_array[i]]) )
|
|
return false;
|
|
}
|
|
else if ( num_attachment_duplicates > 1 )
|
|
{
|
|
// Found more than one duplicate attachment, which is never allowed
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( IsDefined( level.bot_invalid_attachment_combos[attachment_array[i]] ) )
|
|
{
|
|
if ( IsDefined(level.bot_invalid_attachment_combos[attachment_array[i]][attachment_array[j]]) )
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bot_validate_reticle( loadoutBaseName, loadoutValueArray, choice )
|
|
{
|
|
if ( !maps\mp\gametypes\_class::isValidReticle( choice, true ) )
|
|
return false;
|
|
|
|
// The only attachments that are valid to have a reticle are the ones listed in the level.bot_attachment_reticle array
|
|
attachment_names = ["Attachment","Attachment2","Attachment3"];
|
|
foreach( name in attachment_names )
|
|
{
|
|
attachmentLoadoutName = loadoutBaseName + name;
|
|
attachmentLoadoutValue = loadoutValueArray[attachmentLoadoutName];
|
|
if ( isDefined( attachmentLoadoutValue ) && IsDefined( level.bot_attachment_reticle[attachmentLoadoutValue] ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
init_bot_weap_statstable()
|
|
{
|
|
fileName = "mp/statstable.csv";
|
|
colName = 4;
|
|
colPers = 57;
|
|
colDiff = 58;
|
|
colBuiltInAttach = 40;
|
|
|
|
level.bot_weap_statstable = [];
|
|
level.bot_weap_personality = [];
|
|
level.bot_weap_built_in_attachments = [];
|
|
|
|
/#
|
|
level.all_bot_attachments_from_attachtable = level.bot_attachmenttable["veteran"];
|
|
#/
|
|
|
|
level.bot_weap_personality["iw5_combatknife"] = "cqb";
|
|
|
|
// level.bot_weap_statstable[loadoutValueName][self.personality][self.difficulty]
|
|
|
|
row = 1;
|
|
while(1)
|
|
{
|
|
rowNum = TableLookupByRow( fileName, row, 0 );
|
|
if ( rowNum == "" )
|
|
break;
|
|
|
|
weapName = TableLookupByRow( fileName, row, colName );
|
|
weapDiff = TableLookupByRow( fileName, row, colDiff );
|
|
weapPers = TableLookupByRow( fileName, row, colPers );
|
|
|
|
if ( weapName != "" && weapPers != "" )
|
|
level.bot_weap_personality[weapName] = weapPers;
|
|
|
|
if ( weapDiff != "" && weapName != "" && weapPers != "" )
|
|
{
|
|
baseWeaponName = weapName;
|
|
if( isLootWeapon( weapName ) )
|
|
baseWeaponName = maps\mp\gametypes\_class::getBaseFromLootVersion( weapName );
|
|
|
|
slotType = "loadoutPrimary";
|
|
if ( maps\mp\gametypes\_class::isValidSecondary( baseWeaponName, false ) )
|
|
{
|
|
slotType = "loadoutSecondary";
|
|
}
|
|
else if ( !maps\mp\gametypes\_class::isValidPrimary( baseWeaponName ) )
|
|
{
|
|
row++;
|
|
continue;
|
|
}
|
|
|
|
/#
|
|
// Validate the attachments for this weapon from the statstable array
|
|
validAttachments = getWeaponAttachmentArrayFromStats( weapName );
|
|
foreach( attachment in validAttachments )
|
|
{
|
|
in_table = array_contains( level.bot_attachmenttable["veteran"], attachment );
|
|
AssertEx( in_table, "Attachment '" + attachment + "' found in statstable for weapon '" + weapName + "' but not found in attachmenttable" );
|
|
|
|
if ( array_contains( level.all_bot_attachments_from_attachtable, attachment ) )
|
|
level.all_bot_attachments_from_attachtable = array_remove( level.all_bot_attachments_from_attachtable, attachment );
|
|
}
|
|
#/
|
|
|
|
if ( !IsDefined( level.bot_weap_statstable[slotType] ) )
|
|
level.bot_weap_statstable[slotType] = [];
|
|
|
|
persList = StrTok( weapPers, "| " );
|
|
diffList = StrTok( weapDiff, "| " );
|
|
|
|
foreach ( personality in persList )
|
|
{
|
|
if ( !IsDefined( level.bot_weap_statstable[slotType][personality] ) )
|
|
level.bot_weap_statstable[slotType][personality] = [];
|
|
|
|
foreach ( difficulty in diffList )
|
|
{
|
|
if ( !IsDefined( level.bot_weap_statstable[slotType][personality][difficulty] ) )
|
|
level.bot_weap_statstable[slotType][personality][difficulty] = [];
|
|
|
|
newIndex = level.bot_weap_statstable[slotType][personality][difficulty].size;
|
|
level.bot_weap_statstable[slotType][personality][difficulty][newIndex] = weapName;
|
|
}
|
|
}
|
|
|
|
built_in_attachment = TableLookupByRow( fileName, row, colBuiltInAttach );
|
|
if ( built_in_attachment != "" )
|
|
{
|
|
level.bot_weap_built_in_attachments[weapName] = built_in_attachment;
|
|
|
|
/#
|
|
// Validate that the built-in attachment is a valid attachment
|
|
in_table = array_contains( level.bot_attachmenttable["veteran"], built_in_attachment );
|
|
AssertEx( in_table, "Built-in attachment '" + built_in_attachment + "' found in statstable for weapon '" + weapName + "' but not found in attachmenttable" );
|
|
#/
|
|
}
|
|
}
|
|
|
|
row++;
|
|
}
|
|
|
|
/#
|
|
foreach( attachment in level.all_bot_attachments_from_attachtable )
|
|
AssertMsg( "attachment '" + attachment + "' enabled for bots in attachmenttable.csv but not found on any bot weapon in statstable.csv" );
|
|
level.all_bot_attachments_from_attachtable = undefined;
|
|
#/
|
|
}
|
|
|
|
bot_loadout_choose_from_statstable( loadoutValue, loadoutValueArray, loadoutValueName, personality, difficulty )
|
|
{
|
|
result = "none";
|
|
if ( personality == "default" )
|
|
personality = "run_and_gun";
|
|
|
|
loadoutValueNameLookup = loadoutValueName;
|
|
if ( loadoutValueNameLookup == "loadoutSecondary" && bot_loadout_set_has_wildcard( loadoutValueArray, "specialty_wildcard_dualprimaries" ) )
|
|
{
|
|
loadoutValueNameLookup = "loadoutPrimary";
|
|
|
|
// For campers / run-and-gunners, pick a second primary from the CQB loadouts as generally we would want something good for close range as a secondary
|
|
// For CQB, pick a potentially longer range weapon from the run_and_gun loadouts
|
|
if ( personality == "camper" || personality == "run_and_gun" )
|
|
personality = "cqb";
|
|
else if ( personality == "cqb" )
|
|
personality = "run_and_gun";
|
|
}
|
|
|
|
if ( !IsDefined( level.bot_weap_statstable ) )
|
|
return result;
|
|
|
|
if ( !IsDefined( level.bot_weap_statstable[loadoutValueNameLookup] ) )
|
|
return result;
|
|
|
|
if ( !IsDefined( level.bot_weap_statstable[loadoutValueNameLookup][personality] ) )
|
|
return result;
|
|
|
|
if ( !IsDefined( level.bot_weap_statstable[loadoutValueNameLookup][personality][difficulty] ) )
|
|
return result;
|
|
|
|
result = bot_loadout_choose_from_set( level.bot_weap_statstable[loadoutValueNameLookup][personality][difficulty], loadoutValue, loadoutValueArray, loadoutValueName );
|
|
|
|
return result;
|
|
}
|
|
|
|
bot_validate_perk( choice, loadoutValueName, loadoutValueArray )
|
|
{
|
|
perkClass = "Perk_Slot1";
|
|
if ( loadoutValueName == "loadoutPerk3" || loadoutValueName == "loadoutPerk4" )
|
|
perkClass = "Perk_Slot2";
|
|
else if ( loadoutValueName == "loadoutPerk5" || loadoutValueName == "loadoutPerk6" )
|
|
perkClass = "Perk_Slot3";
|
|
|
|
if ( !bot_loadout_item_allowed( "perk", choice, perkClass ) )
|
|
return false;
|
|
|
|
index = int( GetSubStr( loadoutValueName, 11 ) );
|
|
|
|
wildcard_required = "";
|
|
if ( index == 2 )
|
|
wildcard_required = "specialty_wildcard_perkslot1";
|
|
else if ( index == 4 )
|
|
wildcard_required = "specialty_wildcard_perkslot2";
|
|
else if ( index == 6 )
|
|
wildcard_required = "specialty_wildcard_perkslot3";
|
|
|
|
if ( wildcard_required != "" )
|
|
{
|
|
if ( !bot_loadout_set_has_wildcard(loadoutValueArray, wildcard_required) )
|
|
return false;
|
|
}
|
|
|
|
for ( i = index - 1; i > 0; i-- )
|
|
{
|
|
prevPerkName = "loadoutPerk" + i;
|
|
|
|
if ( loadoutValueArray[prevPerkName] == "none" || loadoutValueArray[prevPerkName] == "specialty_null" )
|
|
continue;
|
|
|
|
// Make sure we havent picked the same perk twice
|
|
if ( choice == loadoutValueArray[prevPerkName] )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bot_loadout_choose_from_default_class( class, loadoutValueName )
|
|
{
|
|
class_num = int(GetSubStr( class, 5, 6 )) - 1;
|
|
|
|
switch ( loadoutValueName )
|
|
{
|
|
case "loadoutPrimary":
|
|
return maps\mp\gametypes\_class::table_getWeapon( level.classTableName, class_num, 0 );
|
|
case "loadoutPrimaryAttachment":
|
|
return maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 0, 0);
|
|
case "loadoutPrimaryAttachment2":
|
|
return maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 0, 1 );
|
|
case "loadoutPrimaryAttachment3":
|
|
return maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 0, 2 );
|
|
case "loadoutPrimaryCamo":
|
|
return maps\mp\gametypes\_class::table_getWeaponCamo( level.classTableName, class_num, 0 );
|
|
case "loadoutPrimaryReticle":
|
|
return maps\mp\gametypes\_class::table_getWeaponReticle( level.classTableName, class_num, 0 );
|
|
case "loadoutSecondary":
|
|
return maps\mp\gametypes\_class::table_getWeapon( level.classTableName, class_num, 1 );
|
|
case "loadoutSecondaryAttachment":
|
|
return maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 1, 0);
|
|
case "loadoutSecondaryAttachment2":
|
|
return maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 1, 1 );
|
|
case "loadoutSecondaryAttachment3":
|
|
return maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 1, 2 );
|
|
case "loadoutSecondaryCamo":
|
|
return maps\mp\gametypes\_class::table_getWeaponCamo( level.classTableName, class_num, 1 );
|
|
case "loadoutSecondaryReticle":
|
|
return maps\mp\gametypes\_class::table_getWeaponReticle( level.classTableName, class_num, 1 );
|
|
case "loadoutEquipment":
|
|
return maps\mp\gametypes\_class::table_getEquipment( level.classTableName, class_num );
|
|
case "loadoutEquipment2":
|
|
return tableLookup( level.classTableName, 0, "loadoutEquipment2", (class_num + 1) ); // we want the actual string, not 0/1 from table_GetEquipmentExtra
|
|
case "loadoutOffhand":
|
|
return maps\mp\gametypes\_class::table_getOffhand( level.classTableName, class_num );
|
|
case "loadoutOffhand2":
|
|
if ( maps\mp\gametypes\_class::table_getOffhandExtra( level.classTableName, class_num ) )
|
|
return maps\mp\gametypes\_class::table_getOffhand( level.classTableName, class_num );
|
|
else
|
|
return "specialty_null";
|
|
case "loadoutStreak1":
|
|
return maps\mp\gametypes\_class::table_getKillstreak( level.classTableName, class_num, 0 );
|
|
case "loadoutStreakModule1a":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 0, 0 );
|
|
case "loadoutStreakModule1b":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 0, 1 );
|
|
case "loadoutStreakModule1c":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 0, 2 );
|
|
case "loadoutStreak2":
|
|
return maps\mp\gametypes\_class::table_getKillstreak( level.classTableName, class_num, 1 );
|
|
case "loadoutStreakModule2a":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 1, 0 );
|
|
case "loadoutStreakModule2b":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 1, 1 );
|
|
case "loadoutStreakModule2c":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 1, 2 );
|
|
case "loadoutStreak3":
|
|
return maps\mp\gametypes\_class::table_getKillstreak( level.classTableName, class_num, 2 );
|
|
case "loadoutStreakModule3a":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 2, 0 );
|
|
case "loadoutStreakModule3b":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 2, 1 );
|
|
case "loadoutStreakModule3c":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 2, 2 );
|
|
case "loadoutStreak4":
|
|
return maps\mp\gametypes\_class::table_getKillstreak( level.classTableName, class_num, 3 );
|
|
case "loadoutStreakModule4a":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 3, 0 );
|
|
case "loadoutStreakModule4b":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 3, 1 );
|
|
case "loadoutStreakModule4c":
|
|
return maps\mp\gametypes\_class::table_getKillstreakModule( level.classTableName, class_num, 3, 2 );
|
|
case "loadoutPerk1":
|
|
case "loadoutPerk2":
|
|
case "loadoutPerk3":
|
|
case "loadoutPerk4":
|
|
case "loadoutPerk5":
|
|
case "loadoutPerk6":
|
|
{
|
|
perkIndex = int( GetSubStr( loadoutValueName, 11 ) ) - 1;
|
|
perk = maps\mp\gametypes\_class::table_getPerk( level.classTableName, class_num, perkIndex );
|
|
if ( perk == "" )
|
|
return "specialty_null";
|
|
return perk;
|
|
}
|
|
case "loadoutWildcard1":
|
|
return maps\mp\gametypes\_class::table_getWildcard( level.classTableName, class_num, 0 );
|
|
case "loadoutWildcard2":
|
|
return maps\mp\gametypes\_class::table_getWildcard( level.classTableName, class_num, 1 );
|
|
case "loadoutWildcard3":
|
|
return maps\mp\gametypes\_class::table_getWildcard( level.classTableName, class_num, 2 );
|
|
}
|
|
|
|
AssertMsg( "Loadout type '" + loadoutValueName + "' is not supported for bot_loadout_choose_from_default_class('" + class + "')" );
|
|
|
|
return class;
|
|
}
|
|
|
|
bot_loadout_choose_from_custom_default_class( index, loadoutValueName ) // self = bot
|
|
{
|
|
assert( IsUsingMatchRulesData() );
|
|
assert( IsBot( self ) );
|
|
|
|
team = self bot_loadout_team();
|
|
|
|
switch ( loadoutValueName )
|
|
{
|
|
case "loadoutPrimary":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "weapon" );
|
|
case "loadoutPrimaryAttachment":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "attachment", 0 );
|
|
case "loadoutPrimaryAttachment2":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "attachment", 1 );
|
|
case "loadoutPrimaryAttachment3":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "attachment", 2 );
|
|
case "loadoutPrimaryCamo":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "camo" );
|
|
case "loadoutPrimaryReticle":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 0, "reticle" );
|
|
case "loadoutSecondary":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "weapon" );
|
|
case "loadoutSecondaryAttachment":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "attachment", 0 );
|
|
case "loadoutSecondaryAttachment2":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "attachment", 1 );
|
|
case "loadoutSecondaryAttachment3":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "attachment", 2 );
|
|
case "loadoutSecondaryCamo":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "camo" );
|
|
case "loadoutSecondaryReticle":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "weaponSetups", 1, "reticle" );
|
|
case "loadoutEquipment":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "equipmentSetups", 0, "equipment" );
|
|
case "loadoutEquipment2":
|
|
if ( getMatchRulesData( "defaultClasses", team, index, "class", "equipmentSetups", 0, "extra" ) )
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "equipmentSetups", 0, "equipment" );
|
|
else
|
|
return "specialty_null";
|
|
case "loadoutOffhand":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "equipmentSetups", 1, "equipment" );
|
|
case "loadoutOffhand2":
|
|
return "specialty_null";
|
|
case "loadoutStreak1":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 0, "streak" );
|
|
case "loadoutStreakModule1a":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 0, "modules", 0 );
|
|
case "loadoutStreakModule1b":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 0, "modules", 1 );
|
|
case "loadoutStreakModule1c":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 0, "modules", 2 );
|
|
case "loadoutStreak2":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 1, "streak" );
|
|
case "loadoutStreakModule2a":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 1, "modules", 0 );
|
|
case "loadoutStreakModule2b":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 1, "modules", 1 );
|
|
case "loadoutStreakModule2c":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 1, "modules", 2 );
|
|
case "loadoutStreak3":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 2, "streak" );
|
|
case "loadoutStreakModule3a":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 2, "modules", 0 );
|
|
case "loadoutStreakModule3b":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 2, "modules", 1 );
|
|
case "loadoutStreakModule3c":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 2, "modules", 2 );
|
|
case "loadoutStreak4":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 3, "streak" );
|
|
case "loadoutStreakModule4a":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 3, "modules", 0 );
|
|
case "loadoutStreakModule4b":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 3, "modules", 1 );
|
|
case "loadoutStreakModule4c":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "assaultStreaks", 3, "modules", 2 );
|
|
case "loadoutPerk1":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "perkSlots", 0 );
|
|
case "loadoutPerk2":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "perkSlots", 1 );
|
|
case "loadoutPerk3":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "perkSlots", 2 );
|
|
case "loadoutPerk4":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "perkSlots", 3 );
|
|
case "loadoutPerk5":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "perkSlots", 4 );
|
|
case "loadoutPerk6":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "perkSlots", 5 );
|
|
case "loadoutWildcard1":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "wildcardSlots", 0 );
|
|
case "loadoutWildcard2":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "wildcardSlots", 1 );
|
|
case "loadoutWildcard3":
|
|
return getMatchRulesData( "defaultClasses", team, index, "class", "wildcardSlots", 2 );
|
|
default:
|
|
AssertMsg("'" + loadoutValueName + "' not supported in bot_loadout_choose_from_custom_default_class()");
|
|
}
|
|
|
|
return "none";
|
|
}
|
|
|
|
|
|
init_bot_attachmenttable()
|
|
{
|
|
fileName = "mp/attachmenttable.csv";
|
|
colName = 3;
|
|
colDiff = 15;
|
|
colReticle = 7;
|
|
|
|
level.bot_attachmenttable = [];
|
|
level.bot_attachment_reticle = [];
|
|
|
|
/#
|
|
level.bot_att_cross_reference = [];
|
|
#/
|
|
|
|
row = 1;
|
|
while(1)
|
|
{
|
|
attachmentName = TableLookupByRow( fileName, row, colName );
|
|
|
|
if ( attachmentName == "done" )
|
|
{
|
|
AssertEx( TableLookupByRow( fileName, row+1, colName ) == "", fileName + " needs to have 'done' as its last element so bots can parse it correctly" );
|
|
break;
|
|
}
|
|
|
|
attachmentDiff = TableLookupByRow( fileName, row, colDiff );
|
|
|
|
if ( attachmentName != "" && attachmentDiff != "" )
|
|
{
|
|
/#
|
|
if ( IsDefined( level.bot_att_cross_reference[attachmentName] ) && level.bot_att_cross_reference[attachmentName] != attachmentDiff )
|
|
AssertMsg( "base ref '" + attachmentName + "' is associated with conflicting Bot Difficulty in attachmenttable.csv" );
|
|
level.bot_att_cross_reference[attachmentName] = attachmentDiff;
|
|
#/
|
|
|
|
attachmentReticle = TableLookupByRow( fileName, row, colReticle );
|
|
if ( attachmentReticle == "TRUE" )
|
|
level.bot_attachment_reticle[attachmentName] = true;
|
|
|
|
diffList = StrTok( attachmentDiff, "| " );
|
|
|
|
// We are assuming that Veteran bots can use all bot attachments. If this is false, need to adjust the attachment validation logic in init_bot_weap_statstable
|
|
Assert( array_contains( diffList, "veteran" ) );
|
|
|
|
foreach ( difficulty in diffList )
|
|
{
|
|
if ( !IsDefined( level.bot_attachmenttable[difficulty] ) )
|
|
level.bot_attachmenttable[difficulty] = [];
|
|
|
|
if ( !array_contains( level.bot_attachmenttable[difficulty], attachmentName ) )
|
|
{
|
|
newIndex = level.bot_attachmenttable[difficulty].size;
|
|
level.bot_attachmenttable[difficulty][newIndex] = attachmentName;
|
|
}
|
|
}
|
|
}
|
|
|
|
row++;
|
|
|
|
AssertEx(row < 999, "Missing 'done' at the bottom of attachmenttable.csv");
|
|
}
|
|
|
|
/#
|
|
level.bot_att_cross_reference = undefined;
|
|
#/
|
|
|
|
level.bot_invalid_attachment_combos = [];
|
|
level.allowable_double_attachments = []; // Attachments that are allowed twice
|
|
|
|
attachmentComboTable = "mp/attachmentcombos.csv";
|
|
column = 0;
|
|
while(1)
|
|
{
|
|
column++;
|
|
currentAttachment = TableLookupByRow( attachmentComboTable, 0, column );
|
|
if ( currentAttachment == "" )
|
|
break;
|
|
|
|
row = 0;
|
|
while(1)
|
|
{
|
|
row++;
|
|
attachmentTesting = TableLookupByRow( attachmentComboTable, row, 0 );
|
|
if ( attachmentTesting == "" )
|
|
break;
|
|
|
|
if ( attachmentTesting == currentAttachment )
|
|
{
|
|
if ( TableLookupByRow( attachmentComboTable, row, column ) != "no" )
|
|
level.allowable_double_attachments[attachmentTesting] = true;
|
|
}
|
|
else
|
|
{
|
|
if ( TableLookupByRow( attachmentComboTable, row, column ) == "no" )
|
|
level.bot_invalid_attachment_combos[currentAttachment][attachmentTesting] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bot_loadout_choose_from_attachmenttable( loadoutValue, loadoutValueArray, loadoutValueName, personality, difficulty )
|
|
{
|
|
// Set fallbacks in case nothing is found that matches
|
|
result = "none";
|
|
|
|
if ( !IsDefined( level.bot_attachmenttable ) )
|
|
return result;
|
|
|
|
if ( !IsDefined( level.bot_attachmenttable[difficulty] ) )
|
|
return result;
|
|
|
|
result = bot_loadout_choose_from_set( level.bot_attachmenttable[difficulty], loadoutValue, loadoutValueArray, loadoutValueName );
|
|
|
|
return result;
|
|
}
|
|
|
|
init_bot_reticletable()
|
|
{
|
|
fileName = "mp/reticletable.csv";
|
|
colName = 1;
|
|
colBotValid = 8;
|
|
|
|
level.bot_reticletable = [];
|
|
|
|
row = 0;
|
|
while ( 1 )
|
|
{
|
|
reticleName = TableLookupByRow( fileName, row, colName );
|
|
|
|
if ( !IsDefined( reticleName ) || reticleName == "" )
|
|
break;
|
|
|
|
AssertEx(maps\mp\gametypes\_class::isValidReticle( reticleName, true ),"Reticle '" + reticleName + "' found in reticletable.csv but not valid according to isValidReticle");
|
|
botValid = TableLookupByRow( fileName, row, colBotValid );
|
|
if ( IsDefined( botValid ) && Int( botValid ) )
|
|
level.bot_reticletable[ level.bot_reticletable.size ] = reticleName;
|
|
|
|
row++;
|
|
}
|
|
}
|
|
|
|
bot_loadout_choose_from_reticletable( loadoutValue, loadoutValueArray, loadoutValueName, personality, difficulty )
|
|
{
|
|
// Set fallbacks in case nothing is found that matches
|
|
result = "none";
|
|
|
|
if ( !IsDefined( level.bot_reticletable ) )
|
|
return result;
|
|
|
|
should_pick_reticle = RandomInt(100) > 50;
|
|
if ( should_pick_reticle )
|
|
result = bot_loadout_choose_from_set( level.bot_reticletable, loadoutValue, loadoutValueArray, loadoutValueName );
|
|
|
|
return result;
|
|
}
|
|
|
|
init_bot_camotable()
|
|
{
|
|
fileName = "mp/camotable.csv";
|
|
colName = 1;
|
|
colMaterial = 8;
|
|
colBotValid = 9;
|
|
|
|
level.bot_camotable = [];
|
|
|
|
row = 0;
|
|
while ( 1 )
|
|
{
|
|
camoName = TableLookupByRow( fileName, row, colName );
|
|
|
|
if ( !IsDefined( camoName ) || camoName == "" )
|
|
break;
|
|
|
|
camoMaterial = TableLookupByRow( fileName, row, colMaterial );
|
|
if ( IsDefined( camoMaterial ) && camoMaterial != "" )
|
|
{
|
|
AssertEx(maps\mp\gametypes\_class::isValidCamo( camoName, true ),"Camo '" + camoName + "' found in camotable.csv but not valid according to isValidCamo");
|
|
botValid = TableLookupByRow( fileName, row, colBotValid );
|
|
if ( IsDefined( botValid ) && Int( botValid ) )
|
|
level.bot_camotable[ level.bot_camotable.size ] = camoName;
|
|
}
|
|
|
|
row++;
|
|
}
|
|
}
|
|
|
|
bot_loadout_choose_from_camotable( loadoutValue, loadoutValueArray, loadoutValueName, personality, difficulty )
|
|
{
|
|
// Set fallbacks in case nothing is found that matches
|
|
result = "none";
|
|
|
|
if ( !IsDefined( level.bot_camotable ) )
|
|
return result;
|
|
|
|
should_pick_camo = RandomInt(100) > 50;
|
|
if ( should_pick_camo )
|
|
result = bot_loadout_choose_from_set( level.bot_camotable, loadoutValue, loadoutValueArray, loadoutValueName );
|
|
|
|
return result;
|
|
}
|
|
|
|
bot_loadout_item_valid_for_rank( loadoutValue, item, difficulty )
|
|
{
|
|
if ( !IsPlayer(self) )
|
|
return true;
|
|
|
|
if ( !IsDefined( level.bot_min_rank_for_item[item] ) )
|
|
{
|
|
table_name = "mp/unlockTable.csv";
|
|
row = TableLookupRowNum( table_name, 0, item );
|
|
level.bot_min_rank_for_item[item] = INT(TableLookupByRow( table_name, row, 2 ));
|
|
}
|
|
|
|
if ( loadoutValue == "classtable_any" && difficulty == "recruit" )
|
|
return true; // Anything is valid for Recruits if it is in the classtable
|
|
|
|
if ( !IsDefined(self.rank_for_items) )
|
|
{
|
|
if ( difficulty == "recruit" )
|
|
{
|
|
self.rank_for_items = 3;
|
|
}
|
|
else
|
|
{
|
|
self.rank_for_items = self.pers["rank"];
|
|
if ( !IsDefined(self.rank_for_items) )
|
|
self.rank_for_items = level.bot_rnd_rank[difficulty][0];
|
|
}
|
|
}
|
|
|
|
if ( level.bot_min_rank_for_item[item] <= self.rank_for_items )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bot_loadout_valid_choice( loadoutValue, loadoutValueArray, loadoutValueName, choice )
|
|
{
|
|
valid = true;
|
|
|
|
switch ( loadoutValueName )
|
|
{
|
|
case "loadoutPrimary":
|
|
baseWeaponName = choice;
|
|
if ( isLootWeapon( baseWeaponName ) )
|
|
baseWeaponName = maps\mp\gametypes\_class::getBaseFromLootVersion( baseWeaponName );
|
|
valid = bot_loadout_item_allowed( "weapon", baseWeaponName, undefined );
|
|
valid = valid && maps\mp\gametypes\_class::isValidPrimary( choice );
|
|
break;
|
|
case "loadoutEquipment":
|
|
valid = bot_loadout_item_allowed( "perk", choice, "Lethal" );
|
|
valid = valid && maps\mp\gametypes\_class::isValidEquipment( choice, bot_loadout_set_has_wildcard(loadoutValueArray, "specialty_wildcard_dualtacticals") );
|
|
valid = valid && bot_loadout_item_valid_for_rank( loadoutValue, choice, self BotGetDifficulty() );
|
|
break;
|
|
case "loadoutEquipment2":
|
|
valid = (choice == "specialty_null" || choice == loadoutValueArray["loadoutEquipment"]);
|
|
break;
|
|
case "loadoutOffhand":
|
|
valid = bot_loadout_item_allowed( "perk", choice, "Tactical" );
|
|
valid = valid && maps\mp\gametypes\_class::isValidOffhand( choice, bot_loadout_set_has_wildcard(loadoutValueArray, "specialty_wildcard_duallethals") );
|
|
valid = valid && bot_loadout_item_valid_for_rank( loadoutValue, choice, self BotGetDifficulty() );
|
|
break;
|
|
case "loadoutOffhand2":
|
|
valid = (choice == "specialty_null");
|
|
AssertEx(valid, "loadoutOffhand2 not supported");
|
|
break;
|
|
case "loadoutPrimaryAttachment":
|
|
valid = self bot_validate_weapon( loadoutValueArray["loadoutPrimary"], choice );
|
|
break;
|
|
case "loadoutPrimaryAttachment2":
|
|
valid = self bot_validate_weapon( loadoutValueArray["loadoutPrimary"], loadoutValueArray["loadoutPrimaryAttachment"], choice );
|
|
break;
|
|
case "loadoutPrimaryAttachment3":
|
|
valid = self bot_validate_weapon( loadoutValueArray["loadoutPrimary"], loadoutValueArray["loadoutPrimaryAttachment"], loadoutValueArray["loadoutPrimaryAttachment2"], choice );
|
|
break;
|
|
case "loadoutPrimaryReticle":
|
|
valid = self bot_validate_reticle( "loadoutPrimary", loadoutValueArray, choice );
|
|
break;
|
|
case "loadoutPrimaryCamo":
|
|
valid = (!IsDefined( self.botLoadoutFavoriteCamoPrimary ) || (choice == self.botLoadoutFavoriteCamoPrimary));
|
|
valid = valid && maps\mp\gametypes\_class::isValidCamo( choice );
|
|
break;
|
|
case "loadoutSecondary":
|
|
valid = (choice != loadoutValueArray["loadoutPrimary"]);
|
|
baseWeaponName = choice;
|
|
if ( isLootWeapon( baseWeaponName ) )
|
|
baseWeaponName = maps\mp\gametypes\_class::getBaseFromLootVersion( baseWeaponName );
|
|
valid = valid && bot_loadout_item_allowed( "weapon", baseWeaponName, undefined );
|
|
valid = valid && maps\mp\gametypes\_class::isValidSecondary( choice, bot_loadout_set_has_wildcard(loadoutValueArray, "specialty_wildcard_dualprimaries") );
|
|
break;
|
|
case "loadoutSecondaryAttachment":
|
|
valid = self bot_validate_weapon( loadoutValueArray["loadoutSecondary"], choice, "none" );
|
|
break;
|
|
case "loadoutSecondaryAttachment2":
|
|
valid = self bot_validate_weapon( loadoutValueArray["loadoutSecondary"], loadoutValueArray["loadoutSecondaryAttachment"], choice );
|
|
break;
|
|
case "loadoutSecondaryAttachment3":
|
|
valid = self bot_validate_weapon( loadoutValueArray["loadoutSecondary"], loadoutValueArray["loadoutSecondaryAttachment"], loadoutValueArray["loadoutSecondaryAttachment2"], choice );
|
|
break;
|
|
case "loadoutSecondaryReticle":
|
|
valid = self bot_validate_reticle( "loadoutSecondary", loadoutValueArray, choice );
|
|
break;
|
|
case "loadoutSecondaryCamo":
|
|
valid = (!IsDefined(self.botLoadoutFavoriteCamoSecondary ) || (choice == self.botLoadoutFavoriteCamoSecondary));
|
|
valid = valid && maps\mp\gametypes\_class::isValidCamo( choice );
|
|
break;
|
|
case "loadoutStreak1":
|
|
case "loadoutStreak2":
|
|
case "loadoutStreak3":
|
|
case "loadoutStreak4":
|
|
valid = bot_killstreak_is_valid_internal( choice, "bots", undefined );
|
|
valid = valid && bot_loadout_item_allowed( "killstreak", choice, undefined );
|
|
break;
|
|
case "loadoutStreakModule1a":
|
|
case "loadoutStreakModule1b":
|
|
case "loadoutStreakModule1c":
|
|
case "loadoutStreakModule2a":
|
|
case "loadoutStreakModule2b":
|
|
case "loadoutStreakModule2c":
|
|
case "loadoutStreakModule3a":
|
|
case "loadoutStreakModule3b":
|
|
case "loadoutStreakModule3c":
|
|
case "loadoutStreakModule4a":
|
|
case "loadoutStreakModule4b":
|
|
case "loadoutStreakModule4c":
|
|
valid = bot_loadout_item_allowed( "module", choice, undefined );
|
|
break;
|
|
case "loadoutPerk1":
|
|
case "loadoutPerk2":
|
|
case "loadoutPerk3":
|
|
case "loadoutPerk4":
|
|
case "loadoutPerk5":
|
|
case "loadoutPerk6":
|
|
valid = (choice == "specialty_null") || bot_validate_perk( choice, loadoutValueName, loadoutValueArray );
|
|
valid = valid && bot_loadout_item_valid_for_rank( loadoutValue, choice, self BotGetDifficulty() );
|
|
break;
|
|
case "loadoutWildcard1":
|
|
case "loadoutWildcard2":
|
|
case "loadoutWildcard3":
|
|
valid = true;
|
|
break;
|
|
default:
|
|
AssertMsg("'" + loadoutValueName + "' needs validation in bot_loadout_valid_choice");
|
|
break;
|
|
};
|
|
|
|
return valid;
|
|
}
|
|
|
|
bot_loadout_choose_from_set( valueChoices, loadoutValue, loadoutValueArray, loadoutValueName, isTemplate )
|
|
{
|
|
chosenValue = "none";
|
|
chosenTemplate = undefined;
|
|
validCount = 0.0;
|
|
|
|
if ( array_contains( valueChoices, "specialty_null" ) )
|
|
chosenValue = "specialty_null";
|
|
else if ( loadoutValueName == "loadoutEquipment" || loadoutValueName == "loadoutOffhand" || string_find( loadoutValueName, "Perk" ) >= 0 )
|
|
chosenValue = "specialty_null";
|
|
|
|
if ( loadoutValue == "classtable_any" )
|
|
{
|
|
// Bot should pick perks / wildcards / etc all from the same default class
|
|
if ( !IsDefined(self.default_class_chosen) )
|
|
self.default_class_chosen = Random(["class1","class2","class3","class4","class5"]);
|
|
valueChoices = [self.default_class_chosen];
|
|
}
|
|
|
|
foreach ( choice in valueChoices )
|
|
{
|
|
template = undefined;
|
|
|
|
if ( GetSubStr( choice, 0, 9 ) == "template_" )
|
|
{
|
|
/#
|
|
if ( IsDefined( isTemplate ) && isTemplate )
|
|
AssertMsg( "template_ entries should not reference other template_ entries as the random weighting does not work right for that" );
|
|
#/
|
|
|
|
template = choice;
|
|
templateValues = level.botLoadoutTemplates[choice][loadoutValueName];
|
|
assert( IsDefined( templateValues ) );
|
|
choice = bot_loadout_choose_from_set( StrTok( templateValues, "| " ), loadoutValue, loadoutValueArray, loadoutValueName, true );
|
|
|
|
// If we have already chosen this template for any field, always choose the same template again when given the option
|
|
if ( IsDefined( template ) && IsDefined( self.chosenTemplates[template] ) )
|
|
return choice;
|
|
}
|
|
|
|
if ( choice == "attachmenttable" )
|
|
return bot_loadout_choose_from_attachmenttable( loadoutValue, loadoutValueArray, loadoutValueName, self.personality, self.difficulty );
|
|
|
|
if ( choice == "weap_statstable" )
|
|
return bot_loadout_choose_from_statstable( loadoutValue, loadoutValueArray, loadoutValueName, self.personality, self.difficulty );
|
|
|
|
if ( choice == "reticletable" )
|
|
return bot_loadout_choose_from_reticletable( loadoutValue, loadoutValueArray, loadoutValueName, self.personality, self.difficulty );
|
|
|
|
if ( choice == "camotable" )
|
|
return bot_loadout_choose_from_camotable( loadoutValue, loadoutValueArray, loadoutValueName, self.personality, self.difficulty );
|
|
|
|
if ( GetSubStr( choice, 0, 5 ) == "class" && int( GetSubStr( choice, 5, 6 ) ) > 0 )
|
|
choice = bot_loadout_choose_from_default_class( choice, loadoutValueName );
|
|
|
|
if ( IsSubStr( loadoutValueName, "loadoutStreakModule" ) )
|
|
{
|
|
streak_num = INT(GetSubStr(loadoutValueName,19,20));
|
|
streak_in_slot = loadoutValueArray["loadoutStreak" + streak_num];
|
|
module_letter = GetSubStr(loadoutValueName,20,21);
|
|
switch ( streak_in_slot )
|
|
{
|
|
case "remote_mg_sentry_turret":
|
|
if ( module_letter == "a" )
|
|
choice = "sentry_guardian";
|
|
break;
|
|
case "warbird":
|
|
if ( module_letter == "a" )
|
|
choice = "warbird_ai_attack";
|
|
break;
|
|
case "assault_ugv":
|
|
if ( module_letter == "a" )
|
|
choice = "assault_ugv_ai";
|
|
else if ( module_letter == "b" )
|
|
choice = "assault_ugv_mg";
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( self bot_loadout_valid_choice( loadoutValue, loadoutValueArray, loadoutValueName, choice ) )
|
|
{
|
|
validCount = validCount + 1.0;
|
|
if ( RandomFloat( 1.0 ) <= (1.0 / validCount) )
|
|
{
|
|
chosenValue = choice;
|
|
chosenTemplate = template;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( IsDefined( chosenTemplate ) )
|
|
self.chosenTemplates[chosenTemplate] = true;
|
|
|
|
return chosenValue;
|
|
}
|
|
|
|
bot_loadout_choose_values( loadoutValueArray )
|
|
{
|
|
self.chosenTemplates = [];
|
|
|
|
foreach( loadoutValueName, loadoutValue in loadoutValueArray )
|
|
{
|
|
valueChoices = StrTok( loadoutValue, "| " );
|
|
|
|
chosenValue = self bot_loadout_choose_from_set( valueChoices, loadoutValue, loadoutValueArray, loadoutValueName );
|
|
/#
|
|
debugLoadoutValue = GetDvar( "bot_Debug" + loadoutValueName, "" );
|
|
if ( IsDefined( debugLoadoutValue ) && debugLoadoutValue != "" )
|
|
chosenValue = debugLoadoutValue;
|
|
#/
|
|
loadoutValueArray[loadoutValueName] = chosenValue;
|
|
}
|
|
|
|
if ( self BotGetDifficulty() != "recruit" )
|
|
{
|
|
pick13_count = bot_get_pick_13_count( loadoutValueArray );
|
|
num_attempts_tried = 0;
|
|
while( pick13_count < level.customClassPickCount && num_attempts_tried < 5 )
|
|
{
|
|
if ( pick13_count == level.customClassPickCount-1 )
|
|
loadoutValueArray = bot_find_1_loadout_option( loadoutValueArray );
|
|
else if ( pick13_count <= level.customClassPickCount-2 )
|
|
loadoutValueArray = bot_find_2_loadout_options( loadoutValueArray );
|
|
|
|
pick13_count = bot_get_pick_13_count( loadoutValueArray );
|
|
num_attempts_tried++;
|
|
}
|
|
/*/#
|
|
bot_min_allowable_items = level.customClassPickCount;
|
|
if ( level.customClassPickCount > 19 )
|
|
bot_min_allowable_items--; // If the limit is 20, bots might not be able to fill it completely up, so allow 1 unusued slot
|
|
if ( pick13_count < bot_min_allowable_items )
|
|
{
|
|
println( "loadoutValueArray" );
|
|
keys = getArrayKeys( loadoutValueArray );
|
|
for ( i = 0; i < keys.size; i++ )
|
|
println( " loadoutValueArray[\"" + keys[ i ] + "\"] = ", loadoutValueArray[ keys[ i ] ] );
|
|
|
|
AssertMsg("Bot loadout has " + pick13_count + " items, expected " + level.customClassPickCount + ". See log for details.");
|
|
}
|
|
#/*/
|
|
}
|
|
|
|
return loadoutValueArray;
|
|
}
|
|
|
|
bot_find_1_loadout_option( loadoutValueArray )
|
|
{
|
|
Assert( bot_get_pick_13_count( loadoutValueArray ) == level.customClassPickCount-1 );
|
|
|
|
// First try to pick a second secondary attachment
|
|
if ( loadoutValueArray["loadoutSecondaryAttachment2"] == "none" )
|
|
{
|
|
loadoutValueArray["loadoutSecondaryAttachment2"] = self bot_loadout_choose_from_set( ["attachmenttable"], "attachmenttable", loadoutValueArray, "loadoutSecondaryAttachment2" );
|
|
/#
|
|
debugLoadoutValue = GetDvar( "bot_DebugloadoutSecondaryAttachment2" );
|
|
if ( IsDefined( debugLoadoutValue ) && debugLoadoutValue != "" )
|
|
loadoutValueArray["loadoutSecondaryAttachment2"] = debugLoadoutValue;
|
|
#/
|
|
}
|
|
|
|
if ( bot_get_pick_13_count( loadoutValueArray ) == level.customClassPickCount )
|
|
return loadoutValueArray;
|
|
|
|
remove_or_add_grenade = "add";
|
|
has_full_wildcards = loadoutValueArray["loadoutWildcard1"] != "specialty_null" && loadoutValueArray["loadoutWildcard2"] != "specialty_null" && loadoutValueArray["loadoutWildcard3"] != "specialty_null";
|
|
if ( RandomInt(100) < 50 && !has_full_wildcards )
|
|
remove_or_add_grenade = "remove";
|
|
|
|
if ( remove_or_add_grenade == "remove" )
|
|
{
|
|
if ( loadoutValueArray["loadoutEquipment"] != "specialty_null" )
|
|
{
|
|
loadoutValueArray["loadoutEquipment"] = "specialty_null";
|
|
/#
|
|
debugLoadoutValue = GetDvar( "bot_DebugloadoutEquipment" );
|
|
if ( IsDefined( debugLoadoutValue ) && debugLoadoutValue != "" )
|
|
loadoutValueArray["loadoutloadoutEquipment"] = debugLoadoutValue;
|
|
#/
|
|
if ( bot_get_pick_13_count( loadoutValueArray ) == level.customClassPickCount-2 )
|
|
return loadoutValueArray;
|
|
}
|
|
}
|
|
else if ( remove_or_add_grenade == "add" )
|
|
{
|
|
if ( loadoutValueArray["loadoutEquipment"] != "specialty_null" && loadoutValueArray["loadoutEquipment2"] == "specialty_null" )
|
|
{
|
|
loadoutValueArray["loadoutEquipment2"] = loadoutValueArray["loadoutEquipment"];
|
|
/#
|
|
debugLoadoutValue = GetDvar( "bot_DebugloadoutEquipment2" );
|
|
if ( IsDefined( debugLoadoutValue ) && debugLoadoutValue != "" )
|
|
loadoutValueArray["loadoutloadoutEquipment2"] = debugLoadoutValue;
|
|
#/
|
|
if ( bot_get_pick_13_count( loadoutValueArray ) == level.customClassPickCount )
|
|
return loadoutValueArray;
|
|
}
|
|
}
|
|
|
|
if ( !has_full_wildcards )
|
|
{
|
|
// Finally just try remove stuff so we have 2 open slots instead of 1, and we can put in a wildcard + perk or whatever
|
|
values_to_remove = ["loadoutSecondaryAttachment2","loadoutSecondaryAttachment","loadoutStreak3","loadoutStreak2","loadoutStreak1"];
|
|
foreach ( value in values_to_remove )
|
|
{
|
|
if ( loadoutValueArray[value] != "none" )
|
|
{
|
|
loadoutValueArray[value] = "none";
|
|
/#
|
|
debugLoadoutValue = GetDvar( "bot_Debug" + value, "" );
|
|
if ( IsDefined( debugLoadoutValue ) && debugLoadoutValue != "" )
|
|
loadoutValueArray[value] = debugLoadoutValue;
|
|
#/
|
|
if ( bot_get_pick_13_count( loadoutValueArray ) == level.customClassPickCount-2 )
|
|
return loadoutValueArray;
|
|
}
|
|
}
|
|
}
|
|
|
|
return loadoutValueArray;
|
|
}
|
|
|
|
bot_find_2_loadout_options( loadoutValueArray )
|
|
{
|
|
Assert( bot_get_pick_13_count( loadoutValueArray ) <= level.customClassPickCount-2 );
|
|
|
|
all_wildcards = ["loadoutWildcard1","loadoutWildcard2","loadoutWildcard3"];
|
|
|
|
// First try to pick extra perks
|
|
extra_perk_slots = array_randomize(["loadoutPerk2","loadoutPerk4","loadoutPerk6"]);
|
|
foreach( perk_slot in extra_perk_slots )
|
|
{
|
|
if ( loadoutValueArray[perk_slot] == "specialty_null" )
|
|
{
|
|
wildcard = undefined;
|
|
foreach ( potential_wildcard_slot in all_wildcards )
|
|
{
|
|
if ( loadoutValueArray[potential_wildcard_slot] == "specialty_null" )
|
|
{
|
|
wildcard = potential_wildcard_slot;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( IsDefined(wildcard) )
|
|
{
|
|
has_debug_values_for_wildcard_or_perk = false;
|
|
/#
|
|
debugLoadoutValue = GetDvar( "bot_Debug" + wildcard, "" );
|
|
if ( IsDefined( debugLoadoutValue ) && debugLoadoutValue != "" )
|
|
has_debug_values_for_wildcard_or_perk = true;
|
|
debugLoadoutValue = GetDvar( "bot_Debug" + perk_slot + "" );
|
|
if ( IsDefined( debugLoadoutValue ) && debugLoadoutValue != "" )
|
|
has_debug_values_for_wildcard_or_perk = true;
|
|
#/
|
|
if ( !has_debug_values_for_wildcard_or_perk )
|
|
{
|
|
perk_num = INT(GetSubStr(perk_slot,11,12));
|
|
Assert(perk_num == 6 || perk_num == 4 || perk_num == 2);
|
|
loadoutValueArray[wildcard] = "specialty_wildcard_perkslot" + (perk_num/2);
|
|
loadoutValueArray[perk_slot] = self bot_loadout_choose_from_set( ["template_any"], "template_any", loadoutValueArray, perk_slot );
|
|
if ( loadoutValueArray[perk_slot] == "specialty_null" )
|
|
{
|
|
// Couldn't find a valid perk for this slot
|
|
loadoutValueArray[wildcard] = "specialty_null";
|
|
continue;
|
|
}
|
|
|
|
if ( bot_get_pick_13_count( loadoutValueArray ) > level.customClassPickCount-2 )
|
|
return loadoutValueArray;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Second, try to pick equipment
|
|
if ( loadoutValueArray["loadoutEquipment"] == "specialty_null" )
|
|
{
|
|
template = "template_any_noncamper";
|
|
if ( level.bot_personality_type[self.personality] == "stationary" )
|
|
template = "template_any_camper";
|
|
|
|
loadoutValueArray["loadoutEquipment"] = self bot_loadout_choose_from_set( [template], template, loadoutValueArray, "loadoutEquipment" );
|
|
/#
|
|
debugLoadoutValue = GetDvar( "loadoutEquipment" );
|
|
if ( IsDefined( debugLoadoutValue ) && debugLoadoutValue != "" )
|
|
loadoutValueArray["loadoutEquipment"] = debugLoadoutValue;
|
|
#/
|
|
}
|
|
|
|
if ( loadoutValueArray["loadoutEquipment"] != "specialty_null" && loadoutValueArray["loadoutEquipment2"] == "specialty_null" )
|
|
{
|
|
loadoutValueArray["loadoutEquipment2"] = loadoutValueArray["loadoutEquipment"];
|
|
/#
|
|
debugLoadoutValue = GetDvar( "bot_DebugloadoutEquipment2" );
|
|
if ( IsDefined( debugLoadoutValue ) && debugLoadoutValue != "" )
|
|
loadoutValueArray["loadoutloadoutEquipment2"] = debugLoadoutValue;
|
|
#/
|
|
}
|
|
|
|
return loadoutValueArray;
|
|
}
|
|
|
|
bot_get_pick_13_count( loadoutValueArray )
|
|
{
|
|
// copied from _class.gsc
|
|
|
|
requiredPrimaryAttachment = tablelookup( "mp/statstable.csv", 4, loadoutValueArray["loadoutPrimary"], 40 );
|
|
requiredSecondaryAttachment = tablelookup( "mp/statstable.csv", 4, loadoutValueArray["loadoutSecondary"], 40 );
|
|
|
|
pick13ActiveWeaponCount = 0;
|
|
if ( loadoutValueArray["loadoutPrimary"] != "iw5_combatknife" )
|
|
pick13ActiveWeaponCount++;
|
|
if ( loadoutValueArray["loadoutPrimaryAttachment"] != "none" && requiredPrimaryAttachment == "" ) // if there is a required attachment, it goes in this spot, and doesn't count against pick 13
|
|
pick13ActiveWeaponCount++;
|
|
if ( loadoutValueArray["loadoutPrimaryAttachment2"] != "none" )
|
|
pick13ActiveWeaponCount++;
|
|
if ( loadoutValueArray["loadoutPrimaryAttachment3"] != "none" )
|
|
pick13ActiveWeaponCount++;
|
|
if ( loadoutValueArray["loadoutSecondary"] != "iw5_combatknife" )
|
|
pick13ActiveWeaponCount++;
|
|
if ( loadoutValueArray["loadoutSecondaryAttachment"] != "none" && requiredSecondaryAttachment == "" ) // if there is a required attachment, it goes in this spot, and doesn't count against pick 13
|
|
pick13ActiveWeaponCount++;
|
|
if ( loadoutValueArray["loadoutSecondaryAttachment2"] != "none" )
|
|
pick13ActiveWeaponCount++;
|
|
if ( loadoutValueArray["loadoutSecondaryAttachment3"] != "none" )
|
|
pick13ActiveWeaponCount++;
|
|
if ( loadoutValueArray["loadoutEquipment"] != "specialty_null" )
|
|
pick13ActiveWeaponCount++;
|
|
if ( loadoutValueArray["loadoutEquipment2"] != "specialty_null" )
|
|
pick13ActiveWeaponCount++;
|
|
if ( loadoutValueArray["loadoutOffhand"] != "specialty_null" )
|
|
pick13ActiveWeaponCount++;
|
|
if ( isDefined( loadoutValueArray["loadoutOffhandExtra"] ) && loadoutValueArray["loadoutOffhandExtra"] )
|
|
pick13ActiveWeaponCount++;
|
|
for ( i = 1; i <= 6; i++ )
|
|
{
|
|
if ( loadoutValueArray["loadoutPerk" + i] != "specialty_null" )
|
|
pick13ActiveWeaponCount++;
|
|
}
|
|
for ( i = 1; i <= 3; i++ )
|
|
{
|
|
if ( loadoutValueArray["loadoutWildcard" + i] != "specialty_null" )
|
|
pick13ActiveWeaponCount++;
|
|
}
|
|
for ( i = 1; i <= 4; i++ )
|
|
{
|
|
if ( loadoutValueArray["loadoutStreak" + i] != "none" )
|
|
pick13ActiveWeaponCount++;
|
|
}
|
|
|
|
return pick13ActiveWeaponCount;
|
|
}
|
|
|
|
bot_loadout_get_difficulty()
|
|
{
|
|
difficulty = self BotGetDifficulty();
|
|
|
|
if ( difficulty == "default" )
|
|
{
|
|
// Make sure we pick a difficulty if its set to "default"
|
|
self maps\mp\bots\_bots_util::bot_set_difficulty( "default" );
|
|
difficulty = self BotGetDifficulty();
|
|
}
|
|
|
|
Assert( difficulty != "default" );
|
|
|
|
return difficulty;
|
|
}
|
|
|
|
bot_loadout_class_callback( force_keep_same_loadout )
|
|
{
|
|
while( !IsDefined(level.bot_loadouts_initialized) )
|
|
wait(0.05);
|
|
|
|
while ( !IsDefined( self.personality ) )
|
|
wait(0.05);
|
|
|
|
force_new_loadout = false;
|
|
force_same_loadout = false;
|
|
if ( IsDefined(force_keep_same_loadout) && force_keep_same_loadout )
|
|
{
|
|
// force_same_loadout should take precedence over force_new_loadout
|
|
force_same_loadout = true;
|
|
}
|
|
else
|
|
{
|
|
if ( IsDefined(self.bot_pick_new_loadout_next_spawn) && self.bot_pick_new_loadout_next_spawn )
|
|
{
|
|
force_new_loadout = true;
|
|
self.bot_pick_new_loadout_next_spawn = undefined;
|
|
}
|
|
}
|
|
Assert(!(force_new_loadout && force_same_loadout));
|
|
|
|
loadoutValueArray = [];
|
|
|
|
difficulty = self bot_loadout_get_difficulty();
|
|
self.difficulty = difficulty;
|
|
personality = self BotGetPersonality();
|
|
|
|
if ( !IsDefined(self.bot_last_loadout_num) )
|
|
self.bot_cur_loadout_num = 0;
|
|
|
|
self.bot_last_loadout_num = self.bot_cur_loadout_num;
|
|
|
|
// If bot already has a loadout, stick with it most of the time
|
|
if ( IsDefined( self.botLastLoadout ) && !force_new_loadout )
|
|
{
|
|
if ( force_same_loadout )
|
|
return self.botLastLoadout;
|
|
|
|
same_difficulty = self.botLastLoadoutDifficulty == difficulty;
|
|
same_personality = self.botLastLoadoutPersonality == personality;
|
|
if ( same_difficulty && same_personality && ( !IsDefined(self.hasDied) || self.hasDied ) && !IsDefined(self.respawn_with_launcher) )
|
|
{
|
|
should_keep_same_loadout = RandomFloat( 1.0 ) > SCR_CONST_BOT_CHANGE_LOADOUT_CHANCE;
|
|
if ( should_keep_same_loadout )
|
|
return self.botLastLoadout;
|
|
}
|
|
}
|
|
|
|
if ( !bot_custom_classes_allowed() )
|
|
{
|
|
loadoutValueArray = bot_default_class_random();
|
|
self bot_pick_personality_from_weapon( loadoutValueArray[ "loadoutPrimary" ] );
|
|
}
|
|
else
|
|
{
|
|
loadoutValueArray = self bot_loadout_pick( personality, difficulty );
|
|
loadoutValueArray = self bot_loadout_choose_values( loadoutValueArray );
|
|
|
|
if ( isdefined( level.bot_funcs["gametype_loadout_modify"] ) )
|
|
loadoutValueArray = self [[level.bot_funcs["gametype_loadout_modify"]]]( loadoutValueArray );
|
|
|
|
AssertEx(IsDefined(loadoutValueArray), "Bot '" + self.name + "'spawning (randomized loadout) with loadoutValueArray not defined");
|
|
}
|
|
|
|
/#
|
|
if ( GetDvarInt("scr_botsDisableLoadout") )
|
|
{
|
|
loadoutValueArray["loadoutPerk1"] = "specialty_null";
|
|
loadoutValueArray["loadoutPerk2"] = "specialty_null";
|
|
loadoutValueArray["loadoutPerk3"] = "specialty_null";
|
|
loadoutValueArray["loadoutPerk4"] = "specialty_null";
|
|
loadoutValueArray["loadoutPerk5"] = "specialty_null";
|
|
loadoutValueArray["loadoutPerk6"] = "specialty_null";
|
|
loadoutValueArray["loadoutEquipment"] = "specialty_null";
|
|
loadoutValueArray["loadoutEquipment2"] = "specialty_null";
|
|
loadoutValueArray["loadoutOffhand"] = "specialty_null";
|
|
|
|
foreach( index, value in loadoutValueArray )
|
|
{
|
|
if ( index != "loadoutPrimary" && value != "specialty_null" )
|
|
loadoutValueArray[index] = "none";
|
|
}
|
|
}
|
|
#/
|
|
|
|
if ( loadoutValueArray["loadoutPrimary"] == "none" )
|
|
{
|
|
// Choose a fallback weapon and switch personality to match
|
|
self.bot_fallback_personality = undefined;
|
|
loadoutValueArray["loadoutPrimary"] = self bot_loadout_choose_fallback_primary( loadoutValueArray );
|
|
loadoutValueArray["loadoutPrimaryCamo"] = "none";
|
|
loadoutValueArray["loadoutPrimaryAttachment"] = "none";
|
|
loadoutValueArray["loadoutPrimaryAttachment2"] = "none";
|
|
loadoutValueArray["loadoutPrimaryAttachment3"] = "none";
|
|
loadoutValueArray["loadoutPrimaryReticle"] = "none";
|
|
if ( IsDefined( self.bot_fallback_personality ) )
|
|
{
|
|
if ( self.bot_fallback_personality == "weapon" )
|
|
self bot_pick_personality_from_weapon( loadoutValueArray[ "loadoutPrimary" ] );
|
|
else
|
|
self maps\mp\bots\_bots_util::bot_set_personality( self.bot_fallback_personality );
|
|
|
|
personality = self.personality;
|
|
|
|
self.bot_fallback_personality = undefined;
|
|
}
|
|
}
|
|
|
|
if ( IsDefined( loadoutValueArray["loadoutPrimaryCamo"] ) && loadoutValueArray["loadoutPrimaryCamo"] != "none" && !IsDefined(self.botLoadoutFavoriteCamoPrimary) )
|
|
self.botLoadoutFavoriteCamoPrimary = loadoutValueArray["loadoutPrimaryCamo"];
|
|
if ( IsDefined( loadoutValueArray["loadoutSecondaryCamo"] ) && loadoutValueArray["loadoutSecondaryCamo"] != "none" && !IsDefined(self.botLoadoutFavoriteCamoSecondary) )
|
|
self.botLoadoutFavoriteCamoSecondary = loadoutValueArray["loadoutSecondaryCamo"];
|
|
|
|
if ( IsDefined(self.respawn_with_launcher) )
|
|
{
|
|
Assert( IsDefined( level.bot_respawn_launcher_name ) );
|
|
launcher_to_use = level.bot_respawn_launcher_name[self BotGetDifficulty()];
|
|
Assert( IsDefined( launcher_to_use ) );
|
|
Assert( maps\mp\gametypes\_class::isValidSecondary( launcher_to_use, false ) );
|
|
if ( bot_loadout_item_allowed( "weapon", launcher_to_use, undefined ) )
|
|
{
|
|
loadoutValueArray["loadoutSecondary"] = launcher_to_use;
|
|
loadoutValueArray["loadoutSecondaryAttachment"] = "none";
|
|
loadoutValueArray["loadoutSecondaryAttachment2"] = "none";
|
|
loadoutValueArray["loadoutSecondaryAttachment3"] = "none";
|
|
self.bot_pick_new_loadout_next_spawn = true;
|
|
}
|
|
self.respawn_with_launcher = undefined;
|
|
}
|
|
|
|
// Killstreaks should be valid now so its safe to check them here (these functions assert internally)
|
|
maps\mp\gametypes\_class::isValidKillstreak(loadoutValueArray["loadoutStreak1"]);
|
|
maps\mp\gametypes\_class::isValidKillstreak(loadoutValueArray["loadoutStreak2"]);
|
|
maps\mp\gametypes\_class::isValidKillstreak(loadoutValueArray["loadoutStreak3"]);
|
|
|
|
/*
|
|
if ( self maps\mp\bots\_bots::bot_israndom() )
|
|
{
|
|
if ( array_contains( self.pers[ "loadoutPerks" ], "specialty_extra_attachment" ) )
|
|
{
|
|
// Pick again for attachment3 and attachment2 on secondary
|
|
otherAttachmentLoadout = self bot_loadout_pick( personality, difficulty );
|
|
loadoutValueArray["loadoutPrimaryAttachment3"] = otherAttachmentLoadout["loadoutPrimaryAttachment2"];
|
|
if ( array_contains( self.pers[ "loadoutPerks" ], "specialty_twoprimaries" ) )
|
|
loadoutValueArray["loadoutSecondaryAttachment2"] = otherAttachmentLoadout["loadoutPrimaryAttachment2"];
|
|
else
|
|
loadoutValueArray["loadoutSecondaryAttachment2"] = otherAttachmentLoadout["loadoutSecondaryAttachment2"];
|
|
loadoutValueArray = self bot_loadout_choose_values( loadoutValueArray );
|
|
loadoutValueArray = self bot_loadout_setup_perks( loadoutValueArray );
|
|
}
|
|
}
|
|
*/
|
|
|
|
self.bot_cur_loadout_num++;
|
|
self.botLastLoadout = loadoutValueArray;
|
|
self.botLastLoadoutDifficulty = difficulty;
|
|
self.botLastLoadoutPersonality = personality;
|
|
|
|
AssertEx(IsDefined(loadoutValueArray), "Bot returning undefined from bot_loadout_class_callback");
|
|
return loadoutValueArray;
|
|
}
|
|
|
|
bot_setup_loadout_callback()
|
|
{
|
|
personality = self BotGetPersonality();
|
|
difficulty = self bot_loadout_get_difficulty();
|
|
|
|
loadoutSet = bot_loadout_set( personality, difficulty, false );
|
|
if ( IsDefined( loadoutSet ) && isDefined( loadoutSet.loadouts ) && loadoutSet.loadouts.size > 0 )
|
|
{
|
|
self.classCallback = ::bot_loadout_class_callback;
|
|
return true;
|
|
}
|
|
|
|
bot_name_without_personality = GetSubStr(self.name,0,self.name.size-10); // At this point in the setup, the bot personality might be wrong, so don't display it here
|
|
AssertMsg("Bot <" + bot_name_without_personality + "> has no possible loadouts for personality <" + personality + "> and difficulty <" + difficulty + ">");
|
|
self.classCallback = undefined;
|
|
return false;
|
|
} |