From 66bea25aaa2b388397b3ca8defec80955c323df4 Mon Sep 17 00:00:00 2001 From: efinst0rm Date: Sat, 25 Mar 2023 13:53:20 -0500 Subject: [PATCH] Add autobalance support. Setting scr_teambalance 1 will now autobalance teams after 60 seconds. --- data/scripts/mp/teams/_teams.gsc | Bin 0 -> 12284 bytes data/scripts/mp/teams/_teams.gsc_raw | 670 +++++++++++++++++++++++++++ 2 files changed, 670 insertions(+) create mode 100644 data/scripts/mp/teams/_teams.gsc create mode 100644 data/scripts/mp/teams/_teams.gsc_raw diff --git a/data/scripts/mp/teams/_teams.gsc b/data/scripts/mp/teams/_teams.gsc new file mode 100644 index 0000000000000000000000000000000000000000..bb53019a4c6f7f0da902752f6d9a0c1b16c48cff GIT binary patch literal 12284 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 literal 0 HcmV?d00001 diff --git a/data/scripts/mp/teams/_teams.gsc_raw b/data/scripts/mp/teams/_teams.gsc_raw new file mode 100644 index 00000000..1260a17b --- /dev/null +++ b/data/scripts/mp/teams/_teams.gsc_raw @@ -0,0 +1,670 @@ +#using scripts\codescripts\struct; + +#using scripts\shared\callbacks_shared; +#using scripts\shared\persistence_shared; +#using scripts\shared\system_shared; +#using scripts\shared\util_shared; + +#insert scripts\shared\shared.gsh; + +#using scripts\mp\gametypes\_globallogic_ui; +#using scripts\mp\gametypes\_spectating; + +#using scripts\mp\_util; + +#precache( "material", "mpflag_spectator" ); +#precache( "string", "MP_AUTOBALANCE_NOW" ); + +#namespace teams; + +REGISTER_SYSTEM( "teams", &__init__, undefined ) + +function __init__() +{ + callback::on_start_gametype( &init ); + + level.getEnemyTeam = &getEnemyTeam; + level.use_team_based_logic_for_locking_on = true; +} + +function init() +{ + game["strings"]["autobalance"] = &"MP_AUTOBALANCE_NOW"; + + if(GetDvarString( "scr_teambalance") == "") + SetDvar("scr_teambalance", "0"); + level.teambalance = GetDvarint( "scr_teambalance"); + level.teambalancetimer = 0; + + if(GetDvarString( "scr_timeplayedcap") == "") + SetDvar("scr_timeplayedcap", "1800"); + level.timeplayedcap = int(GetDvarint( "scr_timeplayedcap")); + + level.freeplayers = []; + + if( level.teamBased ) + { + level.alliesplayers = []; + level.axisplayers = []; + + callback::on_connect( &on_player_connect ); + callback::on_joined_team( &on_joined_team ); + callback::on_joined_spectate( &on_joined_spectators ); + level thread update_team_balance(); + + wait .15; + + level thread update_player_times(); + + } + else + { + callback::on_connect( &on_free_player_connect ); + + wait .15; + + level thread update_player_times(); + + } +} + +function on_player_connect() +{ + self thread track_played_time(); +} + +function on_free_player_connect() +{ + self thread track_free_played_time(); +} + +function on_joined_team() +{ + /#println( "joined team: " + self.pers["team"] );#/ + self update_time(); +} + +function on_joined_spectators() +{ + self.pers["teamTime"] = undefined; +} + +function track_played_time() +{ + self endon( "disconnect" ); + + if ( !isdefined( self.pers["totalTimePlayed"] ) ) + { + self.pers["totalTimePlayed"] = 0; + } + + foreach ( team in level.teams ) + { + self.timePlayed[team] = 0; + } + self.timePlayed["free"] = 0; + self.timePlayed["other"] = 0; + self.timePlayed["alive"] = 0; + + // dont reset time played in War when going into final fight, this is used for calculating match bonus + if ( !isdefined( self.timePlayed["total"] ) || !( (level.gameType == "twar") && (0 < game["roundsplayed"]) && (0 < self.timeplayed["total"]) ) ) + self.timePlayed["total"] = 0; + + while ( level.inPrematchPeriod ) + WAIT_SERVER_FRAME; + + for ( ;; ) + { + if ( game["state"] == "playing" ) + { + if ( isdefined( level.teams[self.sessionteam] ) ) + { + self.timePlayed[self.sessionteam]++; + self.timePlayed["total"]++; + + if ( level.mpCustomMatch ) + { + self.pers["sbtimeplayed"] = self.timeplayed["total"]; + self.sbtimeplayed = self.pers["sbtimeplayed"]; + } + + if ( IsAlive( self ) ) + self.timePlayed["alive"]++; + } + else if ( self.sessionteam == "spectator" ) + { + self.timePlayed["other"]++; + } + } + + wait ( 1.0 ); + } +} + + +function update_player_times() +{ + const minWait = 10.0; + const step = 1.0; + varWait = minWait; + + nextToUpdate = 0; + for ( ;; ) + { + varWait = varWait - step; + nextToUpdate++; + + if ( nextToUpdate >= level.players.size ) + { + nextToUpdate = 0; + + if ( varWait > 0 ) + { + wait ( varWait ); + } + + varWait = minWait; + } + + if ( isdefined( level.players[nextToUpdate] ) ) + { + level.players[nextToUpdate] update_played_time(); + level.players[nextToUpdate] persistence::check_contract_expirations(); + } + + wait ( step ); + } +} + +function update_played_time() +{ + pixbeginevent("updatePlayedTime"); + + if ( level.rankedMatch || level.leagueMatch ) + { + foreach( team in level.teams ) + { + if ( self.timePlayed[team] ) + { + if ( level.teambased ) + { + self AddPlayerStat( "time_played_"+team, int( min( self.timePlayed[team], level.timeplayedcap ) ) ); + } + + self AddPlayerStatWithGameType( "time_played_total", int( min( self.timePlayed[team], level.timeplayedcap ) ) ); + } + } + + if ( self.timePlayed["other"] ) + { + self AddPlayerStat( "time_played_other", int( min( self.timePlayed["other"], level.timeplayedcap ) ) ); + self AddPlayerStatWithGameType( "time_played_total", int( min( self.timePlayed["other"], level.timeplayedcap ) ) ); + } + + if ( self.timePlayed["alive"] ) + { + timeAlive = int( min( self.timePlayed["alive"], level.timeplayedcap ) ); + self persistence::increment_contract_times( timeAlive ); + self AddPlayerStat( "time_played_alive", timeAlive ); + } + } + + if ( level.onlineGame ) + { + timeAlive = int( min( self.timePlayed["alive"], level.timeplayedcap ) ); + self.pers["time_played_alive"] += timeAlive; + } + + pixendevent(); + + if ( game["state"] == "postgame" ) + return; + + foreach( team in level.teams ) + { + self.timePlayed[team] = 0; + } + self.timePlayed["other"] = 0; + self.timePlayed["alive"] = 0; +} + + +function update_time() +{ + if ( game["state"] != "playing" ) + return; + + self.pers["teamTime"] = getTime(); +} + +function update_balance_dvar() +{ + for(;;) + { + teambalance = GetDvarint( "scr_teambalance"); + if(level.teambalance != teambalance) + level.teambalance = GetDvarint( "scr_teambalance"); + + timeplayedcap = GetDvarint( "scr_timeplayedcap"); + if(level.timeplayedcap != timeplayedcap) + level.timeplayedcap = int(GetDvarint( "scr_timeplayedcap")); + + wait 1; + } +} + + +function update_team_balance() +{ + level thread update_balance_dvar(); + + wait .15; + + if ( level.teamBalance && util::isRoundBased() && level.numlives ) + { + if ( isDefined( game["BalanceTeamsNextRound"] ) ) + iPrintLnbold( &"MP_AUTOBALANCE_NEXT_ROUND" ); + + level waittill( "game_ended" ); + wait 1; + + if ( isDefined( game["BalanceTeamsNextRound"] ) ) + { + level balance_teams(); + game["BalanceTeamsNextRound"] = undefined; + } + else if ( !get_team_balance() ) + { + game["BalanceTeamsNextRound"] = true; + } + } + else + { + level endon ( "game_ended" ); + + for ( ;; ) + { + if ( level.teamBalance ) + { + if ( !get_team_balance() ) + { + iPrintLnBold( &"MP_AUTOBALANCE_SECONDS", 15 ); + wait 15.0; + + if ( !get_team_balance() ) + level balance_teams(); + } + + wait 59.0; + } + + wait 1.0; + } + } + +} + + +function get_team_balance() +{ + level.team["allies"] = 0; + level.team["axis"] = 0; + + players = level.players; + + for ( i = 0; i < players.size; i++ ) + { + if ( ( isdefined( players[i].pers["team"] ) ) && ( players[i].pers["team"] == "allies" ) ) + level.team["allies"]++; + else if ( ( isdefined( players[i].pers["team"] ) ) && ( players[i].pers["team"] == "axis" ) ) + level.team["axis"]++; + } + + if ( ( level.team["allies"] > ( level.team["axis"] + level.teamBalance ) ) || ( level.team["axis"] > ( level.team["allies"] + level.teamBalance ) ) ) + return false; + else + return true; +} + + +function balance_teams() +{ + iPrintLnBold( game["strings"]["autobalance"] ); + //Create/Clear the team arrays + AlliedPlayers = []; + AxisPlayers = []; + + // Populate the team arrays + players = level.players; + + for ( i = 0; i < players.size; i++ ) + { + if ( !isdefined( players[i].pers["teamTime"] ) ) + continue; + + if ( ( isdefined( players[i].pers["team"] ) ) && ( players[i].pers["team"] == "allies" ) ) + AlliedPlayers[AlliedPlayers.size] = players[i]; + else if ( ( isdefined( players[i].pers["team"] ) ) && ( players[i].pers["team"] == "axis" ) ) + AxisPlayers[AxisPlayers.size] = players[i]; + } + + MostRecent = undefined; + + while ( ( AlliedPlayers.size > ( AxisPlayers.size + 1 ) ) || ( AxisPlayers.size > ( AlliedPlayers.size + 1 ) ) ) + { + if ( AlliedPlayers.size > ( AxisPlayers.size + 1 ) ) + { + // Move the player that's been on the team the shortest ammount of time (highest teamTime value) + // Ignore players capturing or carrying objects + for ( j = 0; j < AlliedPlayers.size; j++ ) + { + + if ( !isdefined( MostRecent ) ) + MostRecent = AlliedPlayers[j]; + else if ( AlliedPlayers[j].pers["teamTime"] > MostRecent.pers["teamTime"] ) + MostRecent = AlliedPlayers[j]; + } + + if ( isdefined( MostRecent ) ) + MostRecent change( "axis" ); + else + { + // Move the player that's been on the team the shortest ammount of time + for ( j = 0; j < AlliedPlayers.size; j++ ) + { + if ( !isdefined( MostRecent ) ) + MostRecent = AlliedPlayers[j]; + else if ( AlliedPlayers[j].pers["teamTime"] > MostRecent.pers["teamTime"] ) + MostRecent = AlliedPlayers[j]; + } + + MostRecent change( "axis" ); + } + } + else if ( AxisPlayers.size > ( AlliedPlayers.size + 1 ) ) + { + // Move the player that's been on the team the shortest ammount of time (highest teamTime value) + // Ignore players capturing or carrying objects + for ( j = 0; j < AxisPlayers.size; j++ ) + { + + if ( !isdefined( MostRecent ) ) + MostRecent = AxisPlayers[j]; + else if ( AxisPlayers[j].pers["teamTime"] > MostRecent.pers["teamTime"] ) + MostRecent = AxisPlayers[j]; + } + + if ( isdefined( MostRecent ) ) + MostRecent change( "allies" ); + else + { + // Move the player that's been on the team the shortest ammount of time + for ( j = 0; j < AxisPlayers.size; j++ ) + { + if ( !isdefined( MostRecent ) ) + MostRecent = AxisPlayers[j]; + else if ( AxisPlayers[j].pers["teamTime"] > MostRecent.pers["teamTime"] ) + MostRecent = AxisPlayers[j]; + } + + MostRecent change( "allies" ); + } + } + + MostRecent = undefined; + AlliedPlayers = []; + AxisPlayers = []; + + players = level.players; + + for ( i = 0; i < players.size; i++ ) + { + if ( ( isdefined( players[i].pers["team"] ) ) && ( players[i].pers["team"] == "allies" ) ) + AlliedPlayers[AlliedPlayers.size] = players[i]; + else if ( ( isdefined( players[i].pers["team"] ) ) && ( players[i].pers["team"] == "axis" ) ) + AxisPlayers[AxisPlayers.size] = players[i]; + } + } +} + + +function change( team ) +{ + if (self.sessionstate != "dead") + { + // Set a flag on the player to they aren't robbed points for dying - the callback will remove the flag + self.switching_teams = true; + self.switchedTeamsResetGadgets = true; + self.joining_team = team; + self.leaving_team = self.pers["team"]; + + // Suicide the player so they can't hit escape and fail the team balance + self suicide(); + } + + self.pers["team"] = team; + self.team = team; + self.pers["weapon"] = undefined; + self.pers["spawnweapon"] = undefined; + self.pers["savedmodel"] = undefined; + self.pers["teamTime"] = undefined; + self.sessionteam = self.pers["team"]; + + self globallogic_ui::updateObjectiveText(); + + // update spectator permissions immediately on change of team + self spectating::set_permissions(); + + self SetClientScriptMainMenu( game[ "menu_start_menu" ] ); + self openMenu(game[ "menu_start_menu" ]); + + self notify("end_respawn"); +} + +function count_players() +{ + players = level.players; + + playerCounts = []; + foreach( team in level.teams ) + { + playerCounts[team] = 0; + } + + foreach( player in level.players ) + { + if( player == self ) + continue; + + team = player.pers["team"]; + if( isdefined(team) && isdefined( level.teams[team] ) ) + playerCounts[team]++; + } + return playerCounts; +} + + +function track_free_played_time() +{ + self endon( "disconnect" ); + + foreach( team in level.teams ) + { + self.timePlayed[team] = 0; + } + + self.timePlayed["other"] = 0; + self.timePlayed["total"] = 0; + self.timePlayed["alive"] = 0; + + for ( ;; ) + { + if ( game["state"] == "playing" ) + { + team = self.pers["team"]; + if ( isdefined( team ) && isdefined( level.teams[team] ) && self.sessionteam != "spectator" ) + { + self.timePlayed[team]++; + self.timePlayed["total"]++; + if ( IsAlive( self ) ) + self.timePlayed["alive"]++; + } + else + { + self.timePlayed["other"]++; + } + } + + wait ( 1.0 ); + } +} + +function set_player_model( team, weapon ) +{ + self DetachAll(); + self SetMoveSpeedScale( 1 ); + self SetSprintDuration( 4 ); + self SetSprintCooldown( 0 ); +} + +function get_flag_model( teamRef ) +{ + assert(isdefined(game["flagmodels"])); + assert(isdefined(game["flagmodels"][teamRef])); + return ( game["flagmodels"][teamRef] ); +} + +function get_flag_carry_model( teamRef ) +{ + assert(isdefined(game["carry_flagmodels"])); + assert(isdefined(game["carry_flagmodels"][teamRef])); + return ( game["carry_flagmodels"][teamRef] ); +} + +function getTeamIndex( team ) +{ + if( !isdefined( team ) ) + { + return TEAM_FREE; + } + + if( team == "free" ) + { + return TEAM_FREE; + } + + if( team == "allies" ) + { + return TEAM_ALLIES; + } + + if( team == "axis" ) + { + return TEAM_AXIS; + } + + return TEAM_FREE; +} + +function getEnemyTeam( player_team ) +{ + foreach( team in level.teams ) + { + if ( team == player_team ) + continue; + + if ( team == "spectator" ) + continue; + + return team; + } + + return util::getOtherTeam( player_team ); +} + +function GetEnemyPlayers() +{ + enemies = []; + + foreach( player in level.players ) + { + if( player.team == "spectator" ) + { + continue; + } + + if( ( level.teamBased && player.team != self.team ) || ( !level.teamBased && player != self ) ) + { + ARRAY_ADD( enemies, player ); + } + } + + return enemies; +} + +function GetFriendlyPlayers() +{ + friendlies = []; + + foreach( player in level.players ) + { + if( ( player.team == self.team ) && ( player != self ) ) + { + ARRAY_ADD( friendlies, player ); + } + } + + return friendlies; +} + +function WaitUntilTeamChange( player, callback, arg, end_condition1, end_condition2, end_condition3 ) +{ + if( isdefined( end_condition1 ) ) + self endon( end_condition1 ); + if( isdefined( end_condition2 ) ) + self endon( end_condition2 ); + if( isdefined( end_condition3 ) ) + self endon( end_condition3 ); + + event = player util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); + + if( isdefined( callback ) ) + { + self [[ callback ]]( arg, event ); + } +} + + +function WaitUntilTeamChangeSingleTon( player, singletonString, callback, arg, end_condition1, end_condition2, end_condition3 ) +{ + self notify( singletonString ); + self endon( singletonString ); + if( isdefined( end_condition1 ) ) + self endon( end_condition1 ); + if( isdefined( end_condition2 ) ) + self endon( end_condition2 ); + if( isdefined( end_condition3 ) ) + self endon( end_condition3 ); + + event = player util::waittill_any( "joined_team", "disconnect", "joined_spectators" ); + + if( isdefined( callback ) ) + { + self thread [[ callback ]]( arg, event ); + } +} + + +function HideToSameTeam() +{ + if( level.teambased ) + { + self SetVisibleToAllExceptTeam( self.team ); + } + else + { + self SetVisibleToAll(); + self SetInvisibleToPlayer( self.owner ); + } +} +