s1-scripts-dev/raw/maps/mp/bots/_bots_loadout.gsc
2025-05-21 16:23:17 +02:00

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;
}