From ea43f931e60e9370b01d739967c434438b4ca5a0 Mon Sep 17 00:00:00 2001 From: WantedDV <122710241+WantedDV@users.noreply.github.com> Date: Sat, 12 Oct 2024 02:44:08 -0230 Subject: [PATCH 1/2] bots and ranked patched for servers - bots wait for a player to join - bots auto-balance and drop bots for incoming players - veteran difficulty is now accessible for combat training and dedicated servers - new dvars: `bots_enabled`, `bot_difficulty` (0:Mixed 1:Recruit 2:Regular 3:Hardened 4:Veteran) - enabled contracts and online features from combat training for servers --- data/cdata/custom_scripts/mp/bots.gsc | 354 +++++++++++++++++++++++- data/cdata/custom_scripts/mp/ranked.gsc | 18 +- src/client/component/bots.cpp | 3 + 3 files changed, 359 insertions(+), 16 deletions(-) diff --git a/data/cdata/custom_scripts/mp/bots.gsc b/data/cdata/custom_scripts/mp/bots.gsc index 6873916..c34c57b 100644 --- a/data/cdata/custom_scripts/mp/bots.gsc +++ b/data/cdata/custom_scripts/mp/bots.gsc @@ -3,13 +3,66 @@ main() { - initdvars(); + initDvars(); + initLevelVariables(); replacefunc(scripts\mp\bots\bots::init, ::init_stub); + replacefunc(scripts\mp\bots\bots::bot_get_host_team, ::get_human_player_team); + replacefunc(scripts\mp\bots\bots::bot_connect_monitor, ::bot_connect_monitor); replacefunc(scripts\mp\bots\bots_util::bot_get_client_limit, ::bot_get_client_limit); + replacefunc(scripts\mp\hostmigration::waitlongdurationwithhostmigrationpause, ::_wait); } -initdvars() +initDvars() { + // setdvar("bots_enabled", 1); + // setdvar("bot_difficulty", 0); + // setdvar("party_maxplayers", 18); // controls how many bots are allowed to spawn +} + +initLevelVariables() +{ + if(!getdvarint("xblive_privatematch")) + { + allowedBotDifficulty(getdvarint( "bot_difficulty", 0 )); + } +} + +allowedBotDifficulty(difficulty) +{ + level.bot_difficulty_defaults = []; + switch (difficulty) + { + case 4: + level.bot_difficulty_defaults[level.bot_difficulty_defaults.size] = "veteran"; + break; + case 3: + level.bot_difficulty_defaults[level.bot_difficulty_defaults.size] = "hardened"; + break; + case 2: + level.bot_difficulty_defaults[level.bot_difficulty_defaults.size] = "regular"; + break; + case 1: + level.bot_difficulty_defaults[level.bot_difficulty_defaults.size] = "recruit"; + break; + case 0: + default: + level.bot_difficulty_defaults[level.bot_difficulty_defaults.size] = "hardened"; + level.bot_difficulty_defaults[level.bot_difficulty_defaults.size] = "regular"; + level.bot_difficulty_defaults[level.bot_difficulty_defaults.size] = "recruit"; + break; + } +} + +_wait(time) +{ + wait(time); +} + +human() +{ + if ( !isplayer( self ) || isai( self ) ) + return 0; + return 1; } gethostplayer() @@ -18,32 +71,41 @@ gethostplayer() for ( var_1 = 0; var_1 < var_0.size; var_1++ ) { - if ( var_0[var_1] ishost() ) + if ( var_0[var_1] human() ) return var_0[var_1]; } } -get_host() +get_human_player_loop() { - host = gethostplayer(); - - while (!isdefined(host)) + player = undefined; + + while (!isdefined(player)) { wait(0.25); - host = gethostplayer(); + player = gethostplayer(); } - return host; + return player; } -wait_for_host() +get_human_player_team() { - host = get_host(); + player = get_human_player_loop(); + return player bot_get_player_team(); +} + +wait_for_human_player() +{ + player = get_human_player_loop(); - while ( isdefined( host ) && !host.hasspawned && !isdefined( host.selectedclass ) ) + while ( isdefined( player ) && !player.hasspawned && !isdefined( player.selectedclass ) ) { + level.pausing_bot_connect_monitor = 1; wait(0.25); } + + level.pausing_bot_connect_monitor = 0; return 1; } @@ -54,11 +116,12 @@ init_stub() thread bot_triggers(); initbotlevelvariables(); + if(!getdvarint( "bots_enabled", 0 )) return; + refresh_existing_bots(); - wait_for_host(); + wait_for_human_player(); - var_0 = botautoconnectenabled(); - setmatchdata( "hasBots", 1 ); + setmatchdata("hasBots", 1); level thread bot_connect_monitor(); } @@ -79,3 +142,264 @@ bot_get_client_limit() setdvar( "sv_maxclients", maxclients ); return maxclients; } + +bot_connect_monitor( var_0, var_1 ) +{ + level endon( "game_ended" ); + self notify( "bot_connect_monitor" ); + self endon( "bot_connect_monitor" ); + level.pausing_bot_connect_monitor = 0; + childthread monitor_pause_spawning(); + scripts\mp\hostmigration::waitlongdurationwithhostmigrationpause( 0.5 ); + var_2 = 1.5; + + if ( !isdefined( level.bot_cm_spawned_bots ) ) + level.bot_cm_spawned_bots = 0; + + if ( !isdefined( level.bot_cm_waited_players_time ) ) + level.bot_cm_waited_players_time = 0; + + if ( !isdefined( level.bot_cm_human_picked ) ) + level.bot_cm_human_picked = 0; + + for (;;) + { + if ( level.pausing_bot_connect_monitor ) + { + scripts\mp\hostmigration::waitlongdurationwithhostmigrationpause( var_2 ); + continue; + } + + var_3 = isdefined( level.bots_ignore_team_balance ) || !level.teambased; + var_4 = botgetteamlimit( 0 ); + var_5 = botgetteamlimit( 1 ); + + if ( level.rankedmatch ) + { + var_6 = "default"; + var_7 = "default"; + } + else + { + var_6 = botgetteamdifficulty( 0 ); + var_7 = botgetteamdifficulty( 1 ); + } + + var_8 = "allies"; + var_9 = "axis"; + var_10 = bot_client_counts(); + var_11 = cat_array_get( var_10, "humans" ); + + if ( var_11 > 1 ) + { + var_12 = bot_get_host_team(); + + if (isdefined( var_12 ) && var_12 != "spectator" ) + { + var_8 = var_12; + var_9 = scripts\mp\utility::getotherteam( var_12 ); + } + else + { + var_13 = cat_array_get( var_10, "humans_allies" ); + var_14 = cat_array_get( var_10, "humans_axis" ); + + if ( var_14 > var_13 ) + { + var_8 = "axis"; + var_9 = "allies"; + } + } + } + else + { + var_15 = get_human_player(); + + if ( isdefined( var_15 ) ) + { + var_16 = var_15 bot_get_player_team(); + + if ( isdefined( var_16 ) && var_16 != "spectator" ) + { + var_8 = var_16; + var_9 = scripts\mp\utility::getotherteam( var_16 ); + } + } + } + + var_17 = scripts\mp\bots\bots_util::bot_get_team_limit(); + var_18 = scripts\mp\bots\bots_util::bot_get_team_limit(); + + if ( var_17 + var_18 < scripts\mp\bots\bots_util::bot_get_client_limit() ) + { + if ( var_17 < var_4 ) + var_17++; + else if ( var_18 < var_5 ) + var_18++; + } + + var_19 = cat_array_get( var_10, "humans_" + var_8 ); + var_20 = cat_array_get( var_10, "humans_" + var_9 ); + var_21 = var_19 + var_20; + var_22 = cat_array_get( var_10, "spectator" ); + var_23 = 0; + + for ( var_24 = 0; var_22 > 0; var_22-- ) + { + var_25 = var_19 + var_23 + 1 <= var_17; + var_26 = var_20 + var_24 + 1 <= var_18; + + if ( var_25 && !var_26 ) + { + var_23++; + continue; + } + + if ( !var_25 && var_26 ) + { + var_24++; + continue; + } + + if ( var_25 && var_26 ) + { + if ( var_22 % 2 == 1 ) + { + var_23++; + continue; + } + + var_24++; + } + } + + var_27 = cat_array_get( var_10, "bots_" + var_8 ); + var_28 = cat_array_get( var_10, "bots_" + var_9 ); + var_29 = var_27 + var_28; + + if ( var_29 > 0 ) + level.bot_cm_spawned_bots = 1; + + var_30 = 0; + + if ( !level.bot_cm_human_picked ) + { + var_30 = !bot_get_human_picked_team(); + + if ( !var_30 ) + level.bot_cm_human_picked = 1; + } + + if ( var_30 ) + { + var_31 = !getdvarint( "systemlink" ) && !getdvarint( "onlinegame" ); + var_32 = !var_3 && var_5 != var_4 && !level.bot_cm_spawned_bots && ( level.bot_cm_waited_players_time < 10 || !scripts\mp\utility::gameflag( "prematch_done" ) ); + + if ( var_31 || var_32 ) + { + level.bot_cm_waited_players_time = level.bot_cm_waited_players_time + var_2; + scripts\mp\hostmigration::waitlongdurationwithhostmigrationpause( var_2 ); + continue; + } + } + + var_33 = int( min( var_17 - var_19 - var_23, var_4 ) ); + var_34 = int( min( var_18 - var_20 - var_24, var_5 ) ); + var_35 = 1; + var_36 = var_33 + var_34 + var_11; + var_37 = var_4 + var_5 + var_11; + + for ( var_38 = [ -1, -1 ]; var_36 < scripts\mp\bots\bots_util::bot_get_client_limit() && var_36 < var_37; var_35 = !var_35 ) + { + if ( var_35 && var_33 < var_4 && bot_can_join_team( var_8 ) ) + var_33++; + else if ( !var_35 && var_34 < var_5 && bot_can_join_team( var_9 ) ) + var_34++; + + var_36 = var_33 + var_34 + var_11; + + if ( var_38[var_35] == var_36 ) + break; + + var_38[var_35] = var_36; + } + + if ( var_4 == var_5 && !var_3 && var_23 == 1 && var_24 == 0 && var_34 > 0 ) + { + if ( !isdefined( level.bot_prematchdonetime ) && scripts\mp\utility::gameflag( "prematch_done" ) ) + level.bot_prematchdonetime = gettime(); + + if ( var_30 && ( !isdefined( level.bot_prematchdonetime ) || gettime() - level.bot_prematchdonetime < 10000 ) ) + var_34--; + } + + var_39 = var_33 - var_27; + var_40 = var_34 - var_28; + var_41 = 1; + + if ( var_3 ) + { + var_42 = var_17 + var_18; + var_43 = var_4 + var_5; + var_44 = var_19 + var_20; + var_45 = var_27 + var_28; + var_46 = int( min( var_42 - var_44, var_43 ) ); + var_47 = var_46 - var_45; + + if ( var_47 == 0 ) + var_41 = 0; + else if ( var_47 > 0 ) + { + var_39 = int( var_47 / 2 ) + var_47 % 2; + var_40 = int( var_47 / 2 ); + } + else if ( var_47 < 0 ) + { + var_48 = var_47 * -1; + var_39 = -1 * int( min( var_48, var_27 ) ); + var_40 = -1 * ( var_48 + var_39 ); + } + } + else if ( ( var_39 * var_40 < 0 && !isdefined( level.bots_disable_team_switching ) ) ) + { + var_49 = int( min( abs( var_39 ), abs( var_40 ) ) ); + + if ( var_39 > 0 ) + move_bots_from_team_to_team( var_49, var_9, var_8, var_6 ); + else if ( var_40 > 0 ) + move_bots_from_team_to_team( var_49, var_8, var_9, var_7 ); + + var_41 = 0; + } + + if ( var_41 ) + { + if ( var_40 < 0 ) + drop_bots( var_40 * -1, var_9 ); + + if ( var_39 < 0 ) + drop_bots( var_39 * -1, var_8 ); + + if ( var_40 > 0 ) + level thread spawn_bots( var_40, var_9, undefined, undefined, "spawned_enemies", var_7 ); + + if ( var_39 > 0 ) + level thread spawn_bots( var_39, var_8, undefined, undefined, "spawned_allies", var_6 ); + + if ( var_40 > 0 && var_39 > 0 ) + level scripts\engine\utility::waittill_multiple( "spawned_enemies", "spawned_allies" ); + else if ( var_40 > 0 ) + level waittill( "spawned_enemies" ); + else if ( var_39 > 0 ) + level waittill( "spawned_allies" ); + } + + if ( var_7 != var_6 ) + { + bots_update_difficulty( var_9, var_7 ); + bots_update_difficulty( var_8, var_6 ); + } + + scripts\mp\hostmigration::waitlongdurationwithhostmigrationpause( var_2 ); + } +} \ No newline at end of file diff --git a/data/cdata/custom_scripts/mp/ranked.gsc b/data/cdata/custom_scripts/mp/ranked.gsc index 2f46b4e..8708965 100644 --- a/data/cdata/custom_scripts/mp/ranked.gsc +++ b/data/cdata/custom_scripts/mp/ranked.gsc @@ -1,6 +1,17 @@ main() { - // Rank fixes. + // Ranked fixes + if(!getdvarint("xblive_privatematch")) + { + level.onlinegame = 1; + level.rankedmatch = 1; + + setdvar("systemlink", 0); + setdvar("onlinegame", 1); + + replacefunc(scripts\mp\utility::rankingenabled, ::rankingenabled); + } + replacefunc(scripts\mp\menus::addtoteam, ::addtoteam_stub); replacefunc(scripts\mp\menus::watchforteamchange, ::watchforteamchange_stub); @@ -8,6 +19,11 @@ main() replacefunc(scripts\mp\playerlogic::connect_validateplayerteam, ::connect_validateplayerteam_stub); } +rankingenabled() +{ + return !(!isplayer( self ) || isai( self )); +} + addtoteam_stub( team, firstConnect, changeTeamsWithoutRespawning ) { if ( isdefined( self.team ) ) diff --git a/src/client/component/bots.cpp b/src/client/component/bots.cpp index 5e6c6b0..40370cf 100644 --- a/src/client/component/bots.cpp +++ b/src/client/component/bots.cpp @@ -90,6 +90,9 @@ namespace bots public: void post_unpack() override { + game::Dvar_RegisterBool("bots_enabled", true, game::DVAR_FLAG_READ, "Enable bots and activate bot management systems"); + game::Dvar_RegisterInt("bot_difficulty", 0, 0, 5, game::DVAR_FLAG_READ, "Bot difficulty. 0:Mixed 1:Recruit 2:Regular 3:Hardened 4:Veteran"); + sv_kick_client_num_hook.create(game::SV_CmdsMP_KickClientNum, sv_kick_client_num_stub); get_bot_name_hook.create(game::SV_BotGetRandomName, get_random_bot_name); From 33c322ab19d4c59c0c6a9f6a8be576c98bed4869 Mon Sep 17 00:00:00 2001 From: WantedDV <122710241+WantedDV@users.noreply.github.com> Date: Sat, 12 Oct 2024 11:25:29 -0230 Subject: [PATCH 2/2] wait to start match timer - match will now wait without forefeit until a player joins and selects a loadout - game timer will start at 0 - if a player joins and leaves wait for a new player --- data/cdata/custom_scripts/mp/bots.gsc | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/data/cdata/custom_scripts/mp/bots.gsc b/data/cdata/custom_scripts/mp/bots.gsc index c34c57b..526eb8e 100644 --- a/data/cdata/custom_scripts/mp/bots.gsc +++ b/data/cdata/custom_scripts/mp/bots.gsc @@ -10,6 +10,7 @@ main() replacefunc(scripts\mp\bots\bots::bot_connect_monitor, ::bot_connect_monitor); replacefunc(scripts\mp\bots\bots_util::bot_get_client_limit, ::bot_get_client_limit); replacefunc(scripts\mp\hostmigration::waitlongdurationwithhostmigrationpause, ::_wait); + replacefunc(scripts\mp\gamelogic::waitforplayers, ::waitforplayers); } initDvars() @@ -17,6 +18,10 @@ initDvars() // setdvar("bots_enabled", 1); // setdvar("bot_difficulty", 0); // setdvar("party_maxplayers", 18); // controls how many bots are allowed to spawn + setdvar("scr_game_graceperiod", 15); + setdvar("scr_game_playerwaittime", 5); + setdvar("scr_game_matchstarttime", 5); + level.ready_to_start = 0; } initLevelVariables() @@ -58,6 +63,34 @@ _wait(time) wait(time); } +waitForPlayers( maxTime ) +{ + startTime = gettime(); + endTime = startTime + maxTime * 1000 - 200; + + if ( maxTime > 5 ) + minTime = gettime() + getDvarInt( "min_wait_for_players" ) * 1000; + else + minTime = 0; + + numToWaitFor = ( level.connectingPlayers/3 ); + + for ( ;; ) + { + if ( isDefined( game["roundsPlayed"] ) && game["roundsPlayed"] ) + break; + + totalSpawnedPlayers = level.maxPlayerCount; + + curTime = gettime(); + + if( level.ready_to_start ) + break; + + wait 0.05; + } +} + human() { if ( !isplayer( self ) || isai( self ) ) @@ -105,7 +138,10 @@ wait_for_human_player() wait(0.25); } + if(!isdefined( player )) wait_for_human_player(); // if the player leaves wait for new player + level.pausing_bot_connect_monitor = 0; + level.ready_to_start = 1; return 1; }