]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/team_keepaway
authorMario <mario.mario@y7mail.com>
Thu, 5 May 2022 12:06:25 +0000 (22:06 +1000)
committerMario <mario.mario@y7mail.com>
Thu, 5 May 2022 12:06:25 +0000 (22:06 +1000)
1  2 
gamemodes-server.cfg
qcsrc/common/scores.qh
qcsrc/common/stats.qh
qcsrc/server/world.qc

diff --combined gamemodes-server.cfg
index 41bf9f22dc8ec75ca720bdc57eaef0ed989c4281,ba4812b57afd8bdc4c5da48d31320788f122acb2..8108315b6a8ffcda572866fb958cc865b38a4eae
@@@ -29,7 -29,6 +29,7 @@@ alias sv_hook_gamestart_k
  alias sv_hook_gamestart_ft
  alias sv_hook_gamestart_inv
  alias sv_hook_gamestart_duel
 +alias sv_hook_gamestart_tka
  // there is currently no hook for when the match is restarted
  // see sv_hook_readyrestart for previous uses of this hook
  //alias sv_hook_gamerestart
@@@ -59,7 -58,6 +59,7 @@@ alias sv_vote_gametype_hook_on
  alias sv_vote_gametype_hook_rc
  alias sv_vote_gametype_hook_tdm
  alias sv_vote_gametype_hook_duel
 +alias sv_vote_gametype_hook_tka
  
  // Example preset to allow 1v1ctf to be used for the gametype voting screen.
  // Aliases can have max 31 chars so the gametype can have max 9 chars.
@@@ -210,13 -208,6 +210,13 @@@ set g_duel_respawn_delay_large_count 
  set g_duel_respawn_delay_max 0
  set g_duel_respawn_waves 0
  set g_duel_weapon_stay 0
 +set g_tka_respawn_delay_small 0
 +set g_tka_respawn_delay_small_count 0
 +set g_tka_respawn_delay_large 0
 +set g_tka_respawn_delay_large_count 0
 +set g_tka_respawn_delay_max 0
 +set g_tka_respawn_waves 0
 +set g_tka_weapon_stay 0
  
  
  // =========
@@@ -233,7 -224,7 +233,7 @@@ set g_ca_point_limit -1 "Clan Arena poi
  set g_ca_point_leadlimit -1 "Clan Arena point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
  set g_ca_spectate_enemies 0 "allow eliminated players to spectate enemy players during Clan Arena games"
  set g_ca_warmup 10 "time players get to run around before the round starts"
- set g_ca_damage2score_multiplier 0.01
+ set g_ca_damage2score 100  "every this amount of damage done give players 1 point"
  set g_ca_round_timelimit 180 "round time limit in seconds"
  set g_ca_teams_override 0
  set g_ca_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any"
@@@ -454,10 -445,13 +454,13 @@@ set g_keyhunt_team_spawns 0 "when 1, pl
  set g_lms 0 "Last Man Standing: everyone starts with a certain amount of lives, and the survivor wins"
  set g_lms_lives_override -1
  set g_lms_extra_lives 0
- set g_lms_regenerate 0
+ set g_lms_regenerate 0 "health and/or armor regeneration, according to g_balance_health_regen and g_balance_armor_regen"
+ set g_lms_rot 0 "health and/or armor rotting, according to g_balance_health_rot and g_balance_armor_rot"
  set g_lms_last_join 3 "if g_lms_join_anytime is 0, new players can only join if the worst active player has (fraglimit - g_lms_last_join) or more lives; in other words, new players can no longer join once the worst player loses more than g_lms_last_join lives"
  set g_lms_join_anytime 1      "1: new players can join, but get same amount of lives as the worst player; 0: new players can only join if the worst active player has (fraglimit - g_lms_last_join) or more lives"
+ set g_lms_items 0 "enables items to spawn, weaponarena still disables weapons and ammo (to force all items to spawn, use g_pickup_items 1 instead)"
  set g_lms_weaponarena "most_available" "starting weapons - takes the same options as g_weaponarena"
+ set g_lms_forfeit_min_match_time 30 "end the match early if at least this many seconds have elapsed and less than 2 players are playing due to forfeits"
  
  
  // =========
@@@ -568,34 -562,3 +571,34 @@@ set g_duel 0 "Duel: frag the opponent m
  //set g_duel_warmup 180 "Have a short warmup period before beginning the actual duel"
  set g_duel_with_powerups 0 "Enable powerups to spawn in the duel gamemode"
  set g_duel_not_dm_maps 0 "when this is set, DM maps will NOT be listed in duel"
 +
 +// ===============
 +//  team keepaway
 +// ===============
 +set g_tka 0 "another game mode which focuses around a ball"
 +set g_tka_on_dm_maps 0 "when this is set, all DM and KA maps automatically support TKA"
 +set g_tka_teams 2 "how many teams are in team keepaway (set by mapinfo)"
 +set g_tka_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any"
 +set g_tka_teams_override 0    "how many teams are in team keepaway"
 +set g_tka_point_limit -1 "TKA point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 +set g_tka_point_leadlimit -1 "TKA point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 +set g_tka_score_team 1 "allow points to be awarded to teammates for any kill when the ball is in your team's possession"
 +set g_tka_score_bckill 1 "points for killing the ball barrier (Ball Carrier Kill)"
 +set g_tka_score_killac 1 "points for kills while holding the ball (Kill As Carrier)"
 +set g_tka_score_timeinterval 1 "amount of time it takes between intervals for timepoints to be added to the score"
 +set g_tka_score_timepoints 0 "points to add to score per timeinterval, 0 for no points"
 +set g_tka_ballcarrier_effects 8 "Add together the numbers you want: EF_ADDITIVE (32) / EF_NODEPTHTEST (8192) / EF_DIMLIGHT (8)"
 +set g_tka_ballcarrier_highspeed 1 "speed multiplier done to the person holding the ball (recommended when used with some mutators)"
 +set g_tka_ballcarrier_damage  1       "damage multiplier while holding the ball"
 +set g_tka_ballcarrier_force   1       "force multiplier while holding the ball"
 +set g_tka_ballcarrier_selfdamage      1       "self damage multiplier while holding the ball"
 +set g_tka_ballcarrier_selfforce       1       "self force multiplier while holding the ball"
 +set g_tka_noncarrier_warn     1       "warn players when they kill without holding the ball"
 +set g_tka_noncarrier_damage   1       "damage done to other players if both you and they don't have the ball"
 +set g_tka_noncarrier_force    1       "force done to other players if both you and they don't have the ball"
 +set g_tka_noncarrier_selfdamage       1       "self damage if you don't have the ball"
 +set g_tka_noncarrier_selfforce        1       "self force if you don't have the ball"
 +set g_tkaball_effects 0 "Add together the numbers you want: EF_ADDITIVE (32) / EF_NODEPTHTEST (8192) / EF_DIMLIGHT (8)"
 +set g_tkaball_trail_color     254     "particle trail color from player/ball"
 +set g_tkaball_damageforcescale        3 "Scale of force which is applied to the ball by weapons/explosions/etc"
 +set g_tkaball_respawntime     10      "if no one picks up the ball, how long to wait until the ball respawns"
diff --combined qcsrc/common/scores.qh
index 0eed6ea4f5505d43f988f3ccdcaab5281c2ec3ce,45af93992aacdf33f7d8338867fe73a43807b2a3..f5889cfb305d3602572b2072ce358b6946541c7e
@@@ -15,7 -15,6 +15,6 @@@ STATIC_INIT(Scores_renumber) { FOREACH(
   * Score indices
   */
  
- // game mode specific indices are not in common/, but in server/scores_rules.qc!
  #ifdef GAMEQC
  // fields not networked via the score system
  REGISTER_SP(END);
@@@ -88,10 -87,6 +87,10 @@@ REGISTER_SP(NEXBALL_FAULTS)
  
  REGISTER_SP(ONS_TAKES);
  REGISTER_SP(ONS_CAPS);
 +
 +REGISTER_SP(TKA_PICKUPS);
 +REGISTER_SP(TKA_BCTIME);
 +REGISTER_SP(TKA_CARRIERKILLS);
  #endif
  
  
diff --combined qcsrc/common/stats.qh
index ecdc64cac5a8a03bf1e6063cd7900859d700315d,128f090c4fd9ec94ff1269b3336bda705be60c8e..36678050166e41b100d47757237e0f5703171036
@@@ -79,6 -79,9 +79,9 @@@ float game_stopped
  float game_starttime; //point in time when the countdown to game start is over
  float round_starttime; //point in time when the countdown to round start is over
  int autocvar_leadlimit;
+ int overtimes; // overtimes added (-1 = sudden death)
+ int timeout_status; // (values: 0, 1, 2) contains whether a timeout is not active (0), was called but still at leadtime (1) or is active (2)
  // TODO: world.qh can't be included here due to circular includes!
  #define autocvar_fraglimit cvar("fraglimit")
  #define autocvar_fraglimit_override cvar("fraglimit_override")
@@@ -91,7 -94,6 +94,6 @@@ REGISTER_STAT(GAMESTARTTIME, float, gam
  /** arc heat in [0,1] */
  REGISTER_STAT(PRESSED_KEYS, int)
  REGISTER_STAT(FUEL, int)
- REGISTER_STAT(NB_METERSTART, float)
  /** compressShotOrigin */
  REGISTER_STAT(SHOTORG, int)
  REGISTER_STAT(LEADLIMIT, float, autocvar_leadlimit)
@@@ -100,7 -102,7 +102,7 @@@ REGISTER_STAT(LEADLIMIT_AND_FRAGLIMIT, 
  REGISTER_STAT(LAST_PICKUP, float)
  REGISTER_STAT(HUD, int)
  REGISTER_STAT(HIT_TIME, float)
- REGISTER_STAT(DAMAGE_DEALT_TOTAL, int)
+ REGISTER_STAT(HITSOUND_DAMAGE_DEALT_TOTAL, int)
  REGISTER_STAT(TYPEHIT_TIME, float)
  REGISTER_STAT(AIR_FINISHED, float)
  REGISTER_STAT(VEHICLESTAT_HEALTH, int)
@@@ -116,6 -118,8 +118,8 @@@ REGISTER_STAT(SECRETS_TOTAL, int, secre
  REGISTER_STAT(SECRETS_FOUND, int, secrets_found)
  REGISTER_STAT(RESPAWN_TIME, float)
  REGISTER_STAT(ROUNDSTARTTIME, float, round_starttime)
+ REGISTER_STAT(OVERTIMES, int, overtimes)
+ REGISTER_STAT(TIMEOUT_STATUS, int, timeout_status)
  REGISTER_STAT(MONSTERS_TOTAL, int)
  REGISTER_STAT(MONSTERS_KILLED, int)
  REGISTER_STAT(NADE_BONUS, float)
@@@ -134,7 -138,6 +138,7 @@@ REGISTER_STAT(ITEMSTIME, int, autocvar_
  REGISTER_STAT(KILL_TIME, float)
  REGISTER_STAT(VEIL_ORB, float)
  REGISTER_STAT(VEIL_ORB_ALPHA, float)
 +REGISTER_STAT(TKA_BALLSTATUS, int)
  
  #ifdef SVQC
  float autocvar_sv_showfps = 0;
@@@ -339,6 -342,9 +343,9 @@@ REGISTER_STAT(DOM_PPS_BLUE, float
  REGISTER_STAT(DOM_PPS_YELLOW, float)
  REGISTER_STAT(DOM_PPS_PINK, float)
  
+ // nexball
+ REGISTER_STAT(NB_METERSTART, float)
  #ifdef SVQC
  float autocvar_g_teleport_maxspeed;
  #endif
@@@ -359,6 -365,8 +366,8 @@@ REGISTER_STAT(Q3COMPAT, int, q3compat
  #ifdef SVQC
  #include "physics/movetypes/movetypes.qh"
  float warmup_limit;
+ float round_limit;
+ int rounds_played;
  #endif
  
  #ifdef SVQC
@@@ -398,6 -406,8 +407,8 @@@ REGISTER_STAT(MOVEVARS_AIRCONTROL, floa
  REGISTER_STAT(FRAGLIMIT, float, autocvar_fraglimit)
  REGISTER_STAT(TIMELIMIT, float, autocvar_timelimit)
  REGISTER_STAT(WARMUP_TIMELIMIT, float, warmup_limit)
+ REGISTER_STAT(ROUNDS_PLAYED, int, rounds_played)
+ REGISTER_STAT(ROUND_TIMELIMIT, float, round_limit)
  #ifdef SVQC
  float autocvar_sv_wallfriction;
  #define autocvar_sv_gravity cvar("sv_gravity")
diff --combined qcsrc/server/world.qc
index 2d5ce09a7e170275dd0cc1d9e531b13e3df9f671,fca7c1741497e7a092245ad376744a1b3e4b630a..94ef8128e62efbed0e29d53eabfdb7e0a130bc7d
@@@ -301,9 -301,6 +301,9 @@@ void cvar_changes_init(
                BADCVAR("g_tdm");
                BADCVAR("g_tdm_on_dm_maps");
                BADCVAR("g_tdm_teams");
 +              BADCVAR("g_tka");
 +              BADCVAR("g_tka_on_dm_maps");
 +              BADCVAR("g_tka_teams");
                BADCVAR("g_vip");
                BADCVAR("leadlimit");
                BADCVAR("nextmap");
                BADCVAR("g_physics_clientselect");
                BADCVAR("g_pinata");
                BADCVAR("g_powerups");
+               BADCVAR("g_powerups_drop_ondeath");
                BADCVAR("g_player_brightness");
                BADCVAR("g_rocket_flying");
                BADCVAR("g_rocket_flying_disabledelays");
                BADCVAR("sv_maxrate");
                BADCVAR("sv_motd");
                BADCVAR("sv_public");
-               BADCVAR("sv_ready_restart");
                BADCVAR("sv_showfps");
+               BADCVAR("sv_showspectators");
                BADCVAR("sv_status_privacy");
                BADCVAR("sv_taunt");
                BADCVAR("sv_vote_call");
@@@ -692,6 -690,15 +693,15 @@@ spawnfunc(worldspawn
  {
        server_is_dedicated = boolean(stof(cvar_defstring("is_dedicated")));
  
+       if (autocvar_sv_termsofservice_url && autocvar_sv_termsofservice_url != "")
+       {
+               strcpy(sv_termsofservice_url_escaped, strreplace(":", "|", autocvar_sv_termsofservice_url));
+       }
+       else
+       {
+               strcpy(sv_termsofservice_url_escaped, "INVALID");
+       }
        bool wantrestart = false;
        {
                if (!server_is_dedicated)
  
        if(autocvar_g_campaign)
                CampaignPreInit();
+       else
+               PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode
  
        Map_MarkAsRecent(mapname);
  
-       PlayerStats_GameReport_Init(); // we need this to be initiated before InitGameplayMode
        InitGameplayMode();
        static_init_late();
        static_init_precache();
@@@ -1216,7 -1223,7 +1226,7 @@@ void DumpStats(float final
        FOREACH_CLIENT(IS_REAL_CLIENT(it) || (IS_BOT_CLIENT(it) && autocvar_sv_logscores_bots), {
                s = strcat(":player:see-labels:", GetPlayerScoreString(it, 0), ":");
                s = strcat(s, ftos(rint(time - CS(it).jointime)), ":");
-               if(IS_PLAYER(it) || MUTATOR_CALLHOOK(GetPlayerStatus, it))
+               if(IS_PLAYER(it) || INGAME_JOINED(it))
                        s = strcat(s, ftos(it.team), ":");
                else
                        s = strcat(s, "spectator:");
@@@ -1269,6 -1276,7 +1279,7 @@@ only called if a time or frag limit ha
  */
  void NextLevel()
  {
+       cvar_set("_endmatch", "0");
        game_stopped = true;
        intermission_running = true; // game over
  
  
        GameLogClose();
  
-       FOREACH_CLIENT(IS_PLAYER(it), {
+       int winner_team = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), {
                FixIntermissionClient(it);
                if(it.winning)
-                       bprint(playername(it.netname, it.team, false), " ^7wins.\n");
+               {
+                       if (teamplay && !winner_team)
+                       {
+                               winner_team = it.team;
+                               bprint(Team_ColorCode(winner_team), Team_ColorName_Upper(winner_team), "^7 team wins the match\n");
+                       }
+                       bprint(playername(it.netname, it.team, false), " ^7wins\n");
+               }
        });
  
        target_music_kill();
  }
  
  
float InitiateSuddenDeath()
int InitiateSuddenDeath()
  {
        // Check first whether normal overtimes could be added before initiating suddendeath mode
        // - for this timelimit_overtime needs to be >0 of course
                if(!checkrules_suddendeathend)
                {
                        if(autocvar_g_campaign)
+                       {
                                checkrules_suddendeathend = time; // no suddendeath in campaign
+                       }
                        else
+                       {
                                checkrules_suddendeathend = time + 60 * autocvar_timelimit_suddendeath;
+                               overtimes = -1;
+                       }
                        if(g_race && !g_race_qualifying)
                                race_StartCompleting();
                }
  void InitiateOvertime() // ONLY call this if InitiateSuddenDeath returned true
  {
        ++checkrules_overtimesadded;
+       overtimes = checkrules_overtimesadded;
        //add one more overtime by simply extending the timelimit
        cvar_set("timelimit", ftos(autocvar_timelimit + autocvar_timelimit_overtime));
        Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_OVERTIME_TIME, autocvar_timelimit_overtime * 60);
@@@ -1385,13 -1407,13 +1410,13 @@@ float GetWinningCode(float fraglimitrea
  // set the .winning flag for exactly those players with a given field value
  void SetWinners(.float field, float value)
  {
-       FOREACH_CLIENT(IS_PLAYER(it), { it.winning = (it.(field) == value); });
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { it.winning = (it.(field) == value); });
  }
  
  // set the .winning flag for those players with a given field value
  void AddWinners(.float field, float value)
  {
-       FOREACH_CLIENT(IS_PLAYER(it), {
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), {
                if(it.(field) == value)
                        it.winning = 1;
        });
  // clear the .winning flags
  void ClearWinners()
  {
-       FOREACH_CLIENT(IS_PLAYER(it), { it.winning = 0; });
+       FOREACH_CLIENT(IS_PLAYER(it) || INGAME(it), { it.winning = 0; });
  }
  
  int fragsleft_last;
@@@ -1599,19 -1621,18 +1624,18 @@@ void CheckRules_World(
                leadlimit = 0; // no leadlimit for now
        }
  
-       if(timelimit > 0)
-       {
-               timelimit += game_starttime;
-       }
-       else if (timelimit < 0)
+       if (autocvar__endmatch || timelimit < 0)
        {
                // endmatch
                NextLevel();
                return;
        }
  
-       float wantovertime;
-       wantovertime = 0;
+       if(timelimit > 0)
+               timelimit += game_starttime;
+       int overtimes_prev = overtimes;
+       int wantovertime = 0;
  
        if(checkrules_suddendeathend)
        {
                                if(readyplayers || playerswithlaps >= 2)
                                {
                                        checkrules_suddendeathend = 0;
-                                       ReadyRestart(); // go to race
+                                       ReadyRestart(true); // go to race
                                        return;
                                }
                                else
  
        if(checkrules_status == WINNING_YES)
        {
+               if (overtimes == -1 && overtimes != overtimes_prev)
+               {
+                       // if suddendeathend overtime has just begun, revert it
+                       checkrules_suddendeathend = 0;
+                       overtimes = overtimes_prev;
+               }
                //print("WINNING\n");
                NextLevel();
        }
@@@ -1858,25 -1885,25 +1888,25 @@@ void readplayerstartcvars(
        else if (s == "all" || s == "1")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "All Weapons";
+               g_weaponarena_list = "All Weapons Arena";
                g_weaponarena_weapons = weapons_all();
        }
        else if (s == "devall")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Dev All Weapons";
+               g_weaponarena_list = "Dev All Weapons Arena";
                g_weaponarena_weapons = weapons_devall();
        }
        else if (s == "most")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Most Weapons";
+               g_weaponarena_list = "Most Weapons Arena";
                g_weaponarena_weapons = weapons_most();
        }
        else if (s == "all_available")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "All Available Weapons";
+               g_weaponarena_list = "All Available Weapons Arena";
  
                // this needs to run after weaponsInMapAll is initialized
                InitializeEntity(NULL, weaponarena_available_all_update, INITPRIO_FINDTARGET);
        else if (s == "devall_available")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Dev All Available Weapons";
+               g_weaponarena_list = "Dev All Available Weapons Arena";
  
                // this needs to run after weaponsInMapAll is initialized
                InitializeEntity(NULL, weaponarena_available_devall_update, INITPRIO_FINDTARGET);
        else if (s == "most_available")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "Most Available Weapons";
+               g_weaponarena_list = "Most Available Weapons Arena";
  
                // this needs to run after weaponsInMapAll is initialized
                InitializeEntity(NULL, weaponarena_available_most_update, INITPRIO_FINDTARGET);
        else if (s == "none")
        {
                g_weaponarena = 1;
-               g_weaponarena_list = "No Weapons";
+               g_weaponarena_list = "No Weapons Arena";
        }
        else
        {
                        if(wep != WEP_Null)
                        {
                                g_weaponarena_weapons |= (wep.m_wepset);
-                               g_weaponarena_list = strcat(g_weaponarena_list, wep.m_name, " & ");
+                               g_weaponarena_list = strcat(g_weaponarena_list, wep.netname, " & ");
                        }
                }
-               g_weaponarena_list = strzone(substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3));
+               if (g_weaponarena_list != "") // remove trailing " & "
+                       g_weaponarena_list = substring(g_weaponarena_list, 0, strlen(g_weaponarena_list) - 3);
+               else // no valid weapon found
+                       g_weaponarena_list = "No Weapons Arena";
        }
  
        if (g_weaponarena)
                g_weapon_stay = 0; // incompatible
                start_weapons = g_weaponarena_weapons;
                start_items |= IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS;
+               g_weaponarena_list = strzone(g_weaponarena_list);
        }
        else
        {
@@@ -2115,7 -2146,7 +2149,7 @@@ void readlevelcvars(
  
      MUTATOR_CALLHOOK(ReadLevelCvars);
  
-       if (!warmup_stage)
+       if (!warmup_stage && !autocvar_g_campaign)
                game_starttime = time + cvar("g_start_delay");
  
        FOREACH(Weapons, it != WEP_Null, { it.wr_init(it); });
@@@ -2289,9 -2320,10 +2323,10 @@@ void EndFrame(
                        STAT(TYPEHIT_TIME, it) = time;
                } else if (e.killsound) {
                        STAT(KILL_TIME, it) = time;
-               } else if (e.damage_dealt) {
+               } else if (e.hitsound_damage_dealt) {
                        STAT(HIT_TIME, it) = time;
-                       STAT(DAMAGE_DEALT_TOTAL, it) += ceil(e.damage_dealt);
+                       // NOTE: this is not accurate as client code doesn't need so much accuracy for its purposes
+                       STAT(HITSOUND_DAMAGE_DEALT_TOTAL, it) += ceil(e.hitsound_damage_dealt);
                }
        });
        // add 1 frametime because after this, engine SV_Physics
        float altime = time + frametime * (1 + autocvar_g_antilag_nudge);
        FOREACH_CLIENT(true, {
                it.typehitsound = false;
-               it.damage_dealt = 0;
+               it.hitsound_damage_dealt = 0;
                it.killsound = false;
                antilag_record(it, CS(it), altime);
        });
@@@ -2424,6 -2456,8 +2459,8 @@@ void Shutdown(
  
                WeaponStats_Shutdown();
                MapInfo_Shutdown();
+               strfree(sv_termsofservice_url_escaped);
        }
        else if(world_initialized == 0)
        {