diff --git a/.gitignore b/.gitignore index 3dc63c6..f37fdbd 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ !/raw/maps/mp/bots/ !/raw/bw-assets !/raw/bots.txt +!/raw/maps/mp/teams/ !/README.md diff --git a/raw/maps/mp/teams/_teams-src.gsc b/raw/maps/mp/teams/_teams-src.gsc new file mode 100644 index 0000000..11a24a8 --- /dev/null +++ b/raw/maps/mp/teams/_teams-src.gsc @@ -0,0 +1,577 @@ +#include maps/mp/gametypes/_spectating; +#include maps/mp/gametypes/_globallogic_ui; +#include maps/mp/gametypes/_persistence; +#include maps/mp/_utility; + +init() +{ + precacheshader( "mpflag_spectator" ); + game["strings"]["autobalance"] = &"MP_AUTOBALANCE_NOW"; + precachestring( &"MP_AUTOBALANCE_NOW" ); + + if ( GetDvar( "scr_teambalance" ) == "" ) + { + setdvar( "scr_teambalance", "0" ); + } + + level.teambalance = GetDvarInt( "scr_teambalance" ); + level.teambalancetimer = 0; + + if ( GetDvar( "scr_timeplayedcap" ) == "" ) + { + setdvar( "scr_timeplayedcap", "1800" ); + } + + level.timeplayedcap = int( GetDvarInt( "scr_timeplayedcap" ) ); + level.freeplayers = []; + + if ( level.teambased ) + { + level.alliesplayers = []; + level.axisplayers = []; + level thread onplayerconnect(); + level thread updateTeamBalance(); + wait( 0.15 ); + level thread updateplayertimes(); + } + else + { + level thread onfreeplayerconnect(); + wait( 0.15 ); + level thread updateplayertimes(); + } +} + + +onplayerconnect() +{ + for ( ;; ) + { + level waittill( "connecting", player ); + player thread onjoinedteam(); + player thread onjoinedspectators(); + player thread trackplayedtime(); + } +} + + +onfreeplayerconnect() +{ + for ( ;; ) + { + level waittill( "connecting", player ); + player thread trackfreeplayedtime(); + } +} + + +onjoinedteam() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "joined_team" ); + self logstring( "joined team: " + self.pers["team"] ); + self updateteamtime(); + } +} + + +onjoinedspectators() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "joined_spectators" ); + self.pers["teamTime"] = undefined; + } +} + +trackplayedtime() +{ + self endon( "disconnect" ); + + foreach ( team in level.teams ) + { + self.timePlayed[team] = 0; + } + + self.timePlayed["free"] = 0; + self.timePlayed["other"] = 0; + self.timePlayed["alive"] = 0; + + + if ( !isdefined( self.timePlayed["total"] ) || !( ( level.gameType == "twar" ) && ( 0 < game["roundsplayed"] ) && ( 0 < self.timeplayed["total"] ) ) ) + self.timePlayed["total"] = 0; + + while ( level.inprematchperiod ) + { + wait( 0.05 ); + } + + for ( ;; ) + { + if ( game["state"] == "playing" ) + { + if ( isdefined( level.teams[self.sessionteam] ) ) + { + self.timeplayed[self.sessionteam]++; + self.timeplayed["total"]++; + + if ( isalive( self ) ) + { + self.timeplayed["alive"]++; + } + } + else if ( self.sessionteam == "spectator" ) + { + self.timeplayed["other"]++; + } + } + + wait ( 1.0 ); + } + +} + +updateplayertimes() +{ + nexttoupdate = 0; + + for ( ;; ) + { + nexttoupdate++; + + if ( nexttoupdate >= level.players.size ) + { + nexttoupdate = 0; + } + + if ( isdefined( level.players[nexttoupdate] ) ) + { + level.players[nexttoupdate] updateplayedtime(); + level.players[nexttoupdate] maps\mp\gametypes\_persistence::checkcontractexpirations(); + } + + wait( 1 ); + } +} + +updateplayedtime() +{ + pixbeginevent( "updatePlayedTime" ); + + foreach ( team in level.teams ) + { + if ( self.timeplayed[team] ) + { + 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 maps\mp\gametypes\_persistence::incrementcontracttimes( timealive ); + self addplayerstat( "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; +} + +updateteamtime() +{ + if ( game["state"] != "playing" ) + { + return; + } + + self.pers["teamTime"] = GetTime(); +} + +updateteambalancedvar() +{ + 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 ); + } +} + +updateTeamBalance() +{ + level.teamLimit = level.maxclients / 2; + + level thread updateTeamBalanceDvar(); + + wait .15; + + if ( level.teamBalance && isRoundBased() && level.numlives ) + { + if ( isDefined( game["BalanceTeamsNextRound"] ) ) + iPrintLnbold( &"MP_AUTOBALANCE_NEXT_ROUND" ); + + level waittill( "game_ended" ); + wait 1; + + if ( isDefined( game["BalanceTeamsNextRound"] ) ) + { + level balanceTeams(); + game["BalanceTeamsNextRound"] = undefined; + } + else if ( !getTeamBalance() ) + { + game["BalanceTeamsNextRound"] = true; + } + } + else + { + level endon ( "game_ended" ); + + for ( ;; ) + { + if ( level.teamBalance ) + { + if ( !getTeamBalance() ) + { + iPrintLnBold( &"MP_AUTOBALANCE_SECONDS", 15 ); + wait 15.0; + + if ( !getTeamBalance() ) + level balanceTeams(); + } + + wait 59.0; + } + + wait 1.0; + } + } + +} + +getTeamBalance() +{ + 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; +} + +balanceTeams() +{ + 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 changeTeam( "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 changeTeam( "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 changeTeam( "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 changeTeam( "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]; + } + } +} + +changeTeam( team ) +{ + teams[0] = "allies"; + teams[1] = "axis"; + assignment = team; + + if ( assignment != self.pers["team"] ) + { + if ( self.sessionstate == "playing" || self.sessionstate == "dead" ) + { + self.switching_teams = true; + self.joining_team = assignment; + self.leaving_team = self.pers["team"]; + self suicide(); + } + } + + self.pers["team"] = assignment; + self.team = assignment; + self.pers["class"] = undefined; + self.class = undefined; + self.pers["weapon"] = undefined; + self.pers["savedmodel"] = undefined; + + self maps\mp\gametypes\_globallogic_ui::updateObjectiveText(); + self maps\mp\gametypes\_spectating::setspectatepermissions(); + + if ( level.teamBased ) + self.sessionteam = assignment; + else + { + self.sessionteam = "none"; + self.ffateam = assignment; + } + + if ( !isAlive( self ) ) + self.statusicon = "hud_status_dead"; + + + self notify( "joined_team" ); + level notify( "joined_team" ); + self setclientscriptmainmenu( game["menu_class"] ); + self openmenu( game["menu_class"] ); + self notify( "end_respawn" ); +} + +countplayers() +{ + 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; +} + +trackfreeplayedtime() +{ + self endon( "disconnect" ); + + foreach ( team in level.teams ) + { + self.timeplayed[team] = 0; + } + + self.timeplayed["other"] = 0; + self.timeplayed["total"] = 0; + self.timeplayed["alive"] = 0; + + while ( 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 ); + } +} + +set_player_model( team, weapon ) +{ + weaponclass = getweaponclass( weapon ); + bodytype = "default"; + + switch ( weaponclass ) + { + case "weapon_sniper": + bodytype = "rifle"; + break; + + case "weapon_cqb": + bodytype = "spread"; + break; + + case "weapon_lmg": + bodytype = "mg"; + break; + + case "weapon_smg": + bodytype = "smg"; + break; + } + + self detachall(); + self setmovespeedscale( 1 ); + self setsprintduration( 4 ); + self setsprintcooldown( 0 ); + + if ( level.multiteam ) + { + bodytype = "default"; + + switch ( team ) + { + case "team7": + case "team8": + team = "allies"; + break; + } + } + + self [[ game[ "set_player_model" ][ team ][ bodytype ] ]](); +} + +getteamflagmodel( teamref ) +{ + return game["flagmodels"][teamref]; +} + +getteamflagcarrymodel( teamref ) +{ + return game["carry_flagmodels"][teamref]; +} + +getteamflagicon( teamref ) +{ + return game["carry_icon"][teamref]; +} diff --git a/raw/maps/mp/teams/_teams.gsc b/raw/maps/mp/teams/_teams.gsc new file mode 100644 index 0000000..2642b0a Binary files /dev/null and b/raw/maps/mp/teams/_teams.gsc differ diff --git a/raw/scripts/mp/spectatorKick-compiled.gsc b/raw/scripts/mp/spectatorKick-compiled.gsc new file mode 100644 index 0000000..435d1ec Binary files /dev/null and b/raw/scripts/mp/spectatorKick-compiled.gsc differ diff --git a/raw/scripts/mp/spectatorKick.gsc b/raw/scripts/mp/spectatorKick.gsc new file mode 100644 index 0000000..54b3bf5 --- /dev/null +++ b/raw/scripts/mp/spectatorKick.gsc @@ -0,0 +1,35 @@ +init() +{ + if ( getDvar( "g_inactivitySpectator" ) == "" ) + setDvar( "g_inactivitySpectator", 0.0 ); + + level.inactivitySpectator = getDvarFloat( "g_inactivitySpectator" ) * 1000; + + if ( level.inactivitySpectator <= 0 ) + return; + + thread watchPlayers(); +} + +watchPlayers() +{ + for ( ;; ) + { + wait 1.5; + + theTime = getTime(); + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + + if ( isDefined( player ) && !player.hasSpawned ) + { + if ( !isDefined( player.specTime ) ) + player.specTime = theTime; + else if ( ( theTime - player.specTime ) >= level.inactivitySpectator ) + kick( player getEntityNumber() ); + } + } + } +}