]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Juhu/battle-royale
authorJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Fri, 1 Jul 2022 02:46:01 +0000 (04:46 +0200)
committerJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Fri, 1 Jul 2022 02:46:01 +0000 (04:46 +0200)
30 files changed:
1  2 
.gitlab-ci.yml
_hud_descriptions.cfg
balance-xdf.cfg
gamemodes-client.cfg
gamemodes-server.cfg
hud_luma.cfg
hud_luminos.cfg
hud_luminos_minimal.cfg
hud_luminos_minimal_xhair.cfg
hud_luminos_old.cfg
hud_nexuiz.cfg
qcsrc/client/csqcmodel_hooks.qc
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/common/deathtypes/all.inc
qcsrc/common/ent_cs.qc
qcsrc/common/gamemodes/gamemode/br/sv_br.qc
qcsrc/common/gamemodes/gamemode/br/sv_dropship.qc
qcsrc/common/gamemodes/gamemode/br/sv_ring.qc
qcsrc/common/mutators/mutator/buffs/sv_buffs.qc
qcsrc/common/notifications/all.inc
qcsrc/common/notifications/all.qh
qcsrc/common/scores.qh
qcsrc/common/stats.qh
qcsrc/common/vehicles/sv_vehicles.qc
qcsrc/menu/xonotic/util.qc
qcsrc/server/chat.qc
qcsrc/server/client.qc
qcsrc/server/damage.qc
qcsrc/server/impulse.qc
qcsrc/server/world.qc

diff --combined .gitlab-ci.yml
index 48a269313029caefc00db00edd016ddd65db8f20,8b56b81ff39f4e9dda7e29a01cca19b8d679fd06..26bdd574e918531d4e605326bd952dc59eab30a5
@@@ -7,9 -7,19 +7,19 @@@ workflow
  before_script:\r
    - ln -s $PWD data/xonotic-data.pk3dir\r
  \r
+   - export MAKEFLAGS=-j$(nproc); echo MAKEFLAGS=$MAKEFLAGS\r
+ #   FIXME: -march=native -mtune=native _changes the hash_, why?!?\r
+ # - export CC="gcc -pipe -march=native -mtune=native"\r
+   - export CC="gcc -pipe"\r
\r
    - git clone --depth=1 --branch=main https://gitlab.com/xonotic/gmqcc.git gmqcc\r
-   - cd gmqcc && make -j $(nproc) && export QCC="$PWD/gmqcc"\r
-   - cd ..\r
+   - make -C gmqcc || exit 1\r
+   - export QCC="$PWD/gmqcc/gmqcc"\r
\r
+   # Makefile: don't complain about lack of tags (fetching them is slow)\r
+   - export QCCFLAGS_WATERMARK=gitlab_pipeline\r
+   # Makefile: don't compress anything or complain about lack of zip program\r
+   - export ZIP=/bin/true\r
  \r
  test_compilation_units:\r
    rules:\r
        - qcsrc/**/*\r
    stage: test\r
    script:\r
-     - ./qcsrc/tools/compilationunits.sh\r
+     - make test\r
  \r
  test_sv_game:\r
    stage: test\r
    script:\r
      - git clone --depth=1 --branch=div0-stable https://gitlab.com/xonotic/darkplaces.git darkplaces\r
-     - cd darkplaces && make sv-debug -j $(nproc) && export ENGINE="$PWD/darkplaces-dedicated -xonotic"\r
-     - cd ..\r
+     - make -C darkplaces sv-release || exit 1\r
+     - export ENGINE="$PWD/darkplaces/darkplaces-dedicated -xonotic -noconfig -nohome"\r
+     - make qc || exit 1\r
  \r
      - mkdir -p data/maps\r
+     - wget -O data/maps/gitlab-ci.bsp https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/_init/_init.bsp\r
\r
+     - while read LINE; do\r
+         echo $LINE;\r
+         [ "$LINE" = "All tests OK" ] && PASS=1;\r
+       done < <(${ENGINE} +developer 1 +map gitlab-ci +sv_cmd runtest +wait +quit)\r
+     - test "$PASS" = "1" || { echo 'sv_cmd runtest failed!'; exit 1; }\r
\r
+     - ${ENGINE} +map gitlab-ci +sv_cmd dumpnotifs +wait +quit\r
+     - diff notifications.cfg data/data/notifications_dump.cfg ||\r
+         { echo 'Please update notifications.cfg using `dumpnotifs`!'; exit 1; }\r
\r
      - wget -O data/stormkeep.pk3 http://beta.xonotic.org/autobuild-bsp/latest/stormkeep.pk3\r
      - wget -O data/maps/stormkeep.mapinfo https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.mapinfo\r
      - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints\r
      - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache\r
-     - make\r
-     - EXPECT=2ed9b21b906b98ba3e9c358b1f41d547\r
-     - HASH=$(${ENGINE} -noconfig -nohome +timestamps 1 +exec serverbench.cfg\r
\r
 -    - EXPECT=8e36763a3b4590356bcc449b2452d0ea\r
++    - EXPECT=79ebc686ac31153de17e6b8b268c3ac2\r
+     - HASH=$(${ENGINE} +timestamps 1 +exec serverbench.cfg\r
        | tee /dev/stderr\r
        | sed -e 's,^\[[^]]*\] ,,'\r
        | grep '^:'\r
      - test "$HASH" == "$EXPECT"\r
      - exit $?\r
  \r
- test_sv_unit:\r
-   stage: test\r
-   script:\r
-     - git clone --depth=1 --branch=div0-stable https://gitlab.com/xonotic/darkplaces.git darkplaces\r
-     - cd darkplaces && make sv-debug -j $(nproc) && export ENGINE="$PWD/darkplaces-dedicated -xonotic"\r
-     - cd ..\r
\r
-     - mkdir maps && wget -O maps/gitlab-ci.bsp https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/_init/_init.bsp\r
-     - make\r
-     - while read line; do\r
-         echo $line;\r
-         if [[ $line == "All tests OK" ]]; then exit 0; fi;\r
-       done < <(${ENGINE} +developer 1 +map gitlab-ci +sv_cmd runtest +exit)\r
-     - exit 1\r
  \r
  # NOTE: The generated docs are incomplete - they don't contain code behind SVQC CSQC MENUQC GAMEQC ifdefs.\r
  # With them added to PREDEFINED, it would take over half an hour to generate the docs and even then\r
  # they might not be complete. Doxygen doesn't handle #elif and might not understand some QC definitions.\r
- doxygen:  # rename to 'pages' when gitlab.com allows pages to exceed 100MiB\r
-   before_script:\r
-     - ln -s $PWD data/xonotic-data.pk3dir # is this needed?\r
-     - apt-get update\r
-     - apt-get -y install doxygen graphviz\r
-   stage: deploy\r
-   script:\r
-     - cd qcsrc && doxygen\r
-     - mv html ../public\r
-     - mkdir -p ~/.ssh\r
-     - for i in {0..0}; do eval $(printf "echo \$id_rsa_%02d\n" $i) >> ~/.ssh/id_rsa_base64; done\r
-     - base64 --decode ~/.ssh/id_rsa_base64 > ~/.ssh/id_rsa\r
-     - chmod 600 ~/.ssh/id_rsa\r
-     - echo -e "Host *\n\tStrictHostKeyChecking no\n\tLogLevel ERROR\n" >> ~/.ssh/config\r
-     - git config --global user.name "Gitlab CI"\r
-     - git config --global user.email "<>"\r
-     - git clone --single-branch --depth 1 ${DEPLOY_HOST}:${DEPLOY_REPO} ~/deploy_\r
-     - mkdir ~/deploy && mv ~/deploy_/.git ~/deploy && rm -r ~/deploy_\r
-     - cp -r ../public/* ~/deploy\r
-     - cd ~/deploy && git add -A . && git commit -m "Deploy ${CI_BUILD_REF}" && git push origin gh-pages\r
-   artifacts:\r
-     paths:\r
-       - public\r
-   only:\r
-     - master\r
#doxygen:  # rename to 'pages' when gitlab.com allows pages to exceed 100MiB\r
#  before_script:\r
#    - ln -s $PWD data/xonotic-data.pk3dir # is this needed?\r
#    - apt-get update\r
#    - apt-get -y install doxygen graphviz\r
#  stage: deploy\r
#  script:\r
#    - cd qcsrc && doxygen\r
#    - mv html ../public\r
#    - mkdir -p ~/.ssh\r
#    - for i in {0..0}; do eval $(printf "echo \$id_rsa_%02d\n" $i) >> ~/.ssh/id_rsa_base64; done\r
#    - base64 --decode ~/.ssh/id_rsa_base64 > ~/.ssh/id_rsa\r
#    - chmod 600 ~/.ssh/id_rsa\r
#    - echo -e "Host *\n\tStrictHostKeyChecking no\n\tLogLevel ERROR\n" >> ~/.ssh/config\r
#    - git config --global user.name "Gitlab CI"\r
#    - git config --global user.email "<>"\r
#    - git clone --single-branch --depth 1 ${DEPLOY_HOST}:${DEPLOY_REPO} ~/deploy_\r
#    - mkdir ~/deploy && mv ~/deploy_/.git ~/deploy && rm -r ~/deploy_\r
#    - cp -r ../public/* ~/deploy\r
#    - cd ~/deploy && git add -A . && git commit -m "Deploy ${CI_BUILD_REF}" && git push origin gh-pages\r
#  artifacts:\r
#    paths:\r
#      - public\r
#  only:\r
#    - master\r
diff --combined _hud_descriptions.cfg
index eaf4a7fe4e865e58c13e5a16b62929767005184e,545e0b92b51209e986926dcec132e4fdfe345f6d..10b8eb115d56446f986356835fff0b76735cd0e1
@@@ -198,7 -198,6 +198,7 @@@ seta hud_panel_modicons_bg_padding "" "
  seta hud_panel_modicons_ca_layout "" "2 possible layouts: 0) number of alive players; 1) icons and number of alive players"
  seta hud_panel_modicons_dom_layout "" "3 possible layouts: 0) only icons; 1) icons and percentage of average pps (points per second); 2) icons and average pps"
  seta hud_panel_modicons_freezetag_layout "" "2 possible layouts: 0) number of alive players; 1) icons and number of alive players"
 +seta hud_panel_modicons_br_layout "" "2 possible layouts: 0) number of alive squads/players; 1) icons and number of alive squads/players"
  
  seta hud_panel_pressedkeys_pos "" "position of this base of the panel"
  seta hud_panel_pressedkeys_size "" "size of this panel"
@@@ -275,16 -274,7 +275,7 @@@ seta hud_panel_centerprint_align "" "te
  seta hud_panel_centerprint_flip "" "invert messages order"
  seta hud_panel_centerprint_fontscale "" "scale the text font by this amount"
  seta hud_panel_centerprint_fontscale_bold "" "scale the bold text font by this amount"
- seta hud_panel_centerprint_time "" "message duration (NOTE: certain messages have a fixed duration)"
- seta hud_panel_centerprint_fade_in "" "how long a message takes to fade in"
- seta hud_panel_centerprint_fade_out "" "how long a message takes to fade out (this time is included in the message duration and can't be > 5)"
- seta hud_panel_centerprint_fade_subsequent "" "enable extra fading effects for each additional message, so that the more messages you have the more they become faded out"
- seta hud_panel_centerprint_fade_subsequent_passone "" "division factor for the first pass for alpha fading, with 2 all messages after the first have half alpha"
- seta hud_panel_centerprint_fade_subsequent_passone_minalpha "" "minimum factor that the first pass can fade to"
- seta hud_panel_centerprint_fade_subsequent_passtwo "" "division factor for the second pass for alpha fading, it applies another fade on top of the first pass to make it more transitioned"
- seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "" "minimum factor that the second pass can fade to"
- seta hud_panel_centerprint_fade_subsequent_minfontsize "" "minimum factor for the font size from the subsequent fading effects"
- seta hud_panel_centerprint_fade_minfontsize "" "minimum factor for the font size from the fading in/out effects"
+ seta hud_panel_centerprint_fontscale_title "" "scale the title text font by this amount"
  
  seta hud_panel_minigameboard_pos "" "position of this panel"
  seta hud_panel_minigameboard_size "" "size of this panel"
diff --combined balance-xdf.cfg
index 3bc9cc7aaa878ebafd3995a8a5b1631644ecdf67,67e8810b5435f4c1954b59902336391083667002..895cca9b33f9c92b48c7a8acc51095c886ed195c
@@@ -49,14 -49,6 +49,14 @@@ set g_lms_start_ammo_rockets 16
  set g_lms_start_ammo_cells 180
  set g_lms_start_ammo_plasma 180
  set g_lms_start_ammo_fuel 0
 +set g_br_start_health 100
 +set g_br_start_armor 0
 +set g_br_start_ammo_shells 0
 +set g_br_start_ammo_nails 0
 +set g_br_start_ammo_rockets 0
 +set g_br_start_ammo_cells 0
 +set g_br_start_ammo_plasma 0
 +set g_br_start_ammo_fuel 0
  set g_balance_nix_roundtime 25
  set g_balance_nix_incrtime 1.6
  set g_balance_nix_ammo_shells 60
@@@ -229,9 -221,9 +229,9 @@@ set g_balance_powerup_invincible_takefo
  set g_balance_powerup_invincible_time 30
  set g_balance_powerup_invisibility_alpha 0.15
  set g_balance_powerup_invisibility_time 30
- set g_balance_powerup_speed_attackrate 0.8
- set g_balance_powerup_speed_highspeed 1.5
- set g_balance_powerup_speed_time 30
+ set g_balance_powerup_speed_attackrate 0.7692307692   // 1/1.3
+ set g_balance_powerup_speed_highspeed 1.3
+ set g_balance_powerup_speed_time 30 // q3 haste lasts 30 seconds unless count field set otherwise
  set g_balance_powerup_strength_damage 3
  set g_balance_powerup_strength_force 3
  set g_balance_powerup_strength_time 30
diff --combined gamemodes-client.cfg
index cf3e35b69b0e80138f6a69872ed1a46b7dd82776,71d272a174e7c50dcb868fa34e472d043668a250..ad35029ec3f01292d801df317b635edc6419eb72
@@@ -32,7 -32,6 +32,7 @@@ alias cl_hook_gamestart_k
  alias cl_hook_gamestart_ft
  alias cl_hook_gamestart_inv
  alias cl_hook_gamestart_duel
- alias cl_hook_gameend "rpn /cl_matchcount dup load 1 + =" // increase match count every time a game ends
 +alias cl_hook_gamestart_br
+ alias cl_hook_gameend
  alias cl_hook_shutdown
  alias cl_hook_activeweapon
diff --combined gamemodes-server.cfg
index 8f54e796e0ec0d4f23a61d03be6554ff1292df3e,318749021fe8d329ae55e6cbb58de4eafb3486ef..6fa607dc4879e226d20d86194358a94c26bbc737
@@@ -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_br
  // 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_br
  
  // 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_br_respawn_delay_small 0
 +set g_br_respawn_delay_small_count 0
 +set g_br_respawn_delay_large 0
 +set g_br_respawn_delay_large_count 0
 +set g_br_respawn_delay_max 0
 +set g_br_respawn_waves 0
 +set g_br_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"
@@@ -331,7 -322,7 +331,7 @@@ exec ctfscoring-samual.cf
  // ====================
  set g_cts 0 "CTS: complete the stage"
  set g_cts_selfdamage 1 "0 = disable all selfdamage and falldamage in cts"
- set g_cts_finish_kill_delay 10 "prevent cheating by running back to the start line, and starting out with more speed than otherwise possible"
+ set g_cts_finish_kill_delay 2 "kill player this many seconds after stage completion to prevent cheating by starting out with more speed than otherwise possible; set it to 0 to not kill or to -1 to kill instantly"
  set g_cts_send_rankings_cnt 15 "send this number of map records to clients"
  set g_cts_removeprojectiles 0 "remove projectiles when the player dies, to prevent using weapons earlier in the stage than intended"
  
@@@ -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"
  
  
  // =========
@@@ -557,8 -551,6 +560,6 @@@ set g_invasion_monster_count 10 "numbe
  set g_invasion_zombies_only 0 "only spawn zombies"
  set g_invasion_spawn_delay 0.25
  set g_invasion_spawnpoint_spawn_delay 0.5
- set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
- set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
  set g_invasion_type 0 "type of invasion mode - 0: round-based, 1: hunting, 2: complete the stage (note: use mapinfo to set this)"
  
  // ======
@@@ -568,45 -560,3 +569,45 @@@ 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"
 +
 +// ======
 +//  battle royale
 +// ======
 +set g_br 0 "Battle Royale: survive on a shrinking battlefield"
 +set g_br_not_assault_maps 0 "when this is set, CTF maps will NOT be listed in BR"
 +set g_br_not_ctf_maps 0 "when this is set, CTF maps will NOT be listed in BR"
 +set g_br_not_dm_maps 0 "when this is set, DM maps will NOT be listed in BR"
 +set g_br_minplayers 2 "minimum players to start the BR match"
 +set g_br_squad_size 3 "maximum squad size"
 +set g_br_squad_colors 1 "assign each squad a random color scheme and force players to use it"
 +set g_br_squad_waypoint_distance 1500 "minimum distance required to show an ally waypoint"
 +set g_br_startweapons 0 "when disabled, players land from the dropship without weapons"
 +set g_br_bleed 0.02 "amount of health rot while injured"
 +set g_br_bleedlinear 1 "linear amount of health rot while injured"
 +set g_br_bleeding_health 0.5 "start health mutliplier when injured"
 +set g_br_bleeding_armor 50 "start armor when injured"
 +set g_br_revive_speed 0.4 "Speed for reviving an injured squadmate"
 +set g_br_revive_clearspeed 1.6 "Speed at which reviving progress gets lost when out of range"
 +set g_br_revive_extra_size 100 "Distance in qu that you can stand from an injured squadmate to keep reviving them"
 +set g_br_revive_health 0.25 "start health multiplier when revived"
 +set g_br_dropship_color "0.5 0 0.5" "dropship color"
 +set g_br_dropship_scale 3 "dropship scale"
 +set g_br_dropship_speed -1 "dropship speed, -1 to decide based on map size"
 +set g_br_drop_damage 0.5 "multiplier of damage taken while dropping"
 +set g_br_drop_speed_max 2.5 "max air speed multiplier while dropping"
 +set g_br_drop_speed_min 1.25 "min air speed multiplier while dropping"
 +set g_br_drop_speed_vertical_min 0.1 "minimum vertical speed portion while dropping"
 +set g_br_drop_accel_dive 50 "dive acceleration while dropping"
 +set g_br_drop_accel_turn 600 "turn acceleration while dropping"
 +set g_br_drop_distance_force 500 "minimum distance to the end of the dropship path before players are force dropped"
 +set g_br_ring_alpha 0.5 "ring transparency"
 +set g_br_ring_color "1 0 0" "ring color"
 +set g_br_ring_radius -1 "ring radius (use -1 to automatically determine the radius based on the map size)"
 +set g_br_ring_duration 150 "total time the ring spends closing"
 +set g_br_ring_timing "0.6 0.8 0.9" "timing values relative to g_br_ring_duration at which the ring waits and transitions into the next stage"
 +set g_br_ring_strength "2.5 5 10 20 50" "damage values for each stage including beginning and end"
 +set g_br_ring_wait 30 "wait time before each ring stage"
 +set g_br_ring_center_factor 0.25 "factor by which the ring can deviate from the center of the map, 0 means always completely centered, 1 means completely random within world bounds"
 +set g_br_ring_fadedistance 0.5 "value multiplied by the current ring radius defines the distance at which the ring slowly becomes visible until it reaches g_br_ring_alpha when standing right in front of it"
 +set g_br_ring_fadedistance_min 2000 "minimum value generated by g_br_ring_fadedistance"
 +set g_br_ring_exitvehicle 0 "players can't use vehicles outside of the ring"
diff --combined hud_luma.cfg
index e346d41da7de69377b0e416582774e5baa72f718,0903a0584b40b7ed482ec29efc79fbff5ae86966..ed1f6537cb7fbb784c13e94869a10821e33ae8d7
@@@ -133,8 -133,8 +133,8 @@@ seta hud_panel_notify_time "10
  seta hud_panel_notify_fadetime "3"
  seta hud_panel_notify_icon_aspect "1"
  
- seta hud_panel_timer_pos "0.456000 0"
- seta hud_panel_timer_size "0.088000 0.030000"
+ seta hud_panel_timer_pos "0.455000 0"
+ seta hud_panel_timer_size "0.090000 0.050000"
  seta hud_panel_timer_bg "border_plain_north"
  seta hud_panel_timer_bg_color ""
  seta hud_panel_timer_bg_color_team ""
@@@ -199,7 -199,6 +199,7 @@@ seta hud_panel_modicons_bg_padding "0
  seta hud_panel_modicons_ca_layout "1"
  seta hud_panel_modicons_dom_layout "1"
  seta hud_panel_modicons_freezetag_layout "1"
 +seta hud_panel_modicons_br_layout "1"
  
  seta hud_panel_pressedkeys_pos "0.445000 0.710000"
  seta hud_panel_pressedkeys_size "0.110000 0.090000"
@@@ -275,17 -274,8 +275,8 @@@ seta hud_panel_centerprint_bg_padding "
  seta hud_panel_centerprint_align "0.5"
  seta hud_panel_centerprint_flip "0"
  seta hud_panel_centerprint_fontscale "1"
- seta hud_panel_centerprint_fontscale_bold "1.4"
- seta hud_panel_centerprint_time "3"
- seta hud_panel_centerprint_fade_in "0.15"
- seta hud_panel_centerprint_fade_out "0.15"
- seta hud_panel_centerprint_fade_subsequent "1"
- seta hud_panel_centerprint_fade_subsequent_passone "3"
- seta hud_panel_centerprint_fade_subsequent_passone_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_passtwo "10"
- seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
- seta hud_panel_centerprint_fade_minfontsize "0"
+ seta hud_panel_centerprint_fontscale_bold "1.2"
+ seta hud_panel_centerprint_fontscale_title "1.3"
  
  seta hud_panel_minigameboard_pos "0.22 0.15"
  seta hud_panel_minigameboard_size "0.50 0.60"
diff --combined hud_luminos.cfg
index 769cbae0596988830417a386784fe212e1af0bf6,a0849e5aabf9c4c55acf5cd00264f77f6830d5c4..0cf8c3f9b9d4afb747957a57db89aa68cdff6c6b
@@@ -133,8 -133,8 +133,8 @@@ seta hud_panel_notify_time "10
  seta hud_panel_notify_fadetime "3"
  seta hud_panel_notify_icon_aspect "2"
  
- seta hud_panel_timer_pos "0.800000 0.040000"
- seta hud_panel_timer_size "0.070000 0.040000"
+ seta hud_panel_timer_pos "0.790000 0.040000"
+ seta hud_panel_timer_size "0.090000 0.050000"
  seta hud_panel_timer_bg "border_small_timer"
  seta hud_panel_timer_bg_color ""
  seta hud_panel_timer_bg_color_team ""
@@@ -199,7 -199,6 +199,7 @@@ seta hud_panel_modicons_bg_padding "0
  seta hud_panel_modicons_ca_layout "1"
  seta hud_panel_modicons_dom_layout "1"
  seta hud_panel_modicons_freezetag_layout "1"
 +seta hud_panel_modicons_br_layout "1"
  
  seta hud_panel_pressedkeys_pos "0.450000 0.720000"
  seta hud_panel_pressedkeys_size "0.110000 0.090000"
@@@ -275,17 -274,8 +275,8 @@@ seta hud_panel_centerprint_bg_padding "
  seta hud_panel_centerprint_align "0.5"
  seta hud_panel_centerprint_flip "0"
  seta hud_panel_centerprint_fontscale "1"
- seta hud_panel_centerprint_fontscale_bold "1.4"
- seta hud_panel_centerprint_time "3"
- seta hud_panel_centerprint_fade_in "0.15"
- seta hud_panel_centerprint_fade_out "0.15"
- seta hud_panel_centerprint_fade_subsequent "1"
- seta hud_panel_centerprint_fade_subsequent_passone "3"
- seta hud_panel_centerprint_fade_subsequent_passone_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_passtwo "10"
- seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
- seta hud_panel_centerprint_fade_minfontsize "0"
+ seta hud_panel_centerprint_fontscale_bold "1.2"
+ seta hud_panel_centerprint_fontscale_title "1.3"
  
  seta hud_panel_minigameboard_pos "0.22 0.15"
  seta hud_panel_minigameboard_size "0.50 0.60"
diff --combined hud_luminos_minimal.cfg
index 84e53e538c51f09a848f5b31072b2cd95ef54b62,3d93b90b38e538724a51ee64d391acb66d3517d8..326b208b568fcf579da29e54dfbe6ec43f377f3b
@@@ -133,8 -133,8 +133,8 @@@ seta hud_panel_notify_time "10
  seta hud_panel_notify_fadetime "3"
  seta hud_panel_notify_icon_aspect "2"
  
- seta hud_panel_timer_pos "0.435000 0"
- seta hud_panel_timer_size "0.135000 0.060000"
+ seta hud_panel_timer_pos "0.430000 0"
+ seta hud_panel_timer_size "0.140000 0.060000"
  seta hud_panel_timer_bg "0"
  seta hud_panel_timer_bg_color ""
  seta hud_panel_timer_bg_color_team ""
@@@ -159,7 -159,7 +159,7 @@@ seta hud_panel_radar_maximized_size "0.
  seta hud_panel_radar_maximized_rotation "1"
  seta hud_panel_radar_maximized_zoommode "3"
  
- seta hud_panel_score_pos "0.465000 0.045000"
+ seta hud_panel_score_pos "0.465000 0.060000"
  seta hud_panel_score_size "0.090000 0.060000"
  seta hud_panel_score_bg ""
  seta hud_panel_score_bg_color ""
@@@ -169,7 -169,7 +169,7 @@@ seta hud_panel_score_bg_border "
  seta hud_panel_score_bg_padding ""
  seta hud_panel_score_rankings "1"
  
- seta hud_panel_racetimer_pos "0.360000 0.090000"
+ seta hud_panel_racetimer_pos "0.360000 0.130000"
  seta hud_panel_racetimer_size "0.280000 0.090000"
  seta hud_panel_racetimer_bg "0"
  seta hud_panel_racetimer_bg_color ""
@@@ -188,7 -188,7 +188,7 @@@ seta hud_panel_vote_bg_border "
  seta hud_panel_vote_bg_padding ""
  seta hud_panel_vote_alreadyvoted_alpha "0.8"
  
- seta hud_panel_modicons_pos "0.560000 0"
+ seta hud_panel_modicons_pos "0.580000 0"
  seta hud_panel_modicons_size "0.050000 0.100000"
  seta hud_panel_modicons_bg ""
  seta hud_panel_modicons_bg_color ""
@@@ -199,7 -199,6 +199,7 @@@ seta hud_panel_modicons_bg_padding "
  seta hud_panel_modicons_ca_layout "1"
  seta hud_panel_modicons_dom_layout "1"
  seta hud_panel_modicons_freezetag_layout "1"
 +seta hud_panel_modicons_br_layout "1"
  
  seta hud_panel_pressedkeys_pos "0.450000 0.650000"
  seta hud_panel_pressedkeys_size "0.100000 0.110000"
@@@ -275,17 -274,8 +275,8 @@@ seta hud_panel_centerprint_bg_padding "
  seta hud_panel_centerprint_align "0.5"
  seta hud_panel_centerprint_flip "0"
  seta hud_panel_centerprint_fontscale "1"
- seta hud_panel_centerprint_fontscale_bold "1.4"
- seta hud_panel_centerprint_time "3"
- seta hud_panel_centerprint_fade_in "0.15"
- seta hud_panel_centerprint_fade_out "0.15"
- seta hud_panel_centerprint_fade_subsequent "1"
- seta hud_panel_centerprint_fade_subsequent_passone "3"
- seta hud_panel_centerprint_fade_subsequent_passone_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_passtwo "10"
- seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
- seta hud_panel_centerprint_fade_minfontsize "0"
+ seta hud_panel_centerprint_fontscale_bold "1.2"
+ seta hud_panel_centerprint_fontscale_title "1.3"
  
  seta hud_panel_minigameboard_pos "0.22 0.15"
  seta hud_panel_minigameboard_size "0.50 0.60"
index 6f4d92eb2e11178eae09932cc4a71fa4c23f92a9,c1a69a88ca192f5affc15999e4cbdc6b3ee39114..ced316edc14f66e73ddc2b519d0137f228649765
@@@ -133,8 -133,8 +133,8 @@@ seta hud_panel_notify_time "10
  seta hud_panel_notify_fadetime "3"
  seta hud_panel_notify_icon_aspect "2"
  
- seta hud_panel_timer_pos "0.435000 0"
- seta hud_panel_timer_size "0.135000 0.060000"
+ seta hud_panel_timer_pos "0.430000 0"
+ seta hud_panel_timer_size "0.140000 0.060000"
  seta hud_panel_timer_bg "0"
  seta hud_panel_timer_bg_color ""
  seta hud_panel_timer_bg_color_team ""
@@@ -159,7 -159,7 +159,7 @@@ seta hud_panel_radar_maximized_size "0.
  seta hud_panel_radar_maximized_rotation "1"
  seta hud_panel_radar_maximized_zoommode "3"
  
- seta hud_panel_score_pos "0.465000 0.045000"
+ seta hud_panel_score_pos "0.465000 0.060000"
  seta hud_panel_score_size "0.090000 0.060000"
  seta hud_panel_score_bg ""
  seta hud_panel_score_bg_color ""
@@@ -169,7 -169,7 +169,7 @@@ seta hud_panel_score_bg_border "
  seta hud_panel_score_bg_padding ""
  seta hud_panel_score_rankings "1"
  
- seta hud_panel_racetimer_pos "0.360000 0.090000"
+ seta hud_panel_racetimer_pos "0.360000 0.130000"
  seta hud_panel_racetimer_size "0.280000 0.090000"
  seta hud_panel_racetimer_bg "0"
  seta hud_panel_racetimer_bg_color ""
@@@ -188,7 -188,7 +188,7 @@@ seta hud_panel_vote_bg_border "
  seta hud_panel_vote_bg_padding ""
  seta hud_panel_vote_alreadyvoted_alpha "0.8"
  
- seta hud_panel_modicons_pos "0.560000 0"
+ seta hud_panel_modicons_pos "0.580000 0"
  seta hud_panel_modicons_size "0.050000 0.100000"
  seta hud_panel_modicons_bg ""
  seta hud_panel_modicons_bg_color ""
@@@ -199,7 -199,6 +199,7 @@@ seta hud_panel_modicons_bg_padding "
  seta hud_panel_modicons_ca_layout "1"
  seta hud_panel_modicons_dom_layout "1"
  seta hud_panel_modicons_freezetag_layout "1"
 +seta hud_panel_modicons_br_layout "1"
  
  seta hud_panel_pressedkeys_pos "0.450000 0.690000"
  seta hud_panel_pressedkeys_size "0.100000 0.110000"
@@@ -275,17 -274,8 +275,8 @@@ seta hud_panel_centerprint_bg_padding "
  seta hud_panel_centerprint_align "0.5"
  seta hud_panel_centerprint_flip "0"
  seta hud_panel_centerprint_fontscale "1"
- seta hud_panel_centerprint_fontscale_bold "1.4"
- seta hud_panel_centerprint_time "3"
- seta hud_panel_centerprint_fade_in "0.15"
- seta hud_panel_centerprint_fade_out "0.15"
- seta hud_panel_centerprint_fade_subsequent "1"
- seta hud_panel_centerprint_fade_subsequent_passone "3"
- seta hud_panel_centerprint_fade_subsequent_passone_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_passtwo "10"
- seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
- seta hud_panel_centerprint_fade_minfontsize "0"
+ seta hud_panel_centerprint_fontscale_bold "1.2"
+ seta hud_panel_centerprint_fontscale_title "1.3"
  
  seta hud_panel_minigameboard_pos "0.22 0.15"
  seta hud_panel_minigameboard_size "0.50 0.60"
diff --combined hud_luminos_old.cfg
index 62df374279c0a4312072bca12c6cd8b95c4dd742,f0842bc640a3e4d97913886c3031f9cb51dc9607..08eacfb65f781bfd68f097fe4a6c2bc0a902d0c4
@@@ -134,7 -134,7 +134,7 @@@ seta hud_panel_notify_fadetime "3
  seta hud_panel_notify_icon_aspect "2"
  
  seta hud_panel_timer_pos "0.870000 0"
- seta hud_panel_timer_size "0.130000 0.060000"
+ seta hud_panel_timer_size "0.130000 0.090000"
  seta hud_panel_timer_bg "0"
  seta hud_panel_timer_bg_color ""
  seta hud_panel_timer_bg_color_team ""
@@@ -199,7 -199,6 +199,7 @@@ seta hud_panel_modicons_bg_padding "
  seta hud_panel_modicons_ca_layout "1"
  seta hud_panel_modicons_dom_layout "1"
  seta hud_panel_modicons_freezetag_layout "1"
 +seta hud_panel_modicons_br_layout "1"
  
  seta hud_panel_pressedkeys_pos "0.410000 0.710000"
  seta hud_panel_pressedkeys_size "0.180000 0.130000"
@@@ -275,17 -274,8 +275,8 @@@ seta hud_panel_centerprint_bg_padding "
  seta hud_panel_centerprint_align "0.5"
  seta hud_panel_centerprint_flip "0"
  seta hud_panel_centerprint_fontscale "1"
- seta hud_panel_centerprint_fontscale_bold "1.4"
- seta hud_panel_centerprint_time "3"
- seta hud_panel_centerprint_fade_in "0.15"
- seta hud_panel_centerprint_fade_out "0.15"
- seta hud_panel_centerprint_fade_subsequent "1"
- seta hud_panel_centerprint_fade_subsequent_passone "3"
- seta hud_panel_centerprint_fade_subsequent_passone_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_passtwo "10"
- seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
- seta hud_panel_centerprint_fade_minfontsize "0"
+ seta hud_panel_centerprint_fontscale_bold "1.2"
+ seta hud_panel_centerprint_fontscale_title "1.3"
  
  seta hud_panel_minigameboard_pos "0.22 0.15"
  seta hud_panel_minigameboard_size "0.50 0.60"
diff --combined hud_nexuiz.cfg
index 85541b69d522b8fbdd83afb891e116f57f3f89a0,2b035c8a4eccc1b9b25ab3441b4d1c2a151c0844..e51b0445c760637e21ac923c785975beaf128afe
@@@ -134,7 -134,7 +134,7 @@@ seta hud_panel_notify_fadetime "3
  seta hud_panel_notify_icon_aspect "2"
  
  seta hud_panel_timer_pos "0.850000 0"
- seta hud_panel_timer_size "0.150000 0.060000"
+ seta hud_panel_timer_size "0.150000 0.080000"
  seta hud_panel_timer_bg ""
  seta hud_panel_timer_bg_color "0 0.5 0.35"
  seta hud_panel_timer_bg_color_team ""
@@@ -199,7 -199,6 +199,7 @@@ seta hud_panel_modicons_bg_padding "
  seta hud_panel_modicons_ca_layout "1"
  seta hud_panel_modicons_dom_layout "1"
  seta hud_panel_modicons_freezetag_layout "1"
 +seta hud_panel_modicons_br_layout "1"
  
  seta hud_panel_pressedkeys_pos "0.440000 0.760000"
  seta hud_panel_pressedkeys_size "0.120000 0.094368"
@@@ -275,17 -274,8 +275,8 @@@ seta hud_panel_centerprint_bg_padding "
  seta hud_panel_centerprint_align "0.5"
  seta hud_panel_centerprint_flip "0"
  seta hud_panel_centerprint_fontscale "1"
- seta hud_panel_centerprint_fontscale_bold "1.4"
- seta hud_panel_centerprint_time "3"
- seta hud_panel_centerprint_fade_in "0.15"
- seta hud_panel_centerprint_fade_out "0.15"
- seta hud_panel_centerprint_fade_subsequent "1"
- seta hud_panel_centerprint_fade_subsequent_passone "3"
- seta hud_panel_centerprint_fade_subsequent_passone_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_passtwo "10"
- seta hud_panel_centerprint_fade_subsequent_passtwo_minalpha "0.5"
- seta hud_panel_centerprint_fade_subsequent_minfontsize "0.75"
- seta hud_panel_centerprint_fade_minfontsize "0"
+ seta hud_panel_centerprint_fontscale_bold "1.2"
+ seta hud_panel_centerprint_fontscale_title "1.3"
  
  seta hud_panel_minigameboard_pos "0.22 0.15"
  seta hud_panel_minigameboard_size "0.50 0.60"
index e5363d0596954618a435c3979d3fd05765e02ba0,121af1947a9b256ae2b4e0e7013ea4865db10898..5f421f8d28fbfb9e6928ba1fb514eefc6728cb26
  .int lodmodelindex0;
  .int lodmodelindex1;
  .int lodmodelindex2;
- void CSQCPlayer_LOD_Apply(entity this)
+ void CSQCPlayer_LOD_Apply(entity this, bool isplayer)
  {
+       int detailreduction = ((isplayer) ? autocvar_cl_playerdetailreduction : autocvar_cl_modeldetailreduction);
        // LOD model loading
        if(this.lodmodelindex0 != this.modelindex)
        {
        }
  
        // apply LOD
-       if(autocvar_cl_playerdetailreduction <= 0)
+       if(detailreduction <= 0)
        {
-               if(autocvar_cl_playerdetailreduction <= -2)
+               if(detailreduction <= -2)
                        this.modelindex = this.lodmodelindex2;
-               else if(autocvar_cl_playerdetailreduction <= -1)
+               else if(detailreduction <= -1)
                        this.modelindex = this.lodmodelindex1;
                else
                        this.modelindex = this.lodmodelindex0;
        }
        else
        {
-               float distance = vlen(this.origin - view_origin);
-               float f = (distance * current_viewzoom + 100.0) * autocvar_cl_playerdetailreduction;
+               float distance = vlen(((isplayer) ? this.origin : NearestPointOnBox(this, view_origin)) - view_origin); // TODO: perhaps it should just use NearestPointOnBox all the time, player hitbox can potentially be huge
+               float f = (distance * current_viewzoom + 100.0) * detailreduction;
                f *= 1.0 / bound(0.01, view_quality, 1);
                if(f > autocvar_cl_loddistance2)
                        this.modelindex = this.lodmodelindex2;
@@@ -236,7 -238,7 +238,7 @@@ void CSQCPlayer_ModelAppearance_Apply(e
  
        bool forceplayercolors_enabled = false;
        #define fpc autocvar_cl_forceplayercolors
-       if (ISGAMETYPE(DUEL))
+       if (gametype.m_1v1)
        {
                if ((myteam != NUM_SPECTATOR) && (fpc == 1 || fpc == 2 || fpc == 3 || fpc == 5))
                        forceplayercolors_enabled = true;
        }
        else
        {
 -              if (fpc == 1 || fpc == 2)
 +              if ((!(ISGAMETYPE(BR) && STAT(SQUADCOLORS)) && fpc == 1) || fpc == 2)
                        forceplayercolors_enabled = true;
        }
  
        {
                if(autocvar_cl_forcemyplayercolors && islocalplayer)
                        this.colormap = 1024 + autocvar_cl_forcemyplayercolors;
-               else if (autocvar_cl_forceuniqueplayercolors && !islocalplayer && !ISGAMETYPE(DUEL))
+               else if (autocvar_cl_forceuniqueplayercolors && !islocalplayer && !gametype.m_1v1)
                {
                        // Assign each enemy unique colors
                        // pick colors from 0 to 14 since 15 is the rainbow color
  
        // GLOWMOD AND DEATH FADING
        if(this.colormap > 0)
-               this.glowmod = colormapPaletteColor(((this.colormap >= 1024) ? this.colormap : entcs_GetClientColors(this.colormap - 1)) & 0x0F, true) * 2;
+               this.glowmod = colormapPaletteColor(((this.colormap >= 1024) ? this.colormap : entcs_GetClientColors(this.colormap - 1)) & 0x0F, true);
        else
                this.glowmod = '1 1 1';
  
                }
        }
  
+       // don't let the engine increase player's glowmod
+       if (autocvar_r_hdr_glowintensity > 1)
+               this.glowmod /= autocvar_r_hdr_glowintensity;
        //printf("CSQCPlayer_ModelAppearance_Apply(): state = %s, colormap = %f, glowmod = %s\n", (this.csqcmodel_isdead ? "DEAD" : "ALIVE"), this.colormap, vtos(this.glowmod));
  }
  
@@@ -663,7 -669,7 +669,7 @@@ void CSQCModel_Hook_PreDraw(entity this
        if((this.isplayermodel & ISPLAYER_MODEL) && this.drawmask) // this checks if it's a player MODEL!
        {
                CSQCPlayer_ModelAppearance_Apply(this, (this.isplayermodel & ISPLAYER_LOCAL));
-               CSQCPlayer_LOD_Apply(this);
+               CSQCPlayer_LOD_Apply(this, true);
  
                if(!isplayer)
                {
                        }
                }
        }
+       else
+               CSQCPlayer_LOD_Apply(this, false);
  
        CSQCModel_AutoTagIndex_Apply(this);
  
index 94660979584bf1e73301f6e98d1d9ed1a6149e22,01df92c4b4d14e8a08bdb6f931ad5a60d79a76eb..3914c83ca4df30a0c705372985a0954d81e44d81
@@@ -9,7 -9,6 +9,7 @@@
  #include <common/constants.qh>
  #include <common/ent_cs.qh>
  #include <common/mapinfo.qh>
 +#include <common/gamemodes/gamemode/br/br.qh>
  #include <common/minigames/cl_minigames.qh>
  #include <common/net_linked.qh>
  #include <common/scores.qh>
@@@ -210,6 -209,11 +210,11 @@@ void Scoreboard_InitScores(
  //float lastpnum;
  void Scoreboard_UpdatePlayerTeams()
  {
+       static float update_time;
+       if (time <= update_time)
+               return;
+       update_time = time;
        entity pl, tmp;
        //int num = 0;
        for(pl = players.sort_next; pl; pl = pl.sort_next)
@@@ -393,12 -397,12 +398,12 @@@ void Cmd_Scoreboard_Help(
  // otherwise the previous exclusive rule warns anyway
  // e.g. -teams,rc,cts,lms/kills ?+rc/kills
  #define SCOREBOARD_DEFAULT_COLUMNS \
 -"ping pl fps name |" \
 +"ping pl fps +br/squad name |" \
  " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
 -" -teams,lms/deaths +ft,tdm/deaths" \
 +" -teams,lms,br/deaths +ft,tdm/deaths" \
  " +tdm/sum" \
 -" -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
 -" -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
 +" -teams,lms,rc,cts,inv,ka,br/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
 +" -cts,dm,tdm,ka,ft,br/frags" /* tdm already has this in "score" */ \
  " +tdm,ft,dom,ons,as/teamkills"\
  " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
  " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
  " +as/objectives +nb/faults +nb/goals" \
  " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
  " +dom/ticks +dom/takes" \
 -" -lms,rc,cts,inv,nb/score"
 +" -lms,rc,cts,inv,nb,br/score" \
 +" +br/revivals" \
 +" +br/rank"
  
  void Cmd_Scoreboard_SetFields(int argc)
  {
@@@ -631,7 -633,7 +636,7 @@@ string Scoreboard_GetName(entity pl
        {
                sbt_field_icon0 = "gfx/scoreboard/player_ready";
        }
 -      else if(!teamplay)
 +      else if(!teamplay && !(ISGAMETYPE(BR) && STAT(SQUADCOLORS)))
        {
                int f = entcs_GetClientColors(pl.sv_entnum);
                {
@@@ -1653,7 -1655,7 +1658,7 @@@ vector Scoreboard_Rankings_Draw(vector 
  
                str = count_ordinal(i+1);
                drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
-               drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
+               drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
                str = ColorTranslateRGB(grecordholder[i]);
                if(cut)
                        str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
@@@ -2108,8 -2110,7 +2113,7 @@@ void Scoreboard_Draw(
  
        // print information about respawn status
        float respawn_time = STAT(RESPAWN_TIME);
-       if(!intermission)
-       if(respawn_time)
+       if(!intermission && respawn_time)
        {
                if(respawn_time < 0)
                {
index c877f8862405d8bf7d3b096181fa09d0f830784e,ac42a5bb25a15644f420f482ea6e3c4f56767a71..a5b6b2b0bb9c1a0ac4431d253dd02a5b2e901132
@@@ -10,12 -10,11 +10,12 @@@ REGISTER_DEATHTYPE(GENERIC
  REGISTER_DEATHTYPE(HURTTRIGGER,             DEATH_SELF_VOID,                DEATH_MURDER_VOID,              "")
  REGISTER_DEATHTYPE(KILL,                    DEATH_SELF_SUICIDE,             NULL,                           "")
  REGISTER_DEATHTYPE(LAVA,                    DEATH_SELF_LAVA,                DEATH_MURDER_LAVA,              "")
 +REGISTER_DEATHTYPE(RING,                    DEATH_SELF_RING,                DEATH_MURDER_RING,              "")
  REGISTER_DEATHTYPE(MIRRORDAMAGE,            DEATH_SELF_BETRAYAL,            NULL,                           "")
  REGISTER_DEATHTYPE(MONSTER_MAGE,            DEATH_SELF_MON_MAGE,            DEATH_MURDER_MONSTER,           "monster")
- REGISTER_DEATHTYPE(MONSTER_SHAMBLER_CLAW,   DEATH_SELF_MON_SHAMBLER_CLAW,   DEATH_MURDER_MONSTER,           "monster")
- REGISTER_DEATHTYPE(MONSTER_SHAMBLER_SMASH,  DEATH_SELF_MON_SHAMBLER_SMASH,  DEATH_MURDER_MONSTER,           "monster")
- REGISTER_DEATHTYPE(MONSTER_SHAMBLER_ZAP,    DEATH_SELF_MON_SHAMBLER_ZAP,    DEATH_MURDER_MONSTER,           "monster")
+ REGISTER_DEATHTYPE(MONSTER_GOLEM_CLAW,      DEATH_SELF_MON_GOLEM_CLAW,      DEATH_MURDER_MONSTER,           "monster")
+ REGISTER_DEATHTYPE(MONSTER_GOLEM_SMASH,     DEATH_SELF_MON_GOLEM_SMASH,     DEATH_MURDER_MONSTER,           "monster")
+ REGISTER_DEATHTYPE(MONSTER_GOLEM_ZAP,       DEATH_SELF_MON_GOLEM_ZAP,       DEATH_MURDER_MONSTER,           "monster")
  REGISTER_DEATHTYPE(MONSTER_SPIDER,          DEATH_SELF_MON_SPIDER,          DEATH_MURDER_MONSTER,           "monster")
  REGISTER_DEATHTYPE(MONSTER_WYVERN,          DEATH_SELF_MON_WYVERN,          DEATH_MURDER_MONSTER,           "monster")
  REGISTER_DEATHTYPE(MONSTER_ZOMBIE_JUMP,     DEATH_SELF_MON_ZOMBIE_JUMP,     DEATH_MURDER_MONSTER,           "monster")
diff --combined qcsrc/common/ent_cs.qc
index b976d651ed8b7cf5465a2360765bfcec02ed381e,5366371458fe52fadb86e3c2d7fc3dd8b2ca44db..222e74900298be4c56dd708b2f1234ebaab368ac
@@@ -2,12 -2,12 +2,12 @@@
  
  #if defined(CSQC)
        #include <common/gamemodes/_mod.qh>
-       #include <common/resources.qh>
+       #include <common/resources/resources.qh>
  #elif defined(MENUQC)
  #elif defined(SVQC)
        #include <common/gamemodes/_mod.qh>
-       #include <common/resources.qh>
-       #include <server/resources.qh>
+       #include <common/resources/resources.qh>
+       #include <common/resources/sv_resources.qh>
  #endif
  
  REGISTRY(EntCSProps, BITS(16) - 1)
@@@ -188,8 -188,7 +188,8 @@@ ENTCS_PROP(SOLID, true, sv_solid, solid
                        {
                                if (radar_showenemies) break;
                                if (SAME_TEAM(to, player)) break;
-                               if (!(IS_PLAYER(to) || to.caplayer)) break;
 +                              if (SAME_SQUAD(to, player)) break;
+                               if (!(IS_PLAYER(to) || INGAME(to))) break;
                        }
                        sf &= ENTCS_PUBLICMASK; // no private updates
                } while (0);
index 83c2f202fcbb9dceea3f223d1ae10a4b534cd4c2,0000000000000000000000000000000000000000..dee0dbfebae040aeadd04002fb9600a2fff56c1c
mode 100644,000000..100644
--- /dev/null
@@@ -1,1221 -1,0 +1,1221 @@@
- #include <server/resources.qh>
 +// battle royale
 +// author: Juhu
 +
 +#include "sv_br.qh"
 +#include <server/elimination.qh>
-     entity player = M_ARGV(0, entity);
++#include <common/resources/sv_resources.qh>
 +#include <common/mutators/base.qh>
 +
 +#define BR_KILLS_INSTANTLY(pl, dt) \
 +    (!IN_SQUAD((pl)) || (br_SquadFindLastAlive((pl).br_squad, true) == (pl)) || ((dt) == DEATH_HURTTRIGGER.m_id) \
 +    || ((dt) == DEATH_KILL.m_id) || ((dt) == DEATH_TEAMCHANGE.m_id) || ((dt) == DEATH_AUTOTEAMCHANGE.m_id) \
 +    || DEATH_ISWEAPON(dt, WEP_VAPORIZER))
 +
 +float br_CalculatePlayerDropAngle(entity this);
 +void br_LastPlayerForSquad_Notify(entity squad);
 +void br_RemovePlayer(entity player);
 +void br_Revive(entity player);
 +void br_Start();
 +bool br_CheckPlayers();
 +int br_WinningCondition();
 +
 +entity dropship;
 +
 +bool squads_colored = false;
 +
 +const float br_drop_time_secs = 1;
 +const float drop_speed_vertical_max = 0.9;
 +bool br_started = false;
 +.bool br_ring_warned;
 +.float br_drop_time;
 +.float br_force_drop_distance;
 +.int br_drop_launch;
 +.int br_drop_detached;
 +.bool br_drop_instructions;
 +.float br_ring_damage_time;
 +
 +.entity br_bleeding_inflictor;
 +.entity br_bleeding_attacker;
 +.int br_bleeding_deathtype;
 +..entity br_bleeding_weaponentity;
 +
 +// weapon set restoring for revive/drop
 +.WepSet br_wepset_old;
 +.Weapon br_weapon_prev[MAX_WEAPONSLOTS];
 +.float br_lastweapon_prev[MAX_WEAPONSLOTS];
 +
 +float autocvar_g_br_revive_health = 0.25;
 +float autocvar_g_br_bleeding_health = 0.5;
 +float autocvar_g_br_bleeding_armor = 50;
 +float autocvar_g_br_drop_damage = 0.5;
 +float autocvar_g_br_drop_speed_max = 2.5;
 +float autocvar_g_br_drop_speed_min = 1.25;
 +float autocvar_g_br_drop_speed_vertical_min = 0.1;
 +bool autocvar_g_br_squad_colors = true;
 +float autocvar_g_br_drop_accel_dive = 50;
 +float autocvar_g_br_drop_accel_turn = 600;
 +bool autocvar_g_br_startweapons = false;
 +float autocvar_g_br_squad_waypoint_distance = 1500;
 +
 +MUTATOR_HOOKFUNCTION(br, reset_map_global)
 +{
 +    dropship_path_length = 0; // this should kill the dropship
 +    dropship_path_direction = '0 0 0';
 +
 +    if(ring)
 +        delete(ring);
 +    ring = dropship = NULL;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, reset_map_players)
 +{
 +    FOREACH_CLIENT(true, {
 +        GameRules_scoring_add(it, BR_RANK, -GameRules_scoring_add(it, BR_RANK, 0));
 +        GameRules_scoring_add(it, BR_SQUAD, -GameRules_scoring_add(it, BR_SQUAD, 0));
 +        GameRules_scoring_add(it, BR_REVIVALS, -GameRules_scoring_add(it, BR_REVIVALS, 0));
 +
 +        STAT(DROP, it) = DROP_LANDED;
 +        STAT(BLEEDING, it) = false;
 +
 +        br_RemovePlayer(it);
 +
 +        it.br_wepset_old = start_weapons;
 +        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +        {
 +            it.br_weapon_prev[slot] = WEP_Null;
 +            it.br_lastweapon_prev[slot] = 0;
 +        }
 +    });
 +
 +    br_SquadUpdateInfo();
 +    return true;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, CheckRules_World)
 +{
 +    if(!br_started && !warmup_stage && (time > game_starttime) && br_CheckPlayers())
 +        br_Start();
 +
 +    M_ARGV(0, float) = br_WinningCondition();
 +    return true;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, GiveFragsForKill, CBC_ORDER_FIRST)
 +{
 +    M_ARGV(2, float) = 0; // no frags counted in Battle Royale
 +    return true;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, ForbidPlayerScore_Clear)
 +{
 +    // don't clear player score
 +    return true;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, GetPlayerStatus)
 +{
 +    entity player = M_ARGV(0, entity);
 +    return IN_SQUAD(player);
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, ClientConnect)
 +{
 +    entity player = M_ARGV(0, entity);
 +
 +    STAT(SQUADCOLORS, player) = squads_colored;
 +
 +    if(ring)
 +        ring_timelimit(ring);
 +
 +    br_SquadUpdateInfo();
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, ClientDisconnect)
 +{
 +    entity player = M_ARGV(0, entity);
 +
 +    br_RemovePlayer(player);
 +    br_SquadUpdateInfo();
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, PutClientInServer)
 +{
 +    entity player = M_ARGV(0, entity);
 +
 +    if (!br_started)
 +    {
 +        if(!warmup_stage && (time > game_starttime) && br_CheckPlayers())
 +            STAT(DROP, player) = DROP_TRANSPORT; // inhibits the spawn effect when the match is about to start
 +        return false;
 +    }
 +
 +    if (IN_SQUAD(player))
 +        Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_JOIN_DEAD);
 +    else
 +        Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_JOIN_LATE);
 +
 +    TRANSMUTE(Observer, player);
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, MakePlayerObserver)
 +{
 +    entity player = M_ARGV(0, entity);
 +    bool is_forced = M_ARGV(1, bool);
 +
 +    if(is_forced && IN_SQUAD(player))
 +    {
 +        br_SquadMember_Remove(player);
 +        br_SquadUpdateInfo();
 +    }
 +
 +    if(IN_SQUAD(player))
 +    {
 +        player.frags = FRAGS_PLAYER_OUT_OF_GAME;
 +        return true;
 +    }
 +
 +    return false;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, SpectateCopy)
 +{
 +    entity spectatee = M_ARGV(0, entity);
 +    entity client = M_ARGV(1, entity);
 +
 +    STAT(DROP, client) = STAT(DROP, spectatee);
 +    STAT(BLEEDING, client) = STAT(BLEEDING, spectatee);
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, SpectateSet)
 +{
 +    entity client = M_ARGV(0, entity);
 +    entity target = M_ARGV(1, entity);
 +
 +    return (IN_SQUAD(client) && !client.br_squad.br_squad_dead && DIFF_SQUAD(client, target));
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, SpectateNext)
 +{
 +    entity client = M_ARGV(0, entity);
 +
 +    if(!IS_REAL_CLIENT(client) || !IN_SQUAD(client) || client.br_squad.br_squad_dead)
 +        return false;
 +
 +    entity new_target;
 +
 +    if(client.enemy && client.enemy.br_squad_next)
 +        new_target = client.enemy.br_squad_next;
 +    else
 +        new_target = client.br_squad.br_squad_first;
 +
 +    while((new_target == client) || IS_DEAD(new_target) || !IS_PLAYER(new_target))
 +    {
 +        new_target = new_target.br_squad_next;
 +        if(!new_target)
 +            new_target = client.br_squad.br_squad_first;
 +    }
 +    M_ARGV(1, entity) = new_target;
 +
 +    return true;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, SpectatePrev)
 +{
 +    entity client = M_ARGV(0, entity);
 +
 +    if(!IS_REAL_CLIENT(client) || !IN_SQUAD(client) || client.br_squad.br_squad_dead)
 +        return MUT_SPECPREV_CONTINUE;
 +
 +    entity new_target;
 +
 +    if(client.enemy && client.enemy.br_squad_prev)
 +        new_target = client.enemy.br_squad_prev;
 +    else
 +        new_target = client.br_squad.br_squad_last;
 +
 +    while((new_target == client) || IS_DEAD(new_target) || !IS_PLAYER(new_target))
 +    {
 +        new_target = new_target.br_squad_prev;
 +        if(!new_target)
 +            new_target = client.br_squad.br_squad_last;
 +    }
 +    M_ARGV(1, entity) = new_target;
 +
 +    return MUT_SPECPREV_FOUND;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, ForbidSpawn)
 +{
 +    return br_started;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, WantWeapon)
 +{
 +    if(autocvar_g_br_startweapons)
 +        return false;
 +
 +    M_ARGV(1, float) = 0;
 +    return true;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, SetStartItems)
 +{
 +    start_items &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS);
 +
 +    start_health       = warmup_start_health       = cvar("g_br_start_health");
 +    start_armorvalue   = warmup_start_armorvalue   = cvar("g_br_start_armor");
 +    start_ammo_shells  = warmup_start_ammo_shells  = cvar("g_br_start_ammo_shells");
 +    start_ammo_nails   = warmup_start_ammo_nails   = cvar("g_br_start_ammo_nails");
 +    start_ammo_rockets = warmup_start_ammo_rockets = cvar("g_br_start_ammo_rockets");
 +    start_ammo_cells   = warmup_start_ammo_cells   = cvar("g_br_start_ammo_cells");
 +    start_ammo_plasma  = warmup_start_ammo_plasma  = cvar("g_br_start_ammo_plasma");
 +    start_ammo_fuel    = warmup_start_ammo_fuel    = cvar("g_br_start_ammo_fuel");
 +}
 +
 +// adjusted freezetag reviving code
 +#ifdef IN_REVIVING_RANGE
 +    #undef IN_REVIVING_RANGE
 +#endif
 +
 +#define IN_REVIVING_RANGE(player, it, revive_extra_size) \
 +    (it != player && IS_PLAYER(it) && !IS_DEAD(it) && SAME_SQUAD(it, player) \
 +    && boxesoverlap(player.absmin - revive_extra_size, player.absmax + revive_extra_size, it.absmin, it.absmax))
 +
 +MUTATOR_HOOKFUNCTION(br, PlayerPreThink, CBC_ORDER_FIRST)
 +{
 +    entity player = M_ARGV(0, entity);
 +
 +    if (game_stopped || !frametime || !IS_PLAYER(player))
 +        return true;
 +
 +    if(ring)
 +    {
 +        const float ring_damage_interval = 0.75;
 +        vector current_origin;
 +        if(!player.vehicle)
 +            current_origin = player.origin + player.view_ofs;
 +        else
 +            current_origin = player.vehicle.origin;
 +        if(vlen(current_origin - ring.origin) > ring_calculate_current_radius(ring))
 +        {
 +            if(!player.br_ring_warned)
 +            {
 +                player.br_ring_warned = true;
 +                player.br_ring_damage_time = time + ring_damage_interval;
 +                Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_BR_RING_WARN);
 +            }
 +
 +            // ring damage
 +            if (player.br_ring_damage_time < time)
 +            {
 +                if(player.vehicle) // if the player is controlling a vehicle
 +                {
 +                    if(autocvar_g_br_ring_exitvehicle)
 +                        vehicles_exit(player.vehicle, VHEF_RELEASE); // begone!
 +                    else
 +                        vehicles_damage(player.vehicle, ring, ring, 10 * ring.strength * ring_damage_interval, DEATH_RING.m_id, DMG_NOWEP, player.vehicle.origin, '0 0 0');
 +                }
 +                Damage(player, ring, ring, ring.strength * ring_damage_interval, DEATH_RING.m_id, DMG_NOWEP, player.origin, '0 0 0');
 +                player.br_ring_damage_time = time + ring_damage_interval;
 +            }
 +        }
 +        else
 +        {
 +            player.br_ring_warned = false;
 +        }
 +    }
 +
 +    if((STAT(DROP, player) == DROP_FALLING) && (player.br_drop_detached != 2) && IN_SQUAD(player) && (player != player.br_squad.br_squad_drop_leader)){
 +        // atck2 has to be released then pressed to detach
 +        if(!(STAT(PRESSED_KEYS, player) & KEY_ATCK2)){
 +            if(player.br_drop_detached == 0){
 +                player.br_drop_detached = 1;
 +            }
 +        }
 +        else{
 +            if(player.br_drop_detached == 1){
 +                player.br_drop_detached = 2;
 +                Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
 +            }
 +        }
 +    }
 +
 +    if(STAT(DROP, player) == DROP_TRANSPORT){
 +        if(time > (player.br_squad.br_drop_time + br_drop_time_secs))
 +        {
 +            if(!player.br_drop_instructions)
 +            {
 +                player.br_drop_instructions = true;
 +                Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_DROPSHIP);
 +            }
 +
 +            // jump has to be released then pressed to launch
 +            if(!(STAT(PRESSED_KEYS, player) & KEY_JUMP)){
 +                if(player.br_drop_launch == 0){
 +                    player.br_drop_launch = 1;
 +                }
 +            }
 +            else{
 +                if(player.br_drop_launch == 1){
 +                    player.br_drop_launch = 2;
 +                }
 +            }
 +        }
 +
 +        if(!(IS_REAL_CLIENT(player) && (player.br_drop_launch == 2)) && (dropship_path_length > player.br_squad.br_force_drop_distance)){
 +            player.velocity = dropship_path_direction * dropship_speed;
 +        }
 +        else{
 +            if(!(IN_SQUAD(player) && player.br_squad.br_squad_drop_leader))
 +            {
 +                player.effects &= ~EF_NODRAW;
 +                player.takedamage = DAMAGE_AIM;
 +                player.solid = SOLID_SLIDEBOX;
 +                if(!autocvar__notarget)
 +                    player.flags &= ~FL_NOTARGET;
 +                Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
 +                STAT(DROP, player) = DROP_FALLING;
 +                player.br_drop_detached = 0;
 +                float mindropspeed = PHYS_MAXAIRSPEED(player) * max(autocvar_g_br_drop_speed_min, 0); // no maxspeed_mod available here
 +                float maxdropspeed_ratio = drop_speed_vertical_max; // moving straight down is glitchy
 +                float mindropspeed_ratio = bound(0, autocvar_g_br_drop_speed_vertical_min, drop_speed_vertical_max);
 +                float pitch_view = max(player.v_angle.x, 0);
 +
 +                // pitch_view angle needs to be between 0 and 90 degrees
 +                if(pitch_view > 90)
 +                    pitch_view = 180 - pitch_view;
 +
 +                player.velocity.x = cos(player.angles.y * DEG2RAD);
 +                player.velocity.y = sin(player.angles.y * DEG2RAD);
 +                player.velocity.z = -tan(bound(asin(mindropspeed_ratio), pitch_view * DEG2RAD, asin(maxdropspeed_ratio)));
 +
 +                player.velocity = normalize(player.velocity) * mindropspeed;
 +
 +                player.angles.x = br_CalculatePlayerDropAngle(player) - 90;
 +                player.angles.y = vectoangles(vec2(player.velocity)).y + 180;
 +                player.angles.z = 180;
 +
 +                if(IN_SQUAD(player) && ((IS_REAL_CLIENT(player) && (player.br_drop_launch == 2)) || br_SquadIsBotsOnly(player.br_squad)))
 +                {
 +                    player.br_squad.br_squad_drop_leader = player;
 +
 +                    vector drop_base_offset;
 +                    drop_base_offset.x = cos((player.angles.y + 90) * DEG2RAD);
 +                    drop_base_offset.y = sin((player.angles.y + 90) * DEG2RAD);
 +                    drop_base_offset.z = 0;
 +                    drop_base_offset = drop_base_offset * vlen(vec2(player.maxs - player.mins)) + drop_base_offset * 32; // I hope individual players never get different mins/maxs
 +
 +                    vector drop_offset = drop_base_offset;
 +
 +                    FOREACH_CLIENT(IS_PLAYER(it) && (it != player) && SAME_SQUAD(it, player) && (STAT(DROP, it) == DROP_TRANSPORT), {
 +                        it.effects &= ~EF_NODRAW;
 +                        it.takedamage = DAMAGE_AIM;
 +                        it.solid = SOLID_SLIDEBOX;
 +                        if(!autocvar__notarget)
 +                            it.flags &= ~FL_NOTARGET;
 +                        Kill_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CPID_BR_DROP);
 +                        STAT(DROP, it) = DROP_FALLING;
 +                        it.br_drop_detached = 0;
 +                        Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_BR_DROP_DETACH);
 +
 +                        setorigin(it, player.origin + drop_offset); // FIXME: this can teleport players into brushes/void
 +                        drop_offset += drop_base_offset;
 +
 +                        it.velocity = player.velocity;
 +                        it.angles = player.angles;
 +                    });
 +                }
 +            }
 +        }
 +    }
 +
 +    // adjusted freezetag reviving code
 +    entity revivers_last = NULL;
 +    entity revivers_first = NULL;
 +
 +    bool player_is_reviving = false;
 +    bool player_is_being_revived = false;
 +    vector revive_extra_size = '1 1 1' * max(autocvar_g_br_revive_extra_size, 0);
 +    FOREACH_CLIENT(IS_PLAYER(it), {
 +        // check if player is reviving anyone
 +        if (STAT(BLEEDING, it))
 +        {
 +            if (STAT(BLEEDING, player))
 +                continue;
 +            if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
 +                continue;
 +            player_is_reviving = true;
 +            break;
 +        }
 +
 +        if (!STAT(BLEEDING, player))
 +            continue; // both player and it are NOT bleeding
 +        if (!IN_REVIVING_RANGE(player, it, revive_extra_size))
 +            continue;
 +
 +        // found a squadmate that is reviving player
 +        if (revivers_last)
 +            revivers_last.chain = it;
 +        revivers_last = it;
 +        if (!revivers_first)
 +            revivers_first = it;
 +        player_is_being_revived = true;
 +    });
 +    if (revivers_last)
 +        revivers_last.chain = NULL;
 +
 +    if (!player_is_being_revived) // no squadmate nearby
 +    {
 +        float clearspeed = max(autocvar_g_br_revive_clearspeed, 0);
 +        if (STAT(BLEEDING, player))
 +            STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) - frametime * clearspeed, 1);
 +        else if (!player_is_reviving)
 +            STAT(REVIVE_PROGRESS, player) = 0; // reviving nobody
 +    }
 +    else // OK, there is at least one squadmate reviving us
 +    {
 +        float spd = max(autocvar_g_br_revive_speed, 0);
 +        STAT(REVIVE_PROGRESS, player) = bound(0, STAT(REVIVE_PROGRESS, player) + frametime * spd, 1);
 +
 +        if(STAT(REVIVE_PROGRESS, player) >= 1)
 +        {
 +            br_Revive(player);
 +
 +            // EVERY squad mate nearby gets a point (even if multiple!)
 +            for(entity it = revivers_first; it; it = it.chain)
 +            {
 +                GameRules_scoring_add(it, BR_REVIVALS, +1);
 +            }
 +
 +            Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_BR_REVIVED, revivers_first.netname);
 +            Send_Notification(NOTIF_ONE, revivers_first, MSG_CENTER, CENTER_BR_REVIVE, player.netname);
 +            Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_REVIVED, player.netname, revivers_first.netname);
 +            if(autocvar_sv_eventlog)
 +            {
 +                string revivers = "";
 +                for(entity it = revivers_first; it; it = it.chain)
 +                    revivers = strcat(revivers, ftos(it.playerid), ",");
 +                revivers = substring(revivers, 0, strlen(revivers) - 1);
 +                GameLogEcho(strcat(":br:revival:", ftos(player.playerid), ":", revivers));
 +            }
 +        }
 +
 +        for(entity it = revivers_first; it; it = it.chain)
 +            STAT(REVIVE_PROGRESS, it) = STAT(REVIVE_PROGRESS, player);
 +    }
 +
 +    if (STAT(BLEEDING, player))
 +    {
 +        entity player_wp = player.waypointsprite_attached;
 +        if (player_is_being_revived)
 +        {
 +            WaypointSprite_UpdateSprites(player_wp, WP_BRReviving, WP_Null, WP_Null);
 +            WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_BR_REVIVING_COLOR);
 +        }
 +        else
 +        {
 +            WaypointSprite_UpdateSprites(player_wp, WP_BRBleeding, WP_Null, WP_Null);
 +            WaypointSprite_UpdateTeamRadar(player_wp, RADARICON_WAYPOINT, WP_BR_BLEEDING_COLOR);
 +        }
 +
 +        WaypointSprite_UpdateMaxHealth(player_wp, 1);
 +        WaypointSprite_UpdateHealth(player_wp, STAT(REVIVE_PROGRESS, player));
 +    }
 +
 +    return true;
 +}
 +
 +#undef IN_REVIVING_RANGE
 +
 +MUTATOR_HOOKFUNCTION(br, PM_Physics)
 +{
 +    entity player = M_ARGV(0, entity);
 +    float maxspeed_mod = M_ARGV(1, float);
 +    float dt = M_ARGV(2, float); // tick rate
 +
 +    if(STAT(DROP, player) == DROP_TRANSPORT)
 +        return true;
 +
 +    // set the drop stat to landed on the next frame if it was set on landing
 +    if(STAT(DROP, player) == DROP_LANDING)
 +        STAT(DROP, player) = DROP_LANDED;
 +
 +    // TODO: improve dropping physics
 +    if(STAT(DROP, player) == DROP_FALLING){
 +        if(!IS_ONGROUND(player) && (player.waterlevel < WATERLEVEL_SWIMMING) && ((tracebox(player.origin, player.mins, player.maxs, player.origin - '0 0 1', MOVE_NOMONSTERS, player), trace_fraction) >= 1)) // IS_ONGROUND doesn't work if jump is held (jump is theoretically blocked until landed)
 +        {
 +            ITEMS_STAT(player) |= IT_USING_JETPACK;
 +            bool has_drop_leader = IN_SQUAD(player) && (player.br_drop_detached != 2) && (player.br_squad.br_squad_drop_leader && (STAT(DROP, player.br_squad.br_squad_drop_leader) == DROP_FALLING));
 +            bool player_is_drop_leader = has_drop_leader && (player == player.br_squad.br_squad_drop_leader);
 +            if(player_is_drop_leader || !has_drop_leader)
 +            {
 +                float maxairspeed = PHYS_MAXAIRSPEED(player) * max(maxspeed_mod, 1);
 +                float maxdropspeed = maxairspeed * max(autocvar_g_br_drop_speed_max, 0);
 +                float mindropspeed = maxairspeed * max(autocvar_g_br_drop_speed_min, 0);
 +                float maxdropspeed_ratio = drop_speed_vertical_max; // moving straight down is glitchy
 +                float mindropspeed_ratio = bound(0, autocvar_g_br_drop_speed_vertical_min, drop_speed_vertical_max);
 +                float accel_dive = max(autocvar_g_br_drop_accel_dive, 0);
 +                float accel_turn = max(autocvar_g_br_drop_accel_turn, 0);
 +                float dropspeed = vlen(player.velocity);
 +                float dropspeed_xy = vlen(vec2(player.velocity));
 +                float pitch_current = br_CalculatePlayerDropAngle(player);
 +                float pitch_view = max(player.v_angle.x, 0);
 +
 +                // pitch_view angle needs to be between 0 and 90 degrees
 +                if(pitch_view > 90)
 +                    pitch_view = 180 - pitch_view;
 +
 +                float pitch_diff = pitch_current - pitch_view;
 +                float pitch_ratio_wish = 0;
 +
 +                // calculate how much the player wants to change pitch (ratio is at least 0.1)
 +                // ratio is between -1 (looking straight down) and +1 (looking straight ahead or up)
 +                if((pitch_diff < 0) && (pitch_current < 90))
 +                    pitch_ratio_wish = bound(-1, sin(pitch_diff / (90 - pitch_current) * M_PI_2), -0.1);
 +                else if((pitch_diff > 0) && (pitch_current > 0))
 +                    pitch_ratio_wish = bound(0.1, sin(pitch_diff / pitch_current * M_PI_2), 1);
 +
 +                makevectors(player.v_angle);
 +                // horizontal wishvel as usual
 +                vector wishvel = v_forward * CS(player).movement.x + v_right * CS(player).movement.y;
 +                wishvel = normalize(wishvel) * min(1, vlen(wishvel) / maxairspeed);
 +                // vertical wishvel using forward movement and the previously calculated ratio
 +                wishvel.z = pitch_ratio_wish * bound(0, CS(player).movement.x / maxairspeed, 1);
 +                // apply turn acceleration to wishvel
 +                wishvel.x *= accel_turn;
 +                wishvel.y *= accel_turn;
 +                wishvel.z *= accel_turn;
 +                player.velocity += wishvel * dt;
 +                player.velocity = normalize(eZ * player.velocity.z + normalize(vec2(player.velocity)) * dropspeed_xy);
 +
 +                // if there is no horizontal movement point the horizontal vector towards the view direction
 +                if(vlen(vec2(player.velocity)) == 0)
 +                    player.velocity += (eX * cos(player.angles.y * DEG2RAD) + eY * sin(player.angles.y * DEG2RAD)) * sqrt(1 - pow(maxdropspeed_ratio, 2));
 +
 +                // modify mindropspeed_ratio and maxdropspeed_ratio so that the player does not rotate beyond the view angle
 +                float pitch_ratio_view = sin(pitch_view * DEG2RAD);
 +                if(pitch_ratio_wish > 0)
 +                    mindropspeed_ratio = bound(mindropspeed_ratio, pitch_ratio_view, maxdropspeed_ratio);
 +                else if(pitch_ratio_wish < 0)
 +                    maxdropspeed_ratio = bound(mindropspeed_ratio, pitch_ratio_view, maxdropspeed_ratio);
 +
 +                // constrain to vertical min/maxdropspeed
 +                if(player.velocity.z > -mindropspeed_ratio)
 +                    player.velocity.z = -mindropspeed_ratio;
 +                if(player.velocity.z < -maxdropspeed_ratio)
 +                    player.velocity.z = -maxdropspeed_ratio;
 +
 +                // adjust horizontal speed so that vertical speed + horizontal speed = maxdropspeed
 +                float dropangle = br_CalculatePlayerDropAngle(player);
 +                const float accelangle = 20;
 +                dropspeed = bound(mindropspeed, dropspeed + accel_dive * (dropangle - accelangle) / accelangle * dt, maxdropspeed);
 +                player.velocity = normalize(player.velocity) * dropspeed;
 +
 +                player.angles.x = dropangle - 90;
 +                player.angles.y = vectoangles(vec2(player.velocity)).y + 180;
 +                player.angles.z = 180;
 +
 +                if(player_is_drop_leader)
 +                {
 +                    FOREACH_CLIENT(IS_PLAYER(it) && (it != player) && SAME_SQUAD(it, player) && (it.br_drop_detached != 2) && (STAT(DROP, it) == DROP_FALLING), {
 +                        it.velocity = player.velocity;
 +                        it.angles = player.angles;
 +                    });
 +                }
 +                else if((player.br_drop_detached != 2) && IN_SQUAD(player))
 +                {
 +                    player.br_drop_detached = 2;
 +                    Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
 +                }
 +            }
 +            else
 +            {
 +                player.velocity = player.br_squad.br_squad_drop_leader.velocity;
 +                player.angles = player.br_squad.br_squad_drop_leader.angles; // no fixangles, only moves the player model not the player view
 +            }
 +
 +            return true;
 +        }
 +        else
 +        {
 +            if((player.br_drop_detached != 2) && IN_SQUAD(player) && (player != player.br_squad.br_squad_drop_leader))
 +            {
 +                player.br_drop_detached = 2;
 +                Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP);
 +            }
 +
 +            STAT(DROP, player) = DROP_LANDING;
 +            set_movetype(player, MOVETYPE_WALK);
 +            ITEMS_STAT(player) &= ~IT_USING_JETPACK;
 +            player.flags |= FL_PICKUPITEMS;
 +            player.dphitcontentsmask |= DPCONTENTS_BODY;
 +
 +            STAT(WEAPONS, player) = player.br_wepset_old;
 +
 +            .entity weaponentity = weaponentities[0];
 +            W_SwitchWeapon_Force(player, w_getbestweapon(player, weaponentity), weaponentity);
 +        }
 +    }
 +
 +    // injured players can't swim
 +    if(STAT(BLEEDING, player)){
 +        if(player.waterlevel >= WATERLEVEL_SWIMMING)
 +        {
 +            CS(player).movement.z = -60; // drift towards bottom
 +            player.v_angle.x = 0;
 +            player.com_in_jump = false;
 +        }
 +    }
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, Damage_Calculate)
 +{
 +    entity frag_target = M_ARGV(2, entity);
 +    float frag_deathtype = M_ARGV(3, float);
 +
 +    if(STAT(DROP, frag_target) != DROP_LANDED)
 +    {
 +        // weapon impact has no push force while dropping
 +        M_ARGV(6, vector) = '0 0 0';
 +
 +        if(STAT(DROP, frag_target) == DROP_TRANSPORT)
 +            M_ARGV(4, float) = M_ARGV(5, float) = 0; // can't take damage while on the dropship
 +        else if(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER)); // do not adjust vaporizer damage
 +        else
 +        {
 +            switch(frag_deathtype)
 +            {
 +                case DEATH_FALL.m_id:
 +                case DEATH_SHOOTING_STAR.m_id:
 +                    // do not take fall damage when landing from dropship
 +                    M_ARGV(4, float) = M_ARGV(5, float) = 0;
 +                    break;
 +                default:
 +                    // only take half of the usual damage
 +                    M_ARGV(4, float) *= max(autocvar_g_br_drop_damage, 0);
 +                    M_ARGV(5, float) *= max(autocvar_g_br_drop_damage, 0);
 +            }
 +        }
 +    }
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, PlayerDies, CBC_ORDER_FIRST)
 +{
 +    entity frag_attacker = M_ARGV(1, entity);
 +    entity frag_target = M_ARGV(2, entity);
 +    float frag_deathtype = M_ARGV(3, float); // float for some reason, breaks if changed to int
 +
 +    if(!IS_PLAYER(frag_target))
 +        return true;
 +
 +    if(STAT(DROP, frag_target) == DROP_TRANSPORT)
 +    {
 +        frag_target.effects &= ~EF_NODRAW;
 +        frag_target.takedamage = DAMAGE_AIM;
 +        frag_target.solid = SOLID_SLIDEBOX;
 +        if(!autocvar__notarget)
 +            frag_target.flags &= ~FL_NOTARGET;
 +        Kill_Notification(NOTIF_ONE_ONLY, frag_target, MSG_CENTER, CPID_BR_DROP);
 +    }
 +
 +    if(STAT(DROP, frag_target) == DROP_FALLING)
 +    {
 +        if((frag_target.br_drop_detached != 2) && IN_SQUAD(frag_target) && (frag_target != frag_target.br_squad.br_squad_drop_leader))
 +        {
 +            frag_target.br_drop_detached = 2;
 +            Kill_Notification(NOTIF_ONE_ONLY, frag_target, MSG_CENTER, CPID_BR_DROP);
 +        }
 +    }
 +
 +    if(STAT(DROP, frag_target) != DROP_LANDED)
 +    {
 +        set_movetype(frag_target, MOVETYPE_WALK);
 +        frag_target.dphitcontentsmask |= DPCONTENTS_BODY;
 +        STAT(WEAPONS, frag_target) = frag_target.br_wepset_old;
 +        STAT(DROP, frag_target) = DROP_LANDED;
 +    }
 +
 +    if(STAT(BLEEDING, frag_target) || BR_KILLS_INSTANTLY(frag_target, frag_deathtype))
 +    {
 +        if(STAT(BLEEDING, frag_target))
 +        {
 +            Kill_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CPID_BR_DOWN);
 +            STAT(BLEEDING, frag_target) = false;
 +
 +            // restore weapons on death to make weapon drop work
 +            STAT(WEAPONS, frag_target) = frag_target.br_wepset_old;
 +            for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +            {
 +                .entity weaponentity = weaponentities[slot];
 +                frag_target.(weaponentity).m_weapon = frag_target.br_weapon_prev[slot];
 +            }
 +        }
 +        WaypointSprite_Kill(frag_target.br_allywaypoint);
 +
 +        frag_target.respawn_flags = RESPAWN_SILENT | RESPAWN_FORCE;
 +        frag_target.respawn_time = time + 2;
 +        return true;
 +    }
 +
 +    frag_target.flags &= ~FL_PICKUPITEMS;
 +    RemoveGrapplingHooks(frag_target);
 +    StatusEffects_removeall(frag_target, STATUSEFFECT_REMOVE_NORMAL);
 +
 +    SetResource(frag_target, RES_HEALTH, start_health * max(autocvar_g_br_bleeding_health, 0));
 +    SetResource(frag_target, RES_ARMOR, max(autocvar_g_br_bleeding_armor, 0));
 +    Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN_WAIT);
 +    STAT(BLEEDING, frag_target) = true;
 +
 +    FOREACH_CLIENT(IS_PLAYER(it),
 +    {
 +        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +        {
 +            .entity weaponentity = weaponentities[slot];
 +            if(it.(weaponentity).hook.aiment == frag_target)
 +                RemoveHook(it.(weaponentity).hook);
 +        }
 +    });
 +
 +    frag_target.br_wepset_old = STAT(WEAPONS, frag_target);
 +    for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +    {
 +        .entity weaponentity = weaponentities[slot];
 +        frag_target.br_weapon_prev[slot] = frag_target.(weaponentity).m_switchweapon;
 +        frag_target.br_lastweapon_prev[slot] = frag_target.(weaponentity).cnt;
 +    }
 +    STAT(WEAPONS, frag_target) = '0 0 0';
 +
 +    WaypointSprite_Spawn(WP_BRBleeding, 0, 0, frag_target, '0 0 64', NULL, 0, frag_target, waypointsprite_attached, true, RADARICON_WAYPOINT);
 +
 +    if(frag_attacker == frag_target || !frag_attacker || ITEM_DAMAGE_NEEDKILL(frag_deathtype))
 +    {
 +        if(IS_PLAYER(frag_target))
 +            Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN_SELF);
 +        Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_DOWN_SELF, frag_target.netname);
 +    }
 +    else
 +    {
 +        if(IS_PLAYER(frag_target))
 +            Send_Notification(NOTIF_ONE, frag_target, MSG_CENTER, CENTER_BR_DOWN, frag_attacker.netname);
 +        Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_BR_DOWN, frag_target.netname, frag_attacker.netname);
 +    }
 +
 +    br_SquadUpdateInfo();
 +
 +    return true;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, PlayerDied)
 +{
 +    entity player = M_ARGV(0, entity);
 +    if(br_started)
 +    {
 +        br_LastPlayerForSquad_Notify(player.br_squad);
 +        br_SquadUpdateInfo();
 +    }
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, ClientObituary)
 +{
 +    entity frag_inflictor = M_ARGV(0, entity);
 +    entity frag_attacker = M_ARGV(1, entity);
 +    entity frag_target = M_ARGV(2, entity);
 +    float frag_deathtype = M_ARGV(3, float); // float for some reason, breaks if changed to int
 +    //entity frag_weaponentity = M_ARGV(4, entity);
 +
 +    if(!STAT(BLEEDING, frag_target) && !BR_KILLS_INSTANTLY(frag_target, frag_deathtype))
 +    {
 +        frag_target.br_bleeding_inflictor = frag_inflictor;
 +        frag_target.br_bleeding_attacker = frag_attacker;
 +        frag_target.br_bleeding_deathtype = frag_deathtype;
 +        //frag_target.br_bleeding_weaponentity = frag_weaponentity; // TODO: get entity field
 +        return true;
 +    }
 +
 +    if(STAT(BLEEDING, frag_target) && frag_target.br_bleeding_attacker)
 +    {
 +        entity new_inflictor = frag_target.br_bleeding_inflictor;
 +        entity new_attacker = frag_target.br_bleeding_attacker;
 +        int new_deathtype = frag_target.br_bleeding_deathtype;
 +        .entity new_weaponentity = frag_target.br_bleeding_weaponentity;
 +        frag_target.br_bleeding_attacker = frag_target.br_bleeding_inflictor = NULL;
 +
 +        Obituary(new_attacker, new_inflictor, frag_target, new_deathtype, new_weaponentity);
 +        return true;
 +    }
 +
 +    return false;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, SetResource)
 +{
-     int res_type = M_ARGV(1, int);
-     float amount = M_ARGV(2, float);
++    entity player = M_ARGV(7, entity);
 +    if(!IS_PLAYER(player))
 +        return false;
 +
-     entity player = M_ARGV(0, entity);
++    entity res_type = M_ARGV(8, entity);
++    float amount = M_ARGV(9, float);
 +
 +    if(STAT(BLEEDING, player) && (res_type == RES_HEALTH || res_type == RES_ARMOR))
 +    {
 +        if(amount > GetResource(player, res_type)) // prevent the player from getting health or armor in any way
 +            return true;
 +    }
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, GetResourceLimit)
 +{
-     int res_type = M_ARGV(1, int);
++    entity player = M_ARGV(7, entity);
 +
 +    if(!IS_PLAYER(player) || !STAT(BLEEDING, player))
 +        return false;
 +
-             M_ARGV(2, float) *= max(autocvar_g_br_bleeding_health, 0);
++    entity res_type = M_ARGV(8, entity);
 +
 +    switch(res_type)
 +    {
 +        case RES_HEALTH:
-             M_ARGV(2, float) = max(autocvar_g_br_bleeding_armor, 0);
++            M_ARGV(9, float) *= max(autocvar_g_br_bleeding_health, 0);
 +            break;
 +        case RES_ARMOR:
-     reset_map(true);
++            M_ARGV(9, float) = max(autocvar_g_br_bleeding_armor, 0);
 +    }
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, PlayerRegen, CBC_ORDER_FIRST)
 +{
 +    entity player = M_ARGV(0, entity);
 +
 +    if(STAT(BLEEDING, player)){
 +        M_ARGV(7, float) = max(autocvar_g_br_bleed, 0);
 +        M_ARGV(8, float) = max(autocvar_g_br_bleedlinear, 0);
 +        M_ARGV(2, float) = M_ARGV(10, float) = 0;
 +    }
 +    else{
 +        M_ARGV(2, float) = M_ARGV(3, float) = 0; // no regeneration or rot in battle royale
 +    }
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, PlayerCanCrouch)
 +{
 +    entity player = M_ARGV(0, entity);
 +    if(STAT(BLEEDING, player))
 +        M_ARGV(1, bool) = true;
 +    else if(STAT(DROP, player) != DROP_LANDED)
 +        M_ARGV(1, bool) = false;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, PlayerJump)
 +{
 +    entity player = M_ARGV(0, entity);
 +    return STAT(BLEEDING, player) || (STAT(DROP, player) != DROP_LANDED);
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, BotShouldAttack)
 +{
 +    entity bot = M_ARGV(0, entity);
 +    entity target = M_ARGV(1, entity);
 +
 +    return SAME_SQUAD(bot, target) || (STAT(DROP, target) == DROP_TRANSPORT);
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, TurretValidateTarget)
 +{
 +    entity turret = M_ARGV(0, entity);
 +    entity target = M_ARGV(1, entity);
 +
 +    if(!br_started || SAME_SQUAD(turret, target) || (STAT(DROP, target) == DROP_TRANSPORT))
 +    {
 +        M_ARGV(3, float) = -1;
 +        return true;
 +    }
 +
 +    return false;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, AccuracyTargetValid)
 +{
 +    entity attacker = M_ARGV(0, entity);
 +    entity target = M_ARGV(1, entity);
 +
 +    if(SAME_SQUAD(attacker, target) || (STAT(DROP, target) == DROP_TRANSPORT))
 +        return MUT_ACCADD_INDIFFERENT;
 +    return MUT_ACCADD_VALID;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, CustomizeWaypoint)
 +{
 +    entity wp = M_ARGV(0, entity);
 +    entity player = M_ARGV(1, entity);
 +
 +    if(wp.owner == NULL)
 +        return true;
 +
 +    if((wp == wp.owner.br_allywaypoint) && (vdist(wp.owner.origin - player.origin, <, autocvar_g_br_squad_waypoint_distance) || STAT(BLEEDING, wp.owner)))
 +        return true;
 +
 +    if(!IS_PLAYER(player) || DIFF_SQUAD(wp.owner, player))
 +        return true;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, ClientKill)
 +{
 +    entity player = M_ARGV(0, entity);
 +
 +    if(br_started)
 +    {
 +        // no forfeiting once the game started
 +        Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_FORFEIT);
 +        return true;
 +    }
 +
 +    return false;
 +}
 +
 +MUTATOR_HOOKFUNCTION(br, ClientCommand_Spectate)
 +{
 +    entity player = M_ARGV(0, entity);
 +
 +    if(br_started)
 +    {
 +        // no forfeiting once the game started
 +        Send_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CENTER_BR_FORFEIT);
 +        return MUT_SPECCMD_RETURN;
 +    }
 +    return MUT_SPECCMD_CONTINUE;
 +}
 +
 +float br_CalculatePlayerDropAngle(entity this)
 +{
 +    if(this.velocity.z < 0)
 +    {
 +        float dropspeed_xy = vlen(vec2(this.velocity));
 +        float dropspeed_z = fabs(this.velocity.z);
 +        return 90 - atan(dropspeed_xy / dropspeed_z) * RAD2DEG;
 +    }
 +
 +    return 0;
 +}
 +
 +void br_LastPlayerForSquad_Notify(entity squad)
 +{
 +    entity player = br_SquadFindLastAlive(squad, false);
 +    if(player)
 +        Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_ALONE);
 +}
 +
 +void br_RemovePlayer(entity player)
 +{
 +    br_SquadMember_Remove(player);
 +
 +    FOREACH_CLIENT((it.br_bleeding_attacker == player) || (it.br_bleeding_inflictor == player), {
 +        it.br_bleeding_attacker = it.br_bleeding_inflictor = NULL;
 +    });
 +}
 +
 +void br_Revive(entity player)
 +{
 +    if(STAT(BLEEDING, player))
 +    {
 +        Kill_Notification(NOTIF_ONE, player, MSG_CENTER, CPID_BR_DOWN);
 +        STAT(BLEEDING, player) = false;
 +    }
 +    player.flags |= FL_PICKUPITEMS;
 +    SetResource(player, RES_HEALTH, start_health * max(autocvar_g_br_revive_health, 0));
 +    SetResource(player, RES_ARMOR, 0);
 +
 +    STAT(WEAPONS, player) = player.br_wepset_old;
 +    for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +    {
 +        .entity weaponentity = weaponentities[slot];
 +        W_SwitchWeapon_Force(player, player.br_weapon_prev[slot], weaponentity);
 +        player.(weaponentity).cnt = player.br_lastweapon_prev[slot];
 +    }
 +
 +    player.pauseregen_finished = time + autocvar_g_balance_pause_health_regen;
 +
 +    STAT(REVIVE_PROGRESS, player) = 0;
 +    player.revival_time = time;
 +
 +    WaypointSprite_Kill(player.waypointsprite_attached);
 +
 +    br_SquadUpdateInfo();
 +}
 +
 +int br_WinningCondition()
 +{
 +    int total_squads = br_SquadUpdateInfo();
 +
 +    if ((total_squads > 1) || !br_started)
 +        return WINNING_NEVER;
 +
 +    entity winner_squad = NULL;
 +    IL_EACH(squads, !it.br_squad_dead, winner_squad = it);
 +
 +    for(entity member = winner_squad.br_squad_first; member; member = member.br_squad_next)
 +    {
 +        GameRules_scoring_add(member, BR_RANK, 1);
 +    }
 +
 +    delete(round_handler);
 +    round_handler = NULL;
 +
 +    return WINNING_YES;
 +}
 +
 +bool br_isEliminated(entity e)
 +{
 +    return (IN_SQUAD(e) && (IS_DEAD(e) || !IS_PLAYER(e)));
 +}
 +
 +bool br_CheckPlayers()
 +{
 +    total_players = 0;
 +    FOREACH_CLIENT(IS_PLAYER(it), ++total_players);
 +
 +    static int prev_players = 0;
 +    if (total_players >= autocvar_g_br_minplayers || total_players == 0)
 +    {
 +        if(prev_players > 0)
 +            Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
 +        prev_players = 0;
 +        return (total_players >= autocvar_g_br_minplayers);
 +    }
 +
 +    if(prev_players != total_players)
 +    {
 +        Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, autocvar_g_br_minplayers - total_players);
 +        prev_players = total_players;
 +    }
 +
 +    return false;
 +}
 +
 +void br_Start(){
 +    // battle royale does not need those, besides, the timelimit won't be visible anymore after the game started
 +    cvar_set("timelimit", "0");
 +    cvar_set("fraglimit", "0");
 +    cvar_set("leadlimit", "0");
 +
++    reset_map(true, false);
 +
 +    ring = ring_initialize();
 +
 +    dropship = dropship_initialize();
 +
 +    if(!dropship)
 +    {
 +        br_started = true;
 +
 +        delete(ring);
 +        ring = NULL;
 +
 +        FOREACH_CLIENT(IS_PLAYER(it), {
 +            TRANSMUTE(Observer, it);
 +            PutClientInServer(it);
 +        });
 +        LOG_SEVERE("Failed to determine dropship route, aborting...");
 +    }
 +
 +    int num_players = 0;
 +
 +    FOREACH_CLIENT(IS_PLAYER(it), {
 +        STAT(DROP, it) = DROP_TRANSPORT;
 +        PutPlayerInServer(it);
 +
 +        it.br_wepset_old = STAT(WEAPONS, it);
 +        STAT(WEAPONS, it) = '0 0 0';
 +        for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
 +        {
 +            it.br_weapon_prev[slot] = WEP_Null;
 +            it.br_lastweapon_prev[slot] = 0;
 +        }
 +
 +        ++num_players;
 +    });
 +
 +    max_squad_size = max(autocvar_g_br_squad_size, 1);
 +    if(num_players <= max_squad_size)
 +        max_squad_size = ceil(num_players / 2);
 +
 +    for(int num_squads = 0; (num_squads * max_squad_size) < num_players; ++num_squads)
 +    {
 +        entity new_squad = new_pure(squad);
 +        new_squad.br_squad_drop_leader = NULL;
 +        new_squad.br_squad_id = num_squads + 1;
 +
 +        IL_PUSH(squads, new_squad);
 +    }
 +
 +    FOREACH_CLIENT(IS_PLAYER(it), {
 +        entity current_squad = br_SquadGetRandomAvail();
 +        br_SquadMember_Add(current_squad, it);
 +        GameRules_scoring_add(it, BR_SQUAD, current_squad.br_squad_id);
 +
 +        setorigin(it, dropship.origin + eZ * (dropship.mins.z - it.maxs.z - 64)); // FIXME: this can teleport players into brushes/void
 +        it.angles = vectoangles(dropship_path_direction) + '45 0 0';
 +        it.fixangle = true;
 +        it.velocity = '0 0 0';
 +        set_movetype(it, MOVETYPE_FLY);
 +        it.flags &= ~FL_PICKUPITEMS;
 +        it.flags |= FL_NOTARGET;
 +        it.dphitcontentsmask &= ~DPCONTENTS_BODY;
 +        it.effects |= EF_NODRAW;
 +        it.takedamage = DAMAGE_NO;
 +        it.solid = SOLID_NOT;
 +        it.br_drop_instructions = false;
 +        it.br_drop_launch = 0;
 +        UNSET_ONGROUND(it); // otherwise this isn't unset if the player drops in the same frame
 +
 +        WaypointSprite_Spawn(WP_BRAlly, 0, 0, it, '0 0 64', NULL, 0, it, br_allywaypoint, true, RADARICON_WAYPOINT);
 +    });
 +
 +    squads_colored = autocvar_g_br_squad_colors;
 +
 +    FOREACH_CLIENT(IS_REAL_CLIENT(it),
 +    {
 +        STAT(SQUADCOLORS, it) = squads_colored;
 +    });
 +
 +    IL_EACH(squads, true,
 +    {
 +        if(squads_colored)
 +        {
 +            float squad_color;
 +            squad_color = 16 * floor(random() * 15) + floor(random() * 15); // color 15 is special, don't select it as a squad color
 +
 +            for(entity member = it.br_squad_first; member; member = member.br_squad_next)
 +            {
 +                member.colormap = 1024 + squad_color;
 +            }
 +        }
 +
 +        it.br_drop_time = time;
 +
 +        float min_distance = max(autocvar_g_br_drop_distance_force, 0);
 +        if(!br_SquadIsBotsOnly(it))
 +            it.br_force_drop_distance = min_distance;
 +        else
 +            it.br_force_drop_distance = min_distance + random() * max(dropship_path_length - (min_distance + dropship_speed * br_drop_time_secs), 0);
 +    });
 +
 +    round_handler.cnt = 0; // emulate round handler round start
 +    br_started = true;
 +}
 +
 +void br_dummy_Think(entity this){}
 +
 +void br_Initialize()
 +{
 +    br_started = false;
 +    squads_colored = autocvar_g_br_squad_colors;
 +
 +    // emulate the round handler, useful because this will cause a lot of code to correctly treat the stage before the match starts
 +    round_handler = new_pure(round_handler);
 +    round_handler.count = 0;
 +    round_handler.cnt = 1;
 +    round_handler.wait = false;
 +    setthink(round_handler, br_dummy_Think);
 +
 +    EliminatedPlayers_Init(br_isEliminated);
 +}
index 767b5512102837d65cd33ac30c9dee7233b5560f,0000000000000000000000000000000000000000..01ce3bd8ef8746d37f9e3069b69e84474e62ed48
mode 100644,000000..100644
--- /dev/null
@@@ -1,174 -1,0 +1,174 @@@
-         if(!MoveToRandomLocationWithinBounds(this, world.mins, world.maxs, this.dphitcontentsmask, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 100, 8192, 1024))
 +#include "sv_dropship.qh"
 +
 +float autocvar_g_br_dropship_scale = 3;
 +vector autocvar_g_br_dropship_color = '0.5 0 0.5';
 +float autocvar_g_br_dropship_speed = -1;
 +
 +entity dropship_spawn(Vehicle info, float entity_scale, vector color);
 +void dropship_think(entity this);
 +vector dropship_getMultipliers();
 +vector dropship_seekPoint(entity this, vector orig, int axis, int direction, float multiplier);
 +
 +entity dropship_initialize()
 +{
 +    entity this = dropship_spawn(VEH_RACER, autocvar_g_br_dropship_scale, autocvar_g_br_dropship_color);
 +
 +    for(int i = 0; i < 100; ++i) // try to find a dropship path multiple times
 +    {
++        if(!MoveToRandomLocationWithinBounds(this, world.mins, world.maxs, this.dphitcontentsmask, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 100, 8192, 1024, false))
 +            continue;
 +
 +        vector mult;
 +
 +        vector startorigin;
 +        startorigin = dropship_seekPoint(this, this.origin, 2, 2, 1);
 +        startorigin = dropship_seekPoint(this, startorigin, 0, 0, 1);
 +        startorigin = dropship_seekPoint(this, startorigin, 1, 0, 1);
 +        mult = dropship_getMultipliers();
 +        startorigin = dropship_seekPoint(this, startorigin, 0, 1, mult.x);
 +        startorigin = dropship_seekPoint(this, startorigin, 1, 1, mult.y);
 +
 +        vector endorigin;
 +        mult = dropship_getMultipliers();
 +        endorigin = dropship_seekPoint(this, startorigin, 0, 1, 1 - mult.x);
 +        endorigin = dropship_seekPoint(this, endorigin,   1, 1, 1 - mult.y);
 +
 +        endorigin = startorigin + normalize(endorigin - startorigin) * vlen(vec2(world.maxs - world.mins));
 +
 +        tracebox(startorigin, this.mins, this.maxs, endorigin, MOVE_NORMAL, this);
 +        dropship_path_length = trace_fraction * vlen(endorigin - startorigin);
 +        if(dropship_path_length < (vlen(vec2(world.maxs - world.mins)) / 4)) // if the dropship path isn't atleast one quarter of the diagonal length of the map, retry, we're probably in a building
 +            continue;
 +        endorigin = trace_endpos;
 +        dropship_path_direction = normalize(endorigin - startorigin);
 +
 +        setorigin(this, startorigin);
 +        this.angles = vectoangles(dropship_path_direction);
 +        this.velocity = '0 0 0';
 +
 +        dropship_speed = autocvar_g_br_dropship_speed;
 +        // if dropship_speed is negative adjust speed dependant on map size
 +        if(dropship_speed < 0)
 +            dropship_speed = vlen(vec2(world.maxs - world.mins)) / 60; // dropship needs one minute to diagonally fly over the whole map
 +
 +        return this;
 +    }
 +
 +    delete(this);
 +    return NULL;
 +}
 +
 +entity dropship_spawn(Vehicle info, float entity_scale, vector color)
 +{
 +    entity this = new(vehicle);
 +    this.active = ACTIVE_ACTIVE;
 +
 +    _setmodel(this, info.model);
 +
 +    this.vehicle_flags |= VHF_ISVEHICLE;
 +
 +    this.takedamage             = DAMAGE_NO;
 +    this.bot_attack             = false;
 +    this.iscreature             = true;
 +    this.teleportable           = false;
 +    this.damagedbycontents      = false;
 +    this.vehicleid              = info.vehicleid;
 +    this.vehicledef             = info;
 +    this.dphitcontentsmask      = DPCONTENTS_SOLID;
 +    if(autocvar_g_playerclip_collisions)
 +        this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
 +    this.flags                  = FL_NOTARGET;
 +    this.nextthink              = time;
 +    setthink(this, dropship_think);
 +
 +    this.scale = entity_scale;
 +    setsize(this, info.m_mins * entity_scale, info.m_maxs * entity_scale);
 +    set_movetype(this, MOVETYPE_FLY_WORLDONLY);
 +
 +    this.colormod = color;
 +    this.alpha = 1;
 +
 +    CSQCMODEL_AUTOINIT(this);
 +
 +    return this;
 +}
 +
 +void dropship_think(entity this)
 +{
 +    this.nextthink = time;
 +
 +    if(dropship_path_length > 0){
 +        this.alpha = bound(0.01, dropship_path_length / autocvar_g_br_drop_distance_force, 1);
 +        this.velocity = dropship_path_direction * dropship_speed;
 +        dropship_path_length -= dropship_speed * frametime;
 +    }
 +    else{
 +        delete(this);
 +    }
 +
 +    CSQCMODEL_AUTOUPDATE(this);
 +}
 +
 +vector dropship_getMultipliers()
 +{
 +    vector mult;
 +    mult.x = (1 - cos(random() * 90 * DEG2RAD)) * 0.5;
 +    mult.y = min((1 - cos(random() * 90 * DEG2RAD)) * 0.5, 0.5 - mult.x);
 +    mult.z = 0;
 +    bool multswap = (random() >= 0.5);
 +    if(multswap){
 +        float tmp;
 +        tmp = mult.x;
 +        mult.x = mult.y;
 +        mult.y = tmp;
 +    }
 +
 +    return mult;
 +}
 +
 +vector dropship_seekPoint(entity this, vector orig, int axis, int direction, float multiplier)
 +{
 +    vector vec_axis;
 +    switch(axis)
 +    {
 +        default: case 0:
 +            vec_axis = eX;
 +            break;
 +        case 1:
 +            vec_axis = eY;
 +            break;
 +        case 2:
 +            vec_axis = eZ;
 +    }
 +
 +    float first_fraction;
 +    float second_fraction = 0;
 +    vector first_end;
 +    vector second_end = '0 0 0';
 +
 +    first_end = orig;
 +    first_end = first_end - first_end * vec_axis * vec_axis + world.maxs * vec_axis * vec_axis;
 +    first_fraction = (tracebox(orig, this.mins, this.maxs, first_end, MOVE_NORMAL, this), trace_fraction);
 +
 +    if(direction != 2)
 +    {
 +        second_end = orig;
 +        second_end = second_end - second_end * vec_axis * vec_axis + world.mins * vec_axis * vec_axis;
 +        second_fraction = (tracebox(orig, this.mins, this.maxs, second_end, MOVE_NORMAL, this), trace_fraction);
 +    }
 +
 +    float dist_to_edge;
 +    if(((direction == 0) && (first_fraction < second_fraction)) ||
 +       ((direction == 1) && (first_fraction > second_fraction)) ||
 +        (direction == 2))
 +    {
 +        dist_to_edge = (first_end * vec_axis - orig * vec_axis) * first_fraction * multiplier;
 +    }
 +    else
 +    {
 +        dist_to_edge = (second_end * vec_axis - orig * vec_axis) * second_fraction * multiplier;
 +    }
 +    orig = orig + dist_to_edge * vec_axis;
 +
 +    return orig;
 +}
index 815b2633516cf347b39bd6c383d361c295cfaa34,0000000000000000000000000000000000000000..2814c454afda79a29faa5d7fbb93c7370c75481f
mode 100644,000000..100644
--- /dev/null
@@@ -1,225 -1,0 +1,225 @@@
-     MoveToRandomLocationWithinBounds(this, world.mins, world.maxs, DPCONTENTS_SOLID, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 2500, 0, 0);
 +#include "sv_ring.qh"
 +
 +.float br_ring_stage;
 +.float br_ring_stage_strength[BR_RING_STAGE_MAX];
 +.float br_ring_timelimit;
 +
 +void ring_link(entity this);
 +bool ring_send(entity this, entity to, float sf);
 +void ring_think(entity this);
 +void ring_newStage(entity this);
 +bool ring_parseTiming(entity this);
 +void ring_parseStrength(entity this, bool has_invalid);
 +void ring_alignPosition(entity this);
 +
 +float autocvar_g_br_ring_duration = 150;
 +vector autocvar_g_br_ring_color = '1 0 0'; // FIXME: put visual-only cvar client side?
 +float autocvar_g_br_ring_alpha = 0.5; // FIXME: put visual-only cvar client side?
 +float autocvar_g_br_ring_fadedistance = 0.5; // FIXME: put visual-only cvar client side?
 +float autocvar_g_br_ring_fadedistance_min = 2000; // FIXME: put visual-only cvar client side?
 +float autocvar_g_br_ring_radius = -1; // useful for per map settings
 +string autocvar_g_br_ring_timing = "0.6 0.8 0.9";
 +string autocvar_g_br_ring_strength = "2.5 5 10 20 50";
 +float autocvar_g_br_ring_wait = 30;
 +float autocvar_g_br_ring_center_factor = 0.25;
 +
 +entity ring_initialize()
 +{
 +    entity this = spawn();
 +    this.netname = BR_RING_NAME;
 +
 +    setsize(this, '0 0 0', '0 0 0');
 +    set_movetype(this, MOVETYPE_NOCLIP);
 +
 +    this.classname = "ring";
 +    this.br_ring_start = time;
 +    this.br_ring_duration = max(autocvar_g_br_ring_duration, 1);
 +    this.radius = (autocvar_g_br_ring_radius <= 0) ? vlen(vec2(world.maxs - world.mins)) / 2 : autocvar_g_br_ring_radius;
 +    this.br_ring_stage = -1;
 +    this.colormod = autocvar_g_br_ring_color; // TODO: color changing ring
 +    this.alpha = this.br_ring_alpha = bound(0.01, autocvar_g_br_ring_alpha, 1);
 +    this.br_ring_fadedistance = max(autocvar_g_br_ring_fadedistance, 0);
 +    this.br_ring_fadedistance_min = max(autocvar_g_br_ring_fadedistance_min, 1);
 +    this.br_ring_stage_waittime = max(autocvar_g_br_ring_wait, 0);
 +
 +    bool has_invalid_timings = ring_parseTiming(this);
 +    ring_parseStrength(this, has_invalid_timings);
 +
 +    this.strength = this.br_ring_stage_strength[0];
++    MoveToRandomLocationWithinBounds(this, world.mins, world.maxs, DPCONTENTS_SOLID, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 2500, 0, 0, false);
 +    ring_alignPosition(this);
 +    ring_link(this);
 +
 +    return this;
 +}
 +
 +void ring_link(entity this)
 +{
 +    Net_LinkEntity(this, false, 0, ring_send);
 +    this.nextthink = this.br_ring_start + this.br_ring_stage_waittime;
 +    this.br_ring_timelimit = this.nextthink - game_starttime;
 +    ring_timelimit(this);
 +    setthink(this, ring_think);
 +}
 +
 +bool ring_send(entity this, entity to, float sf)
 +{
 +    WriteHeader(MSG_ENTITY, ENT_CLIENT_RING);
 +    WriteByte(MSG_ENTITY, sf);
 +    if(sf & BR_RING_SETUP)
 +    {
 +        WriteVector(MSG_ENTITY, this.origin);
 +        WriteCoord(MSG_ENTITY, this.br_ring_start);
 +        WriteCoord(MSG_ENTITY, this.br_ring_duration);
 +        WriteCoord(MSG_ENTITY, this.radius);
 +        WriteVector(MSG_ENTITY, this.colormod);
 +        WriteCoord(MSG_ENTITY, this.br_ring_alpha);
 +        WriteCoord(MSG_ENTITY, this.br_ring_fadedistance);
 +        WriteCoord(MSG_ENTITY, this.br_ring_fadedistance_min);
 +
 +        WriteByte(MSG_ENTITY, this.br_ring_stage_count);
 +        WriteCoord(MSG_ENTITY, this.br_ring_stage_waittime);
 +        for(int i = 0; i < (this.br_ring_stage_count + 1); ++i)
 +            WriteCoord(MSG_ENTITY, this.br_ring_stage_timing[i]);
 +    }
 +
 +    if(sf & BR_RING_MOVE)
 +    {
 +        WriteVector(MSG_ENTITY, this.origin);
 +        WriteVector(MSG_ENTITY, this.velocity);
 +    }
 +
 +    return true;
 +}
 +
 +void ring_think(entity this)
 +{
 +    float time_elapsed = time - this.br_ring_start;
 +
 +    if(time_elapsed >= (this.br_ring_duration + this.br_ring_stage_waittime * this.br_ring_stage_count))
 +    {
 +        this.velocity = '0 0 0';
 +        this.SendFlags |= BR_RING_MOVE; // not really necessary but for completeness sake
 +        this.nextthink = 0; // ring reached its final state, no further thinking required
 +    }
 +    else
 +    {
 +        for(int stage = this.br_ring_stage_count - 1; stage >= 0; --stage)
 +        {
 +            float stage_duration_current = this.br_ring_duration * this.br_ring_stage_timing[stage];
 +            stage_duration_current += this.br_ring_stage_waittime * stage;
 +
 +            float stage_duration_next = this.br_ring_duration * this.br_ring_stage_timing[stage + 1];
 +            stage_duration_next += this.br_ring_stage_waittime * (stage + 1);
 +
 +            if(time_elapsed >= (stage_duration_current + this.br_ring_stage_waittime))
 +            {
 +                if(stage != this.br_ring_stage)
 +                {
 +                    this.br_ring_stage = stage;
 +                    ring_newStage(this);
 +                    this.SendFlags |= BR_RING_MOVE;
 +
 +                    Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_BR_RING_CLOSE, stage + 1);
 +                }
 +                this.nextthink = this.br_ring_start + stage_duration_next;
 +                break;
 +            }
 +            else if(time_elapsed >= stage_duration_current)
 +            {
 +                if(vlen(this.velocity) > 0)
 +                {
 +                    this.velocity = '0 0 0';
 +                    this.SendFlags |= BR_RING_MOVE;
 +                }
 +                this.nextthink = this.br_ring_start + stage_duration_current + this.br_ring_stage_waittime;
 +                this.br_ring_timelimit = this.nextthink - game_starttime;
 +                ring_timelimit(this);
 +                break;
 +            }
 +        }
 +    }
 +}
 +
 +void ring_newStage(entity this)
 +{
 +    this.strength = this.br_ring_stage_strength[this.br_ring_stage + 1];
 +    if(this.br_ring_stage > 0) // first stage should center the ring a bit, no moving required
 +        this.velocity = (this.radius / this.br_ring_duration) * normalize(eX * (random() * 2 - 1) + eY * (random() * 2 - 1));
 +}
 +
 +bool ring_parseTiming(entity this)
 +{
 +    int num_timings = tokenize(autocvar_g_br_ring_timing);
 +    if(num_timings > (BR_RING_STAGE_MAX - 2))
 +    {
 +        LOG_INFO("too many stages defined by g_br_ring_timing");
 +        num_timings = BR_RING_STAGE_MAX - 2;
 +    }
 +    int invalid_timings = 0;
 +    for(int i = 0; i < num_timings; ++i)
 +    {
 +        float current_timing = stof(argv(i));
 +        if((current_timing > this.br_ring_stage_timing[i - invalid_timings]) && (current_timing < 1))
 +            this.br_ring_stage_timing[i + 1 - invalid_timings] = current_timing;
 +        else
 +        {
 +            ++invalid_timings;
 +            LOG_INFO("invalid timing value \"", argv(i), "\" at position ", itos(i + 1), " in g_br_ring_timing was discarded");
 +        }
 +    }
 +    this.br_ring_stage_count = num_timings + 1 - invalid_timings;
 +    this.br_ring_stage_timing[0] = 0;
 +    this.br_ring_stage_timing[this.br_ring_stage_count] = 1;
 +
 +    return (invalid_timings > 0);
 +}
 +
 +void ring_parseStrength(entity this, bool has_invalid)
 +{
 +    int num_strength = tokenize(autocvar_g_br_ring_strength);
 +    if(!has_invalid) // don't warn about this if we already got errors in the timing list
 +    {
 +        if(num_strength < (this.br_ring_stage_count + 1))
 +        {
 +            LOG_INFO("not enough strength values in g_br_ring_strength for the defined stages in g_br_ring_timing");
 +        }
 +        if(num_strength > (this.br_ring_stage_count + 1))
 +        {
 +            LOG_INFO("too many strength values in g_br_ring_strength for the defined stages in g_br_ring_timing");
 +        }
 +    }
 +    for(int i = 0; i < (this.br_ring_stage_count + 1); ++i)
 +    {
 +        float current_strength;
 +        float prev_strength = ((i > 0) ? this.br_ring_stage_strength[i - 1] : 1);
 +        if(i < num_strength)
 +            current_strength = stof(argv(i));
 +        else
 +            current_strength = prev_strength;
 +
 +        if(current_strength > 0)
 +            this.br_ring_stage_strength[i] = current_strength;
 +        else
 +        {
 +            this.br_ring_stage_strength[i] = prev_strength;
 +            LOG_INFO("invalid strength value \"", argv(i), "\" at position ", itos(i + 1), " in g_br_ring_strength replaced with preceeding value \"", ftos(prev_strength), "\"");
 +        }
 +    }
 +}
 +
 +void ring_timelimit(entity this)
 +{
 +    WriteByte(MSG_ALL, 3); // svc_updatestat
 +    WriteByte(MSG_ALL, 236); // STAT_TIMELIMIT
 +    WriteCoord(MSG_ALL, this.br_ring_timelimit / 60);
 +}
 +
 +void ring_alignPosition(entity this)
 +{
 +    float f = bound(0, autocvar_g_br_ring_center_factor, 1);
 +    vector ringorigin = world.mins + (world.maxs - world.mins) * ((0.5 - f/2) + random() * f);
 +    ringorigin.z = this.origin.z;
 +
 +    setorigin(this, ringorigin);
 +}
index 7c5ef98f88abb27ce8176b1a4b4879b3459b6a7b,6ce016e5681046730193233a5c281c1b7a835165..022b0355724caba314f1986e0166db257bdab4f0
@@@ -818,7 -818,7 +818,7 @@@ MUTATOR_HOOKFUNCTION(buffs, ForbidThrow
                float best_distance = autocvar_g_buffs_swapper_range;
                entity closest = NULL;
                FOREACH_CLIENT(IS_PLAYER(it), {
 -                      if(!IS_DEAD(it) && !STAT(FROZEN, it) && !it.vehicle)
 +                      if(!IS_DEAD(it) && !STAT(FROZEN, it) && !it.vehicle && (STAT(DROP, player) == DROP_LANDED))
                        if(DIFF_TEAM(it, player))
                        {
                                float test = vlen2(player.origin - it.origin);
@@@ -969,8 -969,6 +969,6 @@@ MUTATOR_HOOKFUNCTION(buffs, PlayerRegen
        }
  }
  
- REPLICATE(cvar_cl_buffs_autoreplace, bool, "cl_buffs_autoreplace");
  MUTATOR_HOOKFUNCTION(buffs, BuildMutatorsString)
  {
        if(autocvar_g_buffs > 0) // only report as a mutator if they're enabled
index 6cf30547a78ee2302e3690b33563488e18ad3371,87398b11d222367c1aeafa752eecab84b02e3f0d..2ba2a86ad5fa0dabcc4979f57b3e2f8c0bb41ba7
@@@ -264,7 -264,6 +264,7 @@@ string multiteam_info_sprintf(string in
      MSG_INFO_NOTIF(DEATH_MURDER_FALL,                       N_CONSOLE,  3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "notify_fall",          _("^BG%s%s^K1 was grounded by ^BG%s^K1%s%s"), "")
      MSG_INFO_NOTIF(DEATH_MURDER_FIRE,                       N_CONSOLE,  3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "notify_death",         _("^BG%s%s^K1 was burnt up into a crisp by ^BG%s^K1%s%s"), _("^BG%s%s^K1 felt a little hot from ^BG%s^K1's fire^K1%s%s"))
      MSG_INFO_NOTIF(DEATH_MURDER_LAVA,                       N_CONSOLE,  3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "notify_lava",          _("^BG%s%s^K1 was cooked by ^BG%s^K1%s%s"), "")
 +    MSG_INFO_NOTIF(DEATH_MURDER_RING,                       N_CONSOLE,  2, 1, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "notify_death",         _("^BG%s%s^K1 was thrown into ^K2the ring^K1 by ^BG%s^K1%s%s"), "")
      MSG_INFO_NOTIF(DEATH_MURDER_MONSTER,                    N_CONSOLE,  3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "notify_death",         _("^BG%s%s^K1 was pushed in front of a monster by ^BG%s^K1%s%s"), "")
      MSG_INFO_NOTIF(DEATH_MURDER_NADE,                       N_CONSOLE,  3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "nade_normal",          _("^BG%s%s^K1 was blown up by ^BG%s^K1's Nade%s%s"), "")
      MSG_INFO_NOTIF(DEATH_MURDER_NADE_NAPALM,                N_CONSOLE,  3, 2, "spree_inf s1 s2 s3loc spree_end", "s2 s1",               "nade_napalm",          _("^BG%s%s^K1 was burned to death by ^BG%s^K1's Napalm Nade%s%s"), _("^BG%s%s^K1 got too close to a napalm explosion%s%s"))
      MSG_INFO_NOTIF(DEATH_SELF_FIRE,                         N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 became a bit too crispy%s%s"), _("^BG%s^K1 felt a little hot%s%s"))
      MSG_INFO_NOTIF(DEATH_SELF_GENERIC,                      N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_selfkill",      _("^BG%s^K1 died%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_LAVA,                         N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_lava",          _("^BG%s^K1 turned into hot slag%s%s"), _("^BG%s^K1 found a hot place%s%s"))
 +    MSG_INFO_NOTIF(DEATH_SELF_RING,                         N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 couldn't escape from ^K2the ring^K1%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_MON_MAGE,                     N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was exploded by a Mage%s%s"), "")
-     MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_CLAW,            N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1's innards became outwards by a Shambler%s%s"), "")
-     MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_SMASH,           N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was smashed by a Shambler%s%s"), "")
-     MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_ZAP,             N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was zapped to death by a Shambler%s%s"), "")
+     MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_CLAW,               N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1's innards became outwards by a Golem%s%s"), "")
+     MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_SMASH,              N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was smashed by a Golem%s%s"), "")
+     MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_ZAP,                N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was zapped to death by a Golem%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_MON_SPIDER,                   N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was bitten by a Spider%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_MON_WYVERN,                   N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 was fireballed by a Wyvern%s%s"), "")
      MSG_INFO_NOTIF(DEATH_SELF_MON_ZOMBIE_JUMP,              N_CONSOLE,  2, 1, "s1 s2loc spree_lost", "s1",      "notify_death",         _("^BG%s^K1 joins the Zombies%s%s"), "")
      MSG_INFO_NOTIF(LMS_FORFEIT,                             N_CHATCON,  1, 0, "s1", "",         "",     _("^BG%s^F3 forfeited"), "")
      MSG_INFO_NOTIF(LMS_NOLIVES,                             N_CONSOLE,  1, 0, "s1", "",         "",     _("^BG%s^F3 has no more lives left"), "")
  
 +    MSG_INFO_NOTIF(BR_REVIVED,                              N_CONSOLE,  2, 0, "s1 s2", "",      "",     _("^BG%s^K3 was revived by ^BG%s"), "")
 +    MSG_INFO_NOTIF(BR_DOWN,                                 N_CONSOLE,  2, 0, "s1 s2", "",      "",     _("^BG%s^K1 was downed by ^BG%s"), "")
 +    MSG_INFO_NOTIF(BR_DOWN_SELF,                            N_CONSOLE,  1, 0, "s1", "",         "",     _("^BG%s^K1 downed themself"), "")
 +
      MSG_INFO_NOTIF(MONSTERS_DISABLED,                       N_CONSOLE,  0, 0, "", "",           "",     _("^BGMonsters are currently disabled"), "")
  
      MULTITEAM_INFO(NEXBALL_RETURN_HELD,                     N_CONSOLE,  0, 0, "", "",           "",     _("^BGThe ^TC^TT^BG team held the ball for too long"), "", NAME)
      MSG_CENTER_NOTIF(ASSAULT_DEFENDING,                 N_ENABLE,    0, 0, "",               CPID_ASSAULT_ROLE,      "0 0",  _("^BGYou are defending!"), "")
      MSG_CENTER_NOTIF(ASSAULT_OBJ_DESTROYED,             N_ENABLE,    0, 1, "f1time",         CPID_ASSAULT_ROLE,      "0 0",  _("^BGObjective destroyed in ^F4%s^BG!"), "")
  
-     MSG_CENTER_NOTIF(COUNTDOWN_BEGIN,                   N_ENABLE,    0, 0, "",               CPID_ROUND,             "2 0",  _("^F4Begin!"), "")
-     MSG_CENTER_NOTIF(COUNTDOWN_GAMESTART,               N_ENABLE,    0, 1, "",               CPID_ROUND,             "1 f1", _("^F4Game starts in ^COUNT"), "")
-     MSG_CENTER_NOTIF(COUNTDOWN_ROUNDSTART,              N_ENABLE,    0, 1, "",               CPID_ROUND,             "1 f1", _("^F4Round starts in ^COUNT"), "")
+     MSG_CENTER_NOTIF(COUNTDOWN_BEGIN,                   N_ENABLE,    0, 0, "",               CPID_ROUND,             "2 0",  BOLD(_("^BGBegin!")), "")
+     MSG_CENTER_NOTIF(COUNTDOWN_GAMESTART,               N_ENABLE,    0, 1, "",               CPID_ROUND,             "1 f1", strcat(_("^BGGame starts in"), "\n", BOLD("^COUNT")), "")
+     MSG_CENTER_NOTIF(COUNTDOWN_ROUNDSTART,              N_ENABLE,    0, 2, "f1",             CPID_ROUND,             "1 f2", strcat(_("^BGRound %s starts in"), "\n", BOLD("^COUNT")), "")
      MSG_CENTER_NOTIF(COUNTDOWN_ROUNDSTOP,               N_ENABLE,    0, 0, "",               CPID_ROUND,             "2 0",  _("^F4Round cannot start"), "")
  
      MSG_CENTER_NOTIF(ROUND_TIED,                        N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^BGRound tied"), "")
      MSG_CENTER_NOTIF(DEATH_SELF_FIRE,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You got a little bit too crispy!")), BOLD(_("^K1You felt a little too hot!")))
      MSG_CENTER_NOTIF(DEATH_SELF_GENERIC,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You fragged yourself!")), BOLD(_("^K1You need to be more careful!")))
      MSG_CENTER_NOTIF(DEATH_SELF_LAVA,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You couldn't stand the heat!")), "")
 +    MSG_CENTER_NOTIF(DEATH_SELF_RING,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You couldn't escape from ^K2the ring^K1!")), "")
      MSG_CENTER_NOTIF(DEATH_SELF_MONSTER,                N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You were killed by a monster!")), BOLD(_("^K1You need to watch out for monsters!")))
      MSG_CENTER_NOTIF(DEATH_SELF_NADE,                   N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1You forgot to put the pin back in!")), BOLD(_("^K1Tastes like chicken!")))
      MSG_CENTER_NOTIF(DEATH_SELF_NADE_NAPALM,            N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  BOLD(_("^K1Hanging around a napalm explosion is bad!")), "")
      MULTITEAM_CENTER(KEYHUNT_START,                     N_ENABLE,    0, 0, "",               CPID_KEYHUNT,           "0 0",  _("^BGYou are starting with the ^TC^TT Key"), "", KEY)
  
      MSG_CENTER_NOTIF(LMS_NOLIVES,                       N_ENABLE,    0, 0, "",               CPID_LMS,               "0 0",  _("^BGYou have no lives left, you must wait until the next match"), "")
+     MSG_CENTER_NOTIF(LMS_SPECWARN,                      N_ENABLE,    0, 0, "",               CPID_LMS,               "0 0",  _("^F4WARNING:^BG you can't rejoin this match after spectating.\nUse the same command again to spectate anyway."), "")
  
 +    MSG_CENTER_NOTIF(BR_JOIN_LATE,                      N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGMatch already started, you must wait until the next match"), "")
 +    MSG_CENTER_NOTIF(BR_JOIN_DEAD,                      N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGYou are dead, you must wait until the next match"), "")
 +    MSG_CENTER_NOTIF(BR_FORFEIT,                        N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^BGForfeiting not possible"), "")
 +    MSG_CENTER_NOTIF(BR_DOWN_WAIT,                      N_ENABLE,    0, 0, "",               CPID_BR_DOWN,          "-1 0",  _("^F1Waiting for revive..."), "")
 +    MSG_CENTER_NOTIF(BR_DEAD_SQUAD,                     N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1Your squad has been eliminated"), "")
 +    MSG_CENTER_NOTIF(BR_REVIVE,                         N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^K3You revived ^BG%s"), "")
 +    MSG_CENTER_NOTIF(BR_REVIVED,                        N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^K3You were revived by ^BG%s"), "")
 +    MSG_CENTER_NOTIF(BR_DOWN,                           N_ENABLE,    1, 0, "s1",             CPID_Null,              "0 0",  _("^K1You were downed by ^BG%s"), "")
 +    MSG_CENTER_NOTIF(BR_DOWN_SELF,                      N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1You downed yourself"), "")
 +    MSG_CENTER_NOTIF(BR_DROPSHIP,                       N_ENABLE,    0, 0, "",               CPID_BR_DROP,           "-1 0",  _("^BG^F1Jump^BG to drop"), "")
 +    MSG_CENTER_NOTIF(BR_DROP_DETACH,                    N_ENABLE,    0, 0, "",               CPID_BR_DROP,           "-1 0",  _("^BG^F1Secondary fire^BG to detach"), "")
 +    MSG_CENTER_NOTIF(BR_RING_WARN,                      N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^F4You are outside of ^F2the ring^F4, get back in fast!"), "")
 +    MSG_CENTER_NOTIF(BR_RING_CLOSE,                     N_ENABLE,    0, 1, "f1",             CPID_Null,              "0 0",  _("^F2Ring^F4 is closing! (strength: ^F1%s^F4)"), "")
 +
      MSG_CENTER_NOTIF(MISSING_TEAMS,                     N_ENABLE,    0, 1, "missing_teams",  CPID_MISSING_TEAMS,     "-1 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "")
      MSG_CENTER_NOTIF(MISSING_PLAYERS,                   N_ENABLE,    0, 1, "f1",             CPID_MISSING_PLAYERS,   "-1 0", _("^BGWaiting for %s player(s) to join..."), "")
  
      MSG_CENTER_NOTIF(INSTAGIB_FINDAMMO_FIRST,           N_ENABLE,    0, 0, "",               CPID_INSTAGIB_FINDAMMO, "1 10", _("^BGGet some ammo or you'll be dead in ^F4^COUNT^BG!"), _("^BGGet some ammo! ^F4^COUNT^BG left!"))
      MSG_CENTER_NOTIF(INSTAGIB_LIVES_REMAINING,          N_ENABLE,    0, 1, "f1",             CPID_Null,              "0 0",  _("^F2Extra lives remaining: ^K1%s"), "")
  
-     MSG_CENTER_NOTIF(CAMPAIGN_MESSAGE,                  N_ENABLE,    1, 1, "f1 s1 join_key", CPID_CAMPAIGN_MESSAGE,  "-1 0", strcat(_("Level %s: "), "^BG%s\n^3\n", _("^BGPress ^F2%s^BG to enter the game")), "")
      MSG_CENTER_NOTIF(MOTD,                              N_ENABLE,    1, 0, "s1",             CPID_MOTD,              "-1 0", "^BG%s", "")
  
      MSG_CENTER_NOTIF(NIX_COUNTDOWN,                     N_ENABLE,    0, 2, "item_wepname",   CPID_NIX,               "1 f2", _("^F2^COUNT^BG until weapon change...\nNext weapon: ^F1%s"), "")
      MSG_MULTI_NOTIF(DEATH_MURDER_FALL,                  N_ENABLE,  NULL,           INFO_DEATH_MURDER_FALL,                 NULL)
      MSG_MULTI_NOTIF(DEATH_MURDER_FIRE,                  N_ENABLE,  NULL,           INFO_DEATH_MURDER_FIRE,                 NULL)
      MSG_MULTI_NOTIF(DEATH_MURDER_LAVA,                  N_ENABLE,  NULL,           INFO_DEATH_MURDER_LAVA,                 NULL)
 +    MSG_MULTI_NOTIF(DEATH_MURDER_RING,                  N_ENABLE,  NULL,           INFO_DEATH_MURDER_RING,                 NULL)
      MSG_MULTI_NOTIF(DEATH_MURDER_MONSTER,               N_ENABLE,  NULL,           INFO_DEATH_MURDER_MONSTER,              CENTER_DEATH_SELF_MONSTER)
      MSG_MULTI_NOTIF(DEATH_MURDER_NADE,                  N_ENABLE,  NULL,           INFO_DEATH_MURDER_NADE,                 NULL)
      MSG_MULTI_NOTIF(DEATH_MURDER_NADE_NAPALM,           N_ENABLE,  NULL,           INFO_DEATH_MURDER_NADE_NAPALM,          NULL)
      MSG_MULTI_NOTIF(DEATH_SELF_FIRE,                    N_ENABLE,  NULL,           INFO_DEATH_SELF_FIRE,                   CENTER_DEATH_SELF_FIRE)
      MSG_MULTI_NOTIF(DEATH_SELF_GENERIC,                 N_ENABLE,  NULL,           INFO_DEATH_SELF_GENERIC,                CENTER_DEATH_SELF_GENERIC)
      MSG_MULTI_NOTIF(DEATH_SELF_LAVA,                    N_ENABLE,  NULL,           INFO_DEATH_SELF_LAVA,                   CENTER_DEATH_SELF_LAVA)
 +    MSG_MULTI_NOTIF(DEATH_SELF_RING,                    N_ENABLE,  NULL,           INFO_DEATH_SELF_RING,                   CENTER_DEATH_SELF_RING)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_MAGE,                N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_MAGE,               CENTER_DEATH_SELF_MONSTER)
-     MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_CLAW,       N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_CLAW,      CENTER_DEATH_SELF_MONSTER)
-     MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_SMASH,      N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_SMASH,     CENTER_DEATH_SELF_MONSTER)
-     MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_ZAP,        N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SHAMBLER_ZAP,       CENTER_DEATH_SELF_MONSTER)
+     MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_CLAW,          N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_GOLEM_CLAW,         CENTER_DEATH_SELF_MONSTER)
+     MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_SMASH,         N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_GOLEM_SMASH,        CENTER_DEATH_SELF_MONSTER)
+     MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_ZAP,           N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_GOLEM_ZAP,          CENTER_DEATH_SELF_MONSTER)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_SPIDER,              N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_SPIDER,             CENTER_DEATH_SELF_MONSTER)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_WYVERN,              N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_WYVERN,             CENTER_DEATH_SELF_MONSTER)
      MSG_MULTI_NOTIF(DEATH_SELF_MON_ZOMBIE_JUMP,         N_ENABLE,  NULL,           INFO_DEATH_SELF_MON_ZOMBIE_JUMP,        CENTER_DEATH_SELF_MONSTER)
index a8d3ecb7e7a2ec435032b6aed90352dcc31e711d,fb06d264af250212cea590a3e0b9b51224281898..7b8a70eeb96280578e57c868b0302b0c2bf165e0
@@@ -59,12 -59,9 +59,11 @@@ ENUMCLASS(CPID
        CASE(CPID, KEYHUNT)
        CASE(CPID, KEYHUNT_OTHER)
        CASE(CPID, LMS)
 +      CASE(CPID, BR_DOWN)
 +      CASE(CPID, BR_DROP)
        CASE(CPID, MISSING_TEAMS)
        CASE(CPID, MISSING_PLAYERS)
        CASE(CPID, INSTAGIB_FINDAMMO)
-       CASE(CPID, CAMPAIGN_MESSAGE)
        CASE(CPID, MOTD)
        CASE(CPID, NIX)
        CASE(CPID, ONSLAUGHT)
@@@ -361,8 -358,8 +360,8 @@@ float autocvar_notification_show_sprees
        f1points: point or points depending on f1
        f1ord: count_ordinal of f1
        f1time: process_time of f1
-       f1race_time: mmssss of f1
-       f2race_time: mmssss of f2
+       f1race_time: TIME_ENCODED_TOSTRING of f1
+       f2race_time: TIME_ENCODED_TOSTRING of f2
        race_col: color of race time/position (i.e. good or bad)
        race_diff: show time difference between f2 and f3
        missing_teams: show which teams still need players
@@@ -421,11 -418,11 +420,11 @@@ string BUFF_NAME(int i)
        ARG_CASE(ARG_CS,        "f1points",      (f1 == 1 ? _("point") : _("points"))) \
        ARG_CASE(ARG_CS_SV,     "f1ord",         count_ordinal(f1)) \
        ARG_CASE(ARG_CS_SV,     "f1time",        process_time(2, f1)) \
-       ARG_CASE(ARG_CS_SV_HA,  "f1race_time",   mmssss(f1)) \
-       ARG_CASE(ARG_CS_SV_HA,  "f2race_time",   mmssss(f2)) \
-       ARG_CASE(ARG_CS_SV_HA,  "f3race_time",   mmssss(f3)) \
+       ARG_CASE(ARG_CS_SV_HA,  "f1race_time",   TIME_ENCODED_TOSTRING(f1, true)) \
+       ARG_CASE(ARG_CS_SV_HA,  "f2race_time",   TIME_ENCODED_TOSTRING(f2, true)) \
+       ARG_CASE(ARG_CS_SV_HA,  "f3race_time",   TIME_ENCODED_TOSTRING(f3, true)) \
        ARG_CASE(ARG_CS_SV,     "race_col",      CCR(((f1 == 1) ? "^F1" : "^F2"))) \
-       ARG_CASE(ARG_CS_SV,     "race_diff",     ((f2 > f3) ? sprintf(CCR("^1[+%s]"), mmssss(f2 - f3)) : sprintf(CCR("^2[-%s]"), mmssss(f3 - f2)))) \
+       ARG_CASE(ARG_CS_SV,     "race_diff",     ((f2 > f3) ? sprintf(CCR("^1[+%s]"), TIME_ENCODED_TOSTRING(f2 - f3, true)) : sprintf(CCR("^2[-%s]"), TIME_ENCODED_TOSTRING(f3 - f2, true)))) \
        ARG_CASE(ARG_CS,        "missing_teams", notif_arg_missing_teams(f1)) \
        ARG_CASE(ARG_CS,        "pass_key",      getcommandkey(_("drop flag"), "+use")) \
        ARG_CASE(ARG_CS,        "nade_key",      getcommandkey(_("throw nade"), "dropweapon")) \
@@@ -636,6 -633,7 +635,7 @@@ string notif_arg_item_wepammo(float f1
  {
        string ammoitems = "";
        Weapon wep = REGISTRY_GET(Weapons, f1);
+       // TODO: registry handles
        switch (wep.ammo_type)
        {
                case RES_SHELLS:  ammoitems = ITEM_Shells.m_name;      break;
diff --combined qcsrc/common/scores.qh
index 999ea62be7499d16593da77fdb48b842bd45f07c,45af93992aacdf33f7d8338867fe73a43807b2a3..ada97467154f3f678108128fbfb389b942ddf4ea
@@@ -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(BR_RANK);
 +REGISTER_SP(BR_SQUAD);
 +REGISTER_SP(BR_REVIVALS);
  #endif
  
  
diff --combined qcsrc/common/stats.qh
index be608dae196566a0352440d23aae0799907a2e4a,128f090c4fd9ec94ff1269b3336bda705be60c8e..889aba13c12c7b49949ddae99fc14de9c5084abb
@@@ -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)
@@@ -126,11 -130,6 +130,11 @@@ REGISTER_STAT(HEALING_ORB_ALPHA, float
  REGISTER_STAT(PLASMA, int)
  REGISTER_STAT(FROZEN, int)
  REGISTER_STAT(REVIVE_PROGRESS, float)
 +REGISTER_STAT(BLEEDING, bool)
 +REGISTER_STAT(DROP, int)
 +REGISTER_STAT(SQUADSALIVE, int)
 +REGISTER_STAT(PLAYERSALIVE, int)
 +REGISTER_STAT(SQUADCOLORS, bool)
  REGISTER_STAT(ROUNDLOST, int)
  REGISTER_STAT(CAPTURE_PROGRESS, float)
  REGISTER_STAT(ENTRAP_ORB, float)
@@@ -343,6 -342,9 +347,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
@@@ -363,6 -365,8 +370,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
@@@ -402,6 -406,8 +411,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")
index 87aa28410b3ec398c6634b304c59271426db5032,302b39a0d16d66c12b6c328d992a710cd2d0c373..343cad6591da170f36e7eafe148dddf873b4c536
@@@ -569,7 -569,7 +569,7 @@@ void vehicles_regen(entity this, float 
        }
  }
  
- void vehicles_regen_resource(entity this, float timer, .float regen_field, float field_max, float rpause, float regen, float delta_time, float _healthscale, int resource)
+ void vehicles_regen_resource(entity this, float timer, .float regen_field, float field_max, float rpause, float regen, float delta_time, float _healthscale, Resource resource)
  {
        float resource_amount = GetResource(this, resource);
  
@@@ -942,21 -942,11 +942,21 @@@ void vehicles_enter(entity pl, entity v
        if((IS_BOT_CLIENT(pl) && !autocvar_g_vehicles_allow_bots))
                return;
  
 +      bool veh_ring_no_enter = false;
 +      if(ring && autocvar_g_br_ring_exitvehicle)
 +      {
 +              if(vlen(veh.origin - ring.origin) > ring_calculate_current_radius(ring))
 +                      veh_ring_no_enter = true;
 +      }
 +
        if((!IS_PLAYER(pl))
        || (veh.phase >= time)
        || (pl.vehicle_enter_delay >= time)
        || (STAT(FROZEN, pl))
        || (IS_DEAD(pl))
 +      || (STAT(BLEEDING, pl))
 +      || (STAT(DROP, pl) != DROP_LANDED)
 +      || (veh_ring_no_enter)
        || (pl.vehicle)
        ) { return; }
  
index 227aa8ea85612e3a4c08116bee725feb0c4faf87,f98a8de8876e18ea5258fb5e0b6847234fc87de3..9c9ed148ed1d73ef9a0b12c3817cfafbf1481c89
@@@ -313,11 -313,6 +313,6 @@@ void URI_Get_Callback(float id, float s
        }
  }
  
- void DisableServerBackwardsCompatibility()
- {
-       cvar_set("gameversion_min", ftos(100 * floor(cvar("gameversion") / 100)));
- }
  void UpdateNotification_URI_Get_Callback(float id, float status, string data)
  {
        float n;
        string s;
  
        string un_version = "";
+       string un_tosversion = "";
        string un_download = "";
        string un_url = "";
        string un_bannedservers = "";
                                un_version = s;
                                break;
                        }
+                       case "T":
+                       {
+                               un_tosversion = s;
+                               break;
+                       }
                        case "C":
                        {
                                un_compatexpire = s;
                }
        }
  
-       if(un_version != "")
+       if(un_version != "" && vercmp(cvar_string("g_xonoticversion"), un_version) < 0)
        {
-               if(vercmp(cvar_string("g_xonoticversion"), un_version) < 0)
-               {
-                       // update needed
-                       _Nex_ExtResponseSystem_UpdateTo = strzone(un_version);
-                       if(un_download) { LOG_INFO(_("Update can be downloaded at:"), "\n", un_download); }
-                       if(un_url) { _Nex_ExtResponseSystem_UpdateToURL = strzone(un_url); }
-                       DisableServerBackwardsCompatibility();
-               }
-               else if(cvar_string("g_xonoticversion") == un_version)
-               {
-                       if(un_compatexpire != "")
-                       {
-                               string curdate = strftime(false, "%Y%m%d%H%M%S");
-                               if (strcmp(curdate, un_compatexpire) >= 0)
-                                       DisableServerBackwardsCompatibility();
-                       }
-               }
+               // update needed
+               _Nex_ExtResponseSystem_UpdateTo = strzone(un_version);
+               if(un_download) { LOG_INFO(_("Update can be downloaded at:"), "\n", un_download); }
+               if(un_url) { _Nex_ExtResponseSystem_UpdateToURL = strzone(un_url); }
+       }
+       if(un_tosversion != "")
+       {
+               _Nex_ExtResponseSystem_NewToS = stof(un_tosversion);
        }
  
        if(un_bannedservers != "")
@@@ -464,15 -457,8 +457,8 @@@ void updateCheck(
        if(!_Nex_ExtResponseSystem_Queried)
        {
                _Nex_ExtResponseSystem_Queried = 1;
-               float startcnt;
-               string uri;
-               cvar_set("cl_startcount", ftos(startcnt = cvar("cl_startcount") + 1));
-               // for privacy, munge the start count a little
-               startcnt = floor((floor(startcnt / 10) + random()) * 10);
-               uri = sprintf("http://update.xonotic.org/checkupdate.txt?version=%s&cnt=%d", uri_escape(cvar_string("g_xonoticversion")), startcnt);
-               uri_get(uri, URI_GET_UPDATENOTIFICATION);
+               cvar_set("cl_startcount", ftos(cvar("cl_startcount") + 1));
+               uri_get("https://update.xonotic.org/checkupdate.txt", URI_GET_UPDATENOTIFICATION);
        }
  
        if(_Nex_ExtResponseSystem_PacksStep > 0)
@@@ -582,6 -568,7 +568,7 @@@ void preMenuDraw(
                draw_CenterText(mid - 1 * line, l1, fs, '1 0 0', 1, 0);
                draw_CenterText(mid - 0 * line, l2, fs, '0 0 1', 1, 0);
        }
        if (!campaign_name_previous)
                campaign_name_previous = strzone(strcat(campaign_name, "x")); // force unequal
        if(campaign_name == campaign_name_previous)
@@@ -682,14 -669,13 +669,14 @@@ float updateCompression(
        GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
        /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \
-       /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
 +      /* GAMETYPE(MAPINFO_TYPE_BR) */ \
        /**/
  
  // hidden gametypes come last so indexing always works correctly
  #define HIDDEN_GAMETYPES \
        GAMETYPE(MAPINFO_TYPE_RACE) \
        GAMETYPE(MAPINFO_TYPE_CTS) \
+       GAMETYPE(MAPINFO_TYPE_INVASION) \
        /**/
  
  Gametype GameType_GetID(int cnt)
@@@ -818,6 -804,18 +805,18 @@@ void dialog_hudpanel_main_settings(enti
                                e.configureXonoticTextSliderValues(e);
  }
  
+ bool isServerSingleplayer()
+ {
+       return (cvar_string("net_address") == "127.0.0.1" && cvar_string("net_address_ipv6") == "::1");
+ }
+ void makeServerSingleplayer()
+ {
+       // it doesn't allow clients to connect from different machines
+       localcmd("defer 0.1 \"sv_cmd settemp net_address 127.0.0.1\"\n");
+       localcmd("defer 0.1 \"sv_cmd settemp net_address_ipv6 ::1\"\n");
+ }
  float getFadedAlpha(float currentAlpha, float startAlpha, float targetAlpha)
  {
        if(startAlpha < targetAlpha)
diff --combined qcsrc/server/chat.qc
index da75146bacff6934f446b9a07e31763bc1d80d32,a73de2a1f6c58f2ed97e4303ae5179a7354b07db..37cbcdee296200c2070661264379641c4be9fa7f
@@@ -31,12 -31,10 +31,12 @@@ int Say(entity source, int teamsay, ent
                msgin = formatmessage(source, msgin);
  
        string colorstr;
-       if (!(IS_PLAYER(source) || source.caplayer || IN_SQUAD(source)))
 -      if (!(IS_PLAYER(source) || INGAME(source)))
++      if (!(IS_PLAYER(source) || INGAME(source) || IN_SQUAD(source)))
                colorstr = "^0"; // black for spectators
        else if(teamplay)
                colorstr = Team_ColorCode(source.team);
 +      else if(IN_SQUAD(source))
 +              colorstr = "^7";
        else
        {
                colorstr = "";
                sourcecmsgstr = cmsgstr;
        }
  
-       if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer || IN_SQUAD(source)) && !game_stopped
 -      if (!privatesay && source && !(IS_PLAYER(source) || INGAME(source)) && !game_stopped
++      if (!privatesay && source && !(IS_PLAYER(source) || INGAME(source) || IN_SQUAD(source)) && !game_stopped
                && (teamsay || CHAT_NOSPECTATORS()))
        {
                teamsay = -1; // spectators
                ret = 1;
        }
  
-       if (privatesay && source && !(IS_PLAYER(source) || source.caplayer || IN_SQUAD(source)) && !game_stopped
-               && (IS_PLAYER(privatesay) || privatesay.caplayer || IN_SQUAD(privatesay)) && CHAT_NOSPECTATORS())
 -      if (privatesay && source && !(IS_PLAYER(source) || INGAME(source)) && !game_stopped
 -              && (IS_PLAYER(privatesay) || INGAME(privatesay)) && CHAT_NOSPECTATORS())
++      if (privatesay && source && !(IS_PLAYER(source) || INGAME(source) || IN_SQUAD(source)) && !game_stopped
++              && (IS_PLAYER(privatesay) || INGAME(privatesay) || IN_SQUAD(privatesay)) && CHAT_NOSPECTATORS())
        {
                ret = -1; // just hide the message completely
        }
                        dedicated_print(msgstr); // send to server console too
                        if(sourcecmsgstr != "")
                                centerprint(source, sourcecmsgstr);
-                       FOREACH_CLIENT((IS_PLAYER(it) || it.caplayer || IN_SQUAD(it)) && IS_REAL_CLIENT(it) && it != source && (((it.team == source.team) && !IN_SQUAD(source)) || SAME_SQUAD(it, source)) && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
 -                      FOREACH_CLIENT((IS_PLAYER(it) || INGAME(it)) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
++                      FOREACH_CLIENT((IS_PLAYER(it) || INGAME(it) || IN_SQUAD(it)) && IS_REAL_CLIENT(it) && it != source && (((it.team == source.team) && !IN_SQUAD(source)) || SAME_SQUAD(it, source)) && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
                                sprint(it, msgstr);
                                if(cmsgstr != "")
                                        centerprint(it, cmsgstr);
                {
                        sprint(source, sourcemsgstr);
                        dedicated_print(msgstr); // send to server console too
-                       FOREACH_CLIENT(!(IS_PLAYER(it) || it.caplayer || IN_SQUAD(it)) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
 -                      FOREACH_CLIENT(!(IS_PLAYER(it) || INGAME(it)) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
++                      FOREACH_CLIENT(!(IS_PLAYER(it) || INGAME(it) || IN_SQUAD(it)) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
                                sprint(it, msgstr);
                        });
                        event_log_msg = sprintf(":chat_spec:%d:%s", source.playerid, strreplace("\n", " ", msgin));
diff --combined qcsrc/server/client.qc
index 24f9ef00d7c3ae965481ac15c0b8de0a226b8453,2e6e9e6b852c84a6b6ffe5bec87524fb59e6a260..358b3de4dfa14ca16ef26daed03d8b91d44da3cc
@@@ -30,6 -30,7 +30,7 @@@
  #include <common/notifications/all.qh>
  #include <common/physics/player.qh>
  #include <common/playerstats.qh>
+ #include <common/resources/sv_resources.qh>
  #include <common/state.qh>
  #include <common/stats.qh>
  #include <common/vehicles/all.qh>
@@@ -65,7 -66,6 +66,6 @@@
  #include <server/player.qh>
  #include <server/portals.qh>
  #include <server/race.qh>
- #include <server/resources.qh>
  #include <server/scores.qh>
  #include <server/scores_rules.qh>
  #include <server/spawnpoints.qh>
@@@ -137,7 -137,8 +137,8 @@@ bool ClientData_Send(entity this, entit
        if (CS(e).race_completed)       sf |= BIT(0); // forced scoreboard
        if (CS(to).spectatee_status)    sf |= BIT(1); // spectator ent number follows
        if (CS(e).zoomstate)            sf |= BIT(2); // zoomed
-       if (autocvar_sv_showspectators) sf |= BIT(4); // show spectators
+       if (autocvar_sv_showspectators == 1 || (autocvar_sv_showspectators && IS_SPEC(to)))
+                                       sf |= BIT(4); // show spectators
  
        WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA);
        WriteByte(MSG_ENTITY, sf);
@@@ -234,12 -235,11 +235,13 @@@ void setplayermodel(entity e, string mo
                UpdatePlayerSounds(e);
  }
  
 +bool SpectateNext(entity this);
 +
  /** putting a client as observer in the server */
- void PutObserverInServer(entity this, bool is_forced)
+ void PutObserverInServer(entity this, bool is_forced, bool use_spawnpoint)
  {
        bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this, is_forced);
+       bool recount_ready = false;
        PlayerState_detach(this);
  
        if (IS_PLAYER(this))
                if(IS_REAL_CLIENT(this))
                {
                        if (vote_called) { VoteCount(false); }
-                       ReadyCount();
+                       this.ready = false;
+                       recount_ready = true;
                }
                entcs_update_players(this);
        }
  
-       entity spot = SelectSpawnPoint(this, true);
-       if (!spot) LOG_FATAL("No spawnpoints for observers?!?");
-       this.angles = vec2(spot.angles);
+       if (use_spawnpoint)
+       {
+               entity spot = SelectSpawnPoint(this, true);
+               if (!spot) LOG_FATAL("No spawnpoints for observers?!?");
+               this.angles = vec2(spot.angles);
+               // offset it so that the spectator spawns higher off the ground, looks better this way
+               setorigin(this, spot.origin + STAT(PL_VIEW_OFS, this));
+       }
+       else // change origin to restore previous view origin
+               setorigin(this, this.origin + STAT(PL_VIEW_OFS, this) - STAT(PL_CROUCH_VIEW_OFS, this));
        this.fixangle = true;
-       // offset it so that the spectator spawns higher off the ground, looks better this way
-       setorigin(this, spot.origin + STAT(PL_VIEW_OFS, this));
        if (IS_REAL_CLIENT(this))
        {
                msg_entity = this;
  
        if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
  
+       TRANSMUTE(Observer, this);
+       if(recount_ready) ReadyCount();
        WaypointSprite_PlayerDead(this);
+       accuracy_resend(this);
  
        if (CS(this).killcount != FRAGS_SPECTATOR && !game_stopped && CHAT_NOSPECTATORS())
                Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
  
-       accuracy_resend(this);
        CS(this).spectatortime = time;
        if(this.bot_attack)
                IL_REMOVE(g_bot_targets, this);
                IL_REMOVE(g_monster_targets, this);
        this.monster_attack = false;
        STAT(HUD, this) = HUD_NORMAL;
-       TRANSMUTE(Observer, this);
        this.iscreature = false;
        this.teleportable = TELEPORT_SIMPLE;
        if(this.damagedbycontents)
        this.revival_time = 0;
        this.draggable = drag_undraggable;
  
+       player_powerups_remove_all(this);
        this.items = 0;
        STAT(WEAPONS, this) = '0 0 0';
        this.drawonlytoclient = this;
        }
        else
        {
-               SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
+               SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR); // clears scores too in game modes without teams
                this.frags = FRAGS_SPECTATOR;
        }
  
  
        if (CS(this).just_joined)
                CS(this).just_joined = false;
 +
 +      // force members of alive squads to spectate another squadmate
 +      if(IS_REAL_CLIENT(this) && IN_SQUAD(this) && !this.br_squad.br_squad_dead)
 +      {
 +              SpectateNext(this);
 +              TRANSMUTE(Spectator, this);
 +      }
  }
  
  int player_getspecies(entity this)
@@@ -537,6 -540,9 +549,9 @@@ void PutPlayerInServer(entity this
        PlayerState_attach(this);
        accuracy_resend(this);
  
+       if (teamplay && this.bot_forced_team)
+               SetPlayerTeam(this, this.bot_forced_team, TEAM_CHANGE_MANUAL);
        if (this.team < 0)
                TeamBalance_JoinBestTeam(this);
  
        this.waterlevel = WATERLEVEL_NONE;
        this.watertype = CONTENT_EMPTY;
  
 -      entity spawnevent = new_pure(spawnevent);
 -      spawnevent.owner = this;
 -      Net_LinkEntity(spawnevent, false, 0.5, SpawnEvent_Send);
 +      if(STAT(DROP, this) != DROP_TRANSPORT)
 +      {
 +              entity spawnevent = new_pure(spawnevent);
 +              spawnevent.owner = this;
 +              Net_LinkEntity(spawnevent, false, 0.5, SpawnEvent_Send);
 +      }
  
        // Cut off any still running player sounds.
        stopsound(this, CH_PLAYER_SINGLE);
        Unfreeze(this, false);
  
        MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
        {
                string s = spot.target;
                if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack
  /** Called when a client spawns in the server */
  void PutClientInServer(entity this)
  {
-       if (IS_BOT_CLIENT(this)) {
-               TRANSMUTE(Player, this);
-       } else if (IS_REAL_CLIENT(this)) {
+       if (IS_REAL_CLIENT(this)) {
                msg_entity = this;
                WriteByte(MSG_ONE, SVC_SETVIEW);
                WriteEntity(MSG_ONE, this);
        if (game_stopped)
                TRANSMUTE(Observer, this);
  
+       bool use_spawnpoint = (!this.enemy); // check this.enemy here since SetSpectatee will clear it
        SetSpectatee(this, NULL);
  
        // reset player keys
        MUTATOR_CALLHOOK(PutClientInServer, this);
  
        if (IS_OBSERVER(this)) {
-               PutObserverInServer(this, false);
+               PutObserverInServer(this, false, use_spawnpoint);
        } else if (IS_PLAYER(this)) {
                PutPlayerInServer(this);
        }
@@@ -947,7 -948,6 +960,6 @@@ void DecodeLevelParms(entity this
  void FixClientCvars(entity e)
  {
        // send prediction settings to the client
-       stuffcmd(e, "\nin_bindmap 0 0\n");
        if(autocvar_g_antilag == 3) // client side hitscan
                stuffcmd(e, "cl_cmd settemp cl_prydoncursor_notrace 0\n");
        if(autocvar_sv_gentle)
@@@ -1019,50 -1019,48 +1031,48 @@@ void ClientPreConnect(entity this
  }
  #endif
  
string GetClientVersionMessage(entity this)
void SendWelcomemessage(entity this, bool force_centerprint)
  {
-       if (CS(this).version_mismatch) {
-               if(CS(this).version < autocvar_gameversion) {
-                       return strcat("This is Xonotic ", autocvar_g_xonoticversion,
-                               "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8");
-               } else {
-                       return strcat("This is Xonotic ", autocvar_g_xonoticversion,
-                               "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8");
-               }
-       } else {
-               return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion);
-       }
+       msg_entity = this;
+       WriteHeader(MSG_ONE, TE_CSQC_SERVERWELCOME);
+       SendWelcomemessage_msg_type(this, force_centerprint, MSG_ONE);
  }
  
- string getwelcomemessage(entity this)
+ // NOTE csqc uses the active mutators list sent by this function
+ // to understand which mutators are enabled
+ // also note that they aren't all registered mutators, e.g. jetpack, low gravity
+ void SendWelcomemessage_msg_type(entity this, bool force_centerprint, int msg_type)
  {
+       WriteByte(msg_type, boolean(autocvar_g_campaign));
+       if (boolean(autocvar_g_campaign))
+       {
+               WriteString(msg_type, Campaign_GetTitle());
+               WriteByte(msg_type, Campaign_GetLevelNum());
+               WriteString(msg_type, Campaign_GetMessage());
+               return;
+       }
+       WriteByte(msg_type, force_centerprint);
+       WriteString(msg_type, autocvar_hostname);
+       WriteString(msg_type, autocvar_g_xonoticversion);
+       WriteByte(msg_type, CS(this).version_mismatch);
+       WriteByte(msg_type, (CS(this).version < autocvar_gameversion));
        MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
        string modifications = M_ARGV(0, string);
  
-       if(g_weaponarena)
-       {
-               if(g_weaponarena_random)
-                       modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
-               else
-                       modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
-       }
-       else if(cvar("g_balance_blaster_weaponstartoverride") == 0)
+       if (!g_weaponarena && cvar("g_balance_blaster_weaponstartoverride") == 0)
                modifications = strcat(modifications, ", No start weapons");
        if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
                modifications = strcat(modifications, ", Low gravity");
        if(g_weapon_stay && !g_cts)
                modifications = strcat(modifications, ", Weapons stay");
        if(autocvar_g_jetpack)
-               modifications = strcat(modifications, ", Jet pack");
+               modifications = strcat(modifications, ", Jetpack");
        modifications = substring(modifications, 2, strlen(modifications) - 2);
  
-       string versionmessage = GetClientVersionMessage(this);
-       string s = strcat(versionmessage, "^8\n^8\nserver is ^9", autocvar_hostname, "^8\n");
-       s = strcat(s, "^8\nmatch type is ^1", gamemode_name, "^8\n");
+       WriteString(msg_type, modifications);
  
-       if(modifications != "")
-               s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
+       WriteString(msg_type, g_weaponarena_list);
  
        if(cache_lastmutatormsg != autocvar_g_mutatormsg)
        {
                strcpy(cache_mutatormsg, cache_lastmutatormsg);
        }
  
-       if (cache_mutatormsg != "") {
-               s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
-       }
-       string mutator_msg = "";
-       MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
-       mutator_msg = M_ARGV(0, string);
+       WriteString(msg_type, cache_mutatormsg);
  
-       s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting
-       string motd = autocvar_sv_motd;
-       if (motd != "") {
-               s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
-       }
-       return s;
+       WriteString(msg_type, strreplace("\\n", "\n", autocvar_sv_motd));
  }
  
  /**
@@@ -1189,14 -1175,11 +1187,11 @@@ void ClientConnect(entity this
  
        MUTATOR_CALLHOOK(ClientConnect, this);
  
-       if (IS_REAL_CLIENT(this))
-       {
-               if (!autocvar_g_campaign && !IS_PLAYER(this))
-               {
-                       CS(this).motd_actived_time = -1;
-                       Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOTD, getwelcomemessage(this));
-               }
-       }
+       if (player_count == 1)
+               localcmd("\nsv_hook_firstjoin\n");
+       if (IS_REAL_CLIENT(this) && !IS_PLAYER(this) && !autocvar_g_campaign)
+               CS(this).motd_actived_time = -1; // the welcome message is shown by the client
  }
  /*
  =============
@@@ -1215,7 -1198,7 +1210,7 @@@ void ClientDisconnect(entity this
        PlayerStats_GameReport_FinalizePlayer(this);
        if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
        if (CS(this).active_minigame) part_minigame(this);
 -      if (IS_PLAYER(this)) Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
 +      if (IS_PLAYER(this) && (STAT(DROP, this) != DROP_TRANSPORT)) Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
  
        if (autocvar_sv_eventlog)
                GameLogEcho(strcat(":part:", ftos(this.playerid)));
        player_powerups_remove_all(this); // stop powerup sound
  
        ONREMOVE(this);
+       if (player_count == 0)
+               localcmd("\nsv_hook_lastleave\n");
  }
  
  void ChatBubbleThink(entity this)
@@@ -1448,21 -1434,25 +1446,25 @@@ void respawn(entity this
  void play_countdown(entity this, float finished, Sound samp)
  {
        TC(Sound, samp);
-       if(IS_REAL_CLIENT(this))
-               if(floor(finished - time - frametime) != floor(finished - time))
-                       if(finished - time < 6)
-                               sound (this, CH_INFO, samp, VOL_BASE, ATTEN_NORM);
+       float time_left = finished - time;
+       if(IS_REAL_CLIENT(this) && time_left < 6 && floor(time_left - frametime) != floor(time_left))
+               sound(this, CH_INFO, samp, VOL_BASE, ATTEN_NORM);
  }
  
+ // it removes special powerups not handled by StatusEffects
  void player_powerups_remove_all(entity this)
  {
-       if (this.items & IT_SUPERWEAPON)
+       if (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
        {
                // don't play the poweroff sound when the game restarts or the player disconnects
-               if (time > game_starttime + 1 && IS_CLIENT(this))
+               if (time > game_starttime + 1 && IS_CLIENT(this)
+                       && !(start_items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS)))
+               {
                        sound(this, CH_INFO, SND_POWEROFF, VOL_BASE, ATTEN_NORM);
-               stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound
-               this.items -= (this.items & IT_SUPERWEAPON);
+               }
+               if (this.items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
+                       stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound
+               this.items -= (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS));
        }
  }
  
@@@ -1566,7 -1556,7 +1568,7 @@@ float CalcRot(float current, float stab
                return max(stable, current + (stable - current) * rotfactor * rotframetime);
  }
  
- void RotRegen(entity this, int res, float limit_mod,
+ void RotRegen(entity this, Resource res, float limit_mod,
        float regenstable, float regenfactor, float regenlinear, float regenframetime,
        float rotstable, float rotfactor, float rotlinear, float rotframetime)
  {
@@@ -1810,7 -1800,7 +1812,7 @@@ bool SpectateSet(entity this
        accuracy_resend(this);
  
        if(!SpectateUpdate(this))
-               PutObserverInServer(this, false);
+               PutObserverInServer(this, false, true);
  
        return true;
  }
@@@ -1852,18 -1842,18 +1854,18 @@@ void SetSpectatee(entity this, entity s
                                old_spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
                }
        }
-       if(this.enemy)
+       if(spectatee)
        {
                for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
                {
                        .entity weaponentity = weaponentities[slot];
-                       if(this.enemy.(weaponentity).arc_beam)
-                               this.enemy.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
+                       if(spectatee.(weaponentity).arc_beam)
+                               spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
                }
        }
  
-       if (this.enemy)
-               SetSpectatee_status(this, etof(this.enemy));
+       if (spectatee)
+               SetSpectatee_status(this, etof(spectatee));
  
        // needed to update spectator list
        if(old_spectatee) { ClientData_Touch(old_spectatee); }
@@@ -1964,6 -1954,9 +1966,9 @@@ bool ShowTeamSelection(entity this
  }
  void Join(entity this)
  {
+       if (autocvar_g_campaign && !campaign_bots_may_start && !game_stopped && time >= game_starttime)
+               ReadyRestart(true);
        TRANSMUTE(Player, this);
  
        if(!this.team_selected)
@@@ -2023,8 -2016,7 +2028,7 @@@ int nJoinAllowed(entity this, entity ig
        FOREACH_CLIENT(true, {
                if(it != ignore)
                        ++totalClients;
-               if(IS_REAL_CLIENT(it))
-               if(IS_PLAYER(it) || it.caplayer)
+               if(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME(it)))
                        ++currentlyPlaying;
        });
  
                free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
  
        static float msg_time = 0;
-       if(this && !this.caplayer && ignore && !free_slots && time > msg_time)
+       if(this && !INGAME(this) && ignore && !free_slots && time > msg_time)
        {
                Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT);
                msg_time = time + 0.5;
@@@ -2053,12 -2045,12 +2057,12 @@@ void PrintWelcomeMessage(entity this
                if (autocvar_g_campaign) {
                        if ((IS_PLAYER(this) && PHYS_INPUT_BUTTON_INFO(this)) || (!IS_PLAYER(this))) {
                                CS(this).motd_actived_time = time;
-                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_CAMPAIGN_MESSAGE, Campaign_GetMessage(), Campaign_GetLevelNum());
+                               SendWelcomemessage(this, false);
                        }
                } else {
                        if (PHYS_INPUT_BUTTON_INFO(this)) {
                                CS(this).motd_actived_time = time;
-                               Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOTD, getwelcomemessage(this));
+                               SendWelcomemessage(this, true);
                        }
                }
        }
                                CS(this).motd_actived_time = time;
                        else if ((time - CS(this).motd_actived_time > 2) && IS_PLAYER(this)) { // hide it some seconds after BUTTON_INFO has been released
                                CS(this).motd_actived_time = 0;
-                               Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_CAMPAIGN_MESSAGE);
+                               Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD);
                        }
                } else {
                        if (PHYS_INPUT_BUTTON_INFO(this))
                {
                        // instantly hide MOTD
                        CS(this).motd_actived_time = 0;
-                       if (autocvar_g_campaign)
-                               Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_CAMPAIGN_MESSAGE);
-                       else
-                               Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD);
+                       Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD);
                }
                else if (IS_PLAYER(this) || IS_SPEC(this))
                {
@@@ -2281,6 -2270,14 +2282,14 @@@ void ObserverOrSpectatorThink(entity th
                }
        }
  
+       if (IS_BOT_CLIENT(this) && !CS(this).autojoin_checked)
+       {
+               CS(this).autojoin_checked = true;
+               TRANSMUTE(Player, this);
+               PutClientInServer(this);
+               return;
+       }
        if (this.flags & FL_JUMPRELEASED) {
                if (PHYS_INPUT_BUTTON_JUMP(this) && (joinAllowed(this) || time < CS(this).jointime + MIN_SPEC_TIME)) {
                        this.flags &= ~FL_JUMPRELEASED;
                                TRANSMUTE(Observer, this);
                                PutClientInServer(this);
                        } else if(!SpectateUpdate(this) && !SpectateNext(this)) {
-                               PutObserverInServer(this, false);
+                               PutObserverInServer(this, false, true);
                                this.would_spectate = true;
                        }
                }
                        }
                }
                if(is_spec && !SpectateUpdate(this))
-                       PutObserverInServer(this, false);
+                       PutObserverInServer(this, false, true);
        }
        if (is_spec)
                this.flags |= FL_CLIENT | FL_NOTARGET;
@@@ -2413,7 -2410,7 +2422,7 @@@ void PlayerPreThink (entity this
                // WORKAROUND: only use dropclient in server frames (frametime set).
                // Never use it in cl_movement frames (frametime zero).
                if (blockSpectators && IS_REAL_CLIENT(this)
-                       && (IS_SPEC(this) || IS_OBSERVER(this)) && !this.caplayer
+                       && (IS_SPEC(this) || IS_OBSERVER(this)) && !INGAME(this)
                        && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
                {
                        if (dropclient_schedule(this))
                this.last_vehiclecheck = time + 1;
        }
  
-       if(!CS_CVAR(this).cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button
-       {
-               if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
-                       PlayerUseKey(this);
-               CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
-       }
+       if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
+               PlayerUseKey(this);
+       CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
  
        if (IS_REAL_CLIENT(this))
                PrintWelcomeMessage(this);
                        || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
                                && (!teamplay || autocvar_g_balance_teams)))
                {
-                       campaign_bots_may_start = true;
                        if(joinAllowed(this))
                                Join(this);
                        return;
@@@ -2714,7 -2707,7 +2719,7 @@@ void PlayerPostThink (entity this
                                if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
                                {
                                        Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time);
-                                       PutObserverInServer(this, true);
+                                       PutObserverInServer(this, true, true);
                                }
                                else
                                {
diff --combined qcsrc/server/damage.qc
index 0ccf32bceea3731df58c5d7faca26f8b48ea158c,3cbc07c57d3dddb989bd6b954ad58df86e6186f0..b666218f1a93849737851cdcc50c27ff2cd058ef
@@@ -17,6 -17,7 +17,7 @@@
  #include <common/physics/movetypes/movetypes.qh>
  #include <common/physics/player.qh>
  #include <common/playerstats.qh>
+ #include <common/resources/sv_resources.qh>
  #include <common/state.qh>
  #include <common/teams.qh>
  #include <common/util.qh>
@@@ -31,7 -32,6 +32,6 @@@
  #include <server/items/items.qh>
  #include <server/main.qh>
  #include <server/mutators/_mod.qh>
- #include <server/resources.qh>
  #include <server/scores.qh>
  #include <server/spawnpoints.qh>
  #include <server/teamplay.qh>
@@@ -305,9 -305,9 +305,9 @@@ void Obituary(entity attacker, entity i
        // ======
        // MURDER
        // ======
 -      else if(IS_PLAYER(attacker))
 +      else if(IS_PLAYER(attacker) || IN_SQUAD(attacker))
        {
 -              if(SAME_TEAM(attacker, targ))
 +              if(SAME_TEAM(attacker, targ) || SAME_SQUAD(attacker, targ))
                {
                        LogDeath("tk", deathtype, attacker, targ);
                        GiveFrags(attacker, targ, -1, deathtype, weaponentity);
@@@ -594,7 -594,7 +594,7 @@@ void Damage(entity targ, entity inflict
        // special rule: gravity bombs and sound-based attacks do not affect team mates (other than for disconnecting the hook)
        if(DEATH_ISWEAPON(deathtype, WEP_HOOK) || (deathtype & HITTYPE_SOUND))
        {
 -              if(IS_PLAYER(targ) && SAME_TEAM(targ, attacker))
 +              if(IS_PLAYER(targ) && (SAME_TEAM(targ, attacker) || SAME_SQUAD(targ, attacker)))
                {
                        return;
                }
                if(deathtype != DEATH_TELEFRAG.m_id)
                if(IS_PLAYER(attacker))
                {
-                       if(IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ)))
+                       // avoid dealing damage or force to other independent players
+                       // and avoid dealing damage or force to things owned by other independent players
+                       if((IS_PLAYER(targ) && targ != attacker && (IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(targ))) ||
+                               (targ.realowner && IS_INDEPENDENT_PLAYER(targ.realowner) && attacker != targ.realowner))
                        {
                                damage = 0;
                                force = '0 0 0';
                        }
 -                      else if(!STAT(FROZEN, targ) && SAME_TEAM(attacker, targ))
 +                      else if(!STAT(FROZEN, targ) && (SAME_TEAM(attacker, targ) || SAME_SQUAD(attacker, targ)))
                        {
                                if(autocvar_teamplay_mode == 1)
                                        damage = 0;
  
                        if(IS_PLAYER(victim) || (IS_TURRET(victim) && victim.active == ACTIVE_ACTIVE) || IS_MONSTER(victim) || MUTATOR_CALLHOOK(PlayHitsound, victim, attacker))
                        {
 -                              if (DIFF_TEAM(victim, attacker))
 +                              if (DIFF_TEAM(victim, attacker) && DIFF_SQUAD(victim, attacker))
                                {
                                        if(damage > 0)
                                        {
                                                        if(PHYS_INPUT_BUTTON_CHAT(victim))
                                                                attacker.typehitsound += 1;
                                                        else
-                                                               attacker.damage_dealt += damage;
+                                                               attacker.hitsound_damage_dealt += damage;
                                                }
  
                                                impressive_hits += 1;
@@@ -881,6 -884,8 +884,8 @@@ float RadiusDamageForSource (entity inf
                return 0;
        }
  
+       if (rad < 0) rad = 0;
        RadiusDamage_running = 1;
  
        tfloordmg = autocvar_g_throughfloor_damage;
                if (((cantbe != targ) && !mustbe) || (mustbe == targ))
                if (targ.takedamage)
                {
-                       vector nearest;
-                       vector diff;
-                       float power;
-                       // LordHavoc: measure distance to nearest point on target (not origin)
-                       // (this guarentees 100% damage on a touch impact)
-                       nearest = targ.WarpZone_findradius_nearest;
-                       diff = targ.WarpZone_findradius_dist;
+                       // measure distance from nearest point on target (not origin)
+                       // to nearest point on inflictor (not origin)
+                       vector nearest = targ.WarpZone_findradius_nearest;
+                       vector inflictornearest = NearestPointOnBoundingBox(
+                               inflictororigin - (inflictor.maxs - inflictor.mins) * 0.5,
+                               inflictororigin + (inflictor.maxs - inflictor.mins) * 0.5,
+                               nearest);
+                       vector diff = inflictornearest - nearest;
                        // round up a little on the damage to ensure full damage on impacts
                        // and turn the distance into a fraction of the radius
-                       power = 1 - ((vlen (diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS)) / rad);
-                       //bprint(" ");
-                       //bprint(ftos(power));
-                       //if (targ == attacker)
-                       //      print(ftos(power), "\n");
-                       if (power > 0)
+                       float dist = max(0, vlen(diff) - bound(MIN_DAMAGEEXTRARADIUS, targ.damageextraradius, MAX_DAMAGEEXTRARADIUS));
+                       if (dist <= rad)
                        {
-                               float finaldmg;
-                               if (power > 1)
-                                       power = 1;
-                               finaldmg = coredamage * power + edgedamage * (1 - power);
+                               float power = 1;
+                               if (rad > 0)
+                                       power -= (dist / rad);
+                               // at this point power can't be < 0 or > 1
+                               float finaldmg = coredamage * power + edgedamage * (1 - power);
                                if (finaldmg > 0)
                                {
                                        float a;
@@@ -1197,12 -1200,12 +1200,12 @@@ void Fire_ApplyDamage(entity e
        t = min(frametime, fireendtime - time);
        d = e.fire_damagepersec * t;
  
-       hi = e.fire_owner.damage_dealt;
+       hi = e.fire_owner.hitsound_damage_dealt;
        ty = e.fire_owner.typehitsound;
        Damage(e, e, e.fire_owner, d, e.fire_deathtype, DMG_NOWEP, e.origin, '0 0 0');
        if(e.fire_hitsound && e.fire_owner)
        {
-               e.fire_owner.damage_dealt = hi;
+               e.fire_owner.hitsound_damage_dealt = hi;
                e.fire_owner.typehitsound = ty;
        }
        e.fire_hitsound = true;
diff --combined qcsrc/server/impulse.qc
index de51d7f70d38cb38f2a138ce33fdac309fdd4fea,be66fe968c2b6a2eadc8ac35b8390805ad519289..f98545db0c3aa9ff674083950b46d3e21ec32092
@@@ -381,87 -381,16 +381,16 @@@ void ImpulseCommands(entity this
  
        if (timeout_status == TIMEOUT_ACTIVE) return;  // don't allow any impulses while the game is paused
  
-       // allow only weapon change impulses when not in round time
        if (round_handler_IsActive() && !round_handler_IsRoundStarted())
        {
+               // impulses forbidden while waiting for the start of a round
                #define X(id) case IMP_##id.impulse:
                switch (imp)
                {
-                       X(weapon_group_0)
-                       X(weapon_group_1)
-                       X(weapon_group_2)
-                       X(weapon_group_3)
-                       X(weapon_group_4)
-                       X(weapon_group_5)
-                       X(weapon_group_6)
-                       X(weapon_group_7)
-                       X(weapon_group_8)
-                       X(weapon_group_9)
-                       X(weapon_next_byid)
-                       X(weapon_prev_byid)
-                       X(weapon_next_bygroup)
-                       X(weapon_prev_bygroup)
-                       X(weapon_next_bypriority)
-                       X(weapon_prev_bypriority)
-                       X(weapon_last)
-                       X(weapon_best)
+                       X(weapon_drop)
                        X(weapon_reload)
-                       X(weapon_priority_0_prev)
-             X(weapon_priority_1_prev)
-             X(weapon_priority_2_prev)
-             X(weapon_priority_3_prev)
-             X(weapon_priority_4_prev)
-             X(weapon_priority_5_prev)
-             X(weapon_priority_6_prev)
-             X(weapon_priority_7_prev)
-             X(weapon_priority_8_prev)
-             X(weapon_priority_9_prev)
-             X(weapon_priority_0_next)
-                       X(weapon_priority_1_next)
-                       X(weapon_priority_2_next)
-                       X(weapon_priority_3_next)
-                       X(weapon_priority_4_next)
-                       X(weapon_priority_5_next)
-                       X(weapon_priority_6_next)
-                       X(weapon_priority_7_next)
-                       X(weapon_priority_8_next)
-                       X(weapon_priority_9_next)
-                       X(weapon_priority_0_best)
-             X(weapon_priority_1_best)
-             X(weapon_priority_2_best)
-             X(weapon_priority_3_best)
-             X(weapon_priority_4_best)
-             X(weapon_priority_5_best)
-             X(weapon_priority_6_best)
-             X(weapon_priority_7_best)
-             X(weapon_priority_8_best)
-             X(weapon_priority_9_best)
-             X(weapon_byid_0)
-             X(weapon_byid_1)
-             X(weapon_byid_2)
-             X(weapon_byid_3)
-             X(weapon_byid_4)
-             X(weapon_byid_5)
-             X(weapon_byid_6)
-             X(weapon_byid_7)
-             X(weapon_byid_8)
-             X(weapon_byid_9)
-             X(weapon_byid_10)
-             X(weapon_byid_11)
-             X(weapon_byid_12)
-             X(weapon_byid_13)
-             X(weapon_byid_14)
-             X(weapon_byid_15)
-             X(weapon_byid_16)
-             X(weapon_byid_17)
-             X(weapon_byid_18)
-             X(weapon_byid_19)
-             X(weapon_byid_20)
-             X(weapon_byid_21)
-             X(weapon_byid_22)
-             X(weapon_byid_23)
-                       break;
-                       default: return;
+                       X(use)
+                               return;
                }
  #undef X
        }
@@@ -508,7 -437,7 +437,7 @@@ IMPULSE(waypoint_personal_death
  
  IMPULSE(waypoint_here_follow)
  {
 -      if (!teamplay) return;
 +      if (!teamplay && !IN_SQUAD(this)) return;
        if (IS_DEAD(this)) return;
        if (!MUTATOR_CALLHOOK(HelpMePing, this))
        {
diff --combined qcsrc/server/world.qc
index 4fb2b5795248fae106f5aab7d8b0be3b2ce83b9f,41c9705da06f510026176c49d70f787505ad95fa..32a32e72e493532637396161881c9a8fac7b53a0
@@@ -35,7 -35,6 +35,6 @@@
  #include <server/damage.qh>
  #include <server/gamelog.qh>
  #include <server/hook.qh>
- #include <server/intermission.qh>
  #include <server/ipban.qh>
  #include <server/items/items.qh>
  #include <server/main.qh>
@@@ -46,7 -45,6 +45,6 @@@
  #include <server/scores_rules.qh>
  #include <server/spawnpoints.qh>
  #include <server/teamplay.qh>
- #include <server/weapons/common.qh>
  #include <server/weapons/weaponstats.qh>
  
  const float LATENCY_THINKRATE = 10;
@@@ -279,7 -277,6 +277,6 @@@ void cvar_changes_init(
                BADCVAR("g_duel_not_dm_maps");
                BADCVAR("g_freezetag");
                BADCVAR("g_freezetag_teams");
-               BADCVAR("g_invasion_teams");
                BADCVAR("g_invasion_type");
                BADCVAR("g_jailbreak");
                BADCVAR("g_jailbreak_teams");
                BADCVAR("g_tdm");
                BADCVAR("g_tdm_on_dm_maps");
                BADCVAR("g_tdm_teams");
 +              BADCVAR("g_br");
                BADCVAR("g_vip");
                BADCVAR("leadlimit");
                BADCVAR("nextmap");
                // does nothing gameplay relevant
                BADCVAR("captureleadlimit_override");
                BADCVAR("condump_stripcolors");
-               BADCVAR("gameversion");
                BADCVAR("fs_gamedir");
                BADCVAR("g_allow_oldvortexbeam");
                BADCVAR("g_balance_kill_delay");
                BADCVAR("w_prop_interval");
                BADPREFIX("chat_");
                BADPREFIX("crypto_");
-               BADPREFIX("gameversion_");
+               BADPREFIX("gameversion");
                BADPREFIX("g_chat_");
                BADPREFIX("g_ctf_captimerecord_");
                BADPREFIX("g_hats_");
                BADPREFIX("sv_timeout_");
                BADPREFIX("sv_vote_");
                BADPREFIX("timelimit_");
+               BADPRESUFFIX("g_", "_round_timelimit");
  
                // allowed changes to server admins (please sync this to server.cfg)
                // vi commands:
                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");
                BADCVAR("g_grappling_hook");
                BADCVAR("g_jetpack");
  
-               // temporary for testing
-               // TODO remove before 0.8.3 release
-               BADCVAR("g_ca_weaponarena");
-               BADCVAR("g_freezetag_weaponarena");
-               BADCVAR("g_lms_weaponarena");
-               BADCVAR("g_ctf_stalemate_time");
  #undef BADPRESUFFIX
  #undef BADPREFIX
  #undef BADCVAR
@@@ -691,6 -680,15 +681,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();
  
        WaypointSprite_Init();
  
-       GameLogInit(); // prepare everything
        // NOTE for matchid:
        // changing the logic generating it is okay. But:
        // it HAS to stay <= 64 chars
        // character set: ASCII 33-126 without the following characters: : ; ' " \ $
-       if(autocvar_sv_eventlog)
-       {
-               string num = strftime_s(); // strftime(false, "%s") isn't reliable, see strftime_s description
-               string s = sprintf("%s.%s.%06d", itos(autocvar_sv_eventlog_files_counter), num, floor(random() * 1000000));
-               matchid = strzone(s);
-               GameLogEcho(strcat(":gamestart:", GetGametype(), "_", GetMapname(), ":", s));
-               s = ":gameinfo:mutators:LIST";
-               MUTATOR_CALLHOOK(BuildMutatorsString, s);
-               s = M_ARGV(0, string);
+       // strftime(false, "%s") isn't reliable, see strftime_s description
+       matchid = strzone(sprintf("%d.%s.%06d", autocvar_sv_eventlog_files_counter, strftime_s(), random() * 1000000));
  
-               // initialiation stuff, not good in the mutator system
-               if(!autocvar_g_use_ammunition)
-                       s = strcat(s, ":no_use_ammunition");
-               // initialiation stuff, not good in the mutator system
-               if(autocvar_g_pickup_items == 0)
-                       s = strcat(s, ":no_pickup_items");
-               if(autocvar_g_pickup_items > 0)
-                       s = strcat(s, ":pickup_items");
-               // initialiation stuff, not good in the mutator system
-               if(autocvar_g_weaponarena != "0")
-                       s = strcat(s, ":", autocvar_g_weaponarena, " arena");
-               // TODO to mutator system
-               if(autocvar_g_norecoil)
-                       s = strcat(s, ":norecoil");
-               GameLogEcho(s);
-               GameLogEcho(":gameinfo:end");
-       }
-       else
-               matchid = strzone(ftos(random()));
+       if(autocvar_sv_eventlog)
+               GameLogInit(); // requires matchid to be set
  
        cvar_set("nextmap", "");
  
@@@ -1025,7 -992,7 +993,7 @@@ spawnfunc(light
        delete(this);
  }
  
- bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance)
+ bool MoveToRandomLocationWithinBounds(entity e, vector boundmin, vector boundmax, float goodcontents, float badcontents, float badsurfaceflags, int attempts, float maxaboveground, float minviewdistance, bool frompos)
  {
      float m = e.dphitcontentsmask;
      e.dphitcontentsmask = goodcontents | badcontents;
              continue;
  
                // rule 4: we must "see" some spawnpoint or item
-           entity sp = NULL;
-           IL_EACH(g_spawnpoints, checkpvs(mstart, it),
-           {
-               if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
-               {
-                       sp = it;
-                       break;
-               }
-           });
+               entity sp = NULL;
+               if(frompos)
+               {
+                       if((traceline(mstart, e.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
+                               sp = e;
+               }
+               if(!sp)
+               {
+                       IL_EACH(g_spawnpoints, checkpvs(mstart, it),
+                       {
+                               if((traceline(mstart, it.origin, MOVE_NORMAL, e), trace_fraction) >= 1)
+                               {
+                                       sp = it;
+                                       break;
+                               }
+                       });
+               }
                if(!sp)
                {
                        int items_checked = 0;
  
  float MoveToRandomMapLocation(entity e, float goodcontents, float badcontents, float badsurfaceflags, float attempts, float maxaboveground, float minviewdistance)
  {
-       return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance);
+       return MoveToRandomLocationWithinBounds(e, world.mins, world.maxs, goodcontents, badcontents, badsurfaceflags, attempts, maxaboveground, minviewdistance, false);
  }
  
  /*
@@@ -1215,7 -1190,7 +1191,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:");
@@@ -1268,6 -1243,7 +1244,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);
@@@ -1384,13 -1374,13 +1375,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;
@@@ -1598,19 -1588,18 +1589,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();
        }
@@@ -1857,25 -1852,25 +1853,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
        {
@@@ -2114,7 -2113,7 +2114,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); });
@@@ -2288,9 -2287,10 +2288,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);
        });
@@@ -2423,6 -2423,8 +2424,8 @@@ void Shutdown(
  
                WeaponStats_Shutdown();
                MapInfo_Shutdown();
+               strfree(sv_termsofservice_url_escaped);
        }
        else if(world_initialized == 0)
        {