diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..62275bf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Discord Server + url: https://discord.gg/sKeVmR3/ + about: Please ask and answer support questions here. + - name: XLabsProject Website + url: https://xlabs.dev/ + about: The official website. diff --git a/.github/ISSUE_TEMPLATE/get-help.md b/.github/ISSUE_TEMPLATE/get-help.md new file mode 100644 index 0000000..d324191 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/get-help.md @@ -0,0 +1,29 @@ +--- +name: Get help +about: Get help using S1x +title: '' +labels: discussion +assignees: '' + +--- + +_Do not open an issue here if you need help with modding or have a problem getting the client to run. +It is very likely your problem will be resolved by reading the [FAQ](https://xlabs.dev/s1x_faq) carefully. +Ask in `s1x-support` or `s1x-modding` channels on the [Discord](https://discord.gg/sKeVmR3) server if you still have problems. +Before opening a new issue, please see [Issues](https://github.com/XLabsProject/s1x-client/issues) and check that a similar issue does not already exist +If this does not apply, please continue by filling in the template below._ + +**What are you trying to do?** +A short, concise description of the outcome you are trying to achieve. + +**What problem are you having?** +A clear and concise description of the problem that is blocking you from your desired outcome, ex. "S1x is crashing with this error message: ..." +If S1x is crashing, include the minidump file and the crash address in text form. Screenshots showing the message box with the crash address are not acceptable. + +**What version of S1x are you using?** +Please make sure you are up to date with the latest build from the master branch. +You should be using the official XLabs Launcher. +You should *not* be using any custom builds of the game made for "trickshotting" + +**Anything else we should know?** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/request-a-feature.md b/.github/ISSUE_TEMPLATE/request-a-feature.md new file mode 100644 index 0000000..45f1505 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/request-a-feature.md @@ -0,0 +1,27 @@ +--- +name: Request a feature +about: Suggest a new feature or enhancement +title: '' +labels: feature +assignees: '' + +--- + +_Before opening a new feature request, please see [Issues](https://github.com/XLabsProject/s1x-client/issues) and check that a similar issue does not already exist. +If this a new request, help us help you by filling in the template below. +S1x is not in active development right now. Please keep in mind that if there is not a clear positive impact on the gameplay to be gained by this feature request, it may be closed at the maintainers' discretion._ + +**What problem will this solve?** +A clear and concise description of the problem this new feature is meant to solve, ex. "There is something wrong with the game" or "There is something wrong with S1x's source code". +Please limit your request to a single feature; create multiple feature requests instead. + +**What might be a solution?** +A clear and concise description of what you want to happen. If you are proposing changes to S1x's source code, tell us which component you want to be changed. +If you propose changes to the game, you may use images or videos to illustrate what aspect of the game should be changed. + +**What other alternatives have you already considered?** +A clear and concise description of any alternative solutions or features you've considered. +It may help others to find workarounds until the problem is resolved. + +**Anything else we should know?** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f1091f..5e5332e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,11 @@ on: branches: - "*" types: [opened, synchronize, reopened] + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + jobs: build: name: Build binaries @@ -18,14 +23,6 @@ jobs: - Debug - Release steps: - - name: Wait for previous workflows - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - uses: softprops/turnstyle@v1 - with: - poll-interval-seconds: 10 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Check out files uses: actions/checkout@v3 with: @@ -95,13 +92,6 @@ jobs: - name: Add known hosts run: ssh-keyscan -H ${{ secrets.XLABS_MASTER_SSH_ADDRESS }} >> ~/.ssh/known_hosts - - name: Wait for previous workflows - uses: softprops/turnstyle@v1 - with: - poll-interval-seconds: 10 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Remove old data files run: ssh ${{ secrets.XLABS_MASTER_SSH_USER }}@${{ secrets.XLABS_MASTER_SSH_ADDRESS }} rm -rf ${{ env.XLABS_MASTER_PATH }}/s1x/data/* diff --git a/data/maps/mp/gametypes/_gamelogic.gsc b/data/maps/mp/gametypes/_gamelogic.gsc new file mode 100644 index 0000000..61b3bef --- /dev/null +++ b/data/maps/mp/gametypes/_gamelogic.gsc @@ -0,0 +1,3548 @@ +// S1 GSC SOURCE +// Decompiled by https://github.com/xensik/gsc-tool + +onforfeit( var_0 ) +{ + if ( isdefined( level.forfeitinprogress ) ) + return; + + level endon( "abort_forfeit" ); + level thread forfeitwaitforabort(); + level.forfeitinprogress = 1; + + if ( !level.teambased && level.players.size > 1 ) + wait 10; + else + wait 1.05; + + level.forfeit_aborted = 0; + var_1 = 20.0; + matchforfeittimer( var_1 ); + var_2 = &""; + + if ( !isdefined( var_0 ) ) + { + level.finalkillcam_winner = "none"; + var_2 = game["end_reason"]["players_forfeited"]; + var_3 = level.players[0]; + } + else if ( var_0 == "axis" ) + { + level.finalkillcam_winner = "axis"; + var_2 = game["end_reason"]["allies_forfeited"]; + + if ( level.gametype == "infect" ) + var_2 = game["end_reason"]["survivors_forfeited"]; + + var_3 = "axis"; + } + else if ( var_0 == "allies" ) + { + level.finalkillcam_winner = "allies"; + var_2 = game["end_reason"]["axis_forfeited"]; + + if ( level.gametype == "infect" ) + var_2 = game["end_reason"]["infected_forfeited"]; + + var_3 = "allies"; + } + else if ( level.multiteambased && issubstr( var_0, "team_" ) ) + var_3 = var_0; + else + { + level.finalkillcam_winner = "none"; + var_3 = "tie"; + } + + level.forcedend = 1; + + if ( isplayer( var_3 ) ) + logstring( "forfeit, win: " + var_3 getxuid() + "(" + var_3.name + ")" ); + else + logstring( "forfeit, win: " + var_3 + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + + thread endgame( var_3, var_2 ); +} + +forfeitwaitforabort() +{ + level endon( "game_ended" ); + level waittill( "abort_forfeit" ); + level.forfeit_aborted = 1; + setomnvar( "ui_match_countdown", 0 ); + setomnvar( "ui_match_countdown_title", 0 ); + setomnvar( "ui_match_countdown_toggle", 0 ); +} + +matchforfeittimer_internal( var_0 ) +{ + waittillframeend; + level endon( "match_forfeit_timer_beginning" ); + setomnvar( "ui_match_countdown_title", 3 ); + setomnvar( "ui_match_countdown_toggle", 1 ); + + while ( var_0 > 0 && !level.gameended && !level.forfeit_aborted && !level.ingraceperiod ) + { + setomnvar( "ui_match_countdown", var_0 ); + wait 1; + var_0--; + } +} + +matchforfeittimer( var_0 ) +{ + level notify( "match_forfeit_timer_beginning" ); + var_1 = int( var_0 ); + matchforfeittimer_internal( var_1 ); + setomnvar( "ui_match_countdown", 0 ); + setomnvar( "ui_match_countdown_title", 0 ); + setomnvar( "ui_match_countdown_toggle", 0 ); +} + +default_ondeadevent( var_0 ) +{ + level.finalkillcam_winner = "none"; + + if ( var_0 == "allies" ) + { + logstring( "team eliminated, win: opfor, allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + level.finalkillcam_winner = "axis"; + thread endgame( "axis", game["end_reason"]["allies_eliminated"] ); + } + else if ( var_0 == "axis" ) + { + logstring( "team eliminated, win: allies, allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + level.finalkillcam_winner = "allies"; + thread endgame( "allies", game["end_reason"]["axis_eliminated"] ); + } + else + { + logstring( "tie, allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + level.finalkillcam_winner = "none"; + + if ( level.teambased ) + thread endgame( "tie", game["end_reason"]["tie"] ); + else + thread endgame( undefined, game["end_reason"]["tie"] ); + } +} + +default_ononeleftevent( var_0 ) +{ + if ( level.teambased ) + { + var_1 = maps\mp\_utility::getlastlivingplayer( var_0 ); + var_1 thread givelastonteamwarning(); + } + else + { + var_1 = maps\mp\_utility::getlastlivingplayer(); + logstring( "last one alive, win: " + var_1.name ); + level.finalkillcam_winner = "none"; + thread endgame( var_1, game["end_reason"]["enemies_eliminated"] ); + } + + return 1; +} + +default_ontimelimit() +{ + var_0 = undefined; + level.finalkillcam_winner = "none"; + + if ( level.teambased ) + { + if ( game["teamScores"]["allies"] == game["teamScores"]["axis"] ) + var_0 = "tie"; + else if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] ) + { + level.finalkillcam_winner = "axis"; + var_0 = "axis"; + } + else + { + level.finalkillcam_winner = "allies"; + var_0 = "allies"; + } + + if ( maps\mp\_utility::practiceroundgame() ) + var_0 = "none"; + + logstring( "time limit, win: " + var_0 + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + } + else + { + var_0 = maps\mp\gametypes\_gamescore::gethighestscoringplayer(); + + if ( isdefined( var_0 ) ) + logstring( "time limit, win: " + var_0.name ); + else + logstring( "time limit, tie" ); + } + + thread endgame( var_0, game["end_reason"]["time_limit_reached"] ); +} + +default_onhalftime( var_0 ) +{ + var_1 = undefined; + level.finalkillcam_winner = "none"; + thread endgame( "halftime", game["end_reason"][var_0] ); +} + +forceend() +{ + if ( level.hostforcedend || level.forcedend ) + return; + + var_0 = undefined; + level.finalkillcam_winner = "none"; + + if ( level.teambased ) + { + if ( isdefined( level.ishorde ) ) + var_0 = "axis"; + else if ( game["teamScores"]["allies"] == game["teamScores"]["axis"] ) + var_0 = "tie"; + else if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] ) + { + level.finalkillcam_winner = "axis"; + var_0 = "axis"; + } + else + { + level.finalkillcam_winner = "allies"; + var_0 = "allies"; + } + + logstring( "host ended game, win: " + var_0 + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + } + else + { + var_0 = maps\mp\gametypes\_gamescore::gethighestscoringplayer(); + + if ( isdefined( var_0 ) ) + logstring( "host ended game, win: " + var_0.name ); + else + logstring( "host ended game, tie" ); + } + + level.forcedend = 1; + level.hostforcedend = 1; + + if ( level.splitscreen ) + var_1 = game["end_reason"]["ended_game"]; + else + var_1 = game["end_reason"]["host_ended_game"]; + + thread endgame( var_0, var_1 ); +} + +onscorelimit() +{ + var_0 = game["end_reason"]["score_limit_reached"]; + var_1 = undefined; + level.finalkillcam_winner = "none"; + + if ( level.multiteambased ) + { + var_1 = maps\mp\gametypes\_gamescore::getwinningteam(); + + if ( var_1 == "none" ) + var_1 = "tie"; + } + else if ( level.teambased ) + { + if ( game["teamScores"]["allies"] == game["teamScores"]["axis"] ) + var_1 = "tie"; + else if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] ) + { + var_1 = "axis"; + level.finalkillcam_winner = "axis"; + } + else + { + var_1 = "allies"; + level.finalkillcam_winner = "allies"; + } + + logstring( "scorelimit, win: " + var_1 + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] ); + } + else + { + var_1 = maps\mp\gametypes\_gamescore::gethighestscoringplayer(); + + if ( isdefined( var_1 ) ) + logstring( "scorelimit, win: " + var_1.name ); + else + logstring( "scorelimit, tie" ); + } + + thread endgame( var_1, var_0 ); + return 1; +} + +updategameevents() +{ + if ( maps\mp\_utility::matchmakinggame() && !level.ingraceperiod && !getdvarint( "force_ranking" ) && ( !isdefined( level.disableforfeit ) || !level.disableforfeit ) ) + { + if ( level.multiteambased ) + { + var_0 = 0; + var_1 = 0; + + for ( var_2 = 0; var_2 < level.teamnamelist.size; var_2++ ) + { + var_0 += level.teamcount[level.teamnamelist[var_2]]; + + if ( level.teamcount[level.teamnamelist[var_2]] ) + var_1 += 1; + } + + for ( var_2 = 0; var_2 < level.teamnamelist.size; var_2++ ) + { + if ( var_0 == level.teamcount[level.teamnamelist[var_2]] && game["state"] == "playing" ) + { + thread onforfeit( level.teamnamelist[var_2] ); + return; + } + } + + if ( var_1 > 1 ) + { + level.forfeitinprogress = undefined; + level notify( "abort_forfeit" ); + } + } + else if ( level.teambased ) + { + if ( level.teamcount["allies"] < 1 && level.teamcount["axis"] > 0 && game["state"] == "playing" ) + { + thread onforfeit( "axis" ); + return; + } + + if ( level.teamcount["axis"] < 1 && level.teamcount["allies"] > 0 && game["state"] == "playing" ) + { + thread onforfeit( "allies" ); + return; + } + + if ( level.teamcount["axis"] > 0 && level.teamcount["allies"] > 0 ) + { + level.forfeitinprogress = undefined; + level notify( "abort_forfeit" ); + } + } + else + { + if ( level.teamcount["allies"] + level.teamcount["axis"] == 1 && level.maxplayercount >= 1 && !getdvarint( "virtualLobbyActive", 0 ) ) + { + thread onforfeit(); + return; + } + + if ( level.teamcount["axis"] + level.teamcount["allies"] > 1 ) + { + level.forfeitinprogress = undefined; + level notify( "abort_forfeit" ); + } + } + } + + if ( !maps\mp\_utility::getgametypenumlives() && ( !isdefined( level.disablespawning ) || !level.disablespawning ) ) + return; + + if ( !maps\mp\_utility::gamehasstarted() ) + return; + + if ( level.ingraceperiod ) + return; + + if ( level.multiteambased ) + return; + + if ( level.teambased ) + { + var_3["allies"] = level.livescount["allies"]; + var_3["axis"] = level.livescount["axis"]; + + if ( isdefined( level.disablespawning ) && level.disablespawning ) + { + var_3["allies"] = 0; + var_3["axis"] = 0; + } + + if ( !level.alivecount["allies"] && !level.alivecount["axis"] && !var_3["allies"] && !var_3["axis"] ) + return [[ level.ondeadevent ]]( "all" ); + + if ( !level.alivecount["allies"] && !var_3["allies"] ) + return [[ level.ondeadevent ]]( "allies" ); + + if ( !level.alivecount["axis"] && !var_3["axis"] ) + return [[ level.ondeadevent ]]( "axis" ); + + var_4 = level.alivecount["allies"] == 1 && !var_3["allies"]; + var_5 = level.alivecount["axis"] == 1 && !var_3["axis"]; + + if ( ( var_4 || var_5 ) && !isdefined( level.bot_spawn_from_devgui_in_progress ) ) + { + var_6 = undefined; + + if ( var_4 && !isdefined( level.onelefttime["allies"] ) ) + { + level.onelefttime["allies"] = gettime(); + var_7 = [[ level.ononeleftevent ]]( "allies" ); + + if ( isdefined( var_7 ) ) + { + if ( !isdefined( var_6 ) ) + var_6 = var_7; + + var_6 = var_6 || var_7; + } + } + + if ( var_5 && !isdefined( level.onelefttime["axis"] ) ) + { + level.onelefttime["axis"] = gettime(); + var_8 = [[ level.ononeleftevent ]]( "axis" ); + + if ( isdefined( var_8 ) ) + { + if ( !isdefined( var_6 ) ) + var_6 = var_8; + + var_6 = var_6 || var_8; + } + } + + return var_6; + return; + } + } + else + { + if ( !level.alivecount["allies"] && !level.alivecount["axis"] && ( !level.livescount["allies"] && !level.livescount["axis"] ) ) + return [[ level.ondeadevent ]]( "all" ); + + var_9 = maps\mp\_utility::getpotentiallivingplayers(); + + if ( var_9.size == 1 ) + return [[ level.ononeleftevent ]]( "all" ); + } +} + +waittillfinalkillcamdone() +{ + if ( !isdefined( level.finalkillcam_winner ) ) + return 0; + + level waittill( "final_killcam_done" ); + return 1; +} + +timelimitclock_intermission( var_0 ) +{ + setgameendtime( gettime() + int( var_0 * 1000 ) ); + var_1 = spawn( "script_origin", ( 0.0, 0.0, 0.0 ) ); + var_1 hide(); + + if ( var_0 >= 10.0 ) + wait(var_0 - 10.0); + + for (;;) + { + var_1 playsound( "ui_mp_timer_countdown" ); + wait 1.0; + } +} + +waitforplayers( var_0 ) +{ + var_1 = gettime(); + var_2 = var_1 + var_0 * 1000 - 200; + + if ( var_0 > 5 ) + var_3 = gettime() + getdvarint( "min_wait_for_players" ) * 1000; + else + var_3 = 0; + + if ( isdefined( level.iszombiegame ) && level.iszombiegame ) + var_4 = level.connectingplayers; + else + var_4 = level.connectingplayers / 3; + + var_5 = 0; + + for (;;) + { + if ( isdefined( game["roundsPlayed"] ) && game["roundsPlayed"] ) + break; + + var_6 = level.maxplayercount; + var_7 = gettime(); + + if ( var_6 >= var_4 && var_7 > var_3 || var_7 > var_2 ) + break; + + wait 0.05; + } +} + +prematchperiod() +{ + level endon( "game_ended" ); + level.connectingplayers = getdvarint( "party_partyPlayerCountNum" ); + + if ( level.prematchperiod > 0 ) + { + level.waitingforplayers = 1; + matchstarttimerwaitforplayers(); + level.waitingforplayers = 0; + } + else + matchstarttimerskip(); + + for ( var_0 = 0; var_0 < level.players.size; var_0++ ) + { + level.players[var_0] maps\mp\_utility::freezecontrolswrapper( 0 ); + level.players[var_0] enableweapons(); + level.players[var_0] enableammogeneration(); + var_1 = maps\mp\_utility::getobjectivehinttext( level.players[var_0].pers["team"] ); + + if ( !isdefined( var_1 ) || !level.players[var_0].hasspawned ) + continue; + + level.players[var_0] thread maps\mp\gametypes\_hud_message::hintmessage( var_1 ); + } + + if ( game["state"] != "playing" ) + return; +} + +graceperiod() +{ + level endon( "game_ended" ); + + if ( !isdefined( game["clientActive"] ) ) + { + while ( getplaylistid() == 0 ) + wait 0.05; + + game["clientActive"] = 1; + } + + while ( level.ingraceperiod > 0 ) + { + wait 1.0; + level.ingraceperiod--; + } + + level notify( "grace_period_ending" ); + wait 0.05; + maps\mp\_utility::gameflagset( "graceperiod_done" ); + level.ingraceperiod = 0; + + if ( game["state"] != "playing" ) + return; + + level thread updategameevents(); +} + +sethasdonecombat( var_0, var_1 ) +{ + var_0.hasdonecombat = var_1; + var_0 notify( "hasDoneCombat" ); + var_2 = !isdefined( var_0.hasdoneanycombat ) || !var_0.hasdoneanycombat; + + if ( var_2 && var_1 ) + { + var_0.hasdoneanycombat = 1; + var_0.pers["participation"] = 1; + + if ( isdefined( var_0.pers["hasMatchLoss"] ) && var_0.pers["hasMatchLoss"] ) + return; + + updatelossstats( var_0 ); + } +} + +updatewinstats( var_0 ) +{ + if ( !var_0 maps\mp\_utility::rankingenabled() ) + return; + + if ( ( !isdefined( var_0.hasdoneanycombat ) || !var_0.hasdoneanycombat ) && !( level.gametype == "infect" ) ) + return; + + var_0 maps\mp\gametypes\_persistence::statadd( "losses", -1 ); + var_0 maps\mp\gametypes\_persistence::statadd( "wins", 1 ); + var_0 maps\mp\_utility::updatepersratio( "winLossRatio", "wins", "losses" ); + var_0 maps\mp\gametypes\_persistence::statadd( "currentWinStreak", 1 ); + var_1 = var_0 maps\mp\gametypes\_persistence::statget( "currentWinStreak" ); + + if ( var_1 > var_0 maps\mp\gametypes\_persistence::statget( "winStreak" ) ) + var_0 maps\mp\gametypes\_persistence::statset( "winStreak", var_1 ); + + var_0 maps\mp\gametypes\_persistence::statsetchild( "round", "win", 1 ); + var_0 maps\mp\gametypes\_persistence::statsetchild( "round", "loss", 0 ); + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_" + level.gametype + "_wins" ); + var_0.combatrecordwin = 1; + var_0 maps\mp\gametypes\_missions::processchallengedaily( 25, undefined, undefined ); + var_0 maps\mp\gametypes\_missions::processchallengedaily( 26, undefined, undefined ); + var_0 maps\mp\gametypes\_missions::processchallengedaily( 27, undefined, undefined ); + var_0 maps\mp\gametypes\_missions::processchallengedaily( 28, undefined, undefined ); + var_0 maps\mp\gametypes\_missions::processchallengedaily( 29, undefined, undefined ); + var_0 maps\mp\gametypes\_missions::processchallengedaily( 30, undefined, undefined ); + var_0 maps\mp\gametypes\_missions::processchallengedaily( 36, undefined, undefined ); + var_0 maps\mp\gametypes\_missions::processchallengedaily( 37, undefined, undefined ); + + if ( maps\mp\_utility::isgrapplinghookgamemode() ) + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_tier2_4_iw5_dlcgun12" ); + + if ( level.players.size > 5 ) + { + superstarchallenge( var_0 ); + + switch ( level.gametype ) + { + case "war": + if ( game["teamScores"][var_0.team] >= game["teamScores"][maps\mp\_utility::getotherteam( var_0.team )] + 20 ) + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_war_crushing" ); + + break; + case "hp": + if ( game["teamScores"][var_0.team] >= game["teamScores"][maps\mp\_utility::getotherteam( var_0.team )] + 70 ) + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_hp_crushing" ); + + break; + case "conf": + if ( game["teamScores"][var_0.team] >= game["teamScores"][maps\mp\_utility::getotherteam( var_0.team )] + 15 ) + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_conf_crushing" ); + + break; + case "ball": + if ( game["teamScores"][var_0.team] >= game["teamScores"][maps\mp\_utility::getotherteam( var_0.team )] + 7 ) + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_ball_crushing" ); + + break; + case "infect": + if ( var_0.team == "allies" ) + { + if ( game["teamScores"][var_0.team] >= 4 ) + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_infect_crushing" ); + + if ( game["teamScores"][maps\mp\_utility::getotherteam( var_0.team )] == 1 ) + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_infect_cleanup" ); + } + + break; + case "dm": + if ( isdefined( level.placement["all"][0] ) ) + { + var_2 = level.placement["all"][0]; + var_3 = 9999; + + if ( var_0 == var_2 ) + { + foreach ( var_5 in level.players ) + { + if ( var_0 == var_5 ) + continue; + + var_6 = var_0.score - var_5.score; + + if ( var_6 < var_3 ) + var_3 = var_6; + } + + if ( var_3 >= 7 ) + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_dm_crushing" ); + } + } + + break; + case "gun": + foreach ( var_9 in level.players ) + { + if ( var_0 == var_9 ) + continue; + + if ( var_0.score < var_9.score + 5 ) + break; + } + + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_gun_crushing" ); + break; + case "ctf": + case "twar": + if ( game["shut_out"][var_0.team] ) + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_" + level.gametype + "_crushing" ); + + break; + } + } +} + +superstarchallenge( var_0 ) +{ + var_1 = 0; + var_2 = 9999; + + foreach ( var_4 in level.players ) + { + if ( var_4.kills > var_1 ) + var_1 = var_4.kills; + + if ( var_4.deaths < var_2 ) + var_2 = var_4.deaths; + } + + if ( var_0.kills >= var_1 && var_0.deaths <= var_2 && var_0.kills > 0 && !isai( var_0 ) ) + var_0 maps\mp\gametypes\_missions::processchallenge( "ch_" + level.gametype + "_star" ); +} + +checkgameendchallenges() +{ + if ( level.gametype == "dom" ) + { + foreach ( var_1 in level.domflags ) + { + if ( !isdefined( var_1.ownedtheentireround ) || !var_1.ownedtheentireround ) + continue; + + var_2 = var_1 maps\mp\gametypes\_gameobjects::getownerteam(); + + foreach ( var_4 in level.players ) + { + if ( var_4.team != var_2 ) + continue; + + switch ( var_1.label ) + { + case "_a": + var_4 maps\mp\gametypes\_missions::processchallenge( "ch_dom_alphalock" ); + continue; + case "_b": + var_4 maps\mp\gametypes\_missions::processchallenge( "ch_dom_bravolock" ); + continue; + case "_c": + var_4 maps\mp\gametypes\_missions::processchallenge( "ch_dom_charlielock" ); + continue; + } + } + } + } +} + +updatelossstats( var_0 ) +{ + if ( !var_0 maps\mp\_utility::rankingenabled() ) + return; + + if ( !isdefined( var_0.hasdoneanycombat ) || !var_0.hasdoneanycombat ) + return; + + var_0.pers["hasMatchLoss"] = 1; + var_0 maps\mp\gametypes\_persistence::statadd( "losses", 1 ); + var_0 maps\mp\_utility::updatepersratio( "winLossRatio", "wins", "losses" ); + var_0 maps\mp\gametypes\_persistence::statsetchild( "round", "loss", 1 ); +} + +updatetiestats( var_0 ) +{ + if ( !var_0 maps\mp\_utility::rankingenabled() ) + return; + + if ( !isdefined( var_0.hasdoneanycombat ) || !var_0.hasdoneanycombat ) + return; + + var_0 maps\mp\gametypes\_persistence::statadd( "losses", -1 ); + var_0 maps\mp\gametypes\_persistence::statadd( "ties", 1 ); + var_0 maps\mp\_utility::updatepersratio( "winLossRatio", "wins", "losses" ); + var_0 maps\mp\gametypes\_persistence::statset( "currentWinStreak", 0 ); + var_0.combatrecordtie = 1; +} + +updatewinlossstats( var_0 ) +{ + if ( maps\mp\_utility::privatematch() ) + return; + + if ( maps\mp\_utility::practiceroundgame() ) + return; + + if ( !isdefined( var_0 ) || isdefined( var_0 ) && isstring( var_0 ) && var_0 == "tie" ) + { + foreach ( var_2 in level.players ) + { + if ( isdefined( var_2.connectedpostgame ) ) + continue; + + if ( level.hostforcedend && var_2 ishost() ) + { + var_2 maps\mp\gametypes\_persistence::statset( "currentWinStreak", 0 ); + continue; + } + + updatetiestats( var_2 ); + } + } + else if ( isplayer( var_0 ) ) + { + var_4[0] = var_0; + + if ( level.players.size > 5 ) + var_4 = maps\mp\gametypes\_gamescore::gethighestscoringplayersarray( 3 ); + + foreach ( var_2 in var_4 ) + { + if ( isdefined( var_2.connectedpostgame ) ) + continue; + + if ( level.hostforcedend && var_2 ishost() ) + { + var_2 maps\mp\gametypes\_persistence::statset( "currentWinStreak", 0 ); + continue; + } + + updatewinstats( var_2 ); + } + } + else if ( isstring( var_0 ) ) + { + foreach ( var_2 in level.players ) + { + if ( isdefined( var_2.connectedpostgame ) ) + continue; + + if ( level.hostforcedend && var_2 ishost() ) + { + var_2 maps\mp\gametypes\_persistence::statset( "currentWinStreak", 0 ); + continue; + } + + if ( var_0 == "tie" ) + { + updatetiestats( var_2 ); + continue; + } + + if ( var_2.pers["team"] == var_0 ) + { + updatewinstats( var_2 ); + continue; + } + + var_2 maps\mp\gametypes\_persistence::statset( "currentWinStreak", 0 ); + } + } + + if ( level.players.size > 5 ) + { + var_4 = maps\mp\gametypes\_gamescore::gethighestscoringplayersarray( 3 ); + + for ( var_9 = 0; var_9 < var_4.size; var_9++ ) + { + if ( var_9 == 0 ) + var_4[var_9] maps\mp\gametypes\_missions::processchallenge( "ch_" + level.gametype + "_mvp" ); + + var_4[var_9] maps\mp\gametypes\_missions::processchallenge( "ch_" + level.gametype + "_superior" ); + } + } +} + +freezeplayerforroundend( var_0 ) +{ + self endon( "disconnect" ); + maps\mp\_utility::clearlowermessages(); + + if ( !isdefined( var_0 ) ) + var_0 = 0.05; + + self closepopupmenu(); + self closeingamemenu(); + wait(var_0); + maps\mp\_utility::freezecontrolswrapper( 1 ); +} + +updatematchbonusscores( var_0 ) +{ + if ( !game["timePassed"] ) + return; + + if ( !maps\mp\_utility::matchmakinggame() ) + return; + + if ( maps\mp\_utility::practiceroundgame() ) + return; + + if ( level.teambased ) + { + if ( var_0 == "allies" ) + { + var_1 = "allies"; + var_2 = "axis"; + } + else if ( var_0 == "axis" ) + { + var_1 = "axis"; + var_2 = "allies"; + } + else + { + var_1 = "tie"; + var_2 = "tie"; + } + + if ( var_1 != "tie" ) + clientannouncement( var_1 ); + + foreach ( var_4 in level.players ) + { + if ( isdefined( var_4.connectedpostgame ) ) + continue; + + if ( !var_4 maps\mp\_utility::rankingenabled() ) + continue; + + if ( var_4.timeplayed["total"] < 1 || var_4.pers["participation"] < 1 ) + continue; + + if ( level.hostforcedend && var_4 ishost() ) + continue; + + var_5 = 0; + + if ( var_1 == "tie" ) + { + var_5 = maps\mp\gametypes\_rank::getscoreinfovalue( "tie" ); + var_4.didtie = 1; + var_4.iswinner = 0; + } + else if ( isdefined( var_4.pers["team"] ) && var_4.pers["team"] == var_1 ) + { + var_5 = maps\mp\gametypes\_rank::getscoreinfovalue( "win" ); + var_4.iswinner = 1; + } + else if ( isdefined( var_4.pers["team"] ) && var_4.pers["team"] == var_2 ) + { + var_5 = maps\mp\gametypes\_rank::getscoreinfovalue( "loss" ); + var_4.iswinner = 0; + } + + var_4.matchbonus = int( var_5 ); + } + } + else + { + foreach ( var_4 in level.players ) + { + if ( isdefined( var_4.connectedpostgame ) ) + continue; + + if ( !var_4 maps\mp\_utility::rankingenabled() ) + continue; + + if ( var_4.timeplayed["total"] < 1 || var_4.pers["participation"] < 1 ) + continue; + + if ( level.hostforcedend && var_4 ishost() ) + continue; + + var_4.iswinner = 0; + + for ( var_8 = 0; var_8 < min( level.placement["all"].size, 3 ); var_8++ ) + { + if ( level.placement["all"][var_8] != var_4 ) + continue; + + var_4.iswinner = 1; + } + + var_5 = 0; + + if ( var_4.iswinner ) + var_5 = maps\mp\gametypes\_rank::getscoreinfovalue( "win" ); + else + var_5 = maps\mp\gametypes\_rank::getscoreinfovalue( "loss" ); + + var_4.matchbonus = int( var_5 ); + } + } + + foreach ( var_4 in level.players ) + { + if ( !isdefined( var_4 ) ) + continue; + + if ( !isdefined( var_4.iswinner ) ) + continue; + + var_11 = "loss"; + + if ( var_4.iswinner ) + var_11 = "win"; + + if ( isdefined( var_4.didtie ) && var_4.didtie ) + var_11 = "tie"; + + var_4 thread givematchbonus( var_11, var_4.matchbonus ); + } +} + +givematchbonus( var_0, var_1 ) +{ + self endon( "disconnect" ); + level waittill( "give_match_bonus" ); + maps\mp\gametypes\_rank::giverankxp( var_0, var_1 ); + maps\mp\_utility::logxpgains(); +} + +setxenonranks( var_0 ) +{ + var_1 = level.players; + + for ( var_2 = 0; var_2 < var_1.size; var_2++ ) + { + var_3 = var_1[var_2]; + + if ( !isdefined( var_3.score ) || !isdefined( var_3.pers["team"] ) ) + continue; + } + + for ( var_2 = 0; var_2 < var_1.size; var_2++ ) + { + var_3 = var_1[var_2]; + + if ( !isdefined( var_3.score ) || !isdefined( var_3.pers["team"] ) ) + continue; + + var_4 = var_3.score; + + if ( maps\mp\_utility::getminutespassed() ) + var_4 = var_3.score / maps\mp\_utility::getminutespassed(); + + _func_173( var_3, var_3.clientid, int( var_4 ) ); + } +} + +checktimelimit( var_0 ) +{ + if ( isdefined( level.timelimitoverride ) && level.timelimitoverride ) + return; + + if ( game["state"] != "playing" ) + { + setgameendtime( 0 ); + return; + } + + if ( maps\mp\_utility::gettimelimit() <= 0 ) + { + if ( isdefined( level.starttime ) ) + setgameendtime( level.starttime ); + else + setgameendtime( 0 ); + + return; + } + + if ( !maps\mp\_utility::gameflag( "prematch_done" ) ) + { + setgameendtime( 0 ); + return; + } + + if ( !isdefined( level.starttime ) ) + return; + + if ( maps\mp\_utility::gettimepassedpercentage() > level.timepercentagecutoff ) + setnojipscore( 1 ); + + var_1 = gettimeremaining(); + + if ( maps\mp\_utility::gethalftime() && game["status"] != "halftime" ) + setgameendtime( gettime() + int( var_1 ) - int( maps\mp\_utility::gettimelimit() * 60 * 1000 * 0.5 ) ); + else + setgameendtime( gettime() + int( var_1 ) ); + + if ( var_1 > 0 ) + { + if ( maps\mp\_utility::gethalftime() && checkhalftime( var_0 ) ) + [[ level.onhalftime ]]( "time_limit_reached" ); + + return; + } + + [[ level.ontimelimit ]](); +} + +checkhalftimescore() +{ + if ( !level.halftimeonscorelimit ) + return 0; + + if ( !level.teambased ) + return 0; + + if ( game["status"] != "normal" ) + return 0; + + var_0 = maps\mp\_utility::getwatcheddvar( "scorelimit" ); + + if ( var_0 ) + { + if ( game["teamScores"]["allies"] >= var_0 || game["teamScores"]["axis"] >= var_0 ) + return 0; + + var_1 = int( var_0 / 2 + 0.5 ); + + if ( game["teamScores"]["allies"] >= var_1 || game["teamScores"]["axis"] >= var_1 ) + { + game["roundMillisecondsAlreadyPassed"] = maps\mp\_utility::gettimepassed(); + game["round_time_to_beat"] = maps\mp\_utility::getminutespassed(); + return 1; + } + } + + return 0; +} + +checkhalftime( var_0 ) +{ + if ( !level.teambased ) + return 0; + + if ( game["status"] != "normal" ) + return 0; + + if ( maps\mp\_utility::gettimelimit() ) + { + var_1 = maps\mp\_utility::gettimelimit() * 60 * 1000 * 0.5; + + if ( maps\mp\_utility::gettimepassed() >= var_1 && var_0 < var_1 && var_0 > 0 ) + { + game["roundMillisecondsAlreadyPassed"] = maps\mp\_utility::gettimepassed(); + return 1; + } + } + + return 0; +} + +gettimeremaining() +{ + var_0 = maps\mp\_utility::gettimepassed(); + var_1 = maps\mp\_utility::gettimelimit() * 60 * 1000; + + if ( maps\mp\_utility::gethalftime() && game["status"] == "halftime" && isdefined( level.firsthalftimepassed ) ) + { + var_2 = var_1 * 0.5; + + if ( level.firsthalftimepassed < var_2 ) + { + if ( level.halftimeonscorelimit ) + var_0 = var_1 - level.firsthalftimepassed + var_0 - level.firsthalftimepassed; + else + var_0 += var_2 - level.firsthalftimepassed; + } + } + + return var_1 - var_0; +} + +checkteamscorelimitsoon( var_0 ) +{ + if ( maps\mp\_utility::getwatcheddvar( "scorelimit" ) <= 0 || maps\mp\_utility::isobjectivebased() ) + return; + + if ( isdefined( level.scorelimitoverride ) && level.scorelimitoverride ) + return; + + if ( level.gametype == "conf" ) + return; + + if ( !level.teambased ) + return; + + if ( maps\mp\_utility::gettimepassed() < 60000 ) + return; + + var_1 = estimatedtimetillscorelimit( var_0 ); + + if ( var_1 < 2 ) + level notify( "match_ending_soon", "score" ); +} + +checkplayerscorelimitsoon() +{ + if ( maps\mp\_utility::getwatcheddvar( "scorelimit" ) <= 0 || maps\mp\_utility::isobjectivebased() ) + return; + + if ( level.teambased ) + return; + + if ( maps\mp\_utility::gettimepassed() < 60000 ) + return; + + var_0 = estimatedtimetillscorelimit(); + + if ( var_0 < 2 ) + level notify( "match_ending_soon", "score" ); +} + +checkscorelimit() +{ + if ( maps\mp\_utility::isobjectivebased() ) + return 0; + + if ( isdefined( level.scorelimitoverride ) && level.scorelimitoverride ) + return 0; + + if ( game["state"] != "playing" ) + return 0; + + if ( maps\mp\_utility::getwatcheddvar( "scorelimit" ) <= 0 ) + return 0; + + if ( maps\mp\_utility::gethalftime() && checkhalftimescore() ) + return [[ level.onhalftime ]]( "score_limit_reached" ); + else if ( level.multiteambased ) + { + var_0 = 0; + + for ( var_1 = 0; var_1 < level.teamnamelist.size; var_1++ ) + { + if ( game["teamScores"][level.teamnamelist[var_1]] >= maps\mp\_utility::getwatcheddvar( "scorelimit" ) ) + var_0 = 1; + } + + if ( !var_0 ) + return 0; + } + else if ( level.teambased ) + { + if ( game["teamScores"]["allies"] < maps\mp\_utility::getwatcheddvar( "scorelimit" ) && game["teamScores"]["axis"] < maps\mp\_utility::getwatcheddvar( "scorelimit" ) ) + return 0; + } + else + { + if ( !isplayer( self ) ) + return 0; + + if ( self.score < maps\mp\_utility::getwatcheddvar( "scorelimit" ) ) + return 0; + } + + return onscorelimit(); +} + +updategametypedvars() +{ + level endon( "game_ended" ); + + while ( game["state"] == "playing" ) + { + if ( isdefined( level.starttime ) ) + { + if ( gettimeremaining() < 3000 ) + { + wait 0.1; + continue; + } + } + + wait 1; + } +} + +matchstarttimerwaitforplayers() +{ + setomnvar( "ui_match_countdown_title", 6 ); + setomnvar( "ui_match_countdown_toggle", 0 ); + + if ( level.currentgen ) + setomnvar( "ui_cg_world_blur", 1 ); + + waitforplayers( level.prematchperiod ); + + if ( level.prematchperiodend > 0 && !isdefined( level.hostmigrationtimer ) ) + matchstarttimer( level.prematchperiodend ); +} + +matchstarttimer_internal( var_0 ) +{ + waittillframeend; + level endon( "match_start_timer_beginning" ); + setomnvar( "ui_match_countdown_title", 1 ); + setomnvar( "ui_match_countdown_toggle", 1 ); + + while ( var_0 > 0 && !level.gameended ) + { + setomnvar( "ui_match_countdown", var_0 ); + var_0--; + + if ( level.currentgen ) + setomnvar( "ui_cg_world_blur", 1 ); + + wait 1; + } + + if ( level.currentgen ) + setomnvar( "ui_cg_world_blur_fade_out", 1 ); + + if ( level.xpscale > 1 && !( isdefined( level.ishorde ) && level.ishorde ) && !maps\mp\_utility::privatematch() && !maps\mp\_utility::practiceroundgame() && !( isdefined( level.iszombiegame ) && level.iszombiegame ) ) + { + foreach ( var_2 in level.players ) + var_2 thread maps\mp\gametypes\_hud_message::splashnotify( "double_xp" ); + } + + setomnvar( "ui_match_countdown_toggle", 0 ); + setomnvar( "ui_match_countdown", 0 ); + setomnvar( "ui_match_countdown_title", 2 ); + level endon( "match_forfeit_timer_beginning" ); + wait 1.5; + setomnvar( "ui_match_countdown_title", 0 ); +} + +matchstarttimer( var_0 ) +{ + self notify( "matchStartTimer" ); + self endon( "matchStartTimer" ); + level notify( "match_start_timer_beginning" ); + var_1 = int( var_0 ); + + if ( var_1 >= 2 ) + { + matchstarttimer_internal( var_1 ); + visionsetnaked( "", 3.0 ); + } + else + { + if ( level.currentgen ) + setomnvar( "ui_cg_world_blur_fade_out", 1 ); + + if ( level.xpscale > 1 && !( isdefined( level.ishorde ) && level.ishorde ) && !maps\mp\_utility::privatematch() && !maps\mp\_utility::practiceroundgame() && !( isdefined( level.iszombiegame ) && level.iszombiegame ) ) + { + foreach ( var_3 in level.players ) + var_3 thread maps\mp\gametypes\_hud_message::splashnotify( "double_xp" ); + } + + visionsetnaked( "", 1.0 ); + } +} + +matchstarttimerskip() +{ + visionsetnaked( "", 0 ); +} + +onroundswitch() +{ + if ( !isdefined( game["switchedsides"] ) ) + game["switchedsides"] = 0; + + if ( game["roundsWon"]["allies"] == maps\mp\_utility::getwatcheddvar( "winlimit" ) - 1 && game["roundsWon"]["axis"] == maps\mp\_utility::getwatcheddvar( "winlimit" ) - 1 ) + { + var_0 = getbetterteam(); + + if ( var_0 != game["defenders"] ) + game["switchedsides"] = !game["switchedsides"]; + + level.halftimetype = "overtime"; + game["dynamicEvent_Overtime"] = 1; + } + else + { + level.halftimetype = "halftime"; + game["switchedsides"] = !game["switchedsides"]; + } +} + +checkroundswitch() +{ + if ( !level.teambased ) + return 0; + + if ( !isdefined( level.roundswitch ) || !level.roundswitch ) + return 0; + + if ( game["roundsPlayed"] % level.roundswitch == 0 ) + { + onroundswitch(); + return 1; + } + + return 0; +} + +timeuntilroundend() +{ + if ( level.gameended ) + { + var_0 = ( gettime() - level.gameendtime ) / 1000; + var_1 = level.postroundtime - var_0; + + if ( var_1 < 0 ) + return 0; + + return var_1; + } + + if ( maps\mp\_utility::gettimelimit() <= 0 ) + return undefined; + + if ( !isdefined( level.starttime ) ) + return undefined; + + var_2 = maps\mp\_utility::gettimelimit(); + var_0 = ( gettime() - level.starttime ) / 1000; + var_1 = maps\mp\_utility::gettimelimit() * 60 - var_0; + + if ( isdefined( level.timepaused ) ) + var_1 += level.timepaused; + + return var_1 + level.postroundtime; +} + +freegameplayhudelems() +{ + if ( isdefined( self.perkicon ) ) + { + if ( isdefined( self.perkicon[0] ) ) + { + self.perkicon[0] maps\mp\gametypes\_hud_util::destroyelem(); + self.perkname[0] maps\mp\gametypes\_hud_util::destroyelem(); + } + + if ( isdefined( self.perkicon[1] ) ) + { + self.perkicon[1] maps\mp\gametypes\_hud_util::destroyelem(); + self.perkname[1] maps\mp\gametypes\_hud_util::destroyelem(); + } + + if ( isdefined( self.perkicon[2] ) ) + { + self.perkicon[2] maps\mp\gametypes\_hud_util::destroyelem(); + self.perkname[2] maps\mp\gametypes\_hud_util::destroyelem(); + } + } + + self notify( "perks_hidden" ); + self.lowermessage maps\mp\gametypes\_hud_util::destroyelem(); + self.lowertimer maps\mp\gametypes\_hud_util::destroyelem(); + + if ( isdefined( self.proxbar ) ) + self.proxbar maps\mp\gametypes\_hud_util::destroyelem(); + + if ( isdefined( self.proxbartext ) ) + self.proxbartext maps\mp\gametypes\_hud_util::destroyelem(); +} + +gethostplayer() +{ + var_0 = getentarray( "player", "classname" ); + + for ( var_1 = 0; var_1 < var_0.size; var_1++ ) + { + if ( var_0[var_1] ishost() ) + return var_0[var_1]; + } +} + +hostidledout() +{ + var_0 = gethostplayer(); + + if ( isdefined( var_0 ) && !var_0.hasspawned && !isdefined( var_0.selectedclass ) ) + return 1; + + return 0; +} + +roundendwait( var_0, var_1 ) +{ + foreach ( var_3 in level.players ) + var_3 maps\mp\gametypes\_damage::streamfinalkillcam(); + + var_5 = 0; + + while ( !var_5 ) + { + var_6 = level.players; + var_5 = 1; + + foreach ( var_3 in var_6 ) + { + if ( !isdefined( var_3.doingsplash ) ) + continue; + + if ( !var_3 maps\mp\gametypes\_hud_message::isdoingsplash() ) + continue; + + var_5 = 0; + } + + wait 0.5; + } + + if ( !var_1 ) + { + wait(var_0); + var_6 = level.players; + + foreach ( var_3 in var_6 ) + var_3 setclientomnvar( "ui_round_end", 0 ); + + level notify( "round_end_finished" ); + return; + } + + wait(var_0 / 2); + level notify( "give_match_bonus" ); + wait(var_0 / 2); + var_5 = 0; + + while ( !var_5 ) + { + var_6 = level.players; + var_5 = 1; + + foreach ( var_3 in var_6 ) + { + if ( !isdefined( var_3.doingsplash ) ) + continue; + + if ( !var_3 maps\mp\gametypes\_hud_message::isdoingsplash() ) + continue; + + var_5 = 0; + } + + wait 0.5; + } + + var_6 = level.players; + + foreach ( var_3 in var_6 ) + var_3 setclientomnvar( "ui_round_end", 0 ); + + level notify( "round_end_finished" ); +} + +roundenddof( var_0 ) +{ + self setdepthoffield( 0, 128, 512, 4000, 6, 1.8 ); +} + +callback_startgametype() +{ + maps\mp\_load::main(); + maps\mp\_utility::levelflaginit( "round_over", 0 ); + maps\mp\_utility::levelflaginit( "game_over", 0 ); + maps\mp\_utility::levelflaginit( "block_notifies", 0 ); + level.prematchperiod = 0; + level.prematchperiodend = 0; + level.postgamenotifies = 0; + level.intermission = 0; + setdvar( "bg_compassShowEnemies", getdvar( "scr_game_forceuav" ) ); + + if ( !isdefined( game["gamestarted"] ) ) + { + game["clientid"] = 0; + var_0 = getmapcustom( "allieschar" ); + + if ( !isdefined( var_0 ) || var_0 == "" ) + { + if ( !isdefined( game["allies"] ) ) + var_0 = "sentinel"; + else + var_0 = game["allies"]; + } + + var_1 = getmapcustom( "axischar" ); + + if ( !isdefined( var_1 ) || var_1 == "" ) + { + if ( !isdefined( game["axis"] ) ) + var_1 = "atlas"; + else + var_1 = game["axis"]; + } + + if ( level.multiteambased ) + { + var_2 = getmapcustom( "allieschar" ); + + if ( !isdefined( var_2 ) || var_2 == "" ) + var_2 = "delta_multicam"; + + for ( var_3 = 0; var_3 < level.teamnamelist.size; var_3++ ) + game[level.teamnamelist[var_3]] = var_2; + } + + game["allies"] = var_0; + game["axis"] = var_1; + + if ( !isdefined( game["attackers"] ) || !isdefined( game["defenders"] ) ) + thread common_scripts\utility::error( "No attackers or defenders team defined in level .gsc." ); + + if ( !isdefined( game["attackers"] ) ) + game["attackers"] = "allies"; + + if ( !isdefined( game["defenders"] ) ) + game["defenders"] = "axis"; + + if ( !isdefined( game["state"] ) ) + game["state"] = "playing"; + + if ( level.teambased ) + { + game["strings"]["waiting_for_teams"] = &"MP_WAITING_FOR_TEAMS"; + game["strings"]["opponent_forfeiting_in"] = &"MP_OPPONENT_FORFEITING_IN"; + } + else + { + game["strings"]["waiting_for_teams"] = &"MP_WAITING_FOR_MORE_PLAYERS"; + game["strings"]["opponent_forfeiting_in"] = &"MP_OPPONENT_FORFEITING_IN"; + } + + game["strings"]["press_to_spawn"] = &"PLATFORM_PRESS_TO_SPAWN"; + game["strings"]["match_starting_in"] = &"MP_MATCH_STARTING_IN"; + game["strings"]["match_resuming_in"] = &"MP_MATCH_RESUMING_IN"; + game["strings"]["waiting_for_players"] = &"MP_WAITING_FOR_PLAYERS"; + game["strings"]["spawn_tag_wait"] = &"MP_SPAWN_TAG_WAIT"; + game["strings"]["spawn_next_round"] = &"MP_SPAWN_NEXT_ROUND"; + game["strings"]["waiting_to_spawn"] = &"MP_WAITING_TO_SPAWN"; + game["strings"]["match_starting"] = &"MP_MATCH_STARTING"; + game["strings"]["change_class"] = &"MP_CHANGE_CLASS_NEXT_SPAWN"; + game["strings"]["change_class_cancel"] = &"MP_CHANGE_CLASS_CANCEL"; + game["strings"]["change_class_wait"] = &"MP_CHANGE_CLASS_WAIT"; + game["strings"]["last_stand"] = &"MPUI_LAST_STAND"; + game["strings"]["final_stand"] = &"MPUI_FINAL_STAND"; + game["strings"]["cowards_way"] = &"PLATFORM_COWARDS_WAY_OUT"; + game["colors"]["blue"] = ( 0.25, 0.25, 0.75 ); + game["colors"]["red"] = ( 0.75, 0.25, 0.25 ); + game["colors"]["white"] = ( 1.0, 1.0, 1.0 ); + game["colors"]["black"] = ( 0.0, 0.0, 0.0 ); + game["colors"]["grey"] = ( 0.5, 0.5, 0.5 ); + game["colors"]["green"] = ( 0.25, 0.75, 0.25 ); + game["colors"]["yellow"] = ( 0.65, 0.65, 0.0 ); + game["colors"]["orange"] = ( 1.0, 0.45, 0.0 ); + game["colors"]["cyan"] = ( 0.35, 0.7, 0.9 ); + game["strings"]["allies_name"] = maps\mp\gametypes\_teams::getteamname( "allies" ); + game["icons"]["allies"] = maps\mp\gametypes\_teams::getteamicon( "allies" ); + game["colors"]["allies"] = maps\mp\gametypes\_teams::getteamcolor( "allies" ); + game["strings"]["axis_name"] = maps\mp\gametypes\_teams::getteamname( "axis" ); + game["icons"]["axis"] = maps\mp\gametypes\_teams::getteamicon( "axis" ); + game["colors"]["axis"] = maps\mp\gametypes\_teams::getteamcolor( "axis" ); + + if ( game["colors"]["allies"] == ( 0.0, 0.0, 0.0 ) ) + game["colors"]["allies"] = ( 0.5, 0.5, 0.5 ); + + if ( game["colors"]["axis"] == ( 0.0, 0.0, 0.0 ) ) + game["colors"]["axis"] = ( 0.5, 0.5, 0.5 ); + + [[ level.onprecachegametype ]](); + setdvarifuninitialized( "min_wait_for_players", 5 ); + + if ( level.console ) + { + if ( !level.splitscreen ) + { + if ( _func_27A() ) + level.prematchperiod = maps\mp\gametypes\_tweakables::gettweakablevalue( "game", "graceperiod_ds" ); + else + level.prematchperiod = maps\mp\gametypes\_tweakables::gettweakablevalue( "game", "graceperiod" ); + + level.prematchperiodend = maps\mp\gametypes\_tweakables::gettweakablevalue( "game", "matchstarttime" ); + } + } + else + { + if ( _func_27A() ) + level.prematchperiod = maps\mp\gametypes\_tweakables::gettweakablevalue( "game", "playerwaittime_ds" ); + else + level.prematchperiod = maps\mp\gametypes\_tweakables::gettweakablevalue( "game", "playerwaittime" ); + + level.prematchperiodend = maps\mp\gametypes\_tweakables::gettweakablevalue( "game", "matchstarttime" ); + } + } + else + { + setdvarifuninitialized( "min_wait_for_players", 5 ); + + if ( level.console ) + { + if ( !level.splitscreen ) + { + level.prematchperiod = 5; + level.prematchperiodend = maps\mp\gametypes\_tweakables::gettweakablevalue( "game", "roundstarttime" ); + } + } + else + { + level.prematchperiod = 5; + level.prematchperiodend = maps\mp\gametypes\_tweakables::gettweakablevalue( "game", "roundstarttime" ); + } + } + + if ( !isdefined( game["status"] ) ) + game["status"] = "normal"; + + if ( game["status"] != "overtime" && game["status"] != "halftime" && game["status"] != "overtime_halftime" ) + { + game["teamScores"]["allies"] = 0; + game["teamScores"]["axis"] = 0; + + if ( level.multiteambased ) + { + for ( var_3 = 0; var_3 < level.teamnamelist.size; var_3++ ) + game["teamScores"][level.teamnamelist[var_3]] = 0; + } + } + + if ( !isdefined( game["timePassed"] ) ) + game["timePassed"] = 0; + + if ( !isdefined( game["roundsPlayed"] ) ) + game["roundsPlayed"] = 0; + + setomnvar( "ui_current_round", game["roundsPlayed"] + 1 ); + + if ( !isdefined( game["roundsWon"] ) ) + game["roundsWon"] = []; + + if ( level.teambased ) + { + if ( !isdefined( game["roundsWon"]["axis"] ) ) + game["roundsWon"]["axis"] = 0; + + if ( !isdefined( game["roundsWon"]["allies"] ) ) + game["roundsWon"]["allies"] = 0; + + if ( level.multiteambased ) + { + for ( var_3 = 0; var_3 < level.teamnamelist.size; var_3++ ) + { + if ( !isdefined( game["roundsWon"][level.teamnamelist[var_3]] ) ) + game["roundsWon"][level.teamnamelist[var_3]] = 0; + } + } + } + + level.gameended = 0; + level.forcedend = 0; + level.hostforcedend = 0; + level.hardcoremode = getdvarint( "g_hardcore" ); + + if ( level.hardcoremode ) + logstring( "game mode: hardcore" ); + + level.diehardmode = getdvarint( "scr_diehard" ); + + if ( !level.teambased ) + level.diehardmode = 0; + + if ( level.diehardmode ) + logstring( "game mode: diehard" ); + + level.killstreakrewards = getdvarint( "scr_game_hardpoints" ); + + if ( !isdefined( level.iszombiegame ) ) + level.iszombiegame = 0; + + level.usestartspawns = 1; + level.objectivepointsmod = 1; + level.baseplayermovescale = 1; + level.maxallowedteamkills = 2; + thread maps\mp\_teleport::main(); + thread maps\mp\gametypes\_persistence::init(); + thread maps\mp\gametypes\_menus::init(); + thread maps\mp\gametypes\_hud::init(); + thread maps\mp\gametypes\_serversettings::init(); + thread maps\mp\gametypes\_teams::init(); + thread maps\mp\gametypes\_weapons::init(); + thread maps\mp\gametypes\_killcam::init(); + thread maps\mp\gametypes\_shellshock::init(); + thread maps\mp\gametypes\_deathicons::init(); + thread maps\mp\gametypes\_damagefeedback::init(); + thread maps\mp\gametypes\_healthoverlay::init(); + thread maps\mp\gametypes\_spectating::init(); + thread maps\mp\gametypes\_objpoints::init(); + thread maps\mp\gametypes\_gameobjects::init(); + thread maps\mp\gametypes\_spawnlogic::init(); + thread maps\mp\gametypes\_battlechatter_mp::init(); + thread maps\mp\gametypes\_music_and_dialog::init(); + thread maps\mp\gametypes\_high_jump_mp::init(); + thread maps\mp\_grappling_hook::init(); + thread maps\mp\_matchdata::init(); + thread maps\mp\_awards::init(); + thread maps\mp\_areas::init(); + + if ( !maps\mp\_utility::invirtuallobby() ) + thread maps\mp\killstreaks\_killstreaks_init::init(); + + thread maps\mp\perks\_perks::init(); + thread maps\mp\_events::init(); + thread maps\mp\gametypes\_damage::initfinalkillcam(); + thread maps\mp\_threatdetection::init(); + thread maps\mp\_exo_suit::init(); + thread maps\mp\_reinforcements::init(); + thread maps\mp\_snd_common_mp::init(); + thread maps\mp\_utility::buildattachmentmaps(); + + if ( level.teambased ) + thread maps\mp\gametypes\_friendicons::init(); + + thread maps\mp\gametypes\_hud_message::init(); + thread maps\mp\gametypes\_divisions::init(); + + foreach ( var_5 in game["strings"] ) + precachestring( var_5 ); + + foreach ( var_8 in game["icons"] ) + precacheshader( var_8 ); + + game["gamestarted"] = 1; + level.maxplayercount = 0; + level.wavedelay["allies"] = 0; + level.wavedelay["axis"] = 0; + level.lastwave["allies"] = 0; + level.lastwave["axis"] = 0; + level.waveplayerspawnindex["allies"] = 0; + level.waveplayerspawnindex["axis"] = 0; + level.aliveplayers["allies"] = []; + level.aliveplayers["axis"] = []; + level.activeplayers = []; + + if ( level.multiteambased ) + { + for ( var_3 = 0; var_3 < level.teamnamelist.size; var_3++ ) + { + level._wavedelay[level.teamnamelist[var_3]] = 0; + level._lastwave[level.teamnamelist[var_3]] = 0; + level._waveplayerspawnindex[level.teamnamelist[var_3]] = 0; + level._aliveplayers[level.teamnamelist[var_3]] = []; + } + } + + setdvar( "ui_scorelimit", 0 ); + setdvar( "ui_allow_teamchange", 1 ); + + if ( maps\mp\_utility::getgametypenumlives() ) + setdvar( "g_deadChat", 0 ); + else + setdvar( "g_deadChat", 1 ); + + var_10 = getdvarfloat( "scr_" + level.gametype + "_waverespawndelay" ); + + if ( var_10 > 0 ) + { + level.wavedelay["allies"] = var_10; + level.wavedelay["axis"] = var_10; + level.lastwave["allies"] = 0; + level.lastwave["axis"] = 0; + + if ( level.multiteambased ) + { + for ( var_3 = 0; var_3 < level.teamnamelist.size; var_3++ ) + { + level._wavedelay[level.teamnamelist[var_3]] = var_10; + level._lastwave[level.teamnamelist[var_3]] = 0; + } + } + + level thread wavespawntimer(); + } + + maps\mp\_utility::gameflaginit( "prematch_done", 0 ); + level.graceperiod = 15; + level.ingraceperiod = level.graceperiod; + maps\mp\_utility::gameflaginit( "graceperiod_done", 0 ); + level.roundenddelay = 4; + level.halftimeroundenddelay = 4; + level.noragdollents = getentarray( "noragdoll", "targetname" ); + + if ( level.teambased ) + { + maps\mp\gametypes\_gamescore::updateteamscore( "axis" ); + maps\mp\gametypes\_gamescore::updateteamscore( "allies" ); + + if ( level.multiteambased ) + { + for ( var_3 = 0; var_3 < level.teamnamelist.size; var_3++ ) + maps\mp\gametypes\_gamescore::updateteamscore( level.teamnamelist[var_3] ); + } + } + else + thread maps\mp\gametypes\_gamescore::initialdmscoreupdate(); + + thread updateuiscorelimit(); + level notify( "update_scorelimit" ); + [[ level.onstartgametype ]](); + level.scorepercentagecutoff = getdvarint( "scr_" + level.gametype + "_score_percentage_cut_off", 80 ); + level.timepercentagecutoff = getdvarint( "scr_" + level.gametype + "_time_percentage_cut_off", 80 ); + + if ( !level.console && ( getdvar( "dedicated" ) == "dedicated LAN server" || getdvar( "dedicated" ) == "dedicated internet server" ) ) + thread verifydedicatedconfiguration(); + + setattackingteam(); + thread startgame(); + level thread maps\mp\_utility::updatewatcheddvars(); + level thread timelimitthread(); + level thread maps\mp\gametypes\_damage::dofinalkillcam(); +} + +setattackingteam() +{ + if ( game["attackers"] == "axis" ) + var_0 = 1; + else if ( game["attackers"] == "allies" ) + var_0 = 2; + else + var_0 = 0; + + setomnvar( "ui_attacking_team", var_0 ); +} + +callback_codeendgame() +{ + setplayerteamrank(); + + if ( !level.gameended ) + level thread forceend(); +} + +verifydedicatedconfiguration() +{ + for (;;) + { + if ( level.rankedmatch ) + exitlevel( 0 ); + + if ( !getdvarint( "xblive_privatematch" ) ) + exitlevel( 0 ); + + if ( getdvar( "dedicated" ) != "dedicated LAN server" && getdvar( "dedicated" ) != "dedicated internet server" ) + exitlevel( 0 ); + + wait 5; + } +} + +timelimitthread() +{ + level endon( "game_ended" ); + var_0 = maps\mp\_utility::gettimepassed(); + + while ( game["state"] == "playing" ) + { + thread checktimelimit( var_0 ); + var_0 = maps\mp\_utility::gettimepassed(); + + if ( isdefined( level.starttime ) ) + { + if ( gettimeremaining() < 3000 ) + { + wait 0.1; + continue; + } + } + + wait 1; + } +} + +updateuiscorelimit() +{ + for (;;) + { + level common_scripts\utility::waittill_either( "update_scorelimit", "update_winlimit" ); + + if ( !maps\mp\_utility::isroundbased() || !maps\mp\_utility::isobjectivebased() ) + { + setdvar( "ui_scorelimit", maps\mp\_utility::getwatcheddvar( "scorelimit" ) ); + thread checkscorelimit(); + continue; + } + + setdvar( "ui_scorelimit", maps\mp\_utility::getwatcheddvar( "winlimit" ) ); + } +} + +playtickingsound() +{ + self endon( "death" ); + self endon( "stop_ticking" ); + level endon( "game_ended" ); + var_0 = level.bombtimer; + + for (;;) + { + self playsound( "ui_mp_suitcasebomb_timer" ); + + if ( var_0 > 10 ) + { + var_0 -= 1; + wait 1; + } + else if ( var_0 > 4 ) + { + var_0 -= 0.5; + wait 0.5; + } + else if ( var_0 > 1 ) + { + var_0 -= 0.4; + wait 0.4; + } + else + { + var_0 -= 0.3; + wait 0.3; + } + + maps\mp\gametypes\_hostmigration::waittillhostmigrationdone(); + } +} + +stoptickingsound() +{ + self notify( "stop_ticking" ); +} + +timelimitclock() +{ + level endon( "game_ended" ); + wait 0.05; + var_0 = spawn( "script_origin", ( 0.0, 0.0, 0.0 ) ); + var_0 hide(); + + while ( game["state"] == "playing" ) + { + if ( !level.timerstopped && maps\mp\_utility::gettimelimit() ) + { + var_1 = gettimeremaining() / 1000; + var_2 = int( var_1 + 0.5 ); + var_3 = int( maps\mp\_utility::gettimelimit() * 60 * 0.5 ); + + if ( maps\mp\_utility::gethalftime() && var_2 > var_3 ) + var_2 -= var_3; + + if ( var_2 >= 30 && var_2 <= 60 ) + level notify( "match_ending_soon", "time" ); + + if ( var_2 <= 10 || var_2 <= 30 && var_2 % 2 == 0 ) + { + level notify( "match_ending_very_soon" ); + + if ( var_2 == 0 ) + break; + + var_0 playsound( "ui_mp_timer_countdown" ); + } + + if ( var_1 - floor( var_1 ) >= 0.05 ) + wait(var_1 - floor( var_1 )); + } + + wait 1.0; + } +} + +gametimer() +{ + level endon( "game_ended" ); + level waittill( "prematch_over" ); + level.starttime = gettime(); + level.discardtime = 0; + level.matchdurationstarttime = gettime(); + + if ( isdefined( game["roundMillisecondsAlreadyPassed"] ) ) + { + level.starttime -= game["roundMillisecondsAlreadyPassed"]; + level.firsthalftimepassed = game["roundMillisecondsAlreadyPassed"]; + game["roundMillisecondsAlreadyPassed"] = undefined; + } + + var_0 = gettime(); + + while ( game["state"] == "playing" ) + { + if ( !level.timerstopped ) + game["timePassed"] += gettime() - var_0; + + var_0 = gettime(); + wait 1.0; + } +} + +updatetimerpausedness() +{ + var_0 = level.timerstoppedforgamemode || isdefined( level.hostmigrationtimer ); + + if ( !maps\mp\_utility::gameflag( "prematch_done" ) ) + var_0 = 0; + + if ( !level.timerstopped && var_0 ) + { + level.timerstopped = 1; + level.timerpausetime = gettime(); + } + else if ( level.timerstopped && !var_0 ) + { + level.timerstopped = 0; + level.discardtime += gettime() - level.timerpausetime; + } +} + +pausetimer() +{ + level.timerstoppedforgamemode = 1; + updatetimerpausedness(); +} + +resumetimer() +{ + level.timerstoppedforgamemode = 0; + updatetimerpausedness(); +} + +startgame() +{ + thread gametimer(); + level.timerstopped = 0; + level.timerstoppedforgamemode = 0; + setdvar( "ui_inprematch", 1 ); + prematchperiod(); + maps\mp\_utility::gameflagset( "prematch_done" ); + level notify( "prematch_over" ); + setdvar( "ui_inprematch", 0 ); + level.prematch_done_time = gettime(); + updatetimerpausedness(); + thread timelimitclock(); + thread graceperiod(); + thread maps\mp\gametypes\_missions::roundbegin(); + thread maps\mp\_matchdata::matchstarted(); + var_0 = isdefined( level.ishorde ) && level.ishorde; + var_1 = isdefined( level.iszombiegame ) && level.iszombiegame; + + if ( var_0 || var_1 ) + thread updategameduration(); + + _func_240(); +} + +wavespawntimer() +{ + level endon( "game_ended" ); + + while ( game["state"] == "playing" ) + { + var_0 = gettime(); + + if ( var_0 - level.lastwave["allies"] > level.wavedelay["allies"] * 1000 ) + { + level notify( "wave_respawn_allies" ); + level.lastwave["allies"] = var_0; + level.waveplayerspawnindex["allies"] = 0; + } + + if ( var_0 - level.lastwave["axis"] > level.wavedelay["axis"] * 1000 ) + { + level notify( "wave_respawn_axis" ); + level.lastwave["axis"] = var_0; + level.waveplayerspawnindex["axis"] = 0; + } + + if ( level.multiteambased ) + { + for ( var_1 = 0; var_1 < level.teamnamelist.size; var_1++ ) + { + if ( var_0 - level.lastwave[level.teamnamelist[var_1]] > level._wavedelay[level.teamnamelist[var_1]] * 1000 ) + { + var_2 = "wave_rewpawn_" + level.teamnamelist[var_1]; + level notify( var_2 ); + level.lastwave[level.teamnamelist[var_1]] = var_0; + level.waveplayerspawnindex[level.teamnamelist[var_1]] = 0; + } + } + } + + wait 0.05; + } +} + +getbetterteam() +{ + var_0["allies"] = 0; + var_0["axis"] = 0; + var_1["allies"] = 0; + var_1["axis"] = 0; + var_2["allies"] = 0; + var_2["axis"] = 0; + + foreach ( var_4 in level.players ) + { + var_5 = var_4.pers["team"]; + + if ( isdefined( var_5 ) && ( var_5 == "allies" || var_5 == "axis" ) ) + { + var_0[var_5] += var_4.score; + var_1[var_5] += var_4.kills; + var_2[var_5] += var_4.deaths; + } + } + + if ( var_0["allies"] > var_0["axis"] ) + return "allies"; + else if ( var_0["axis"] > var_0["allies"] ) + return "axis"; + + if ( var_1["allies"] > var_1["axis"] ) + return "allies"; + else if ( var_1["axis"] > var_1["allies"] ) + return "axis"; + + if ( var_2["allies"] < var_2["axis"] ) + return "allies"; + else if ( var_2["axis"] < var_2["allies"] ) + return "axis"; + + if ( randomint( 2 ) == 0 ) + return "allies"; + + return "axis"; +} + +rankedmatchupdates( var_0 ) +{ + if ( !maps\mp\_utility::waslastround() ) + return; + + var_0 = getgamewinner( var_0, 0 ); + + if ( maps\mp\_utility::matchmakinggame() ) + { + setxenonranks(); + + if ( hostidledout() ) + { + level.hostforcedend = 1; + logstring( "host idled out" ); + endlobby(); + } + + updatematchbonusscores( var_0 ); + } + + updatewinlossstats( var_0 ); +} + +displayroundend( var_0, var_1 ) +{ + if ( !maps\mp\_utility::practiceroundgame() ) + { + foreach ( var_3 in level.players ) + { + if ( isdefined( var_3.connectedpostgame ) || var_3.pers["team"] == "spectator" && !var_3 ismlgspectator() ) + continue; + + if ( level.teambased ) + { + var_3 thread maps\mp\gametypes\_hud_message::teamoutcomenotify( var_0, 1, var_1 ); + continue; + } + + var_3 thread maps\mp\gametypes\_hud_message::outcomenotify( var_0, var_1 ); + } + } + + if ( !maps\mp\_utility::waslastround() ) + level notify( "round_win", var_0 ); + + if ( maps\mp\_utility::waslastround() ) + roundendwait( level.roundenddelay, 0 ); + else + roundendwait( level.roundenddelay, 1 ); +} + +displaygameend( var_0, var_1 ) +{ + if ( !maps\mp\_utility::practiceroundgame() ) + { + foreach ( var_3 in level.players ) + { + if ( isdefined( var_3.connectedpostgame ) || var_3.pers["team"] == "spectator" && !var_3 ismlgspectator() ) + continue; + + if ( level.teambased ) + { + var_3 thread maps\mp\gametypes\_hud_message::teamoutcomenotify( var_0, 0, var_1, 1 ); + continue; + } + + var_3 thread maps\mp\gametypes\_hud_message::outcomenotify( var_0, var_1 ); + } + } + + level notify( "game_win", var_0 ); + roundendwait( level.postroundtime, 1 ); +} + +displayroundswitch() +{ + var_0 = level.halftimetype; + + if ( var_0 == "halftime" ) + { + if ( maps\mp\_utility::getwatcheddvar( "roundlimit" ) ) + { + if ( game["roundsPlayed"] * 2 == maps\mp\_utility::getwatcheddvar( "roundlimit" ) ) + var_0 = "halftime"; + else + var_0 = "intermission"; + } + else if ( maps\mp\_utility::getwatcheddvar( "winlimit" ) ) + { + if ( game["roundsPlayed"] == maps\mp\_utility::getwatcheddvar( "winlimit" ) - 1 ) + var_0 = "halftime"; + else + var_0 = "intermission"; + } + else + var_0 = "intermission"; + } + + level notify( "round_switch", var_0 ); + + foreach ( var_2 in level.players ) + { + if ( isdefined( var_2.connectedpostgame ) || var_2.pers["team"] == "spectator" && !var_2 ismlgspectator() ) + continue; + + var_2 thread maps\mp\gametypes\_hud_message::teamoutcomenotify( var_0, 1, game["end_reason"]["switching_sides"] ); + } + + roundendwait( level.halftimeroundenddelay, 0 ); +} + +freezeallplayers( var_0, var_1 ) +{ + if ( !isdefined( var_0 ) ) + var_0 = 0; + + foreach ( var_3 in level.players ) + { + var_3 disableammogeneration(); + var_3 thread freezeplayerforroundend( var_0 ); + var_3 thread roundenddof( 4.0 ); + var_3 freegameplayhudelems(); + + if ( isdefined( var_1 ) && var_1 ) + { + continue; + } + } + + if ( isdefined( level.agentarray ) ) + { + foreach ( var_6 in level.agentarray ) + var_6 maps\mp\_utility::freezecontrolswrapper( 1 ); + } +} + +endgameovertime( var_0, var_1 ) +{ + setdvar( "bg_compassShowEnemies", 0 ); + freezeallplayers( 1.0, 1 ); + + foreach ( var_3 in level.players ) + { + var_3.pers["stats"] = var_3.stats; + var_3.pers["segments"] = var_3.segments; + } + + level notify( "round_switch", "overtime" ); + var_5 = 0; + var_6 = var_0 == "overtime"; + + if ( level.gametype == "ctf" ) + { + var_0 = "tie"; + var_5 = 1; + + if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] ) + var_0 = "axis"; + + if ( game["teamScores"]["allies"] > game["teamScores"]["axis"] ) + var_0 = "allies"; + } + + foreach ( var_3 in level.players ) + { + if ( isdefined( var_3.connectedpostgame ) || var_3.pers["team"] == "spectator" && !var_3 ismlgspectator() ) + continue; + + if ( level.teambased ) + { + var_3 thread maps\mp\gametypes\_hud_message::teamoutcomenotify( var_0, var_5, var_1 ); + continue; + } + + var_3 thread maps\mp\gametypes\_hud_message::outcomenotify( var_0, var_1 ); + } + + roundendwait( level.roundenddelay, 0 ); + + if ( level.gametype == "ctf" ) + var_0 = "overtime_halftime"; + + if ( isdefined( level.finalkillcam_winner ) && var_6 ) + { + level.finalkillcam_timegameended[level.finalkillcam_winner] = maps\mp\_utility::getsecondspassed(); + + foreach ( var_3 in level.players ) + var_3 notify( "reset_outcome" ); + + level notify( "game_cleanup" ); + waittillfinalkillcamdone(); + + if ( level.gametype == "ctf" ) + { + var_0 = "overtime"; + var_1 = game["end_reason"]["tie"]; + } + + foreach ( var_3 in level.players ) + { + if ( isdefined( var_3.connectedpostgame ) || var_3.pers["team"] == "spectator" && !var_3 ismlgspectator() ) + continue; + + if ( level.teambased ) + { + var_3 thread maps\mp\gametypes\_hud_message::teamoutcomenotify( var_0, 0, var_1 ); + continue; + } + + var_3 thread maps\mp\gametypes\_hud_message::outcomenotify( var_0, var_1 ); + } + + roundendwait( level.halftimeroundenddelay, 0 ); + } + + game["status"] = var_0; + level notify( "restarting" ); + game["state"] = "playing"; + setdvar( "ui_game_state", game["state"] ); + map_restart( 1 ); +} + +endgamehalftime( var_0 ) +{ + setdvar( "bg_compassShowEnemies", 0 ); + var_1 = "halftime"; + var_2 = 1; + + if ( isdefined( level.halftime_switch_sides ) && !level.halftime_switch_sides ) + var_2 = 0; + + if ( var_2 ) + { + game["switchedsides"] = !game["switchedsides"]; + var_3 = game["end_reason"]["switching_sides"]; + } + else + var_3 = var_0; + + freezeallplayers( 1.0, 1 ); + + if ( level.gametype == "ctf" ) + { + var_3 = var_0; + var_1 = "tie"; + + if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] ) + var_1 = "axis"; + + if ( game["teamScores"]["allies"] > game["teamScores"]["axis"] ) + var_1 = "allies"; + } + + foreach ( var_5 in level.players ) + { + var_5.pers["stats"] = var_5.stats; + var_5.pers["segments"] = var_5.segments; + } + + level notify( "round_switch", "halftime" ); + + foreach ( var_5 in level.players ) + { + if ( isdefined( var_5.connectedpostgame ) || var_5.pers["team"] == "spectator" && !var_5 ismlgspectator() ) + continue; + + var_5 thread maps\mp\gametypes\_hud_message::teamoutcomenotify( var_1, 1, var_3 ); + } + + roundendwait( level.roundenddelay, 0 ); + + if ( isdefined( level.finalkillcam_winner ) ) + { + level.finalkillcam_timegameended[level.finalkillcam_winner] = maps\mp\_utility::getsecondspassed(); + + foreach ( var_5 in level.players ) + var_5 notify( "reset_outcome" ); + + level notify( "game_cleanup" ); + waittillfinalkillcamdone(); + var_11 = game["end_reason"]["switching_sides"]; + + if ( !var_2 ) + var_11 = var_3; + + foreach ( var_5 in level.players ) + { + if ( isdefined( var_5.connectedpostgame ) || var_5.pers["team"] == "spectator" && !var_5 ismlgspectator() ) + continue; + + var_5 thread maps\mp\gametypes\_hud_message::teamoutcomenotify( "halftime", 1, var_11 ); + } + + roundendwait( level.halftimeroundenddelay, 0 ); + } + + game["status"] = "halftime"; + level notify( "restarting" ); + game["state"] = "playing"; + setdvar( "ui_game_state", game["state"] ); + map_restart( 1 ); +} + +updategameduration() +{ + level endon( "game_ended" ); + + for (;;) + { + var_0 = getgameduration(); + setomnvar( "ui_game_duration", var_0 * 1000 ); + wait 1.0; + } +} + +getgameduration() +{ + var_0 = maps\mp\_utility::getgametimepassedseconds(); + + if ( isdefined( level.ishorde ) && level.ishorde ) + var_0 = gamedurationclamp( var_0 ); + + return var_0; +} + +gamedurationclamp( var_0 ) +{ + if ( var_0 > 86399 ) + return 86399; + + return var_0; +} + +endgame( var_0, var_1, var_2 ) +{ + if ( !isdefined( var_2 ) ) + var_2 = 0; + + if ( game["state"] == "postgame" || level.gameended ) + return; + + game["state"] = "postgame"; + setdvar( "ui_game_state", "postgame" ); + level.gameendtime = gettime(); + level.gameended = 1; + level.ingraceperiod = 0; + level notify( "game_ended", var_0 ); + maps\mp\_utility::levelflagset( "game_over" ); + maps\mp\_utility::levelflagset( "block_notifies" ); + var_3 = getgameduration(); + setomnvar( "ui_game_duration", var_3 * 1000 ); + waitframe(); + setgameendtime( 0 ); + setmatchdata( "gameLengthSeconds", var_3 ); + setmatchdata( "endTimeUTC", getsystemtime() ); + checkgameendchallenges(); + + if ( isdefined( var_0 ) && isstring( var_0 ) && maps\mp\_utility::isovertimetext( var_0 ) ) + { + level.finalkillcam_winner = "none"; + endgameovertime( var_0, var_1 ); + return; + } + + if ( isdefined( var_0 ) && isstring( var_0 ) && var_0 == "halftime" ) + { + level.finalkillcam_winner = "none"; + endgamehalftime( var_1 ); + return; + } + + if ( isdefined( level.finalkillcam_winner ) ) + level.finalkillcam_timegameended[level.finalkillcam_winner] = maps\mp\_utility::getsecondspassed(); + + game["roundsPlayed"]++; + setomnvar( "ui_current_round", game["roundsPlayed"] ); + + if ( level.teambased ) + { + if ( ( var_0 == "axis" || var_0 == "allies" ) && level.gametype != "ctf" ) + game["roundsWon"][var_0]++; + + maps\mp\gametypes\_gamescore::updateteamscore( "axis" ); + maps\mp\gametypes\_gamescore::updateteamscore( "allies" ); + } + else if ( isdefined( var_0 ) && isplayer( var_0 ) ) + game["roundsWon"][var_0.guid]++; + + maps\mp\gametypes\_gamescore::updateplacement(); + rankedmatchupdates( var_0 ); + + foreach ( var_5 in level.players ) + { + var_5 setclientdvar( "ui_opensummary", 1 ); + + if ( maps\mp\_utility::wasonlyround() || maps\mp\_utility::waslastround() ) + var_5 maps\mp\killstreaks\_killstreaks::clearkillstreaks( 1 ); + } + + setdvar( "g_deadChat", 1 ); + setdvar( "ui_allow_teamchange", 0 ); + setdvar( "bg_compassShowEnemies", 0 ); + freezeallplayers( 1.0, 1 ); + + if ( !maps\mp\_utility::wasonlyround() && !var_2 ) + { + displayroundend( var_0, var_1 ); + + if ( isdefined( level.finalkillcam_winner ) ) + { + foreach ( var_5 in level.players ) + var_5 notify( "reset_outcome" ); + + level notify( "game_cleanup" ); + waittillfinalkillcamdone(); + } + + if ( !maps\mp\_utility::waslastround() ) + { + maps\mp\_utility::levelflagclear( "block_notifies" ); + + if ( checkroundswitch() ) + displayroundswitch(); + + foreach ( var_5 in level.players ) + { + var_5.pers["stats"] = var_5.stats; + var_5.pers["segments"] = var_5.segments; + } + + level notify( "restarting" ); + game["state"] = "playing"; + setdvar( "ui_game_state", "playing" ); + map_restart( 1 ); + return; + } + + if ( !level.forcedend ) + var_1 = updateendreasontext( var_0 ); + } + + if ( !isdefined( game["clientMatchDataDef"] ) ) + { + game["clientMatchDataDef"] = "mp/clientmatchdata.def"; + setclientmatchdatadef( game["clientMatchDataDef"] ); + } + + maps\mp\gametypes\_missions::roundend( var_0 ); + var_0 = getgamewinner( var_0, 1 ); + + if ( level.teambased ) + { + setomnvar( "ui_game_victor", 0 ); + + if ( var_0 == "allies" ) + setomnvar( "ui_game_victor", 2 ); + else if ( var_0 == "axis" ) + setomnvar( "ui_game_victor", 1 ); + } + + displaygameend( var_0, var_1 ); + var_11 = gettime(); + + if ( isdefined( level.finalkillcam_winner ) && maps\mp\_utility::wasonlyround() ) + { + foreach ( var_5 in level.players ) + var_5 notify( "reset_outcome" ); + + level notify( "game_cleanup" ); + waittillfinalkillcamdone(); + } + + maps\mp\_utility::levelflagclear( "block_notifies" ); + level.intermission = 1; + level notify( "spawning_intermission" ); + + foreach ( var_5 in level.players ) + { + var_5 closepopupmenu(); + var_5 closeingamemenu(); + var_5 notify( "reset_outcome" ); + var_5 thread maps\mp\gametypes\_playerlogic::spawnintermission(); + } + + processlobbydata(); + wait 1.0; + checkforpersonalbests(); + updatecombatrecord(); + + if ( level.teambased ) + { + if ( var_0 == "axis" || var_0 == "allies" ) + setmatchdata( "victor", var_0 ); + else + setmatchdata( "victor", "none" ); + + setmatchdata( "alliesScore", game["teamScores"]["allies"] ); + setmatchdata( "axisScore", game["teamScores"]["axis"] ); + _func_242( var_0 ); + } + else + setmatchdata( "victor", "none" ); + + level maps\mp\_matchdata::endofgamesummarylogger(); + + foreach ( var_5 in level.players ) + { + if ( var_5 maps\mp\_utility::rankingenabled() ) + var_5 maps\mp\_matchdata::logfinalstats(); + + var_5 maps\mp\gametypes\_playerlogic::logplayerstats(); + } + + setmatchdata( "host", maps\mp\gametypes\_playerlogic::truncateplayername( level.hostname ) ); + + if ( maps\mp\_utility::matchmakinggame() ) + { + setmatchdata( "playlistVersion", isdedicatedserver() ); + setmatchdata( "playlistID", getplaylistversion() ); + setmatchdata( "isDedicated", _func_27A() ); + } + + setmatchdata( "levelMaxClients", level.maxclients ); + sendmatchdata(); + + foreach ( var_5 in level.players ) + { + var_5.pers["stats"] = var_5.stats; + var_5.pers["segments"] = var_5.segments; + } + + tournamentreportwinningteam(); + var_20 = 0; + + if ( maps\mp\_utility::practiceroundgame() ) + var_20 = 5.0; + + if ( isdefined( level.endgamewaitfunc ) ) + [[ level.endgamewaitfunc ]]( var_2, level.postgamenotifies, var_20, var_0 ); + else if ( !var_2 && !level.postgamenotifies ) + { + if ( !maps\mp\_utility::wasonlyround() ) + wait(6.0 + var_20); + else + wait(min( 10.0, 4.0 + var_20 + level.postgamenotifies )); + } + else + wait(min( 10.0, 4.0 + var_20 + level.postgamenotifies )); + + var_21 = "_gamelogic.gsc"; + var_22 = "all"; + + if ( level.teambased && isdefined( var_0 ) ) + var_22 = var_0; + + var_23 = "undefined"; + + if ( isdefined( var_1 ) ) + { + switch ( var_1 ) + { + case 1: + var_23 = "MP_SCORE_LIMIT_REACHED"; + break; + case 2: + var_23 = "MP_TIME_LIMIT_REACHED"; + break; + case 3: + var_23 = "MP_PLAYERS_FORFEITED"; + break; + case 4: + var_23 = "MP_TARGET_DESTROYED"; + break; + case 5: + var_23 = "MP_BOMB_DEFUSED"; + break; + case 6: + var_23 = "MP_GHOSTS_ELIMINATED"; + break; + case 7: + var_23 = "MP_FEDERATION_ELIMINATED"; + break; + case 8: + var_23 = "MP_GHOSTS_FORFEITED"; + break; + case 9: + var_23 = "MP_FEDERATION_FORFEITED"; + break; + case 10: + var_23 = "MP_ENEMIES_ELIMINATED"; + break; + case 11: + var_23 = "MP_MATCH_TIE"; + break; + case 12: + var_23 = "GAME_OBJECTIVECOMPLETED"; + break; + case 13: + var_23 = "GAME_OBJECTIVEFAILED"; + break; + case 14: + var_23 = "MP_SWITCHING_SIDES"; + break; + case 15: + var_23 = "MP_ROUND_LIMIT_REACHED"; + break; + case 16: + var_23 = "MP_ENDED_GAME"; + break; + case 17: + var_23 = "MP_HOST_ENDED_GAME"; + break; + default: + break; + } + } + + if ( !isdefined( var_11 ) ) + var_11 = -1; + + var_24 = 15; + var_25 = var_24; + var_26 = getmatchdata( "playerCount" ); + var_27 = getmatchdata( "lifeCount" ); + + if ( !isdefined( level.matchdata ) ) + { + var_28 = 0; + var_29 = 0; + var_30 = 0; + var_31 = 0; + var_32 = 0; + var_33 = 0; + var_34 = 0; + } + else + { + if ( isdefined( level.matchdata["botJoinCount"] ) ) + var_28 = level.matchdata["botJoinCount"]; + else + var_28 = 0; + + if ( isdefined( level.matchdata["deathCount"] ) ) + var_29 = level.matchdata["deathCount"]; + else + var_29 = 0; + + if ( isdefined( level.matchdata["badSpawnDiedTooFastCount"] ) ) + var_30 = level.matchdata["badSpawnDiedTooFastCount"]; + else + var_30 = 0; + + if ( isdefined( level.matchdata["badSpawnKilledTooFastCount"] ) ) + var_31 = level.matchdata["badSpawnKilledTooFastCount"]; + else + var_31 = 0; + + if ( isdefined( level.matchdata["badSpawnDmgDealtCount"] ) ) + var_32 = level.matchdata["badSpawnDmgDealtCount"]; + else + var_32 = 0; + + if ( isdefined( level.matchdata["badSpawnDmgReceivedCount"] ) ) + var_33 = level.matchdata["badSpawnDmgReceivedCount"]; + else + var_33 = 0; + + if ( isdefined( level.matchdata["badSpawnByAnyMeansCount"] ) ) + var_34 = level.matchdata["badSpawnByAnyMeansCount"]; + else + var_34 = 0; + } + + var_35 = 0; + + if ( isdefined( level.spawnsighttracesused_pres1tu ) ) + var_35 += 1; + + if ( isdefined( level.spawnsighttracesused_posts1tu ) ) + var_35 += 2; + + reconevent( "script_mp_match_end: script_file %s, gameTime %d, match_winner %s, win_reason %s, version %d, joinCount %d, botJoinCount %d, spawnCount %d, deathCount %d, badSpawnDiedTooFastCount %d, badSpawnKilledTooFastCount %d, badSpawnDmgDealtCount %d, badSpawnDmgReceivedCount %d, badSpawnByAnyMeansCount %d, sightTraceMethodsUsed %d", var_21, var_11, var_22, var_23, var_25, var_26, var_28, var_27, var_29, var_30, var_31, var_32, var_33, var_34, var_35 ); + + if ( isdefined( level.ishorde ) && level.ishorde ) + { + if ( isdefined( level.zombiescompleted ) && level.zombiescompleted ) + setdvar( "cg_drawCrosshair", 1 ); + } + + level notify( "exitLevel_called" ); + exitlevel( 0 ); +} + +getgamewinner( var_0, var_1 ) +{ + if ( !isstring( var_0 ) ) + return var_0; + + var_2 = var_0; + + if ( level.teambased && ( maps\mp\_utility::isroundbased() || level.gametype == "ctf" ) && level.gameended ) + { + var_3 = "roundsWon"; + + if ( isdefined( level.winbycaptures ) && level.winbycaptures ) + var_3 = "teamScores"; + + if ( game[var_3]["allies"] == game[var_3]["axis"] ) + var_2 = "tie"; + else if ( game[var_3]["axis"] > game[var_3]["allies"] ) + var_2 = "axis"; + else + var_2 = "allies"; + } + + if ( var_1 && ( var_2 == "allies" || var_2 == "axis" ) ) + level.finalkillcam_winner = var_2; + + return var_2; +} + +updateendreasontext( var_0 ) +{ + if ( !level.teambased ) + return 1; + + if ( maps\mp\_utility::hitroundlimit() ) + return game["end_reason"]["round_limit_reached"]; + + if ( maps\mp\_utility::hitwinlimit() ) + return game["end_reason"]["score_limit_reached"]; + + return game["end_reason"]["objective_completed"]; +} + +estimatedtimetillscorelimit( var_0 ) +{ + var_1 = getscoreperminute( var_0 ); + var_2 = getscoreremaining( var_0 ); + var_3 = 999999; + + if ( var_1 ) + var_3 = var_2 / var_1; + + return var_3; +} + +getscoreperminute( var_0 ) +{ + var_1 = maps\mp\_utility::getwatcheddvar( "scorelimit" ); + var_2 = maps\mp\_utility::gettimelimit(); + var_3 = maps\mp\_utility::gettimepassed() / 60000 + 0.0001; + + if ( isplayer( self ) ) + var_4 = self.score / var_3; + else + var_4 = setclientnamemode( var_0 ) / var_3; + + return var_4; +} + +getscoreremaining( var_0 ) +{ + var_1 = maps\mp\_utility::getwatcheddvar( "scorelimit" ); + + if ( isplayer( self ) ) + var_2 = var_1 - self.score; + else + var_2 = var_1 - setclientnamemode( var_0 ); + + return var_2; +} + +givelastonteamwarning() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + maps\mp\_utility::waittillrecoveredhealth( 3 ); + thread maps\mp\_utility::teamplayercardsplash( "callout_lastteammemberalive", self, self.pers["team"] ); + + if ( level.multiteambased ) + { + foreach ( var_1 in level.teamnamelist ) + { + if ( self.pers["team"] != var_1 ) + thread maps\mp\_utility::teamplayercardsplash( "callout_lastenemyalive", self, var_1 ); + } + } + else + { + var_3 = maps\mp\_utility::getotherteam( self.pers["team"] ); + thread maps\mp\_utility::teamplayercardsplash( "callout_lastenemyalive", self, var_3 ); + } + + level notify( "last_alive", self ); +} + +processlobbydata() +{ + var_0 = 0; + + foreach ( var_2 in level.players ) + { + if ( !isdefined( var_2 ) ) + continue; + + var_2.clientmatchdataid = var_0; + var_0++; + + if ( isdefined( level.iszombiegame ) && level.iszombiegame ) + var_2.clientmatchdataid = var_2 getentitynumber(); + + setclientmatchdata( "players", var_2.clientmatchdataid, "name", maps\mp\gametypes\_playerlogic::truncateplayername( var_2.name ) ); + setclientmatchdata( "players", var_2.clientmatchdataid, "xuid", var_2.xuid ); + } + + maps\mp\_awards::assignawards(); + maps\mp\_scoreboard::processlobbyscoreboards(); + sendclientmatchdata(); + _func_23E(); +} + +trackleaderboarddeathstats( var_0, var_1 ) +{ + thread threadedsetweaponstatbyname( var_0, 1, "deaths" ); +} + +trackattackerleaderboarddeathstats( var_0, var_1, var_2 ) +{ + if ( isdefined( self ) && isplayer( self ) ) + { + if ( var_1 != "MOD_FALLING" ) + { + if ( maps\mp\_utility::ismeleemod( var_1 ) && issubstr( var_0, "tactical" ) ) + return; + + if ( maps\mp\_utility::ismeleemod( var_1 ) && !issubstr( var_0, "riotshield" ) && !issubstr( var_0, "combatknife" ) ) + return; + + thread threadedsetweaponstatbyname( var_0, 1, "kills" ); + var_3 = 0; + + if ( isdefined( var_2 ) && isdefined( var_2.firedads ) ) + var_3 = var_2.firedads; + else + var_3 = self playerads(); + + if ( var_3 < 0.2 ) + thread threadedsetweaponstatbyname( var_0, 1, "hipfirekills" ); + } + + if ( var_1 == "MOD_HEAD_SHOT" ) + thread threadedsetweaponstatbyname( var_0, 1, "headShots" ); + } +} + +setweaponstat( var_0, var_1, var_2 ) +{ + if ( !var_1 ) + return; + + var_3 = maps\mp\_utility::getweaponclass( var_0 ); + + if ( var_3 == "killstreak" || var_3 == "other" ) + return; + + if ( maps\mp\_utility::isenvironmentweapon( var_0 ) ) + return; + + if ( maps\mp\_utility::isbombsiteweapon( var_0 ) ) + return; + + if ( var_0 == maps\mp\_grappling_hook::get_grappling_hook_weapon() ) + return; + + if ( var_3 == "weapon_grenade" || var_3 == "weapon_explosive" ) + { + var_4 = maps\mp\_utility::strip_suffix( var_0, "_lefthand" ); + var_4 = maps\mp\_utility::strip_suffix( var_4, "_mp" ); + maps\mp\gametypes\_persistence::incrementweaponstat( var_4, var_2, var_1 ); + maps\mp\_matchdata::logweaponstat( var_4, var_2, var_1 ); + return; + } + + var_5 = maps\mp\gametypes\_weapons::isprimaryorsecondaryprojectileweapon( var_0 ); + + if ( var_2 != "timeInUse" && var_2 != "deaths" && !var_5 ) + { + var_6 = var_0; + var_0 = self getcurrentweapon(); + + if ( var_0 != var_6 && maps\mp\_utility::iskillstreakweapon( var_0 ) ) + return; + } + + if ( !isdefined( self.trackingweaponname ) ) + self.trackingweaponname = var_0; + + if ( var_0 != self.trackingweaponname ) + { + maps\mp\gametypes\_persistence::updateweaponbufferedstats(); + self.trackingweaponname = var_0; + self.currentfirefightshots = 0; + } + + switch ( var_2 ) + { + case "shots": + self.trackingweaponshots++; + self.currentfirefightshots++; + break; + case "hits": + self.trackingweaponhits++; + break; + case "headShots": + self.trackingweaponheadshots++; + self.trackingweaponhits++; + break; + case "kills": + self.trackingweaponkills++; + break; + case "hipfirekills": + self.trackingweaponhipfirekills++; + break; + case "timeInUse": + self.trackingweaponusetime += var_1; + break; + } + + if ( var_2 == "deaths" ) + { + var_7 = maps\mp\_utility::getbaseweaponname( var_0 ); + + if ( !maps\mp\_utility::iscacprimaryweapon( var_7 ) && !maps\mp\_utility::iscacsecondaryweapon( var_7 ) ) + return; + + var_8 = maps\mp\_utility::getweaponattachmentsbasenames( var_0 ); + maps\mp\gametypes\_persistence::incrementweaponstat( var_7, var_2, var_1 ); + maps\mp\_matchdata::logweaponstat( var_7, "deaths", var_1 ); + + foreach ( var_10 in var_8 ) + maps\mp\gametypes\_persistence::incrementattachmentstat( var_10, var_2, var_1 ); + } +} + +setinflictorstat( var_0, var_1, var_2 ) +{ + if ( !isdefined( var_1 ) ) + return; + + if ( !isdefined( var_0 ) ) + { + var_1 setweaponstat( var_2, 1, "hits" ); + return; + } + + if ( !isdefined( var_0.playeraffectedarray ) ) + var_0.playeraffectedarray = []; + + var_3 = 1; + + for ( var_4 = 0; var_4 < var_0.playeraffectedarray.size; var_4++ ) + { + if ( var_0.playeraffectedarray[var_4] == self ) + { + var_3 = 0; + break; + } + } + + if ( var_3 ) + { + var_0.playeraffectedarray[var_0.playeraffectedarray.size] = self; + var_1 setweaponstat( var_2, 1, "hits" ); + } +} + +threadedsetweaponstatbyname( var_0, var_1, var_2 ) +{ + self endon( "disconnect" ); + waittillframeend; + setweaponstat( var_0, var_1, var_2 ); +} + +checkforpersonalbests() +{ + foreach ( var_1 in level.players ) + { + if ( !isdefined( var_1 ) ) + continue; + + if ( var_1 maps\mp\_utility::rankingenabled() ) + { + var_2 = var_1 getcommonplayerdata( "round", "kills" ); + var_3 = var_1 getcommonplayerdata( "round", "deaths" ); + var_4 = var_1.pers["summary"]["xp"]; + var_5 = var_1.score; + var_6 = getroundaccuracy( var_1 ); + var_7 = var_1 getrankedplayerdata( "bestKills" ); + var_8 = var_1 getrankedplayerdata( "mostDeaths" ); + var_9 = var_1 getrankedplayerdata( "mostXp" ); + var_10 = var_1 getrankedplayerdata( "bestScore" ); + var_11 = var_1 getrankedplayerdata( "bestAccuracy" ); + + if ( var_2 > var_7 ) + var_1 setrankedplayerdata( "bestKills", var_2 ); + + if ( var_4 > var_9 ) + var_1 setrankedplayerdata( "mostXp", var_4 ); + + if ( var_3 > var_8 ) + var_1 setrankedplayerdata( "mostDeaths", var_3 ); + + if ( var_5 > var_10 ) + var_1 setrankedplayerdata( "bestScore", var_5 ); + + if ( var_6 > var_11 ) + var_1 setrankedplayerdata( "bestAccuracy", var_6 ); + + var_1 checkforbestweapon(); + var_1 maps\mp\_matchdata::logplayerxp( var_4, "totalXp" ); + var_1 maps\mp\_matchdata::logplayerxp( var_1.pers["summary"]["score"], "scoreXp" ); + var_1 maps\mp\_matchdata::logplayerxp( var_1.pers["summary"]["challenge"], "challengeXp" ); + var_1 maps\mp\_matchdata::logplayerxp( var_1.pers["summary"]["match"], "matchXp" ); + var_1 maps\mp\_matchdata::logplayerxp( var_1.pers["summary"]["misc"], "miscXp" ); + } + + if ( isdefined( var_1.pers["confirmed"] ) ) + var_1 maps\mp\_matchdata::logkillsconfirmed(); + + if ( isdefined( var_1.pers["denied"] ) ) + var_1 maps\mp\_matchdata::logkillsdenied(); + } +} + +getroundaccuracy( var_0 ) +{ + var_1 = float( var_0 getrankedplayerdata( "totalShots" ) - var_0.pers["previous_shots"] ); + + if ( var_1 == 0 ) + return 0; + + var_2 = float( var_0 getrankedplayerdata( "hits" ) - var_0.pers["previous_hits"] ); + var_3 = clamp( var_2 / var_1, 0.0, 1.0 ) * 10000.0; + return int( var_3 ); +} + +checkforbestweapon() +{ + var_0 = maps\mp\_matchdata::buildbaseweaponlist(); + + for ( var_1 = 0; var_1 < var_0.size; var_1++ ) + { + var_2 = var_0[var_1]; + var_2 = maps\mp\_utility::getbaseweaponname( var_2 ); + var_3 = maps\mp\_utility::getweaponclass( var_2 ); + + if ( !maps\mp\_utility::iskillstreakweapon( var_2 ) && var_3 != "killstreak" && var_3 != "other" ) + { + var_4 = self getrankedplayerdata( "bestWeapon", "kills" ); + var_5 = 0; + + if ( isdefined( self.pers["mpWeaponStats"][var_2] ) && isdefined( self.pers["mpWeaponStats"][var_2]["kills"] ) ) + { + var_5 = self.pers["mpWeaponStats"][var_2]["kills"]; + + if ( var_5 > var_4 ) + { + self setrankedplayerdata( "bestWeapon", "kills", var_5 ); + var_6 = 0; + + if ( isdefined( self.pers["mpWeaponStats"][var_2]["shots"] ) ) + var_6 = self.pers["mpWeaponStats"][var_2]["shots"]; + + var_7 = 0; + + if ( isdefined( self.pers["mpWeaponStats"][var_2]["headShots"] ) ) + var_7 = self.pers["mpWeaponStats"][var_2]["headShots"]; + + var_8 = 0; + + if ( isdefined( self.pers["mpWeaponStats"][var_2]["hits"] ) ) + var_8 = self.pers["mpWeaponStats"][var_2]["hits"]; + + var_9 = 0; + + if ( isdefined( self.pers["mpWeaponStats"][var_2]["deaths"] ) ) + var_9 = self.pers["mpWeaponStats"][var_2]["deaths"]; + + self setrankedplayerdata( "bestWeapon", "shots", var_6 ); + self setrankedplayerdata( "bestWeapon", "headShots", var_7 ); + self setrankedplayerdata( "bestWeapon", "hits", var_8 ); + self setrankedplayerdata( "bestWeapon", "deaths", var_9 ); + var_10 = int( tablelookup( "mp/statstable.csv", 4, var_2, 0 ) ); + self setrankedplayerdata( "bestWeaponIndex", var_10 ); + } + } + } + } +} + +updatecombatrecordforplayertrends() +{ + var_0 = 5; + var_1 = self getrankedplayerdata( "combatRecord", "numTrends" ); + var_1++; + + if ( var_1 > var_0 ) + { + var_1 = var_0; + + if ( var_0 > 1 ) + { + for ( var_2 = 0; var_2 < var_0 - 1; var_2++ ) + { + var_3 = self getrankedplayerdata( "combatRecord", "trend", var_2 + 1, "timestamp" ); + var_4 = self getrankedplayerdata( "combatRecord", "trend", var_2 + 1, "kills" ); + var_5 = self getrankedplayerdata( "combatRecord", "trend", var_2 + 1, "deaths" ); + self setrankedplayerdata( "combatRecord", "trend", var_2, "timestamp", var_3 ); + self setrankedplayerdata( "combatRecord", "trend", var_2, "kills", var_4 ); + self setrankedplayerdata( "combatRecord", "trend", var_2, "deaths", var_5 ); + } + } + } + + var_3 = maps\mp\_utility::gettimeutc_for_stat_recording(); + var_4 = self.kills; + var_5 = self.deaths; + self setrankedplayerdata( "combatRecord", "trend", var_1 - 1, "timestamp", var_3 ); + self setrankedplayerdata( "combatRecord", "trend", var_1 - 1, "kills", var_4 ); + self setrankedplayerdata( "combatRecord", "trend", var_1 - 1, "deaths", var_5 ); + self setrankedplayerdata( "combatRecord", "numTrends", var_1 ); +} + +updatecombatrecordcommondata() +{ + var_0 = maps\mp\_utility::gettimeutc_for_stat_recording(); + setcombatrecordstat( "timeStampLastGame", var_0 ); + incrementcombatrecordstat( "numMatches", 1 ); + incrementcombatrecordstat( "timePlayed", self.timeplayed["total"] ); + incrementcombatrecordstat( "kills", self.kills ); + incrementcombatrecordstat( "deaths", self.deaths ); + incrementcombatrecordstat( "xpEarned", self.pers["summary"]["xp"] ); + + if ( isdefined( self.combatrecordwin ) ) + incrementcombatrecordstat( "wins", 1 ); + + if ( isdefined( self.combatrecordtie ) ) + incrementcombatrecordstat( "ties", 1 ); + + var_1 = self getrankedplayerdata( "combatRecord", level.gametype, "timeStampFirstGame" ); + + if ( var_1 == 0 ) + setcombatrecordstat( "timeStampFirstGame", var_0 ); +} + +incrementcombatrecordstat( var_0, var_1 ) +{ + var_2 = self getrankedplayerdata( "combatRecord", level.gametype, var_0 ); + var_2 += var_1; + self setrankedplayerdata( "combatRecord", level.gametype, var_0, var_2 ); +} + +setcombatrecordstat( var_0, var_1 ) +{ + self setrankedplayerdata( "combatRecord", level.gametype, var_0, var_1 ); +} + +setcombatrecordstatifgreater( var_0, var_1 ) +{ + var_2 = self getrankedplayerdata( "combatRecord", level.gametype, var_0 ); + + if ( var_1 > var_2 ) + setcombatrecordstat( var_0, var_1 ); +} + +updatecombatrecordforplayergamemodes() +{ + if ( level.gametype == "war" || level.gametype == "dm" ) + { + updatecombatrecordcommondata(); + var_0 = self.deaths; + + if ( var_0 == 0 ) + var_0 = 1; + + var_1 = int( self.kills / var_0 ) * 1000; + setcombatrecordstatifgreater( "mostkills", self.kills ); + setcombatrecordstatifgreater( "bestkdr", var_1 ); + } + else if ( level.gametype == "ctf" ) + { + updatecombatrecordcommondata(); + var_2 = maps\mp\_utility::getpersstat( "captures" ); + var_3 = maps\mp\_utility::getpersstat( "returns" ); + incrementcombatrecordstat( "captures", var_2 ); + incrementcombatrecordstat( "returns", var_3 ); + setcombatrecordstatifgreater( "mostcaptures", var_2 ); + setcombatrecordstatifgreater( "mostreturns", var_3 ); + } + else if ( level.gametype == "dom" ) + { + updatecombatrecordcommondata(); + var_2 = maps\mp\_utility::getpersstat( "captures" ); + var_4 = maps\mp\_utility::getpersstat( "defends" ); + incrementcombatrecordstat( "captures", var_2 ); + incrementcombatrecordstat( "defends", var_4 ); + setcombatrecordstatifgreater( "mostcaptures", var_2 ); + setcombatrecordstatifgreater( "mostdefends", var_4 ); + } + else if ( level.gametype == "conf" ) + { + updatecombatrecordcommondata(); + var_5 = maps\mp\_utility::getpersstat( "confirmed" ); + var_6 = maps\mp\_utility::getpersstat( "denied" ); + incrementcombatrecordstat( "confirms", var_5 ); + incrementcombatrecordstat( "denies", var_6 ); + setcombatrecordstatifgreater( "mostconfirms", var_5 ); + setcombatrecordstatifgreater( "mostdenies", var_6 ); + } + else if ( level.gametype == "sd" ) + { + updatecombatrecordcommondata(); + var_7 = maps\mp\_utility::getpersstat( "plants" ); + var_8 = maps\mp\_utility::getpersstat( "defuses" ); + var_9 = maps\mp\_utility::getpersstat( "destructions" ); + incrementcombatrecordstat( "plants", var_7 ); + incrementcombatrecordstat( "defuses", var_8 ); + incrementcombatrecordstat( "detonates", var_9 ); + setcombatrecordstatifgreater( "mostplants", var_7 ); + setcombatrecordstatifgreater( "mostdefuses", var_8 ); + setcombatrecordstatifgreater( "mostdetonates", var_9 ); + } + else if ( level.gametype == "hp" ) + { + updatecombatrecordcommondata(); + var_2 = maps\mp\_utility::getpersstat( "captures" ); + var_4 = maps\mp\_utility::getpersstat( "defends" ); + incrementcombatrecordstat( "captures", var_2 ); + incrementcombatrecordstat( "defends", var_4 ); + setcombatrecordstatifgreater( "mostcaptures", var_2 ); + setcombatrecordstatifgreater( "mostdefends", var_4 ); + } + else if ( level.gametype == "sr" ) + { + updatecombatrecordcommondata(); + var_7 = maps\mp\_utility::getpersstat( "plants" ); + var_8 = maps\mp\_utility::getpersstat( "defuses" ); + var_9 = maps\mp\_utility::getpersstat( "destructions" ); + var_5 = maps\mp\_utility::getpersstat( "confirmed" ); + var_6 = maps\mp\_utility::getpersstat( "denied" ); + incrementcombatrecordstat( "plants", var_7 ); + incrementcombatrecordstat( "defuses", var_8 ); + incrementcombatrecordstat( "detonates", var_9 ); + incrementcombatrecordstat( "confirms", var_5 ); + incrementcombatrecordstat( "denies", var_6 ); + setcombatrecordstatifgreater( "mostplants", var_7 ); + setcombatrecordstatifgreater( "mostdefuses", var_8 ); + setcombatrecordstatifgreater( "mostdetonates", var_9 ); + setcombatrecordstatifgreater( "mostconfirms", var_5 ); + setcombatrecordstatifgreater( "mostdenies", var_6 ); + } + else if ( level.gametype == "infect" ) + { + updatecombatrecordcommondata(); + var_10 = maps\mp\_utility::getplayerstat( "contagious" ); + var_11 = self.kills - var_10; + incrementcombatrecordstat( "infectedKills", var_11 ); + incrementcombatrecordstat( "survivorKills", var_10 ); + setcombatrecordstatifgreater( "mostInfectedKills", var_11 ); + setcombatrecordstatifgreater( "mostSurvivorKills", var_10 ); + } + else if ( level.gametype == "gun" ) + { + updatecombatrecordcommondata(); + var_12 = maps\mp\_utility::getplayerstat( "levelup" ); + var_13 = maps\mp\_utility::getplayerstat( "humiliation" ); + incrementcombatrecordstat( "gunPromotions", var_12 ); + incrementcombatrecordstat( "stabs", var_13 ); + setcombatrecordstatifgreater( "mostGunPromotions", var_12 ); + setcombatrecordstatifgreater( "mostStabs", var_13 ); + } + else if ( level.gametype == "ball" ) + { + updatecombatrecordcommondata(); + var_14 = maps\mp\_utility::getplayerstat( "fieldgoal" ) + maps\mp\_utility::getplayerstat( "touchdown" ) * 2; + var_15 = maps\mp\_utility::getplayerstat( "killedBallCarrier" ); + incrementcombatrecordstat( "pointsScored", var_14 ); + incrementcombatrecordstat( "killedBallCarrier", var_15 ); + setcombatrecordstatifgreater( "mostPointsScored", var_14 ); + setcombatrecordstatifgreater( "mostKilledBallCarrier", var_15 ); + } + else if ( level.gametype == "twar" ) + { + updatecombatrecordcommondata(); + var_2 = maps\mp\_utility::getpersstat( "captures" ); + var_16 = maps\mp\_utility::getplayerstat( "kill_while_capture" ); + incrementcombatrecordstat( "captures", var_2 ); + incrementcombatrecordstat( "killWhileCaptures", var_16 ); + setcombatrecordstatifgreater( "mostCaptures", var_2 ); + setcombatrecordstatifgreater( "mostKillWhileCaptures", var_16 ); + } +} + +updatecombatrecordforplayer() +{ + updatecombatrecordforplayertrends(); + updatecombatrecordforplayergamemodes(); +} + +updatecombatrecord() +{ + foreach ( var_1 in level.players ) + { + if ( !isdefined( var_1 ) ) + continue; + + if ( var_1 maps\mp\_utility::rankingenabled() ) + var_1 updatecombatrecordforplayer(); + + level maps\mp\gametypes\_playerlogic::writesegmentdata( var_1 ); + + if ( maps\mp\_utility::practiceroundgame() ) + level maps\mp\gametypes\_playerlogic::checkpracticeroundlockout( var_1 ); + } +} diff --git a/data/maps/mp/gametypes/_playerlogic.gsc b/data/maps/mp/gametypes/_playerlogic.gsc index 8a81eb0..7e7fba4 100644 --- a/data/maps/mp/gametypes/_playerlogic.gsc +++ b/data/maps/mp/gametypes/_playerlogic.gsc @@ -1211,10 +1211,6 @@ removeplayerondisconnect() initclientdvarssplitscreenspecific() { - if ( level.splitscreen || self issplitscreenplayer() ) - self setclientdvars( "cg_hudGrenadeIconHeight", "37.5", "cg_hudGrenadeIconWidth", "37.5", "cg_hudGrenadeIconOffset", "75", "cg_hudGrenadePointerHeight", "18", "cg_hudGrenadePointerWidth", "37.5", "cg_hudGrenadePointerPivot", "18 40.5", "cg_fovscale", "0.75" ); - else - self setclientdvars( "cg_hudGrenadeIconHeight", "75", "cg_hudGrenadeIconWidth", "75", "cg_hudGrenadeIconOffset", "50", "cg_hudGrenadePointerHeight", "36", "cg_hudGrenadePointerWidth", "75", "cg_hudGrenadePointerPivot", "36 81", "cg_fovscale", "1" ); } initclientdvars() @@ -1236,24 +1232,6 @@ initclientdvars() setdvar( "cg_drawFriendlyNamesAlways", 1 ); else setdvar( "cg_drawFriendlyNamesAlways", 0 ); - - initclientdvarssplitscreenspecific(); - - if ( maps\mp\_utility::getgametypenumlives() ) - self setclientdvars( "cg_deadChatWithDead", 1, "cg_deadChatWithTeam", 0, "cg_deadHearTeamLiving", 0, "cg_deadHearAllLiving", 0 ); - else - self setclientdvars( "cg_deadChatWithDead", 0, "cg_deadChatWithTeam", 1, "cg_deadHearTeamLiving", 1, "cg_deadHearAllLiving", 0 ); - - if ( level.teambased ) - self setclientdvars( "cg_everyonehearseveryone", 0 ); - - if ( getdvarint( "scr_hitloc_debug" ) ) - { - for ( var_0 = 0; var_0 < 6; var_0++ ) - self setclientdvar( "ui_hitloc_" + var_0, "" ); - - self.hitlocinited = 1; - } } getlowestavailableclientid() @@ -1573,7 +1551,7 @@ callback_playerconnect() thread kickifdontspawn(); return; } - else if ( !maps\mp\_utility::matchmakinggame() && maps\mp\_utility::allowteamchoice() ) + else if ( maps\mp\_utility::allowteamchoice() && !isbot( self ) ) { maps\mp\gametypes\_menus::menuspectator(); thread setuioptionsmenu( 1 ); diff --git a/data/maps/mp/gametypes/gun.gsc b/data/maps/mp/gametypes/gun.gsc new file mode 100644 index 0000000..0f18236 --- /dev/null +++ b/data/maps/mp/gametypes/gun.gsc @@ -0,0 +1,517 @@ +// S1 GSC SOURCE +// Decompiled by https://github.com/xensik/gsc-tool + +main() +{ + maps\mp\gametypes\_globallogic::init(); + maps\mp\gametypes\_callbacksetup::setupcallbacks(); + maps\mp\gametypes\_globallogic::setupcallbacks(); + setguns(); + + + maps\mp\_utility::registertimelimitdvar( level.gametype, 10 ); + setDvar( "scr_gun_scorelimit", level.gun_guns.size ); + maps\mp\_utility::registerscorelimitdvar( level.gametype, level.gun_guns.size ); + level thread reinitializescorelimitonmigration(); + maps\mp\_utility::registerroundlimitdvar( level.gametype, 1 ); + maps\mp\_utility::registerwinlimitdvar( level.gametype, 0 ); + maps\mp\_utility::registernumlivesdvar( level.gametype, 0 ); + maps\mp\_utility::registerhalftimedvar( level.gametype, 0 ); + level.matchrules_randomize = 0; + level.matchrules_damagemultiplier = 0; + level.matchrules_vampirism = 0; + + setspecialloadout(); + level.teambased = 0; + level.doprematch = 1; + level.onstartgametype = ::onstartgametype; + level.onspawnplayer = ::onspawnplayer; + level.getspawnpoint = ::getspawnpoint; + level.onplayerkilled = ::onplayerkilled; + level.ontimelimit = ::ontimelimit; + level.onplayerscore = ::onplayerscore; + level.bypassclasschoicefunc = ::gungameclass; + level.assists_disabled = 1; + level.setbacklevel = maps\mp\_utility::getintproperty( "scr_setback_levels", 1 ); + level.lastguntimevo = 0; + + if ( level.matchrules_damagemultiplier ) + level.modifyplayerdamage = maps\mp\gametypes\_damage::gamemodemodifyplayerdamage; + + setteamscore( "ffa" ); + game["dialog"]["gametype"] = "gg_intro"; + game["dialog"]["defense_obj"] = "gbl_start"; + game["dialog"]["offense_obj"] = "gbl_start"; + game["dialog"]["humiliation"] = "gg_humiliation"; + game["dialog"]["lastgun"] = "at_anr1_gg_lastgun"; + + if ( maps\mp\_utility::isgrapplinghookgamemode() ) + game["dialog"]["gametype"] = "grap_" + game["dialog"]["gametype"]; +} + +initializematchrules() +{ + maps\mp\_utility::setcommonrulesfrommatchrulesdata( 1 ); + level.matchrules_randomize = getmatchrulesdata( "gunData", "randomize" ); + setDvar( "scr_gun_scorelimit", level.gun_guns.size ); + maps\mp\_utility::registerscorelimitdvar( level.gametype, level.gun_guns.size ); + setDvar( "scr_gun_winlimit", 1 ); + maps\mp\_utility::registerwinlimitdvar( "gun", 1 ); + setDvar( "scr_gun_roundlimit", 1 ); + maps\mp\_utility::registerroundlimitdvar( "gun", 1 ); + setDvar( "scr_gun_halftime", 0 ); + maps\mp\_utility::registerhalftimedvar( "gun", 0 ); + setDvar( "scr_gun_playerrespawndelay", 0 ); + setDvar( "scr_gun_waverespawndelay", 0 ); + setDvar( "scr_player_forcerespawn", 1 ); + setDvar( "scr_setback_levels", getmatchrulesdata( "gunData", "setbackLevels" ) ); +} + +reinitializescorelimitonmigration() +{ + setDvar( "scr_gun_scorelimit", level.gun_guns.size ); + maps\mp\_utility::registerscorelimitdvar( level.gametype, level.gun_guns.size ); +} + +onstartgametype() +{ + getteamplayersalive( "auto_change" ); + maps\mp\_utility::setobjectivetext( "allies", &"OBJECTIVES_DM" ); + maps\mp\_utility::setobjectivetext( "axis", &"OBJECTIVES_DM" ); + + maps\mp\_utility::setobjectivescoretext( "allies", &"OBJECTIVES_DM_SCORE" ); + maps\mp\_utility::setobjectivescoretext( "axis", &"OBJECTIVES_DM_SCORE" ); + + maps\mp\_utility::setobjectivehinttext( "allies", &"OBJECTIVES_DM_HINT" ); + maps\mp\_utility::setobjectivehinttext( "axis", &"OBJECTIVES_DM_HINT" ); + initspawns(); + allowed = []; + maps\mp\gametypes\_gameobjects::main( allowed ); + level.quickmessagetoall = 1; + level.blockweapondrops = 1; + level thread onplayerconnect(); +} + +initspawns() +{ + level.spawnmins = ( 0.0, 0.0, 0.0 ); + level.spawnmaxs = ( 0.0, 0.0, 0.0 ); + level.spawn_name = "mp_dm_spawn"; + maps\mp\gametypes\_spawnlogic::addspawnpoints( "allies", level.spawn_name ); + maps\mp\gametypes\_spawnlogic::addspawnpoints( "axis", level.spawn_name ); + level.mapcenter = maps\mp\gametypes\_spawnlogic::findboxcenter( level.spawnmins, level.spawnmaxs ); + setmapcenter( level.mapcenter ); +} + +onplayerconnect() +{ + for (;;) + { + level waittill( "connected", player ); + player.gungamegunindex = 0; + player.gungameprevgunindex = 0; + player.stabs = 0; + player.mysetbacks = 0; + player.lastleveluptime = 0; + player.showsetbacksplash = 0; + + if ( level.matchrules_randomize ) + player.gunlist = common_scripts\utility::array_randomize( level.gun_guns ); + + player thread refillammo(); + player thread refillsinglecountammo(); + player thread watchforhostmigration(); + } +} + +getspawnpoint() +{ + var_0 = maps\mp\gametypes\_spawnlogic::getteamspawnpoints( self.pers["team"] ); + + if ( level.ingraceperiod ) + var_1 = maps\mp\gametypes\_spawnlogic::getspawnpoint_random( var_0 ); + else + var_1 = maps\mp\gametypes\_spawnscoring::getspawnpoint_freeforall( var_0 ); + + maps\mp\gametypes\_spawnlogic::recon_set_spawnpoint( var_1 ); + return var_1; +} + +gungameclass() +{ + self.pers["class"] = "gamemode"; + self.pers["lastClass"] = ""; + self.pers["gamemodeLoadout"] = level.gun_loadout; + self.class = self.pers["class"]; + self.lastclass = self.pers["lastClass"]; + self loadweapons( level.gun_guns[0] ); +} + +onspawnplayer() +{ + thread waitloadoutdone(); +} + +waitloadoutdone() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + level waittill( "player_spawned" ); + givenextgun( 1 ); + + if ( self.showsetbacksplash ) + { + self.showsetbacksplash = 0; + thread maps\mp\_events::decreasegunlevelevent(); + } +} + +watchforhostmigration() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + for (;;) + { + self waittill( "player_migrated" ); + + if ( self.sessionstate == "spectator" ) + maps\mp\gametypes\_menus::handleclasschoicedisallowed(); + } +} + +onplayerscore( var_0, var_1, var_2 ) +{ + if ( var_0 == "gained_gun_score" ) + { + var_3 = maps\mp\gametypes\_rank::getscoreinfovalue( var_0 ); + var_1 maps\mp\_utility::setextrascore0( var_1.extrascore0 + var_3 ); + var_1 maps\mp\gametypes\_gamescore::updatescorestatsffa( var_1, var_3 ); + return 1; + } + + if ( var_0 == "dropped_gun_score" ) + { + var_4 = min( level.setbacklevel, self.score ); + return int( var_4 * -1 ); + } + + return 0; +} + +onplayerkilled( var_0, attacker, var_2, sMeansOfDeath, sWeapon, var_5, var_6, var_7, var_8, var_9 ) +{ + if ( !isdefined( attacker ) ) + return; + + if ( sMeansOfDeath == "MOD_TRIGGER_HURT" && !isplayer( attacker ) ) + attacker = self; + + if ( sMeansOfDeath == "MOD_FALLING" || isplayer( attacker ) ) + { + if ( sMeansOfDeath == "MOD_FALLING" || attacker == self || maps\mp\_utility::ismeleemod( sMeansOfDeath ) && sWeapon != "riotshield_mp" || sWeapon == "boost_slam_mp" || sWeapon == "iw5_dlcgun12loot8_mp" ) + { + self playlocalsound( "mp_war_objective_lost" ); + self.gungameprevgunindex = self.gungamegunindex; + self.gungamegunindex = int( max( 0, self.gungamegunindex - level.setbacklevel ) ); + self.lastkillweapon = undefined; + + if ( self.gungameprevgunindex > self.gungamegunindex ) + { + self.mysetbacks++; + maps\mp\_utility::setextrascore1( self.mysetbacks ); + self.showsetbacksplash = 1; + + if ( maps\mp\_utility::ismeleemod( sMeansOfDeath ) || sWeapon == "boost_slam_mp" || sWeapon == "iw5_dlcgun12loot8_mp" ) + { + attacker.stabs++; + attacker.assists = attacker.stabs; + attacker thread maps\mp\_events::setbackenemygunlevelevent(); + + if ( self.gungameprevgunindex == level.gun_guns.size - 1 ) + { + attacker thread maps\mp\_events::setbackfirstplayergunlevelevent(); + attacker maps\mp\_utility::leaderdialogonplayer( "humiliation", "status" ); + } + } + } + } + else if ( sMeansOfDeath == "MOD_PISTOL_BULLET" || sMeansOfDeath == "MOD_RIFLE_BULLET" || sMeansOfDeath == "MOD_HEAD_SHOT" || sMeansOfDeath == "MOD_PROJECTILE" || sMeansOfDeath == "MOD_PROJECTILE_SPLASH" || sMeansOfDeath == "MOD_EXPLOSIVE" || sMeansOfDeath == "MOD_IMPACT" || sMeansOfDeath == "MOD_GRENADE" || sMeansOfDeath == "MOD_GRENADE_SPLASH" || maps\mp\_utility::ismeleemod( sMeansOfDeath ) && sWeapon == "riotshield_mp" ) + { + if ( isdefined( attacker.lastkillweapon ) && attacker.lastkillweapon == sWeapon ) + { + return; + } + + var_10 = level.gun_guns; + + if ( level.matchrules_randomize ) + var_10 = attacker.gunlist; + + var_11 = var_10[attacker.gungamegunindex]; + + if ( !issubstr( sWeapon, maps\mp\_utility::getbaseweaponname( var_11 ) ) ) + { + return; + } + + attacker.lastkillweapon = sWeapon; + + if ( attacker.lastleveluptime + 3000 > gettime() ) + attacker thread maps\mp\_events::quickgunlevelevent(); + + attacker.lastleveluptime = gettime(); + attacker.gungameprevgunindex = attacker.gungamegunindex; + attacker.gungamegunindex++; + attacker thread maps\mp\_events::increasegunlevelevent(); + + if ( attacker.gungamegunindex == level.gun_guns.size - 1 ) + { + maps\mp\_utility::playsoundonplayers( "mp_enemy_obj_captured" ); + level thread maps\mp\_utility::teamplayercardsplash( "callout_top_gun_rank", attacker ); + var_12 = gettime(); + + if ( level.lastguntimevo + 4500 < var_12 ) + { + level thread maps\mp\_utility::leaderdialogonplayers( "lastgun", level.players, "status" ); + level.lastguntimevo = var_12; + } + } + + if ( attacker.gungamegunindex < level.gun_guns.size ) + attacker givenextgun( 0, sWeapon ); + } + } +} + +givenextgun( var_0, var_1 ) +{ + self endon( "disconnect" ); + var_2 = getnextgun(); + self.gun_curgun = var_2; + var_2 = addattachments( var_2 ); + + while ( !self loadweapons( var_2 ) ) + { + waitframe(); + } + + if ( isdefined( var_1 ) ) + self takeweapon( var_1 ); + else + self takeallweapons(); + + maps\mp\_utility::_giveweapon( var_2 ); + self switchtoweaponimmediate( var_2 ); + + if ( isdefined( var_0 ) && var_0 == 1 ) + self setspawnweapon( var_2 ); + + var_3 = maps\mp\_utility::getbaseweaponname( var_2 ); + self.pers["primaryWeapon"] = var_3; + self.primaryweapon = var_2; + self givestartammo( var_2 ); + self switchtoweapon( var_2 ); + self.gungameprevgunindex = self.gungamegunindex; +} + +getnextgun() +{ + var_0 = level.gun_guns; + var_1 = []; + newWeapon = undefined; + + if ( level.matchrules_randomize ) + var_0 = self.gunlist; + + newWeapon = var_0[self.gungamegunindex]; + var_1[var_1.size] = newWeapon; + + if ( self.gungamegunindex + 1 < var_0.size ) + var_1[var_1.size] = var_0[self.gungamegunindex + 1]; + + if ( self.gungamegunindex > 0 ) + var_1[var_1.size] = var_0[self.gungamegunindex - 1]; + + self loadweapons( var_1 ); + return newWeapon; +} + +addattachments( weaponName ) +{ + if ( getdvarint( "scr_gun_loot_variants", 0 ) == 1 ) + { + var_1 = tablelookup( "mp/statstable.csv", 4, weaponName , 40 ); + + if ( isdefined( var_1 ) && var_1 != "" ) + var_2 = maps\mp\gametypes\_class::buildweaponname( weaponName , var_1, "none", "none", 0, 0 ); + else + var_2 = maps\mp\gametypes\_class::buildweaponname( weaponName , "none", "none", "none", 0, 0 ); + } + else + var_2 = maps\mp\gametypes\_class::buildweaponname( weaponName , "none", "none", "none", 0, 0 ); + + return var_2; +} + +ontimelimit() +{ + level.finalkillcam_winner = "none"; + winners = gethighestprogressedplayers(); + + if ( !isdefined( winners ) || !winners.size ) + thread maps\mp\gametypes\_gamelogic::endgame( "tie", game["end_reason"]["time_limit_reached"] ); + else if ( winners.size == 1 ) + thread maps\mp\gametypes\_gamelogic::endgame( winners[0], game["end_reason"]["time_limit_reached"] ); + else if ( winners[winners.size - 1].gungamegunindex > winners[winners.size - 2].gungamegunindex ) + thread maps\mp\gametypes\_gamelogic::endgame( winners[winners.size - 1], game["end_reason"]["time_limit_reached"] ); + else + thread maps\mp\gametypes\_gamelogic::endgame( "tie", game["end_reason"]["time_limit_reached"] ); +} + +gethighestprogressedplayers() +{ + var_0 = -1; + var_1 = []; + + foreach ( var_3 in level.players ) + { + if ( isdefined( var_3.gungamegunindex ) && var_3.gungamegunindex >= var_0 ) + { + var_0 = var_3.gungamegunindex; + var_1[var_1.size] = var_3; + } + } + + return var_1; +} + +refillammo() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + for (;;) + { + self waittill( "reload" ); + self givestartammo( self.primaryweapon ); + } +} + +refillsinglecountammo() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + for (;;) + { + if ( maps\mp\_utility::isreallyalive( self ) && self.team != "spectator" && isdefined( self.primaryweapon ) && self getammocount( self.primaryweapon ) == 0 ) + { + wait 2; + self notify( "reload" ); + wait 1; + continue; + } + + wait 0.05; + } +} + +setguns() +{ + var_0 = getdvarint( "scr_gun_loot_variants", 0 ); + level.gun_guns = []; + level.gun_guns[0] = "iw5_asm1"; + level.gun_guns[1] = "iw5_asaw"; + level.gun_guns[2] = "iw5_himar"; + level.gun_guns[3] = "iw5_kf5"; + level.gun_guns[4] = "iw5_hbra3"; + level.gun_guns[5] = "iw5_mp11"; + level.gun_guns[6] = "iw5_ak12"; + level.gun_guns[7] = "iw5_sn6"; + level.gun_guns[8] = "iw5_arx160"; + level.gun_guns[9] = "iw5_hmr9"; + level.gun_guns[10] = "iw5_maul"; + level.gun_guns[11] = "iw5_dlcgun3"; + level.gun_guns[12] = "iw5_em1"; + level.gun_guns[13] = "iw5_uts19"; + level.gun_guns[14] = "iw5_lsat"; + level.gun_guns[15] = "iw5_rhino"; + level.gun_guns[16] = "iw5_exoxmg"; + level.gun_guns[17] = "iw5_epm3"; + level.gun_guns[18] = "iw5_mors"; + level.gun_guns[19] = "iw5_rw1"; + level.gun_guns[20] = "iw5_vbr"; + level.gun_guns[21] = "iw5_pbw"; + level.gun_guns[22] = "iw5_thor"; + level.gun_guns[23] = "iw5_mahem"; + level.gun_guns[24] = "iw5_exocrossbow"; + + if ( isdefined( var_0 ) && var_0 ) + { + for ( var_1 = 0; var_1 < level.gun_guns.size; var_1++ ) + { + var_2 = level.gun_guns[var_1]; + + if ( maps\mp\_utility::getweaponclass( var_2 ) == "weapon_projectile" || maps\mp\_utility::getweaponclass( var_2 ) == "weapon_sec_special" ) + var_2 = assign_random_loot_variant( var_2, 4 ); + else + var_2 = assign_random_loot_variant( var_2, 10 ); + + level.gun_guns[var_1] = var_2; + } + } +} + +assign_random_loot_variant( var_0, var_1 ) +{ + var_2 = randomint( var_1 ); + + switch ( var_2 ) + { + case 0: + var_0 += "loot0"; + break; + case 1: + var_0 += "loot1"; + break; + case 2: + var_0 += "loot2"; + break; + case 3: + var_0 += "loot3"; + break; + case 4: + var_0 += "loot4"; + break; + case 5: + var_0 += "loot5"; + break; + case 6: + var_0 += "loot6"; + break; + case 7: + var_0 += "loot7"; + break; + case 8: + var_0 += "loot8"; + break; + case 9: + var_0 += "loot9"; + break; + default: + break; + } + + return var_0; +} + +setspecialloadout() +{ + level.gun_loadout = maps\mp\gametypes\_class::getemptyloadout(); + + if ( maps\mp\gametypes\_class::isvalidprimary( level.gun_guns[0] ) ) + level.gun_loadout["loadoutPrimary"] = level.gun_guns[0]; + else if ( maps\mp\gametypes\_class::isvalidsecondary( level.gun_guns[0], 0 ) ) + level.gun_loadout["loadoutSecondary"] = level.gun_guns[0]; +} diff --git a/data/ui_scripts/patches/__init__.lua b/data/ui_scripts/patches/__init__.lua new file mode 100644 index 0000000..d0f4ffc --- /dev/null +++ b/data/ui_scripts/patches/__init__.lua @@ -0,0 +1,19 @@ +if (game:issingleplayer()) then + return +end + +function GetGameModeName() + return Engine.Localize(Engine.TableLookup(GameTypesTable.File, GameTypesTable.Cols.Ref, GameX.GetGameMode(), GameTypesTable.Cols.Name)) +end + +-- Allow players to change teams in game. +function CanChangeTeam() + local f9_local0 = GameX.GetGameMode() + local f9_local1 + if f9_local0 ~= "aliens" and Engine.TableLookup( GameTypesTable.File, GameTypesTable.Cols.Ref, f9_local0, GameTypesTable.Cols.TeamChoice ) == "1" then + f9_local1 = not MLG.IsMLGSpectator() + else + f9_local1 = false + end + return f9_local1 +end diff --git a/deps/GSL b/deps/GSL index 517ed29..cbf5e66 160000 --- a/deps/GSL +++ b/deps/GSL @@ -1 +1 @@ -Subproject commit 517ed29228d18cf2c5004d10826090108e06f049 +Subproject commit cbf5e664fc55cbcc33b10f3058ede325cd133a01 diff --git a/deps/asmjit b/deps/asmjit index 0c03ed2..e136425 160000 --- a/deps/asmjit +++ b/deps/asmjit @@ -1 +1 @@ -Subproject commit 0c03ed2f7497441ac0de232bda2e6b8cc041b2dc +Subproject commit e13642567393a4bfc8c5607b33337b2ed3b0d01e diff --git a/deps/libtomcrypt b/deps/libtomcrypt index 3474ca3..29986d0 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit 3474ca37124c6fe78f5461876542e226a25b5f1f +Subproject commit 29986d04f2dca985ee64fbca1c7431ea3e3422f4 diff --git a/deps/lua b/deps/lua index be908a7..d69789d 160000 --- a/deps/lua +++ b/deps/lua @@ -1 +1 @@ -Subproject commit be908a7d4d8130264ad67c5789169769f824c5d1 +Subproject commit d69789da1ccfa4db7c241de6b471d6b729f1561e diff --git a/deps/minhook b/deps/minhook index 426cb68..49d03ad 160000 --- a/deps/minhook +++ b/deps/minhook @@ -1 +1 @@ -Subproject commit 426cb6880035ee3cceed05384bb3f2db01a20a15 +Subproject commit 49d03ad118cf7f6768c79a8f187e14b8f2a07f94 diff --git a/deps/protobuf b/deps/protobuf index 57786d1..8d5fded 160000 --- a/deps/protobuf +++ b/deps/protobuf @@ -1 +1 @@ -Subproject commit 57786d126249b5ed4f42b579047941805e742949 +Subproject commit 8d5fdedd42ef361dcfc1531fba4f33470273f375 diff --git a/deps/rapidjson b/deps/rapidjson index 06d58b9..012be85 160000 --- a/deps/rapidjson +++ b/deps/rapidjson @@ -1 +1 @@ -Subproject commit 06d58b9e848c650114556a23294d0b6440078c61 +Subproject commit 012be8528783cdbf4b7a9e64f78bd8f056b97e24 diff --git a/deps/sol2 b/deps/sol2 index f81643a..19898d8 160000 --- a/deps/sol2 +++ b/deps/sol2 @@ -1 +1 @@ -Subproject commit f81643aa0c0c507c0cd8400b8cfedc74a34a19f6 +Subproject commit 19898d8d3e6c3def33625082343428be1bb9387b diff --git a/deps/zlib b/deps/zlib index 04f42ce..02a6049 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit 04f42ceca40f73e2978b50e93806c2a18c1281fc +Subproject commit 02a6049eb3884c430268bb0fe3296d597a03174c diff --git a/src/client/component/branding.cpp b/src/client/component/branding.cpp index 27fa22e..99b6883 100644 --- a/src/client/component/branding.cpp +++ b/src/client/component/branding.cpp @@ -41,7 +41,7 @@ namespace branding } localized_strings::override("LUA_MENU_LEGAL_COPYRIGHT", "S1x: " VERSION); - dvars::override::Dvar_SetString("version", utils::string::va("S1x %s", VERSION)); + dvars::override::set_string("version", utils::string::va("S1x %s", VERSION)); ui_get_formatted_build_number_hook.create( SELECT_VALUE(0x14035B3F0, 0x1404A8950), ui_get_formatted_build_number_stub); diff --git a/src/client/component/dedicated.cpp b/src/client/component/dedicated.cpp index 1246244..072f3ce 100644 --- a/src/client/component/dedicated.cpp +++ b/src/client/component/dedicated.cpp @@ -182,13 +182,13 @@ namespace dedicated sv_lanOnly = game::Dvar_RegisterBool("sv_lanOnly", false, game::DVAR_FLAG_NONE, "Don't send heartbeat"); // Disable VirtualLobby - dvars::override::Dvar_RegisterBool("virtualLobbyEnabled", false, game::DVAR_FLAG_NONE | game::DVAR_FLAG_READ); + dvars::override::register_bool("virtualLobbyEnabled", false, game::DVAR_FLAG_NONE | game::DVAR_FLAG_READ); // Disable r_preloadShaders - dvars::override::Dvar_RegisterBool("r_preloadShaders", false, game::DVAR_FLAG_NONE | game::DVAR_FLAG_READ); + dvars::override::register_bool("r_preloadShaders", false, game::DVAR_FLAG_NONE | game::DVAR_FLAG_READ); // Don't allow sv_hostname to be changed by the game - dvars::disable::Dvar_SetString("sv_hostname"); + dvars::disable::set_string("sv_hostname"); // Stop crashing from sys_errors utils::hook::jump(0x1404D6260, sys_error_stub); diff --git a/src/client/component/dvars.cpp b/src/client/component/dvars.cpp index 8ca77dc..2dd26b4 100644 --- a/src/client/component/dvars.cpp +++ b/src/client/component/dvars.cpp @@ -11,7 +11,6 @@ namespace dvars struct dvar_base { unsigned int flags{}; - std::string description{}; }; struct dvar_bool : dvar_base @@ -83,22 +82,22 @@ namespace dvars static std::unordered_set set_int_disables; static std::unordered_set set_string_disables; - void Dvar_SetBool(const std::string& name) + void set_bool(const std::string& name) { set_bool_disables.emplace(name); } - void Dvar_SetFloat(const std::string& name) + void set_float(const std::string& name) { set_float_disables.emplace(name); } - void Dvar_SetInt(const std::string& name) + void set_int(const std::string& name) { set_int_disables.emplace(name); } - void Dvar_SetString(const std::string& name) + void set_string(const std::string& name) { set_string_disables.emplace(name); } @@ -118,54 +117,43 @@ namespace dvars static std::unordered_map set_int_overrides; static std::unordered_map set_string_overrides; - void Dvar_RegisterBool(const std::string& name, const bool value, const unsigned int flags, - const std::string& description) + void register_bool(const std::string& name, const bool value, const unsigned int flags) { dvar_bool values; values.value = value; values.flags = flags; - values.description = description; register_bool_overrides[name] = std::move(values); } - void Dvar_RegisterFloat(const std::string& name, const float value, const float min, const float max, - const unsigned int flags, - const std::string& description) + void register_float(const std::string& name, const float value, const float min, const float max, const unsigned int flags) { dvar_float values; values.value = value; values.min = min; values.max = max; values.flags = flags; - values.description = description; register_float_overrides[name] = std::move(values); } - void Dvar_RegisterInt(const std::string& name, const int value, const int min, const int max, - const unsigned int flags, - const std::string& description) + void register_int(const std::string& name, const int value, const int min, const int max, const unsigned int flags) { dvar_int values; values.value = value; values.min = min; values.max = max; values.flags = flags; - values.description = description; register_int_overrides[name] = std::move(values); } - void Dvar_RegisterString(const std::string& name, const std::string& value, const unsigned int flags, - const std::string& description) + void register_string(const std::string& name, const std::string& value, const unsigned int flags) { dvar_string values; values.value = value; values.flags = flags; - values.description = description; register_string_overrides[name] = std::move(values); } - void Dvar_RegisterVector2(const std::string& name, float x, float y, float min, float max, - const unsigned int flags, const std::string& description) + void register_vector2(const std::string& name, float x, float y, float min, float max, const unsigned int flags) { dvar_vector2 values; values.x = x; @@ -173,12 +161,10 @@ namespace dvars values.min = min; values.max = max; values.flags = flags; - values.description = description; register_vector2_overrides[name] = std::move(values); } - void Dvar_RegisterVector3(const std::string& name, float x, float y, float z, float min, - float max, const unsigned int flags, const std::string& description) + void register_vector3(const std::string& name, float x, float y, float z, float min, float max, const unsigned int flags) { dvar_vector3 values; values.x = x; @@ -187,26 +173,25 @@ namespace dvars values.min = min; values.max = max; values.flags = flags; - values.description = description; register_vector3_overrides[name] = std::move(values); } - void Dvar_SetBool(const std::string& name, const bool value) + void set_bool(const std::string& name, const bool value) { set_bool_overrides[name] = value; } - void Dvar_SetFloat(const std::string& name, const float value) + void set_float(const std::string& name, const float value) { set_float_overrides[name] = value; } - void Dvar_SetInt(const std::string& name, const int value) + void set_int(const std::string& name, const int value) { set_int_overrides[name] = value; } - void Dvar_SetString(const std::string& name, const std::string& value) + void set_string(const std::string& name, const std::string& value) { set_string_overrides[name] = value; } @@ -224,20 +209,19 @@ namespace dvars utils::hook::detour dvar_set_int_hook; utils::hook::detour dvar_set_string_hook; - game::dvar_t* dvar_register_bool(const char* name, bool value, unsigned int flags, const char* description) + game::dvar_t* dvar_register_bool_stub(const char* name, bool value, unsigned int flags, const char* description) { auto* var = find_dvar(override::register_bool_overrides, name); if (var) { value = var->value; flags = var->flags; - description = var->description.data(); } return dvar_register_bool_hook.invoke(name, value, flags, description); } - game::dvar_t* dvar_register_float(const char* name, float value, float min, float max, unsigned int flags, + game::dvar_t* dvar_register_float_stub(const char* name, float value, float min, float max, unsigned int flags, const char* description) { auto* var = find_dvar(override::register_float_overrides, name); @@ -247,13 +231,12 @@ namespace dvars min = var->min; max = var->max; flags = var->flags; - description = var->description.data(); } return dvar_register_float_hook.invoke(name, value, min, max, flags, description); } - game::dvar_t* dvar_register_int(const char* name, int value, int min, int max, unsigned int flags, + game::dvar_t* dvar_register_int_stub(const char* name, int value, int min, int max, unsigned int flags, const char* description) { auto* var = find_dvar(override::register_int_overrides, name); @@ -263,26 +246,24 @@ namespace dvars min = var->min; max = var->max; flags = var->flags; - description = var->description.data(); } return dvar_register_int_hook.invoke(name, value, min, max, flags, description); } - game::dvar_t* dvar_register_string(const char* name, const char* value, unsigned int flags, const char* description) + game::dvar_t* dvar_register_string_stub(const char* name, const char* value, unsigned int flags, const char* description) { auto* var = find_dvar(override::register_string_overrides, name); if (var) { value = var->value.data(); flags = var->flags; - description = var->description.data(); } return dvar_register_string_hook.invoke(name, value, flags, description); } - game::dvar_t* dvar_register_vector2(const char* name, float x, float y, float min, float max, + game::dvar_t* dvar_register_vector2_stub(const char* name, float x, float y, float min, float max, unsigned int flags, const char* description) { auto* var = find_dvar(override::register_vector2_overrides, name); @@ -293,13 +274,12 @@ namespace dvars min = var->min; max = var->max; flags = var->flags; - description = var->description.data(); } return dvar_register_vector2_hook.invoke(name, x, y, min, max, flags, description); } - game::dvar_t* dvar_register_vector3(const char* name, float x, float y, float z, float min, + game::dvar_t* dvar_register_vector3_stub(const char* name, float x, float y, float z, float min, float max, unsigned int flags, const char* description) { auto* var = find_dvar(override::register_vector3_overrides, name); @@ -311,13 +291,12 @@ namespace dvars min = var->min; max = var->max; flags = var->flags; - description = var->description.data(); } return dvar_register_vector3_hook.invoke(name, x, y, z, min, max, flags, description); } - void dvar_set_bool(game::dvar_t* dvar, bool boolean) + void dvar_set_bool_stub(game::dvar_t* dvar, bool boolean) { const auto disabled = find_dvar(disable::set_bool_disables, dvar->name); if (disabled) @@ -334,7 +313,7 @@ namespace dvars return dvar_set_bool_hook.invoke(dvar, boolean); } - void dvar_set_float(game::dvar_t* dvar, float fl) + void dvar_set_float_stub(game::dvar_t* dvar, float fl) { const auto disabled = find_dvar(disable::set_float_disables, dvar->name); if (disabled) @@ -351,7 +330,7 @@ namespace dvars return dvar_set_float_hook.invoke(dvar, fl); } - void dvar_set_int(game::dvar_t* dvar, int integer) + void dvar_set_int_stub(game::dvar_t* dvar, int integer) { const auto disabled = find_dvar(disable::set_int_disables, dvar->name); if (disabled) @@ -368,7 +347,7 @@ namespace dvars return dvar_set_int_hook.invoke(dvar, integer); } - void dvar_set_string(game::dvar_t* dvar, const char* string) + void dvar_set_string_stub(game::dvar_t* dvar, const char* string) { const auto disabled = find_dvar(disable::set_string_disables, dvar->name); if (disabled) @@ -390,17 +369,17 @@ namespace dvars public: void post_unpack() override { - dvar_register_bool_hook.create(SELECT_VALUE(0x140371850, 0x1404C0BE0), &dvar_register_bool); - dvar_register_float_hook.create(SELECT_VALUE(0x140371C20, 0x1404C0FB0), &dvar_register_float); - dvar_register_int_hook.create(SELECT_VALUE(0x140371CF0, 0x1404C1080), &dvar_register_int); - dvar_register_string_hook.create(SELECT_VALUE(0x140372050, 0x1404C1450), &dvar_register_string); - dvar_register_vector2_hook.create(SELECT_VALUE(0x140372120, 0x1404C1520), &dvar_register_vector2); - dvar_register_vector3_hook.create(SELECT_VALUE(0x140372230, 0x1404C1600), &dvar_register_vector3); + dvar_register_bool_hook.create(SELECT_VALUE(0x140371850, 0x1404C0BE0), &dvar_register_bool_stub); + dvar_register_float_hook.create(SELECT_VALUE(0x140371C20, 0x1404C0FB0), &dvar_register_float_stub); + dvar_register_int_hook.create(SELECT_VALUE(0x140371CF0, 0x1404C1080), &dvar_register_int_stub); + dvar_register_string_hook.create(SELECT_VALUE(0x140372050, 0x1404C1450), &dvar_register_string_stub); + dvar_register_vector2_hook.create(SELECT_VALUE(0x140372120, 0x1404C1520), &dvar_register_vector2_stub); + dvar_register_vector3_hook.create(SELECT_VALUE(0x140372230, 0x1404C1600), &dvar_register_vector3_stub); - dvar_set_bool_hook.create(SELECT_VALUE(0x140372B70, 0x1404C1F30), &dvar_set_bool); - dvar_set_float_hook.create(SELECT_VALUE(0x140373420, 0x1404C2A10), &dvar_set_float); - dvar_set_int_hook.create(SELECT_VALUE(0x1403738D0, 0x1404C2F40), &dvar_set_int); - dvar_set_string_hook.create(SELECT_VALUE(0x140373DE0, 0x1404C3610), &dvar_set_string); + dvar_set_bool_hook.create(SELECT_VALUE(0x140372B70, 0x1404C1F30), &dvar_set_bool_stub); + dvar_set_float_hook.create(SELECT_VALUE(0x140373420, 0x1404C2A10), &dvar_set_float_stub); + dvar_set_int_hook.create(SELECT_VALUE(0x1403738D0, 0x1404C2F40), &dvar_set_int_stub); + dvar_set_string_hook.create(SELECT_VALUE(0x140373DE0, 0x1404C3610), &dvar_set_string_stub); } }; } diff --git a/src/client/component/dvars.hpp b/src/client/component/dvars.hpp index 73af256..a0e5c52 100644 --- a/src/client/component/dvars.hpp +++ b/src/client/component/dvars.hpp @@ -4,24 +4,24 @@ namespace dvars { namespace disable { - void Dvar_SetBool(const std::string& name); - void Dvar_SetFloat(const std::string& name); - void Dvar_SetInt(const std::string& name); - void Dvar_SetString(const std::string& name); + void set_bool(const std::string& name); + void set_float(const std::string& name); + void set_int(const std::string& name); + void set_string(const std::string& name); } namespace override { - void Dvar_RegisterBool(const std::string& name, bool value, const unsigned int flags, const std::string& description = ""); - void Dvar_RegisterFloat(const std::string& name, float value, float min, float max, const unsigned int flags, const std::string& description = ""); - void Dvar_RegisterInt(const std::string& name, int value, int min, int max, const unsigned int flags, const std::string& description = ""); - void Dvar_RegisterString(const std::string& name, const std::string& value, const unsigned int flags, const std::string& description = ""); - void Dvar_RegisterVector2(const std::string& name, float x, float y, float min, float max, const unsigned int flags, const std::string& description = ""); - void Dvar_RegisterVector3(const std::string& name, float x, float y, float z, float min, float max, const unsigned int flags, const std::string& description = ""); + void register_bool(const std::string& name, bool value, unsigned int flags); + void register_float(const std::string& name, float value, float min, float max, unsigned int flags); + void register_int(const std::string& name, int value, int min, int max, unsigned int flags); + void register_string(const std::string& name, const std::string& value, unsigned int flags); + void register_vector2(const std::string& name, float x, float y, float min, float max, unsigned int flags); + void register_vector3(const std::string& name, float x, float y, float z, float min, float max, unsigned int flags); - void Dvar_SetBool(const std::string& name, bool boolean); - void Dvar_SetFloat(const std::string& name, float fl); - void Dvar_SetInt(const std::string& name, int integer); - void Dvar_SetString(const std::string& name, const std::string& string); + void set_bool(const std::string& name, bool boolean); + void set_float(const std::string& name, float fl); + void set_int(const std::string& name, int integer); + void set_string(const std::string& name, const std::string& string); } } diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index d4617d8..debeb73 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -233,7 +233,7 @@ namespace filesystem void post_unpack() override { // Set fs_basegame - dvars::override::Dvar_RegisterString("fs_basegame", "s1x", game::DVAR_FLAG_WRITE); + dvars::override::register_string("fs_basegame", "s1x", game::DVAR_FLAG_WRITE); if (game::environment::is_sp()) { diff --git a/src/client/component/fps.cpp b/src/client/component/fps.cpp index 3942304..8edd2ed 100644 --- a/src/client/component/fps.cpp +++ b/src/client/component/fps.cpp @@ -173,7 +173,7 @@ namespace fps scheduler::loop(cg_draw_fps, scheduler::pipeline::renderer); if (game::environment::is_mp()) { - cg_drawPing = game::Dvar_RegisterInt("cg_drawPing", 0, 0, 1, game::DVAR_FLAG_SAVED, "Choose to draw ping"); + cg_drawPing = game::Dvar_RegisterInt("cg_drawPing", 0, 0, 1, game::DVAR_FLAG_SAVED, "Draw ping"); scheduler::loop(cg_draw_ping, scheduler::pipeline::renderer); } diff --git a/src/client/component/gsc/script_loading.cpp b/src/client/component/gsc/script_loading.cpp index ba8d24c..1b958f1 100644 --- a/src/client/component/gsc/script_loading.cpp +++ b/src/client/component/gsc/script_loading.cpp @@ -121,7 +121,7 @@ namespace gsc return nullptr; } - const auto script_file_ptr = static_cast(game::PMem_AllocFromSource_NoDebug(sizeof(game::ScriptFile), 4, 1, 5)); + const auto script_file_ptr = static_cast(game::Hunk_AllocateTempMemoryHighInternal(sizeof(game::ScriptFile))); script_file_ptr->name = file_name; const auto stack = assembler->output_stack(); @@ -130,16 +130,15 @@ namespace gsc const auto script = assembler->output_script(); script_file_ptr->bytecodeLen = static_cast(script.size()); - const auto script_size = script.size(); - // Use PMem for both stack and byte code - const auto buffer_size = script_size + stack.size() + 2; + const auto stack_size = static_cast(stack.size() + 1); + const auto byte_code_size = static_cast(script.size() + 1); - const auto buffer = static_cast(game::PMem_AllocFromSource_NoDebug(static_cast(buffer_size), 4, 1, 5)); - std::memcpy(buffer, script.data(), script_size); - std::memcpy(&buffer[script_size], stack.data(), stack.size()); + script_file_ptr->buffer = static_cast(game::Hunk_AllocateTempMemoryHighInternal(stack_size)); + std::memcpy(const_cast(script_file_ptr->buffer), stack.data(), stack.size()); + + script_file_ptr->bytecode = static_cast(game::PMem_AllocFromSource_NoDebug(byte_code_size, 4, 1, 5)); + std::memcpy(script_file_ptr->bytecode, script.data(), script.size()); - script_file_ptr->bytecode = &buffer[0]; - script_file_ptr->buffer = reinterpret_cast(&buffer[script.size()]); script_file_ptr->compressedLen = 0; loaded_scripts[real_name] = script_file_ptr; diff --git a/src/client/component/map_rotation.cpp b/src/client/component/map_rotation.cpp index f21904c..10a57f9 100644 --- a/src/client/component/map_rotation.cpp +++ b/src/client/component/map_rotation.cpp @@ -5,35 +5,40 @@ #include "command.hpp" #include "console.hpp" #include "scheduler.hpp" +#include "map_rotation.hpp" #include #include namespace map_rotation { - DWORD previousPriority; namespace { - void set_dvar(const std::string& dvar, const std::string& value) - { - command::execute(utils::string::va("%s \"%s\"", dvar.data(), value.data()), true); - } + rotation_data dedicated_rotation; + + const game::dvar_t* sv_map_rotation; + const game::dvar_t* sv_map_rotation_current; + const game::dvar_t* sv_random_map_rotation; void set_gametype(const std::string& gametype) { - set_dvar("g_gametype", gametype); + assert(!gametype.empty()); + + auto* g_gametype = game::Dvar_FindVar("g_gametype"); + game::Dvar_SetString(g_gametype, gametype.data()); } void launch_map(const std::string& mapname) { + assert(!mapname.empty()); + command::execute(utils::string::va("map %s", mapname.data()), false); } void launch_default_map() { auto* mapname = game::Dvar_FindVar("mapname"); - if (mapname && mapname->current.string && strlen(mapname->current.string) && mapname->current.string != - "mp_vlobby_room"s) + if (mapname && std::strcmp(mapname->current.string, "mp_vlobby_room") != 0) { launch_map(mapname->current.string); } @@ -50,55 +55,113 @@ namespace map_rotation } } - std::string load_current_map_rotation() + void apply_rotation(rotation_data& rotation) { - auto* rotation = game::Dvar_FindVar("sv_mapRotationCurrent"); - if (!strlen(rotation->current.string)) + assert(!rotation.empty()); + + std::size_t i = 0; + while (i < rotation.get_entries_size()) { - rotation = game::Dvar_FindVar("sv_mapRotation"); - set_dvar("sv_mapRotationCurrent", rotation->current.string); - } - - return rotation->current.string; - } - - std::vector parse_current_map_rotation() - { - const auto rotation = load_current_map_rotation(); - return utils::string::split(rotation, ' '); - } - - void store_new_rotation(const std::vector& elements, const size_t index) - { - std::string value{}; - - for (auto i = index; i < elements.size(); ++i) - { - if (i != index) + const auto& entry = rotation.get_next_entry(); + if (entry.first == "map"s) { - value.push_back(' '); + console::info("Loading new map: '%s'\n", entry.second.data()); + if (!game::SV_MapExists(entry.second.data())) + { + console::info("map_rotation: '%s' map doesn't exist!\n", entry.second.data()); + launch_default_map(); + return; + } + + launch_map(entry.second); + + // Map was found so we exit the loop + break; } - value.append(elements[i]); + if (entry.first == "gametype"s) + { + console::info("Applying new gametype: '%s'\n", entry.second.data()); + set_gametype(entry.second); + } + + ++i; } - set_dvar("sv_mapRotationCurrent", value); + if (i == rotation.get_entries_size()) + { + console::error("Map rotation does not contain any map. Restarting\n"); + launch_default_map(); + } } - void change_process_priority() + void load_rotation(const std::string& data) { - auto* const dvar = game::Dvar_FindVar("sv_autoPriority"); - if (dvar && dvar->current.enabled) + static auto loaded = false; + if (loaded) { - scheduler::on_game_initialized([]() - { - //printf("=======================setting OLD priority=======================\n"); - SetPriorityClass(GetCurrentProcess(), previousPriority); - }, scheduler::pipeline::main, 1s); + return; + } - previousPriority = GetPriorityClass(GetCurrentProcess()); - //printf("=======================setting NEW priority=======================\n"); - SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); + loaded = true; + try + { + dedicated_rotation.parse(data); + } + catch (const std::exception& ex) + { + console::error("%s: %s contains invalid data!\n", ex.what(), sv_map_rotation->name); + } +#ifdef _DEBUG + console::info("dedicated_rotation size after parsing is '%llu'", dedicated_rotation.get_entries_size()); +#endif + } + + void load_map_rotation() + { + const std::string map_rotation = sv_map_rotation->current.string; + if (!map_rotation.empty()) + { +#ifdef _DEBUG + console::info("%s is not empty. Parsing...\n", sv_map_rotation->name); +#endif + load_rotation(map_rotation); + } + } + + void apply_map_rotation_current(const std::string& data) + { + assert(!data.empty()); + + rotation_data rotation_current; + + try + { + rotation_current.parse(data); + } + catch (const std::exception& ex) + { + console::error("%s: %s contains invalid data!\n", ex.what(), sv_map_rotation_current->name); + } + + game::Dvar_SetString(sv_map_rotation_current, ""); + + if (rotation_current.empty()) + { + console::warn("%s is empty or contains invalid data\n", sv_map_rotation_current->name); + launch_default_map(); + return; + } + + apply_rotation(rotation_current); + } + + void randomize_map_rotation() + { + if (sv_random_map_rotation->current.enabled) + { + console::info("Randomizing the map rotation\n"); + dedicated_rotation.randomize(); } } @@ -110,42 +173,35 @@ namespace map_rotation return; } - const auto rotation = parse_current_map_rotation(); + console::info("Rotating map...\n"); - for (size_t i = 0; !rotation.empty() && i < (rotation.size() - 1); i += 2) + // This takes priority because of backwards compatibility + const std::string map_rotation_current = sv_map_rotation_current->current.string; + if (!map_rotation_current.empty()) { - const auto& key = rotation[i]; - const auto& value = rotation[i + 1]; - - if (key == "gametype") - { - set_gametype(value); - } - else if (key == "map") - { - store_new_rotation(rotation, i + 2); - change_process_priority(); - if (!game::SV_MapExists(value.data())) - { - console::info("map_rotation: '%s' map doesn't exist!\n", value.data()); - launch_default_map(); - return; - } - launch_map(value); - return; - } - else - { - console::info("Invalid map rotation key: %s\n", key.data()); - } +#ifdef _DEBUG + console::info("Applying %s\n", sv_map_rotation_current->name); +#endif + apply_map_rotation_current(map_rotation_current); + return; } - launch_default_map(); + load_map_rotation(); + if (dedicated_rotation.empty()) + { + console::warn("%s is empty or contains invalid data. Restarting map\n", sv_map_rotation->name); + launch_default_map(); + return; + } + + randomize_map_rotation(); + + apply_rotation(dedicated_rotation); } void trigger_map_rotation() { - scheduler::schedule([]() + scheduler::schedule([] { if (game::CL_IsCgameInitialized()) { @@ -159,6 +215,68 @@ namespace map_rotation } + rotation_data::rotation_data() + : index_(0) + { + } + + void rotation_data::randomize() + { + std::random_device rd; + std::mt19937 gen(rd()); + + std::ranges::shuffle(this->rotation_entries_, gen); + } + + void rotation_data::add_entry(const std::string& key, const std::string& value) + { + this->rotation_entries_.emplace_back(std::make_pair(key, value)); + } + + bool rotation_data::contains(const std::string& key, const std::string& value) const + { + return std::ranges::any_of(this->rotation_entries_, [&](const auto& entry) + { + return entry.first == key && entry.second == value; + }); + } + + bool rotation_data::empty() const noexcept + { + return this->rotation_entries_.empty(); + } + + std::size_t rotation_data::get_entries_size() const noexcept + { + return this->rotation_entries_.size(); + } + + rotation_data::rotation_entry& rotation_data::get_next_entry() + { + const auto index = this->index_; + ++this->index_ %= this->rotation_entries_.size(); + return this->rotation_entries_.at(index); + } + + void rotation_data::parse(const std::string& data) + { + const auto tokens = utils::string::split(data, ' '); + for (std::size_t i = 0; !tokens.empty() && i < (tokens.size() - 1); i += 2) + { + const auto& key = tokens[i]; + const auto& value = tokens[i + 1]; + + if (key == "map"s || key == "gametype"s) + { + this->add_entry(key, value); + } + else + { + throw map_rotation_parse_error(); + } + } + } + class component final : public component_interface { public: @@ -169,19 +287,18 @@ namespace map_rotation return; } - scheduler::once([]() + scheduler::once([] { - game::Dvar_RegisterString("sv_mapRotation", "", game::DVAR_FLAG_NONE, ""); - game::Dvar_RegisterString("sv_mapRotationCurrent", "", game::DVAR_FLAG_NONE, ""); - game::Dvar_RegisterBool("sv_autoPriority", true, game::DVAR_FLAG_NONE, "Lowers the process priority during map changes to not cause lags on other servers."); + sv_map_rotation = game::Dvar_RegisterString("sv_mapRotation", "", game::DVAR_FLAG_NONE, ""); + sv_map_rotation_current = game::Dvar_RegisterString("sv_mapRotationCurrent", "", game::DVAR_FLAG_NONE, ""); }, scheduler::pipeline::main); + sv_random_map_rotation = game::Dvar_RegisterBool("sv_randomMapRotation", false, game::DVAR_FLAG_NONE, "Randomize map rotation"); + command::add("map_rotate", &perform_map_rotation); // Hook GScr_ExitLevel utils::hook::jump(0x14032E490, &trigger_map_rotation); - - previousPriority = GetPriorityClass(GetCurrentProcess()); } }; } diff --git a/src/client/component/map_rotation.hpp b/src/client/component/map_rotation.hpp new file mode 100644 index 0000000..c1620c8 --- /dev/null +++ b/src/client/component/map_rotation.hpp @@ -0,0 +1,34 @@ +#pragma once + +namespace map_rotation +{ + struct map_rotation_parse_error : public std::exception + { + [[nodiscard]] const char* what() const noexcept override { return "Map Rotation Parse Erro"; } + }; + + class rotation_data + { + public: + using rotation_entry = std::pair; + + rotation_data(); + + void randomize(); + + // In case a new way to enrich the map rotation is added (other than sv_mapRotation) + // this method should be called to add a new entry (gamemode/map & value) + void add_entry(const std::string& key, const std::string& value); + + [[nodiscard]] bool contains(const std::string& key, const std::string& value) const; + [[nodiscard]] bool empty() const noexcept; + [[nodiscard]] std::size_t get_entries_size() const noexcept; + [[nodiscard]] rotation_entry& get_next_entry(); + + void parse(const std::string& data); + + private: + std::vector rotation_entries_; + std::size_t index_; + }; +} diff --git a/src/client/component/network.cpp b/src/client/component/network.cpp index a658c29..e813a1d 100644 --- a/src/client/component/network.cpp +++ b/src/client/component/network.cpp @@ -243,10 +243,10 @@ namespace network utils::hook::call(0x140439D98, &net_compare_address); // increase cl_maxpackets - dvars::override::Dvar_RegisterInt("cl_maxpackets", 1000, 1, 1000, game::DVAR_FLAG_SAVED); + dvars::override::register_int("cl_maxpackets", 1000, 1, 1000, game::DVAR_FLAG_SAVED); // increase snaps - dvars::override::Dvar_RegisterInt("sv_remote_client_snapshot_msec", 33, 33, 100, game::DVAR_FLAG_NONE); + dvars::override::register_int("sv_remote_client_snapshot_msec", 33, 33, 100, game::DVAR_FLAG_NONE); // ignore impure client utils::hook::jump(0x14043AC0D, reinterpret_cast(0x14043ACA3)); diff --git a/src/client/component/notifies.cpp b/src/client/component/notifies.cpp index 7b8d853..93b1e2a 100644 --- a/src/client/component/notifies.cpp +++ b/src/client/component/notifies.cpp @@ -187,13 +187,15 @@ namespace notifies message.erase(message.begin()); } - scheduler::once([params, message, client_num] + scheduler::once([params, message, msg_index, client_num] { const scripting::entity level{*game::levelEntityId}; const auto player = scripting::call("getEntByNum", {client_num}).as(); + // Remove \x1F before sending the notify only if present + const auto notify_msg = msg_index ? message.substr(1) : message; - notify(level, params[0], {player, message}); - notify(player, params[0], {message}); + notify(level, params[0], {player, notify_msg}); + notify(player, params[0], {notify_msg}); game_log::g_log_printf("%s;%s;%i;%s;%s\n", params[0], diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index ac4b754..2faf6de 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -286,15 +286,15 @@ namespace patches utils::hook::set(0x14023BDC0, 0xC3C033); // disable emblems - dvars::override::Dvar_RegisterInt("emblems_active", 0, 0, 0, game::DVAR_FLAG_NONE); + dvars::override::register_int("emblems_active", 0, 0, 0, game::DVAR_FLAG_NONE); utils::hook::set(0x140479590, 0xC3); // don't register commands // disable elite_clan - dvars::override::Dvar_RegisterInt("elite_clan_active", 0, 0, 0, game::DVAR_FLAG_NONE); + dvars::override::register_int("elite_clan_active", 0, 0, 0, game::DVAR_FLAG_NONE); utils::hook::set(0x14054AB20, 0xC3); // don't register commands // disable codPointStore - dvars::override::Dvar_RegisterInt("codPointStore_enabled", 0, 0, 0, game::DVAR_FLAG_NONE); + dvars::override::register_int("codPointStore_enabled", 0, 0, 0, game::DVAR_FLAG_NONE); // don't register every replicated dvar as a network dvar utils::hook::nop(0x1403534BE, 5); // dvar_foreach @@ -309,27 +309,27 @@ namespace patches utils::hook::set(0x14019B9B9, 0xEB); // some anti tamper thing that kills performance - dvars::override::Dvar_RegisterInt("dvl", 0, 0, 0, game::DVAR_FLAG_READ); + dvars::override::register_int("dvl", 0, 0, 0, game::DVAR_FLAG_READ); // unlock safeArea_* utils::hook::jump(0x140219F5E, 0x140219F67); utils::hook::jump(0x140219F80, 0x140219F8E); - dvars::override::Dvar_RegisterFloat("safeArea_adjusted_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED); - dvars::override::Dvar_RegisterFloat("safeArea_adjusted_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED); - dvars::override::Dvar_RegisterFloat("safeArea_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED); - dvars::override::Dvar_RegisterFloat("safeArea_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED); + dvars::override::register_float("safeArea_adjusted_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED); + dvars::override::register_float("safeArea_adjusted_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED); + dvars::override::register_float("safeArea_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED); + dvars::override::register_float("safeArea_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED); // move chat position on the screen above menu splashes - dvars::override::Dvar_RegisterVector2("cg_hudChatPosition", 5, 170, 0, 640, game::DVAR_FLAG_SAVED); + dvars::override::register_vector2("cg_hudChatPosition", 5, 170, 0, 640, game::DVAR_FLAG_SAVED); // allow servers to check for new packages more often - dvars::override::Dvar_RegisterInt("sv_network_fps", 1000, 20, 1000, game::DVAR_FLAG_SAVED); + dvars::override::register_int("sv_network_fps", 1000, 20, 1000, game::DVAR_FLAG_SAVED); // Massively increate timeouts - dvars::override::Dvar_RegisterInt("cl_timeout", 90, 90, 1800, game::DVAR_FLAG_NONE); // Seems unused - dvars::override::Dvar_RegisterInt("sv_timeout", 90, 90, 1800, game::DVAR_FLAG_NONE); // 30 - 0 - 1800 - dvars::override::Dvar_RegisterInt("cl_connectTimeout", 120, 120, 1800, game::DVAR_FLAG_NONE); // Seems unused - dvars::override::Dvar_RegisterInt("sv_connectTimeout", 120, 120, 1800, game::DVAR_FLAG_NONE); // 60 - 0 - 1800 + dvars::override::register_int("cl_timeout", 90, 90, 1800, game::DVAR_FLAG_NONE); // Seems unused + dvars::override::register_int("sv_timeout", 90, 90, 1800, game::DVAR_FLAG_NONE); // 30 - 0 - 1800 + dvars::override::register_int("cl_connectTimeout", 120, 120, 1800, game::DVAR_FLAG_NONE); // Seems unused + dvars::override::register_int("sv_connectTimeout", 120, 120, 1800, game::DVAR_FLAG_NONE); // 60 - 0 - 1800 game::Dvar_RegisterInt("scr_game_spectatetype", 1, 0, 99, game::DVAR_FLAG_REPLICATED, ""); diff --git a/src/client/component/ranked.cpp b/src/client/component/ranked.cpp index 0f2fdb7..238215b 100644 --- a/src/client/component/ranked.cpp +++ b/src/client/component/ranked.cpp @@ -23,12 +23,12 @@ namespace ranked if (game::environment::is_mp()) { - dvars::override::Dvar_RegisterBool("xblive_privatematch", false, game::DVAR_FLAG_REPLICATED); + dvars::override::register_bool("xblive_privatematch", false, game::DVAR_FLAG_REPLICATED); } if (game::environment::is_dedi() && !utils::flags::has_flag("unranked")) { - dvars::override::Dvar_RegisterBool("xblive_privatematch", false, game::DVAR_FLAG_REPLICATED | game::DVAR_FLAG_WRITE); + dvars::override::register_bool("xblive_privatematch", false, game::DVAR_FLAG_REPLICATED | game::DVAR_FLAG_WRITE); // Some dvar used in gsc game::Dvar_RegisterBool("force_ranking", true, game::DVAR_FLAG_WRITE, "Force ranking"); diff --git a/src/client/component/scripting.cpp b/src/client/component/scripting.cpp index 2446a0f..22adff8 100644 --- a/src/client/component/scripting.cpp +++ b/src/client/component/scripting.cpp @@ -91,6 +91,7 @@ namespace scripting { script_function_table_sort.clear(); script_function_table.clear(); + script_function_table_rev.clear(); canonical_string_table.clear(); } diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 4488a48..ed92c00 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -65,7 +65,8 @@ namespace game WEAK symbol Dvar_IsValidName{0x140370CB0, 0x1404BFF70}; WEAK symbol Dvar_Reset{0x140372950, 0x1404C1DB0}; WEAK symbol Dvar_SetCommand{0x1403730D0, 0x1404C2520}; - WEAK symbol Dvar_SetString{0x140373DE0, 0x1404C3610}; + WEAK symbol Dvar_SetString{0x140373DE0, 0x1404C3610}; + WEAK symbol Dvar_SetBool{0x0, 0x1404C1F30}; WEAK symbol Dvar_SetFromStringByNameFromSource{0x1403737D0, 0x1404C2E40}; WEAK symbol Dvar_ValueToString{0x140374E10, 0x1404C47B0}; @@ -217,8 +218,8 @@ namespace game WEAK symbol SEH_GetCurrentLanguageName{0x140339300, 0x1404745C0}; - WEAK symbol PMem_AllocFromSource_NoDebug{0x0, 0x1404C7BA0}; - WEAK symbol Hunk_AllocateTempMemoryHighInternal{0x0, 0x1404B68B0}; + WEAK symbol PMem_AllocFromSource_NoDebug{0x1403775F0, 0x1404C7BA0}; + WEAK symbol Hunk_AllocateTempMemoryHighInternal{0x140369D60, 0x1404B68B0}; WEAK symbol longjmp{0x14059C5C0, 0x1406FD930}; WEAK symbol _setjmp{0x14059CD00, 0x1406FE070}; diff --git a/src/client/launcher/html/html_frame.cpp b/src/client/launcher/html/html_frame.cpp index d844ffe..d904016 100644 --- a/src/client/launcher/html/html_frame.cpp +++ b/src/client/launcher/html/html_frame.cpp @@ -2,41 +2,65 @@ #include "html_frame.hpp" #include +#include std::atomic html_frame::frame_count_ = 0; +namespace +{ + void* original_func{}; + GUID browser_emulation_guid{ 0xac969931, 0x3566, 0x4b50, {0xae, 0x48, 0x71, 0xb9, 0x6a, 0x75, 0xc8, 0x79} }; + + int WINAPI co_internet_feature_value_internal_stub(const GUID* guid, uint32_t* result) + { + const auto res = static_cast(original_func)(guid, result); + + if (IsEqualGUID(*guid, browser_emulation_guid)) + { + *result = 11000; + return 0; + } + + return res; + } + + void setup_ie_hook() + { + static const auto _ = [] + { + const auto urlmon = utils::nt::library::load("urlmon.dll"s); + const auto target = urlmon.get_iat_entry("iertutil.dll", MAKEINTRESOURCEA(700)); + + original_func = *target; + utils::hook::set(target, co_internet_feature_value_internal_stub); + + return 0; + }(); + (void)_; + } +} + html_frame::callback_params::callback_params(DISPPARAMS* params, VARIANT* res) : result(res) { for (auto i = params->cArgs; i > 0; --i) { - auto* param = ¶ms->rgvarg[i - 1]; + auto param = ¶ms->rgvarg[i - 1]; this->arguments.emplace_back(param); } } -html_frame::html_frame() - : in_place_frame_(this) - , in_place_site_(this) - , ui_handler_(this) - , client_site_(this) - , html_dispatch_(this) +html_frame::html_frame() : in_place_frame_(this), in_place_site_(this), ui_handler_(this), client_site_(this), +html_dispatch_(this) { + setup_ie_hook(); if (frame_count_++ == 0 && OleInitialize(nullptr) != S_OK) { throw std::runtime_error("Unable to initialize the OLE library"); } - auto needs_restart = false; - needs_restart |= set_browser_feature("FEATURE_BROWSER_EMULATION", 11000); - needs_restart |= set_browser_feature("FEATURE_GPU_RENDERING", 1); - - if (needs_restart) - { - utils::nt::relaunch_self(); - utils::nt::terminate(0); - } + set_browser_feature("FEATURE_BROWSER_EMULATION", 11000); + set_browser_feature("FEATURE_GPU_RENDERING", 1); } - html_frame::~html_frame() { if (--frame_count_ <= 0) diff --git a/src/client/std_include.hpp b/src/client/std_include.hpp index 758cc18..8eaa713 100644 --- a/src/client/std_include.hpp +++ b/src/client/std_include.hpp @@ -55,25 +55,27 @@ #undef min #endif -#include -#include #include -#include -#include -#include -#include +#include #include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/common/utils/memory.cpp b/src/common/utils/memory.cpp index bda04ae..51e76e1 100644 --- a/src/common/utils/memory.cpp +++ b/src/common/utils/memory.cpp @@ -76,10 +76,7 @@ namespace utils void memory::free(void* data) { - if (data) - { - std::free(data); - } + std::free(data); } void memory::free(const void* data) diff --git a/src/common/utils/nt.cpp b/src/common/utils/nt.cpp index 5fc5273..4274758 100644 --- a/src/common/utils/nt.cpp +++ b/src/common/utils/nt.cpp @@ -15,7 +15,8 @@ namespace utils::nt library library::get_by_address(void* address) { HMODULE handle = nullptr; - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, static_cast(address), &handle); + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + static_cast(address), &handle); return library(handle); } @@ -157,6 +158,11 @@ namespace utils::nt } void** library::get_iat_entry(const std::string& module_name, const std::string& proc_name) const + { + return this->get_iat_entry(module_name, proc_name.data()); + } + + void** library::get_iat_entry(const std::string& module_name, const char* proc_name) const { if (!this->is_valid()) return nullptr; @@ -166,7 +172,7 @@ namespace utils::nt auto* const target_function = other_module.get_proc(proc_name); if (!target_function) return nullptr; - auto* header = this->get_optional_header(); + const auto* header = this->get_optional_header(); if (!header) return nullptr; auto* import_descriptor = reinterpret_cast(this->get_ptr() + header->DataDirectory @@ -183,16 +189,22 @@ namespace utils::nt while (original_thunk_data->u1.AddressOfData) { - const size_t ordinal_number = original_thunk_data->u1.AddressOfData & 0xFFFFFFF; - - if (ordinal_number > 0xFFFF) continue; - - if (GetProcAddress(other_module.module_, reinterpret_cast(ordinal_number)) == - target_function) + if (thunk_data->u1.Function == reinterpret_cast(target_function)) { return reinterpret_cast(&thunk_data->u1.Function); } + const size_t ordinal_number = original_thunk_data->u1.AddressOfData & 0xFFFFFFF; + + if (ordinal_number <= 0xFFFF) + { + auto* proc = GetProcAddress(other_module.module_, reinterpret_cast(ordinal_number)); + if (reinterpret_cast(proc) == target_function) + { + return reinterpret_cast(&thunk_data->u1.Function); + } + } + ++original_thunk_data; ++thunk_data; } diff --git a/src/common/utils/nt.hpp b/src/common/utils/nt.hpp index 86001be..df9a76c 100644 --- a/src/common/utils/nt.hpp +++ b/src/common/utils/nt.hpp @@ -59,6 +59,13 @@ namespace utils::nt return reinterpret_cast(GetProcAddress(this->module_, process.data())); } + template + T get_proc(const char* name) const + { + if (!this->is_valid()) T{}; + return reinterpret_cast(GetProcAddress(this->module_, name)); + } + template std::function get(const std::string& process) const { @@ -97,6 +104,7 @@ namespace utils::nt PIMAGE_OPTIONAL_HEADER get_optional_header() const; void** get_iat_entry(const std::string& module_name, const std::string& proc_name) const; + void** get_iat_entry(const std::string& module_name, const char* name) const; private: HMODULE module_; diff --git a/src/common/utils/string.cpp b/src/common/utils/string.cpp index a19c6ec..f8129d3 100644 --- a/src/common/utils/string.cpp +++ b/src/common/utils/string.cpp @@ -34,24 +34,26 @@ namespace utils::string return elems; } - std::string to_lower(std::string text) + std::string to_lower(const std::string& text) { - std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input) + std::string result; + std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input) { return static_cast(std::tolower(input)); }); - return text; + return result; } - std::string to_upper(std::string text) + std::string to_upper(const std::string& text) { - std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input) + std::string result; + std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input) { return static_cast(std::toupper(input)); }); - return text; + return result; } bool starts_with(const std::string& text, const std::string& substring) diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp index 28cea97..2757d2e 100644 --- a/src/common/utils/string.hpp +++ b/src/common/utils/string.hpp @@ -2,10 +2,8 @@ #include "memory.hpp" #include -#ifndef ARRAYSIZE template -size_t ARRAYSIZE(Type (&)[n]) { return n; } -#endif +constexpr auto ARRAY_COUNT(Type(&)[n]) { return n; } namespace utils::string { @@ -21,7 +19,7 @@ namespace utils::string char* get(const char* format, const va_list ap) { - ++this->current_buffer_ %= ARRAYSIZE(this->string_pool_); + ++this->current_buffer_ %= ARRAY_COUNT(this->string_pool_); auto entry = &this->string_pool_[this->current_buffer_]; if (!entry->size || !entry->buffer) @@ -82,8 +80,8 @@ namespace utils::string std::vector split(const std::string& s, char delim); - std::string to_lower(std::string text); - std::string to_upper(std::string text); + std::string to_lower(const std::string& text); + std::string to_upper(const std::string& text); bool starts_with(const std::string& text, const std::string& substring); bool ends_with(const std::string& text, const std::string& substring);