From: Juhu <5894800-Juhu_@users.noreply.gitlab.com> Date: Wed, 21 Jun 2023 23:55:28 +0000 (+0200) Subject: Merge branch 'master' into Juhu/battle-royale X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=939d736831489dd57fe5e9744e2fe6b789efc44e;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into Juhu/battle-royale --- 939d736831489dd57fe5e9744e2fe6b789efc44e diff --cc .gitlab-ci.yml index 26bdd574e,5637179f1..57319b01e --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@@ -1,99 -1,118 +1,118 @@@ - workflow: - rules: - - if: $CI_COMMIT_MESSAGE =~ /Transifex autosync/ - when: never - - when: always - - before_script: - - ln -s $PWD data/xonotic-data.pk3dir - - - export MAKEFLAGS=-j$(nproc); echo MAKEFLAGS=$MAKEFLAGS - # FIXME: -march=native -mtune=native _changes the hash_, why?!? - # - export CC="gcc -pipe -march=native -mtune=native" - - export CC="gcc -pipe" - - - git clone --depth=1 --branch=main https://gitlab.com/xonotic/gmqcc.git gmqcc - - make -C gmqcc || exit 1 - - export QCC="$PWD/gmqcc/gmqcc" - - # Makefile: don't complain about lack of tags (fetching them is slow) - - export QCCFLAGS_WATERMARK=gitlab_pipeline - # Makefile: don't compress anything or complain about lack of zip program - - export ZIP=/bin/true - - test_compilation_units: - rules: - - changes: - - qcsrc/**/* - stage: test - script: - - make test - - test_sv_game: - stage: test - script: - - git clone --depth=1 --branch=div0-stable https://gitlab.com/xonotic/darkplaces.git darkplaces - - make -C darkplaces sv-release || exit 1 - - export ENGINE="$PWD/darkplaces/darkplaces-dedicated -xonotic -noconfig -nohome" - - make qc || exit 1 - - - mkdir -p data/maps - - wget -O data/maps/gitlab-ci.bsp https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/_init/_init.bsp - - - while read LINE; do - echo $LINE; - [ "$LINE" = "All tests OK" ] && PASS=1; - done < <(${ENGINE} +developer 1 +map gitlab-ci +sv_cmd runtest +wait +quit) - - test "$PASS" = "1" || { echo 'sv_cmd runtest failed!'; exit 1; } - - - ${ENGINE} +map gitlab-ci +sv_cmd dumpnotifs +wait +quit - - diff notifications.cfg data/data/notifications_dump.cfg || - { echo 'Please update notifications.cfg using `dumpnotifs`!'; exit 1; } - - - wget -O data/stormkeep.pk3 http://beta.xonotic.org/autobuild-bsp/latest/stormkeep.pk3 - - wget -O data/maps/stormkeep.mapinfo https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.mapinfo - - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints - - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache - - - EXPECT=79ebc686ac31153de17e6b8b268c3ac2 - - HASH=$(${ENGINE} +timestamps 1 +exec serverbench.cfg - | tee /dev/stderr - | sed -e 's,^\[[^]]*\] ,,' - | grep '^:' - | grep -v '^:gamestart:' - | grep -v '^:anticheat:' - | md5sum | awk '{ print $1 }') - - echo 'expected:' $EXPECT - - echo ' actual:' $HASH - - test "$HASH" == "$EXPECT" - - exit $? - - - # NOTE: The generated docs are incomplete - they don't contain code behind SVQC CSQC MENUQC GAMEQC ifdefs. - # With them added to PREDEFINED, it would take over half an hour to generate the docs and even then - # they might not be complete. Doxygen doesn't handle #elif and might not understand some QC definitions. - #doxygen: # rename to 'pages' when gitlab.com allows pages to exceed 100MiB - # before_script: - # - ln -s $PWD data/xonotic-data.pk3dir # is this needed? - # - apt-get update - # - apt-get -y install doxygen graphviz - # stage: deploy - # script: - # - cd qcsrc && doxygen - # - mv html ../public - # - mkdir -p ~/.ssh - # - for i in {0..0}; do eval $(printf "echo \$id_rsa_%02d\n" $i) >> ~/.ssh/id_rsa_base64; done - # - base64 --decode ~/.ssh/id_rsa_base64 > ~/.ssh/id_rsa - # - chmod 600 ~/.ssh/id_rsa - # - echo -e "Host *\n\tStrictHostKeyChecking no\n\tLogLevel ERROR\n" >> ~/.ssh/config - # - git config --global user.name "Gitlab CI" - # - git config --global user.email "<>" - # - git clone --single-branch --depth 1 ${DEPLOY_HOST}:${DEPLOY_REPO} ~/deploy_ - # - mkdir ~/deploy && mv ~/deploy_/.git ~/deploy && rm -r ~/deploy_ - # - cp -r ../public/* ~/deploy - # - cd ~/deploy && git add -A . && git commit -m "Deploy ${CI_BUILD_REF}" && git push origin gh-pages - # artifacts: - # paths: - # - public - # only: - # - master + workflow: + rules: + - if: $CI_COMMIT_MESSAGE =~ /Transifex autosync/ + when: never + - when: always + + before_script: + - ln -s $PWD data/xonotic-data.pk3dir + + - export MAKEFLAGS=-j$(nproc); echo MAKEFLAGS=$MAKEFLAGS + # FIXME: -march=native -mtune=native _changes the hash_, why?!? + # - export CC="gcc -pipe -march=native -mtune=native" + - export CC="gcc -pipe" + + - > + if wget -nv https://beta.xonotic.org/pipeline-bin/gmqcc ; then + export QCC="$PWD/gmqcc" + chmod +x "$QCC" + else + git clone --depth=1 --branch=main https://gitlab.com/xonotic/gmqcc.git gmqcc + make -C gmqcc || exit 1 + export QCC="$PWD/gmqcc/gmqcc" + fi + + # Makefile: don't complain about lack of tags (fetching them is slow) + - export QCCFLAGS_WATERMARK=gitlab_pipeline + # Makefile: don't compress anything or complain about lack of zip program + - export ZIP=/bin/true + + test_compilation_units: + rules: + - changes: + - qcsrc/**/* + stage: test + script: + - make test + + test_sv_game: + stage: test + script: + - > + if wget -nv https://beta.xonotic.org/pipeline-bin/xonotic-linux64-dedicated ; then + export ENGINE="$PWD/xonotic-linux64-dedicated" + chmod +x "$ENGINE" + else + git clone --depth=1 --branch=div0-stable https://gitlab.com/xonotic/darkplaces.git darkplaces + make -C darkplaces sv-release || exit 1 + export ENGINE="$PWD/darkplaces/darkplaces-dedicated -xonotic" + fi + - export ENGINE="$ENGINE -noconfig -nohome" + + - make qc || exit 1 + + - mkdir -p data/maps + - wget -nv -O data/maps/_init.bsp https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/_init/_init.bsp + + - while read LINE; do + echo $LINE; + [ "$LINE" = "All tests OK" ] && PASS=1; + done < <(${ENGINE} +developer 1 +map _init +sv_cmd runtest +wait +quit) + - test "$PASS" = "1" || { echo 'sv_cmd runtest failed!'; exit 1; } + + - ${ENGINE} +map _init +sv_cmd dumpnotifs +wait +quit + - diff notifications.cfg data/data/notifications_dump.cfg || + { echo 'Please update notifications.cfg using `dumpnotifs`!'; exit 1; } + + # - wget -nv -O data/stormkeep.pk3 http://beta.xonotic.org/autobuild-bsp/latest/stormkeep.pk3 + # ^^ INCORRECT: /latest/stormkeep.pk3 is the most recently built, not necessarily the one built from master! + # we can't get the one from master directly as there's no /stable/stormkeep.pk3 or /master/stormkeep.pk3 + # and we can't run misc/tools/xonotic-map-compiler-autobuild as it uses commit hashes from xonotic-maps.pk3dir to generate filenames + # but the autobuild server can run it and provide us the resulting pk3: + - wget -nv -O data/stormkeep.pk3 https://beta.xonotic.org/pipeline-bin/stormkeep.pk3 + # see also: misc/infrastructure/xonotic-release-build.cron + - wget -nv -O data/maps/stormkeep.mapinfo https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.mapinfo + - wget -nv -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints + - wget -nv -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache + - - EXPECT=57325fe74835910e451ba42d31de8f34 ++ - EXPECT=03c239e3257ebfd9d34bd11b8af9cbe0 + - HASH=$(${ENGINE} +exec serverbench.cfg + | tee /dev/stderr + | grep '^:' + | grep -v '^:gamestart:' + | grep -v '^:anticheat:' + | md5sum | awk '{ print $1 }') + - echo 'expected:' $EXPECT + - echo ' actual:' $HASH + - test "$HASH" == "$EXPECT" + - exit $? + + + # NOTE: The generated docs are incomplete - they don't contain code behind SVQC CSQC MENUQC GAMEQC ifdefs. + # With them added to PREDEFINED, it would take over half an hour to generate the docs and even then + # they might not be complete. Doxygen doesn't handle #elif and might not understand some QC definitions. + #doxygen: # rename to 'pages' when gitlab.com allows pages to exceed 100MiB + # before_script: + # - ln -s $PWD data/xonotic-data.pk3dir # is this needed? + # - apt-get update + # - apt-get -y install doxygen graphviz + # stage: deploy + # script: + # - cd qcsrc && doxygen + # - mv html ../public + # - mkdir -p ~/.ssh + # - for i in {0..0}; do eval $(printf "echo \$id_rsa_%02d\n" $i) >> ~/.ssh/id_rsa_base64; done + # - base64 --decode ~/.ssh/id_rsa_base64 > ~/.ssh/id_rsa + # - chmod 600 ~/.ssh/id_rsa + # - echo -e "Host *\n\tStrictHostKeyChecking no\n\tLogLevel ERROR\n" >> ~/.ssh/config + # - git config --global user.name "Gitlab CI" + # - git config --global user.email "<>" + # - git clone --single-branch --depth 1 ${DEPLOY_HOST}:${DEPLOY_REPO} ~/deploy_ + # - mkdir ~/deploy && mv ~/deploy_/.git ~/deploy && rm -r ~/deploy_ + # - cp -r ../public/* ~/deploy + # - cd ~/deploy && git add -A . && git commit -m "Deploy ${CI_BUILD_REF}" && git push origin gh-pages + # artifacts: + # paths: + # - public + # only: + # - master diff --cc balance-mario.cfg index 325db8e58,8b9f37ac1..3c1c0a9e4 --- a/balance-mario.cfg +++ b/balance-mario.cfg @@@ -49,14 -49,22 +49,30 @@@ 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_mayhem_start_health 200 + set g_mayhem_start_armor 200 + set g_mayhem_start_ammo_shells 60 + set g_mayhem_start_ammo_nails 320 + set g_mayhem_start_ammo_rockets 160 + set g_mayhem_start_ammo_cells 180 + set g_mayhem_start_ammo_plasma 180 + set g_mayhem_start_ammo_fuel 0 + set g_tmayhem_start_health 200 + set g_tmayhem_start_armor 200 + set g_tmayhem_start_ammo_shells 60 + set g_tmayhem_start_ammo_nails 320 + set g_tmayhem_start_ammo_rockets 160 + set g_tmayhem_start_ammo_cells 180 + set g_tmayhem_start_ammo_plasma 180 + set g_tmayhem_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 diff --cc balance-nexuiz25.cfg index d34e8e309,0b39de222..33a175b00 --- a/balance-nexuiz25.cfg +++ b/balance-nexuiz25.cfg @@@ -49,14 -49,22 +49,30 @@@ set g_lms_start_ammo_rockets 5 set g_lms_start_ammo_cells 50 set g_lms_start_ammo_plasma 50 set g_lms_start_ammo_fuel 0 + set g_mayhem_start_health 250 + set g_mayhem_start_armor 100 + set g_mayhem_start_ammo_shells 50 + set g_mayhem_start_ammo_nails 150 + set g_mayhem_start_ammo_rockets 50 + set g_mayhem_start_ammo_cells 50 + set g_mayhem_start_ammo_plasma 50 + set g_mayhem_start_ammo_fuel 0 + set g_tmayhem_start_health 250 + set g_tmayhem_start_armor 100 + set g_tmayhem_start_ammo_shells 50 + set g_tmayhem_start_ammo_nails 150 + set g_tmayhem_start_ammo_rockets 50 + set g_tmayhem_start_ammo_cells 50 + set g_tmayhem_start_ammo_plasma 50 + set g_tmayhem_start_ammo_fuel 0 +set g_br_start_health 150 +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 15 diff --cc balance-overkill.cfg index 752a89825,3944d7d3b..b8dfbe178 --- a/balance-overkill.cfg +++ b/balance-overkill.cfg @@@ -49,14 -49,22 +49,30 @@@ 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_mayhem_start_health 200 + set g_mayhem_start_armor 100 + set g_mayhem_start_ammo_shells 60 + set g_mayhem_start_ammo_nails 320 + set g_mayhem_start_ammo_rockets 160 + set g_mayhem_start_ammo_cells 180 + set g_mayhem_start_ammo_plasma 180 + set g_mayhem_start_ammo_fuel 0 + set g_tmayhem_start_health 200 + set g_tmayhem_start_armor 100 + set g_tmayhem_start_ammo_shells 60 + set g_tmayhem_start_ammo_nails 320 + set g_tmayhem_start_ammo_rockets 160 + set g_tmayhem_start_ammo_cells 180 + set g_tmayhem_start_ammo_plasma 180 + set g_tmayhem_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 diff --cc balance-samual.cfg index 44b110586,5686c58d6..bf5d1ff0d --- a/balance-samual.cfg +++ b/balance-samual.cfg @@@ -49,14 -49,22 +49,30 @@@ 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_mayhem_start_health 200 + set g_mayhem_start_armor 200 + set g_mayhem_start_ammo_shells 60 + set g_mayhem_start_ammo_nails 320 + set g_mayhem_start_ammo_rockets 160 + set g_mayhem_start_ammo_cells 180 + set g_mayhem_start_ammo_plasma 180 + set g_mayhem_start_ammo_fuel 0 + set g_tmayhem_start_health 200 + set g_tmayhem_start_armor 200 + set g_tmayhem_start_ammo_shells 60 + set g_tmayhem_start_ammo_nails 320 + set g_tmayhem_start_ammo_rockets 160 + set g_tmayhem_start_ammo_cells 180 + set g_tmayhem_start_ammo_plasma 180 + set g_tmayhem_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 diff --cc balance-xdf.cfg index 895cca9b3,d8d4df60d..085842109 --- a/balance-xdf.cfg +++ b/balance-xdf.cfg @@@ -49,14 -49,22 +49,30 @@@ 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_mayhem_start_health 200 + set g_mayhem_start_armor 200 + set g_mayhem_start_ammo_shells 60 + set g_mayhem_start_ammo_nails 320 + set g_mayhem_start_ammo_rockets 160 + set g_mayhem_start_ammo_cells 180 + set g_mayhem_start_ammo_plasma 180 + set g_mayhem_start_ammo_fuel 0 + set g_tmayhem_start_health 200 + set g_tmayhem_start_armor 200 + set g_tmayhem_start_ammo_shells 60 + set g_tmayhem_start_ammo_nails 320 + set g_tmayhem_start_ammo_rockets 160 + set g_tmayhem_start_ammo_cells 180 + set g_tmayhem_start_ammo_plasma 180 + set g_tmayhem_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 diff --cc balance-xonotic.cfg index ba3bf0b5a,2cf8c9eb4..a7b631d61 --- a/balance-xonotic.cfg +++ b/balance-xonotic.cfg @@@ -49,14 -49,22 +49,30 @@@ 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_mayhem_start_health 200 + set g_mayhem_start_armor 200 + set g_mayhem_start_ammo_shells 60 + set g_mayhem_start_ammo_nails 320 + set g_mayhem_start_ammo_rockets 160 + set g_mayhem_start_ammo_cells 180 + set g_mayhem_start_ammo_plasma 180 + set g_mayhem_start_ammo_fuel 0 + set g_tmayhem_start_health 200 + set g_tmayhem_start_armor 200 + set g_tmayhem_start_ammo_shells 60 + set g_tmayhem_start_ammo_nails 320 + set g_tmayhem_start_ammo_rockets 160 + set g_tmayhem_start_ammo_cells 180 + set g_tmayhem_start_ammo_plasma 180 + set g_tmayhem_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 diff --cc balance-xpm.cfg index ba72b43db,87b781a55..d56582ec5 --- a/balance-xpm.cfg +++ b/balance-xpm.cfg @@@ -49,14 -49,22 +49,30 @@@ 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_mayhem_start_health 200 + set g_mayhem_start_armor 200 + set g_mayhem_start_ammo_shells 60 + set g_mayhem_start_ammo_nails 320 + set g_mayhem_start_ammo_rockets 160 + set g_mayhem_start_ammo_cells 180 + set g_mayhem_start_ammo_plasma 180 + set g_mayhem_start_ammo_fuel 0 + set g_tmayhem_start_health 200 + set g_tmayhem_start_armor 200 + set g_tmayhem_start_ammo_shells 60 + set g_tmayhem_start_ammo_nails 320 + set g_tmayhem_start_ammo_rockets 160 + set g_tmayhem_start_ammo_cells 180 + set g_tmayhem_start_ammo_plasma 180 + set g_tmayhem_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 diff --cc gamemodes-client.cfg index ad35029ec,c63bba5a7..252d97a9c --- a/gamemodes-client.cfg +++ b/gamemodes-client.cfg @@@ -32,7 -32,10 +32,11 @@@ alias cl_hook_gamestart_k alias cl_hook_gamestart_ft alias cl_hook_gamestart_inv alias cl_hook_gamestart_duel + alias cl_hook_gamestart_mayhem + alias cl_hook_gamestart_tmayhem + alias cl_hook_gamestart_tka + alias cl_hook_gamestart_surv +alias cl_hook_gamestart_br alias cl_hook_gameend alias cl_hook_shutdown alias cl_hook_activeweapon diff --cc gamemodes-server.cfg index eb4d0a869,01d297e08..4e5481fc2 --- a/gamemodes-server.cfg +++ b/gamemodes-server.cfg @@@ -29,7 -29,10 +29,11 @@@ alias sv_hook_gamestart_k alias sv_hook_gamestart_ft alias sv_hook_gamestart_inv alias sv_hook_gamestart_duel + alias sv_hook_gamestart_mayhem + alias sv_hook_gamestart_tmayhem + alias sv_hook_gamestart_tka + alias sv_hook_gamestart_surv +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 -62,10 +63,11 @@@ 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_mayhem + alias sv_vote_gametype_hook_tmayhem + alias sv_vote_gametype_hook_tka + alias sv_vote_gametype_hook_surv +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 -216,34 +218,41 @@@ 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_mayhem_respawn_delay_small 0 + set g_mayhem_respawn_delay_small_count 0 + set g_mayhem_respawn_delay_large 0 + set g_mayhem_respawn_delay_large_count 0 + set g_mayhem_respawn_delay_max 0 + set g_mayhem_respawn_waves 0 + set g_mayhem_weapon_stay 0 + set g_tmayhem_respawn_delay_small 0 + set g_tmayhem_respawn_delay_small_count 0 + set g_tmayhem_respawn_delay_large 0 + set g_tmayhem_respawn_delay_large_count 0 + set g_tmayhem_respawn_delay_max 0 + set g_tmayhem_respawn_waves 0 + set g_tmayhem_weapon_stay 0 + set g_tka_respawn_delay_small 0 + set g_tka_respawn_delay_small_count 0 + set g_tka_respawn_delay_large 0 + set g_tka_respawn_delay_large_count 0 + set g_tka_respawn_delay_max 0 + set g_tka_respawn_waves 0 + set g_tka_weapon_stay 0 + set g_surv_respawn_delay_small 0 + set g_surv_respawn_delay_small_count 0 + set g_surv_respawn_delay_large 0 + set g_surv_respawn_delay_large_count 0 + set g_surv_respawn_delay_max 0 + set g_surv_respawn_waves 0 + set g_surv_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 // ========= @@@ -570,46 -613,87 +622,131 @@@ set g_duel 0 "Duel: frag the opponent m 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" - // ====== + // ============================== + // free for all and team mayhem + // ============================== + set g_mayhem 0 "Mayhem: Compete for the most damage dealt and kills in this chaotic mayhem!" + set g_tmayhem 0 "Team Mayhem: Compete with your team for the most damage dealt and kills in this chaotic mayhem!" + + set g_mayhem_scoring_upscaler 20 "upscale one frag's worth to be this amount of in score" + set g_tmayhem_scoring_upscaler 20 "upscale one frag's worth to be this amount of in score" + set g_mayhem_scoring_kill_weight 0.25 "how much is a kill worth in frags" + set g_tmayhem_scoring_kill_weight 0.25 "how much is a kill worth in frags" + set g_mayhem_scoring_damage_weight 0.75 "how much is damage equal to player's spawning health+armor worth in frags" + set g_tmayhem_scoring_damage_weight 0.75 "how much is damage equal to player's spawning health+armor worth in frags" + set g_mayhem_scoring_disable_selfdamage2score 0 "disable reducing score with self damage at the cost of full penalty for suicides regardless of how much health was lost suiciding" + set g_tmayhem_scoring_disable_selfdamage2score 0 "disable reducing score with self damage at the cost of full penalty for suicides regardless of how much health was lost suiciding" + + set g_mayhem_point_limit -1 "Mayhem score limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" + set g_mayhem_point_leadlimit -1 "Mayhem score lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" + set g_tmayhem_point_limit -1 "Team Mayhem score limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" + set g_tmayhem_point_leadlimit -1 "Team Mayhem score lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" + + set g_mayhem_weaponarena "most_available" "starting weapons - takes the same options as g_weaponarena" + set g_tmayhem_weaponarena "most_available" "starting weapons - takes the same options as g_weaponarena" + + set g_mayhem_powerups 1 "Allow powerups in mayhem. Only checked if g_powerups is -1 therefore this will be overridden by g_powerups 1 or 0" + set g_tmayhem_powerups 1 "Allow powerups in team mayhem. Only checked if g_powerups is -1 therefore this will be overridden by g_powerups 1 or 0" + set g_mayhem_pickup_items 0 "spawn pickup items in mayhem" + set g_tmayhem_pickup_items 0 "spawn pickup items in team mayhem" + set g_mayhem_pickup_items_remove_weapons_and_ammo 1 "when pickup items are enabled in mayhem still remove weapons and ammo pickups" + set g_tmayhem_pickup_items_remove_weapons_and_ammo 1 "when pickup items are enabled in team mayhem still remove weapons and ammo pickups" + + set g_mayhem_selfdamage 0 "0 = disable selfdamage in mayhem, 1 = enable selfdamage in mayhem" + set g_tmayhem_selfdamage 0 "0 = disable selfdamage in tmayhem, 1 = enable selfdamage in tmayhem" + + set g_mayhem_regenerate 0 "health and/or armor regeneration, according to g_balance_health_regen and g_balance_armor_regen" + set g_tmayhem_regenerate 0 "health and/or armor regeneration, according to g_balance_health_regen and g_balance_armor_regen" + set g_mayhem_rot 0 "health and/or armor rotting, according to g_balance_health_rot and g_balance_armor_rot" + set g_tmayhem_rot 0 "health and/or armor rotting, according to g_balance_health_rot and g_balance_armor_rot" + + set g_tmayhem_teams 2 "how many teams are in team mayhem (set by mapinfo)" + set g_tmayhem_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any" + set g_tmayhem_teams_override 0 "how many teams are in team mayhem" + + // =============== + // team keepaway + // =============== + set g_tka 0 "another game mode which focuses around a ball" + set g_tka_on_ka_maps 1 "when this is set, all KA maps automatically support TKA" + set g_tka_on_tdm_maps 0 "when this is set, all TDM maps automatically support TKA" + set g_tka_teams 2 "how many teams are in team keepaway (set by mapinfo)" + set g_tka_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any" + set g_tka_teams_override 0 "how many teams are in team keepaway" + set g_tka_point_limit -1 "TKA point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" + set g_tka_point_leadlimit -1 "TKA point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" + set g_tka_score_team 1 "allow points to be awarded to teammates for any kill when the ball is in your team's possession" + set g_tka_score_bckill 1 "points for killing the ball barrier (Ball Carrier Kill)" + set g_tka_score_killac 1 "points for kills while holding the ball (Kill As Carrier)" + set g_tka_score_timeinterval 1 "amount of time it takes between intervals for timepoints to be added to the score" + set g_tka_score_timepoints 0 "points to add to score per timeinterval, 0 for no points" + set g_tka_ballcarrier_effects 8 "Add together the numbers you want: EF_ADDITIVE (32) / EF_NODEPTHTEST (8192) / EF_DIMLIGHT (8)" + set g_tka_ballcarrier_highspeed 1 "speed multiplier done to the person holding the ball (recommended when used with some mutators)" + set g_tka_ballcarrier_damage 1 "damage multiplier while holding the ball" + set g_tka_ballcarrier_force 1 "force multiplier while holding the ball" + set g_tka_ballcarrier_selfdamage 1 "self damage multiplier while holding the ball" + set g_tka_ballcarrier_selfforce 1 "self force multiplier while holding the ball" + set g_tka_noncarrier_warn 1 "warn players when they kill without holding the ball" + set g_tka_noncarrier_damage 1 "damage done to other players if both you and they don't have the ball" + set g_tka_noncarrier_force 1 "force done to other players if both you and they don't have the ball" + set g_tka_noncarrier_selfdamage 1 "self damage if you don't have the ball" + set g_tka_noncarrier_selfforce 1 "self force if you don't have the ball" + set g_tkaball_effects 8 "Add together the numbers you want: EF_ADDITIVE (32) / EF_NODEPTHTEST (8192) / EF_DIMLIGHT (8)" + set g_tkaball_trail_color 254 "particle trail color from player/ball" + set g_tkaball_damageforcescale 2 "Scale of force which is applied to the ball by weapons/explosions/etc" + set g_tkaball_respawntime 10 "if no one picks up the ball, how long to wait until the ball respawns" + + // ========== + // survival + // ========== + set g_survival 0 "Survival: identify and eliminate all the hunters before all your allies are gone" + set g_survival_not_lms_maps 0 "when this is set, LMS maps will NOT be listed in survival" + set g_survival_hunter_count 0.25 "number of players who will become hunters, set between 0 and 0.9 to use a multiplier of the current players, or 1 and above to specify an exact number of players" + set g_survival_punish_teamkill 1 "kill the player when they kill an ally" + set g_survival_reward_survival 1 "give a point to all surviving players if the round timelimit is reached, in addition to the points given for kills" + set g_survival_warmup 10 "how long the players will have time to run around the map before the round starts" + set g_survival_round_timelimit 120 "round time limit in seconds" ++ ++// =============== +// 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, use 0 or a negative number to determine squad size based on the amount of players up to the specified number or unlimited if 0" +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" +set g_br_supply_interval 30 "seconds between each supply drop or 0 to disable" +set g_br_vehicle_interval 0 "seconds between each vehicle drop or 0 to disable" diff --cc notifications.cfg index 8093752a4,22a4e70a3..b8b40896b --- a/notifications.cfg +++ b/notifications.cfg @@@ -93,15 -93,18 +93,21 @@@ seta notification_ANNCE_VOTE_ACCEPT "2 seta notification_ANNCE_VOTE_CALL "2" "0 = disabled, 1 = enabled if gentle mode is off, 2 = always enabled" seta notification_ANNCE_VOTE_FAIL "2" "0 = disabled, 1 = enabled if gentle mode is off, 2 = always enabled" - // MSG_INFO notifications (count = 339): + // MSG_INFO notifications: +seta notification_INFO_BR_DOWN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" +seta notification_INFO_BR_DOWN_SELF "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" +seta notification_INFO_BR_REVIVED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_CA_JOIN_LATE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_CA_LEAVE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" + seta notification_INFO_CHAT_DISABLED "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_CHAT_NOSPECTATORS "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" + seta notification_INFO_CHAT_PRIVATE_DISABLED "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" + seta notification_INFO_CHAT_SPECTATOR_DISABLED "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" + seta notification_INFO_CHAT_TEAM_DISABLED "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_COINTOSS "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_CONNECTING "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" + seta notification_INFO_COUNTDOWN_RESTART "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" + seta notification_INFO_COUNTDOWN_STOP "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_CTF_CAPTURE_BROKEN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_CTF_CAPTURE_NEUTRAL "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_CTF_CAPTURE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" diff --cc qcsrc/client/hud/panel/scoreboard.qc index 4880f77e4,35227ffab..afcc258f8 --- a/qcsrc/client/hud/panel/scoreboard.qc +++ b/qcsrc/client/hud/panel/scoreboard.qc @@@ -398,24 -740,23 +741,25 @@@ 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,br/deaths +ft,tdm/deaths" \ + " -teams,rc,cts,surv,inv,lms/kills +ft,tdm,tmayhem/kills ?+rc,inv/kills" \ -" -teams,surv,lms/deaths +ft,tdm,tmayhem/deaths" \ ++" -teams,surv,lms,br/deaths +ft,tdm,tmayhem/deaths" \ " +tdm/sum" \ - " -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" \ -" -teams,lms,rc,cts,surv,inv,ka/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \ -" -cts,dm,tdm,surv,ka,ft,mayhem,tmayhem/frags" /* tdm already has this in "score" */ \ ++" -teams,lms,rc,cts,surv,inv,ka,br/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \ ++" -cts,dm,tdm,surv,ka,ft,mayhem,tmayhem,br/frags" /* tdm already has this in "score" */ \ + " +tdm,ft,dom,ons,as,tmayhem/teamkills"\ + " -rc,cts,surv,nb/dmg -rc,cts,surv,nb/dmgtaken" \ + " +surv/survivals +surv/hunts" \ " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \ " +lms/lives +lms/rank" \ " +kh/kckills +kh/losses +kh/caps" \ " ?+rc/laps ?+rc/time +rc,cts/fastest" \ " +as/objectives +nb/faults +nb/goals" \ - " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \ + " +ka,tka/pickups +ka,tka/bckills +ka,tka/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) { @@@ -636,9 -977,15 +980,15 @@@ 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); + int f; + // NOTE: always adding 1024 allows saving the colormap 0 as a value != 0 + if (playerslots[pl.sv_entnum].colormap >= 1024) + f = playerslots[pl.sv_entnum].colormap - 1024; // override server-side player colors + else + f = entcs_GetClientColors(pl.sv_entnum); + { sbt_field_icon0 = "gfx/scoreboard/playercolor_base"; sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt"; @@@ -750,25 -1139,14 +1142,30 @@@ string Scoreboard_GetField(entity pl, P return ftos(fps); } + case SP_ROUNDS_PL: + return ftos(pl.(scores(field))); + case SP_DMG: case SP_DMGTAKEN: + if (rounds_played) + return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played)); return sprintf("%.1f k", pl.(scores(field)) / 1000); + case SP_BR_SQUAD: + tmp = pl.(scores(field)); + if(tmp == 0) + return string_null; + + if(STAT(SQUADCOLORS)) + { + int f = entcs_GetClientColors(pl.sv_entnum); + sbt_field_icon0 = "gfx/scoreboard/playercolor_base"; + sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt"; + sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0); + sbt_field_icon2 = "gfx/scoreboard/playercolor_pants"; + sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1); + } + return ftos(tmp); + default: case SP_SCORE: tmp = pl.(scores(field)); f = scores_flags(field); diff --cc qcsrc/common/gamemodes/gamemode/br/sv_br.qc index de2f82e4c,000000000..0a0479bee mode 100644,000000..100644 --- a/qcsrc/common/gamemodes/gamemode/br/sv_br.qc +++ b/qcsrc/common/gamemodes/gamemode/br/sv_br.qc @@@ -1,1457 -1,0 +1,1457 @@@ +// battle royale +// author: Juhu + +#include "sv_br.qh" +#include +#include +#include + +#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); +bool br_PositionDropMember(entity this, entity leader, int position, float disconnect_range); +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; +const float drop_distance_disconnect = 32; +const float drop_speed_crash = 0.9; +float br_event_supply_time; +float br_event_vehicle_time; +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; +.float br_drop_position; +.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]; + +// alpha restoring for drop +.float br_alpha_old; + +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.alpha = player.br_alpha_old; + 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; + + bool other_side = false; + int drop_position = 1; + + if(random() < 0.5) + drop_position *= -1; + + FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && (it != player) && SAME_SQUAD(it, player) && (STAT(DROP, it) == DROP_TRANSPORT), { + it.alpha = it.br_alpha_old; + 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); + + it.br_drop_position = drop_position; + if(other_side) + drop_position += copysign(1, drop_position); + drop_position *= -1; + other_side = !other_side; + + br_PositionDropMember(it, player, it.br_drop_position, -1); + + 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(!IS_PLAYER(player)) + return false; + + 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){ + float maxairspeed = PHYS_MAXAIRSPEED(player) * max(maxspeed_mod, 1); + float mindropspeed = maxairspeed * max(autocvar_g_br_drop_speed_min, 0); + float dropspeed = vlen(vec2(player.velocity) + eZ * min(player.velocity.z, 0)); + if(player.velocity.z > 0) + dropspeed -= vlen(player.velocity) - dropspeed; + if(!IS_ONGROUND(player) && (player.waterlevel < WATERLEVEL_SWIMMING) && (dropspeed >= (mindropspeed * drop_speed_crash)) && ((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 maxdropspeed = maxairspeed * max(autocvar_g_br_drop_speed_max, 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_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; + + // except make turning backwards easier by limiting the maximum turning angle to 90 degrees + vector wish_angles = vectoangles(vec2(wishvel)); + vector vel_angles = vectoangles(vec2(player.velocity)); + + float diff_angle = wish_angles.y - vel_angles.y; + if(diff_angle > 180) + diff_angle -= 360; + if(diff_angle < -180) + diff_angle += 360; + + wish_angles.y = (vel_angles.y + bound(-90, diff_angle, 90) + 360) % 360; + makevectors(wish_angles); + + wishvel = normalize(v_forward) * 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 *= 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), { + if(!br_PositionDropMember(it, player, it.br_drop_position, drop_distance_disconnect)) + { + it.br_drop_detached = 2; + Kill_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CPID_BR_DROP); + continue; + } + + 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 + { + if(!br_PositionDropMember(player, player.br_squad.br_squad_drop_leader, player.br_drop_position, drop_distance_disconnect)) + { + player.br_drop_detached = 2; + Kill_Notification(NOTIF_ONE_ONLY, player, MSG_CENTER, CPID_BR_DROP); + } + + 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.alpha = frag_target.br_alpha_old; + 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) +{ + entity player = M_ARGV(7, entity); + if(!IS_PLAYER(player)) + return false; + + 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) +{ + entity player = M_ARGV(7, entity); + + if(!IS_PLAYER(player) || !STAT(BLEEDING, player)) + return false; + + entity res_type = M_ARGV(8, entity); + + switch(res_type) + { + case RES_HEALTH: + M_ARGV(9, float) *= max(autocvar_g_br_bleeding_health, 0); + break; + case RES_ARMOR: + 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, bot) != DROP_LANDED) || (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) || (IN_SQUAD(wp.owner) && 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; +} + +MUTATOR_HOOKFUNCTION(br, PlayerSpawn) +{ + if(!br_started && !warmup_stage && (time > game_starttime) && br_CheckPlayers()) + br_Start(); +} + +MUTATOR_HOOKFUNCTION(br, SV_StartFrame) +{ + if(!br_started) + return false; + + if(autocvar_g_br_supply_interval > 0 && time - br_event_supply_time >= autocvar_g_br_supply_interval) + { + br_event_supply_time = time; + spawn_supply(); + } + + if(autocvar_g_br_vehicle_interval > 0 && time - br_event_vehicle_time >= autocvar_g_br_vehicle_interval) + { + br_event_vehicle_time = time; + spawn_vehicle(); + } + + if(ring) + { + float current_radius = ring_calculate_current_radius(ring); + + IL_EACH(g_items, it.bot_pickup, { + if(vdist(it.origin - ring.origin, >, current_radius)) + it.bot_pickup = false; + }); + } +} + +void(entity this) havocbot_role_br_reviving; +void(entity this) havocbot_role_br_generic; + +bool squad_needs_revive(entity this) +{ + for(entity member = this.br_squad.br_squad_first; member; member = member.br_squad_next) + { + if(IS_DEAD(member) || !IS_PLAYER(member)) + continue; + + if(STAT(BLEEDING, member)) + return true; + } + + return false; +} + +bool br_bot_ignore_in_ring(entity this) +{ + if(!ring) + return false; + + if(vlen(this.origin - ring.origin) > ring_calculate_current_radius(ring)) + return true; + + return false; +} + +void havocbot_goalrating_br_findplayers(entity this, float ratingscale) +{ + if(!IN_SQUAD(this)) + return; + + for(entity member = this.br_squad.br_squad_first; member; member = member.br_squad_next) + { + if(IS_DEAD(member) || !IS_PLAYER(member) || (member == this)) + continue; + + // either wants to be revived by another player or wants to revive another player + if(STAT(BLEEDING, member) != STAT(BLEEDING, this)) + navigation_routerating(this, member, ratingscale, 100000); + } +} + +void havocbot_role_br_reviving(entity this) +{ + if(IS_DEAD(this)) + return; + + if(!squad_needs_revive(this)) + { + LOG_TRACE("changing role to generic"); + this.havocbot_role = havocbot_role_br_generic; + navigation_goalrating_timeout_force(this); + return; + } + + navigation_goalrating_start(this); + havocbot_goalrating_br_findplayers(this, 20000); + navigation_goalrating_end(this); +} + +void havocbot_role_br_generic(entity this) +{ + if(IS_DEAD(this)) + return; + + if(squad_needs_revive(this)) + { + LOG_TRACE("changing role to reviving"); + this.havocbot_role = havocbot_role_br_reviving; + return; + } + + havocbot_role_generic(this); +} + +MUTATOR_HOOKFUNCTION(br, HavocBot_ChooseRole) +{ + entity bot = M_ARGV(0, entity); + + if(!IS_DEAD(bot)) + bot.havocbot_role = havocbot_role_br_generic; + + return true; +} + +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; +} + +bool br_PositionDropMember(entity this, entity leader, int position, float disconnect_range) { + float pl_mins = min(leader.mins.x, leader.mins.y); + float pl_maxs = max(leader.maxs.x, leader.maxs.y); + + vector member_offset; + member_offset.x = cos((leader.angles.y + 90) * DEG2RAD); + member_offset.y = sin((leader.angles.y + 90) * DEG2RAD); + member_offset.z = 0; + member_offset *= pl_maxs - pl_mins + 32; // I hope individual players never get different mins/maxs + member_offset *= position; + + vector member_destination = leader.origin + member_offset; + + tracebox(this.origin, this.mins, this.maxs, member_destination, MOVE_NORMAL, this); + + if((trace_fraction < 1) && (disconnect_range >= 0)) + { + if(vlen(member_destination - trace_endpos) > disconnect_range) + return false; + } + + setorigin(this, trace_endpos); + return true; +} + +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; break; }); + + for(entity member = winner_squad.br_squad_first; member; member = member.br_squad_next) + { + GameRules_scoring_add(member, BR_RANK, 1); + member.winning = true; + } + + 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); ++ reset_map(false); + + ring = ring_initialize(); + dropship = dropship_initialize(); + + if(!ring || !dropship) + { + if(!ring) + LOG_WARN("Failed to determine ring starting point"); + if(!dropship) + LOG_WARN("Failed to determine dropship route"); + + LOG_WARN("Prerequisites not met. Cannot start battle royale, aborting..."); + + if(ring) + { + delete(ring); + ring = NULL; + } + + if(dropship) + { + delete(dropship); + dropship = NULL; + } + + NextLevel(); + return; + } + + br_started = true; + 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; + }); + + if(autocvar_g_br_squad_size >= 1) + max_squad_size = min(autocvar_g_br_squad_size, ceil(num_players / 2)); + else if(autocvar_g_br_squad_size <= -1) + max_squad_size = min(-autocvar_g_br_squad_size, floor(num_players / 2)); + else + max_squad_size = floor(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_RANDOM(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)); + 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.br_alpha_old = it.alpha; + it.alpha = -1; + 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), + { + br_SendSquad(it); + STAT(SQUADCOLORS, it) = squads_colored; + }); + + int squad_colors_taken = 0; + const int squad_colors_num = 6; // there are 6 colors which look distinct enough + const int squad_colors_taken_mask = 2 ** squad_colors_num - 1; + IL_EACH(squads, true, + { + if(squads_colored) + { + float squad_color = 0; + + while(true) + { + squad_color = floor(random() * squad_colors_num); + int squad_color_bit = 1 << squad_color; + + if(!(squad_color_bit & squad_colors_taken)) + { + squad_colors_taken |= squad_color_bit; + + // only select easily distinguishable colors + switch(squad_color) + { + case 0: + squad_color = 0; + break; + case 1: + squad_color = 1; + break; + case 2: + squad_color = 3; + break; + case 3: + squad_color = 5; + break; + case 4: + squad_color = 9; + break; + case 5: + squad_color = 12; + } + break; + } + } + + if(squad_colors_taken == squad_colors_taken_mask) + squad_colors_taken = 0; + + squad_color = 16 * squad_color + squad_color; + + for(entity member = it.br_squad_first; member; member = member.br_squad_next) + { + member.clientcolors = 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); + }); + + br_event_supply_time = br_event_vehicle_time = time; +} + +void br_Initialize() +{ + br_started = false; + squads_colored = autocvar_g_br_squad_colors; + + EliminatedPlayers_Init(br_isEliminated); +} diff --cc qcsrc/common/notifications/all.inc index 2ba2a86ad,57877dacc..4c8926c3f --- a/qcsrc/common/notifications/all.inc +++ b/qcsrc/common/notifications/all.inc @@@ -390,13 -403,8 +405,12 @@@ string multiteam_info_sprintf(string in MULTITEAM_INFO(KEYHUNT_DESTROYED, N_CONSOLE, 1, 0, "s1", "", "", _("^BG%s^BG destroyed the ^TC^TT Key"), "", KEY) MULTITEAM_INFO(KEYHUNT_PICKUP, N_CONSOLE, 1, 0, "s1", "", "", _("^BG%s^BG picked up the ^TC^TT Key"), "", KEY) - 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) @@@ -710,22 -728,9 +735,23 @@@ 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(LMS_VISIBLE_LEADER, N_ENABLE, 0, 0, "", CPID_LMS, "0 0", _("^BGEnemies can now see you on radar!"), "") + MSG_CENTER_NOTIF(LMS_VISIBLE_OTHER, N_ENABLE, 0, 0, "", CPID_LMS, "0 0", _("^BGLeaders can now be seen by enemies on radar!"), "") + 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..."), "") diff --cc qcsrc/common/scores.qh index ada974671,8a01893f1..571e8584e --- a/qcsrc/common/scores.qh +++ b/qcsrc/common/scores.qh @@@ -85,12 -57,40 +57,44 @@@ REGISTER_SP(LMS_LIVES) REGISTER_SP(NEXBALL_GOALS); REGISTER_SP(NEXBALL_FAULTS); - REGISTER_SP(ONS_TAKES); REGISTER_SP(ONS_CAPS); + REGISTER_SP(ONS_TAKES); + + REGISTER_SP(TKA_PICKUPS); + REGISTER_SP(TKA_BCTIME); + REGISTER_SP(TKA_CARRIERKILLS); + + REGISTER_SP(SURV_SURVIVALS); + REGISTER_SP(SURV_HUNTS); + + REGISTER_SP(SCORE); + REGISTER_SP(KILLS); + REGISTER_SP(DEATHS); + REGISTER_SP(TEAMKILLS); + REGISTER_SP(SUICIDES); + REGISTER_SP(DMG); + REGISTER_SP(DMGTAKEN); + + REGISTER_SP(ROUNDS_PL); + + REGISTER_SP(ELO); // not sortable + REGISTER_SP(FPS); // not sortable + + // fields not networked via the score system + REGISTER_SP(END); + + REGISTER_SP(PING); + REGISTER_SP(PL); + REGISTER_SP(NAME); + REGISTER_SP(SEPARATOR); + + REGISTER_SP(KDRATIO); // kills / deaths + REGISTER_SP(SUM); // kills - deaths + REGISTER_SP(FRAGS); // kills - suicides + +REGISTER_SP(BR_RANK); +REGISTER_SP(BR_SQUAD); +REGISTER_SP(BR_REVIVALS); #endif diff --cc qcsrc/common/stats.qh index 889aba13c,96a136cad..f16352ef7 --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@@ -128,21 -128,11 +128,16 @@@ REGISTER_STAT(NADE_BONUS_SCORE, 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) - REGISTER_STAT(ENTRAP_ORB_ALPHA, float) REGISTER_STAT(ITEMSTIME, int, autocvar_sv_itemstime) REGISTER_STAT(KILL_TIME, float) - REGISTER_STAT(VEIL_ORB, float) - REGISTER_STAT(VEIL_ORB_ALPHA, float) + REGISTER_STAT(TKA_BALLSTATUS, int) #ifdef SVQC float autocvar_sv_showfps = 0; diff --cc qcsrc/menu/xonotic/util.qc index 9c9ed148e,ac3c798a9..a28c99a9b --- a/qcsrc/menu/xonotic/util.qc +++ b/qcsrc/menu/xonotic/util.qc @@@ -668,8 -672,8 +672,9 @@@ float updateCompression( GAMETYPE(MAPINFO_TYPE_NEXBALL) \ GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \ GAMETYPE(MAPINFO_TYPE_ASSAULT) \ + GAMETYPE(MAPINFO_TYPE_SURVIVAL) \ /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \ + /* GAMETYPE(MAPINFO_TYPE_BR) */ \ /**/ // hidden gametypes come last so indexing always works correctly diff --cc qcsrc/server/chat.qc index 37cbcdee2,5a72b5977..af62c8c91 --- a/qcsrc/server/chat.qc +++ b/qcsrc/server/chat.qc @@@ -306,7 -336,10 +338,10 @@@ int Say(entity source, int teamsay, ent dedicated_print(msgstr); // send to server console too if(sourcecmsgstr != "") centerprint(source, sourcecmsgstr); - 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), { + if(IS_REAL_CLIENT(source) && ignore_playerinlist(source, it)) // check ignored players from personal chat log (from "ignore" command) + continue; // no sending to this player, thank you very much + sprint(it, msgstr); if(cmsgstr != "") centerprint(it, cmsgstr); @@@ -317,7 -350,10 +352,10 @@@ { sprint(source, sourcemsgstr); dedicated_print(msgstr); // send to server console too - 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), { + if(IS_REAL_CLIENT(source) && ignore_playerinlist(source, it)) // check ignored players from personal chat log (from "ignore" command) + continue; // no sending to this player, thank you very much + sprint(it, msgstr); }); event_log_msg = sprintf(":chat_spec:%d:%s", source.playerid, strreplace("\n", " ", msgin)); diff --cc qcsrc/server/world.qc index 0408f93d2,977f23571..502a886ae --- a/qcsrc/server/world.qc +++ b/qcsrc/server/world.qc @@@ -298,7 -306,12 +306,13 @@@ void cvar_changes_init( BADCVAR("g_tdm"); BADCVAR("g_tdm_on_dm_maps"); BADCVAR("g_tdm_teams"); + BADCVAR("g_tka"); + BADCVAR("g_tka_on_ka_maps"); + BADCVAR("g_tka_on_tdm_maps"); + BADCVAR("g_tka_teams"); + BADCVAR("g_tmayhem"); + BADCVAR("g_tmayhem_teams"); + BADCVAR("g_br"); BADCVAR("g_vip"); BADCVAR("leadlimit"); BADCVAR("nextmap");