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
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"
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"
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
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
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
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
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.
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
// =========
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"
// ====================
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"
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"
// =========
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)"
// ======
//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"
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 ""
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"
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"
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 ""
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"
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"
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 ""
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 ""
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 ""
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 ""
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"
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"
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 ""
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 ""
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 ""
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 ""
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"
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"
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 ""
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"
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"
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 ""
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"
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"
.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;
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));
}
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);
#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>
//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)
// 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)
{
{
sbt_field_icon0 = "gfx/scoreboard/player_ready";
}
- else if(!teamplay)
+ else if(!teamplay && !(ISGAMETYPE(BR) && STAT(SQUADCOLORS)))
{
int f = entcs_GetClientColors(pl.sv_entnum);
{
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);
// print information about respawn status
float respawn_time = STAT(RESPAWN_TIME);
- if(!intermission)
- if(respawn_time)
+ if(!intermission && respawn_time)
{
if(respawn_time < 0)
{
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")
#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)
{
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);
--- /dev/null
- #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);
+}
--- /dev/null
- 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;
+}
--- /dev/null
- 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);
+}
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);
}
}
- 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
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)
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)
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
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")) \
{
string ammoitems = "";
Weapon wep = REGISTRY_GET(Weapons, f1);
+ // TODO: registry handles
switch (wep.ammo_type)
{
case RES_SHELLS: ammoitems = ITEM_Shells.m_name; break;
* 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);
REGISTER_SP(ONS_TAKES);
REGISTER_SP(ONS_CAPS);
+
+REGISTER_SP(BR_RANK);
+REGISTER_SP(BR_SQUAD);
+REGISTER_SP(BR_REVIVALS);
#endif
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")
/** 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)
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)
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)
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(DOM_PPS_YELLOW, float)
REGISTER_STAT(DOM_PPS_PINK, float)
+ // nexball
+ REGISTER_STAT(NB_METERSTART, float)
+
#ifdef SVQC
float autocvar_g_teleport_maxspeed;
#endif
#ifdef SVQC
#include "physics/movetypes/movetypes.qh"
float warmup_limit;
+ float round_limit;
+ int rounds_played;
#endif
#ifdef SVQC
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")
}
}
- 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);
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; }
}
}
- 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 != "")
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)
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)
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)
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)
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));
#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>
#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>
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);
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)
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);
}
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)
}
#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));
}
/**
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
}
/*
=============
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)
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));
}
}
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)
{
accuracy_resend(this);
if(!SpectateUpdate(this))
- PutObserverInServer(this, false);
+ PutObserverInServer(this, false, true);
return true;
}
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); }
}
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)
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;
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))
{
}
}
+ 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;
// 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;
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
{
#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>
#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>
// ======
// 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);
// 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;
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;
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;
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
}
IMPULSE(waypoint_here_follow)
{
- if (!teamplay) return;
+ if (!teamplay && !IN_SQUAD(this)) return;
if (IS_DEAD(this)) return;
if (!MUTATOR_CALLHOOK(HelpMePing, this))
{
#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>
#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;
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
{
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", "");
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);
}
/*
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:");
*/
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);
// 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;
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();
}
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
{
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); });
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);
});
WeaponStats_Shutdown();
MapInfo_Shutdown();
+
+ strfree(sv_termsofservice_url_escaped);
}
else if(world_initialized == 0)
{