-set g_ctf_personalscore_pickup_base 1
-set g_ctf_personalscore_pickup_dropped_early 0
-set g_ctf_personalscore_pickup_dropped_late 1
-set g_ctf_personalscore_capture 20
-set g_ctf_personalscore_kill 5
-set g_ctf_personalpenalty_drop 0
-set g_ctf_personalpenalty_suicidedrop 1
-set g_ctf_personalpenalty_returned 1
-set g_ctf_personalscore_return 5
-set g_ctf_personalscore_return_rogue 3
-set g_ctf_personalscore_return_by_killer 5
-set g_ctf_personalscore_return_rogue_by_killer 5
-// AWIN = 21
-// AFAIL = 0
-// AFAILVOID = 1
-// DWIN = 10
-// ARETRY = -1..0
-// DRETRY = 5
-// ATAKE = 1
+set g_ctf_score_capture 20
+set g_ctf_score_capture_assist 0
+set g_ctf_score_kill 5
+set g_ctf_score_penalty_drop 0
+set g_ctf_score_penalty_suicidedrop 1
+set g_ctf_score_penalty_returned 1
+set g_ctf_score_pickup_base 1
+set g_ctf_score_pickup_dropped_early 0
+set g_ctf_score_pickup_dropped_late 1
+set g_ctf_score_return 5
+++ /dev/null
-set g_ctf_personalscore_pickup_base 1
-set g_ctf_personalscore_pickup_dropped_early 1
-set g_ctf_personalscore_pickup_dropped_late 1
-set g_ctf_personalscore_capture 30
-set g_ctf_personalscore_kill 1
-set g_ctf_personalpenalty_drop 2
-set g_ctf_personalpenalty_suicidedrop 2
-set g_ctf_personalpenalty_returned 0
-set g_ctf_personalscore_return 5
-set g_ctf_personalscore_return_rogue 10
-set g_ctf_personalscore_return_by_killer 6
-set g_ctf_personalscore_return_rogue_by_killer 11
-// AWIN = 31
-// AFAIL = -1
-// AFAILVOID = 1
-// DWIN = 6..7
-// ARETRY = -1
-// DRETRY = 1
-// ATAKE = 1
+++ /dev/null
-set g_ctf_personalscore_pickup_base 0
-set g_ctf_personalscore_pickup_dropped_early 0
-set g_ctf_personalscore_pickup_dropped_late 0
-set g_ctf_personalscore_capture 20
-set g_ctf_personalscore_kill 0
-set g_ctf_personalpenalty_drop 0
-set g_ctf_personalpenalty_suicidedrop 0
-set g_ctf_personalpenalty_returned 0
-set g_ctf_personalscore_return 5
-set g_ctf_personalscore_return_rogue 10
-set g_ctf_personalscore_return_by_killer 5
-set g_ctf_personalscore_return_rogue_by_killer 10
-// AWIN = 20
-// AFAIL = 0
-// AFAILVOID = 0
-// DWIN = 5
-// ARETRY = 0
-// DRETRY = 0
-// ATAKE = 0
-set g_ctf_personalscore_pickup_base 0
-set g_ctf_personalscore_pickup_dropped_early 1
-set g_ctf_personalscore_pickup_dropped_late 1
-set g_ctf_personalscore_capture 25
-set g_ctf_personalscore_kill 3
-set g_ctf_personalpenalty_drop 2
-set g_ctf_personalpenalty_suicidedrop 2
-set g_ctf_personalpenalty_returned 1
-set g_ctf_personalscore_return 2 // lowered so it's better if the killer does the return
-set g_ctf_personalscore_return_rogue 10
-set g_ctf_personalscore_return_by_killer 5
-set g_ctf_personalscore_return_rogue_by_killer 10
-// AWIN = 25
-// AFAIL = -3
-// AFAILVOID = -2
-// DWIN = 8 (5 if someone else returned)
-// ARETRY = -1
-// DRETRY = 3
-// ATAKE = 0
+set g_ctf_score_capture 25
+set g_ctf_score_capture_assist 0
+set g_ctf_score_kill 3
+set g_ctf_score_penalty_drop 2
+set g_ctf_score_penalty_suicidedrop 2
+set g_ctf_score_penalty_returned 1
+set g_ctf_score_pickup_base 0
+set g_ctf_score_pickup_dropped_early 1
+set g_ctf_score_pickup_dropped_late 1
+set g_ctf_score_return 2 // lowered so it's better if the killer does the return
-set g_ctf_personalscore_pickup_base 1
-set g_ctf_personalscore_pickup_dropped_early 1
-set g_ctf_personalscore_pickup_dropped_late 1
-set g_ctf_personalscore_capture 20
-set g_ctf_personalscore_kill 1
-set g_ctf_personalpenalty_drop 0
-set g_ctf_personalpenalty_suicidedrop 1
-set g_ctf_personalpenalty_returned 0
-set g_ctf_personalscore_return 5
-set g_ctf_personalscore_return_rogue 10
-set g_ctf_personalscore_return_by_killer 5
-set g_ctf_personalscore_return_rogue_by_killer 10
-// AWIN = 21
-// AFAIL = 1
-// AFAILVOID = 1
-// DWIN = 6
-// ARETRY = 1
-// DRETRY = 1
-// ATAKE = 1
+set g_ctf_score_capture 20
+set g_ctf_score_capture_assist 0
+set g_ctf_score_kill 1
+set g_ctf_score_penalty_drop 0
+set g_ctf_score_penalty_suicidedrop 1
+set g_ctf_score_penalty_returned 0
+set g_ctf_score_pickup_base 1
+set g_ctf_score_pickup_dropped_early 1
+set g_ctf_score_pickup_dropped_late 1
+set g_ctf_score_return 5
--- /dev/null
+set g_ctf_score_capture 20
+set g_ctf_score_capture_assist 10
+set g_ctf_score_kill 5
+set g_ctf_score_penalty_drop 1
+set g_ctf_score_penalty_suicidedrop 1
+set g_ctf_score_penalty_returned 1
+set g_ctf_score_pickup_base 1
+set g_ctf_score_pickup_dropped_early 1
+set g_ctf_score_pickup_dropped_late 1
+set g_ctf_score_return 10
+++ /dev/null
-set g_ctf_personalscore_pickup_base -1
-set g_ctf_personalscore_pickup_dropped_early 5
-set g_ctf_personalscore_pickup_dropped_late 9
-set g_ctf_personalscore_capture 26
-set g_ctf_personalscore_kill 5
-set g_ctf_personalpenalty_drop 9
-set g_ctf_personalpenalty_suicidedrop 9
-set g_ctf_personalpenalty_returned 0
-set g_ctf_personalscore_return 3
-set g_ctf_personalscore_return_rogue 10
-set g_ctf_personalscore_return_by_killer 3
-set g_ctf_personalscore_return_rogue_by_killer 10
-// AWIN = 25
-// AFAIL = -10
-// AFAILVOID = -10
-// DWIN = 8
-// ARETRY = -1..-4
-// DRETRY = 5
-// ATAKE = -1
+++ /dev/null
-set g_ctf_personalscore_pickup_base -5
-set g_ctf_personalscore_pickup_dropped_early 1
-set g_ctf_personalscore_pickup_dropped_late 5
-set g_ctf_personalscore_capture 30
-set g_ctf_personalscore_kill 5
-set g_ctf_personalpenalty_drop 5
-set g_ctf_personalpenalty_suicidedrop 5
-set g_ctf_personalpenalty_returned 0
-set g_ctf_personalscore_return 3
-set g_ctf_personalscore_return_rogue 10
-set g_ctf_personalscore_return_by_killer 3
-set g_ctf_personalscore_return_rogue_by_killer 10
-// AWIN = 25
-// AFAIL = -10
-// AFAILVOID = -10
-// DWIN = 8
-// ARETRY = -1..-4
-// DRETRY = 5
-// ATAKE = -5
seta g_configversion 0 "Configuration file version (used to upgrade settings) 0: first run, or previous start was <2.4.1 Later, it's overridden by config.cfg, version ranges are defined in config_update.cfg"
-// say aliases
-alias asay_ctf_flagcarrier "say_team flag carrier at %y"
-alias asay_ctf_haveflag "say_team (%l) have the flag"
-alias asay_willgo "say_team will go to %y"
-alias asay_support "say_team (%l) need help, %h%%"
-alias asay_killed "say_team got killed at %d"
-alias asay_noammo "say_team (%l) need %W for %w"
-alias asay_drop "say_team (%l) dropped %w ; impulse 17"
-
// other aliases
alias +hook +button6
alias -hook -button6
set sv_dodging_wall_distance_threshold 10 "the maximum distance from a wall that still allows dodging"
set sv_dodging_sound 1 "if 1 dodging makes a sound. if 0 dodging is silent"
-set leadlimit 0
-set leadlimit_and_fraglimit 0 "if set, leadlimit is ANDed with fraglimit (otherwise ORed)"
-
-// this means that timelimit can be overidden globally and fraglimit can be overidden for each game mode: DM/TDM, Domination, CTF, and Runematch.
-seta timelimit_override -1 "Time limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta fraglimit_override -1 "Frag limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta leadlimit_override -1 "Lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta capturelimit_override -1 "Capture limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta captureleadlimit_override -1 "Capture llead imit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_arena_point_limit -1 "Arena point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_arena_point_leadlimit -1 "Arena point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_domination_point_limit -1 "Domination point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_domination_point_leadlimit -1 "Domination point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_runematch_point_limit -1 "Runematch point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_runematch_point_leadlimit -1 "Runematch point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_keyhunt_point_limit -1 "Keyhunt point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_keyhunt_point_leadlimit -1 "Keyhunt point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_race_laps_limit -1 "Race laps limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_nexball_goallimit -1 "Nexball goal limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_nexball_safepass_maxdist 5000 // Max distance to allow save fassping (0 to turn off safe passing)
-seta g_nexball_safepass_turnrate 0.1 // How fast the safe-pass ball can habge direction
-seta g_nexball_safepass_holdtime 0.75 // How long to remeber last teammate you pointed at
-seta g_nexball_viewmodel_scale 0.25 // How large the ball for the carrier
-seta g_nexball_viewmodel_offset "8 8 0" // Where the ball is located on carrier "forward right up"
-seta g_nexball_tackling 1 // Allow ball theft?
-
-
-seta g_ctf_ignore_frags 0 "1: regular frags give no points"
-
-set g_freezetag 0 "Freeze Tag: Freeze the opposing team(s) to win, unfreeze teammates by standing next to them"
-seta g_freezetag_warmup 5 "Time players get to run around before the round starts"
-seta g_freezetag_point_limit -1 "Freeze Tag point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_freezetag_point_leadlimit -1 "Freeze Tag point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
-seta g_freezetag_revive_speed 0.4 "Speed for reviving a frozen teammate"
-seta g_freezetag_revive_clearspeed 1.6 "Speed at which reviving progress gets lost when out of range"
-seta g_freezetag_revive_extra_size 100 "Distance in qu that you can stand from a frozen teammate to keep reviving him"
-seta g_freezetag_frozen_force 0.6 "How much to multiply the force on a frozen player with"
-
set g_spawn_furthest 0.5 "this amount of the spawns shall be far away from any players"
set g_spawn_useallspawns 0 "use all spawns, e.g. also team spawns in non-teamplay, and all spawns, even enemy spawns, in teamplay"
set g_spawn_near_teammate 0 "if set, players prefer spawns near a team mate"
// respawn delay
set g_respawn_delay 2 "number of seconds you have to wait before you can respawn again"
set g_respawn_waves 0 "respawn in waves (every n seconds), intended to decrease overwhelming base attacks"
-// when variables are set to 0, they take over the global setting...
-// to force disable delay or waves, set them to 0.125
-set g_ctf_respawn_delay 0
-set g_ctf_respawn_waves 0
-set g_ctf_weapon_stay 0
-set g_dm_respawn_delay 0
-set g_dm_respawn_waves 0
-set g_dm_weapon_stay 0
-set g_dom_respawn_delay 0
-set g_dom_respawn_waves 0
-set g_dom_weapon_stay 0
-set g_lms_respawn_delay 0
-set g_lms_respawn_waves 0
-set g_lms_weapon_stay 0
-set g_rune_respawn_delay 0
-set g_rune_respawn_waves 0
-set g_rune_weapon_stay 0
-set g_tdm_respawn_delay 0
-set g_tdm_respawn_waves 0
-set g_tdm_weapon_stay 0
-set g_ka_respawn_delay 0
-set g_ka_respawn_waves 0
-set g_ka_weapon_stay 0
-set g_kh_respawn_delay 0
-set g_kh_respawn_waves 0
-set g_kh_weapon_stay 0
-set g_arena_respawn_delay 0
-set g_arena_respawn_waves 0
-set g_arena_weapon_stay 0
-set g_ca_respawn_delay 0
-set g_ca_respawn_waves 0
-set g_ca_weapon_stay 0
-set g_ca_damage2score_multiplier 0.01
-set g_ca_round_timelimit 180
-set g_nb_respawn_delay 0
-set g_nb_respawn_waves 0
-set g_nb_weapon_stay 0
-set g_as_respawn_delay 0
-set g_as_respawn_waves 0
-set g_as_weapon_stay 0
-set g_ons_respawn_delay 0
-set g_ons_respawn_waves 0
-set g_ons_weapon_stay 0
-set g_rc_respawn_waves 0
-set g_rc_respawn_delay 0
-set g_rc_weapon_stay 0
-set g_cts_respawn_waves 0
-set g_cts_respawn_delay 0
-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_weapon_stay 2
-set g_ft_respawn_waves 0
-set g_ft_respawn_delay 0
-set g_ft_weapon_stay 0
// overtime
seta timelimit_overtime 2 "duration in minutes of one added overtime, added to the timelimit"
seta g_balance_teams 1 "automatically balance out players entering instead of asking them for their preferred team"
seta g_balance_teams_prevent_imbalance 1 "prevent players from changing to larger teams"
set g_balance_teams_scorefactor 0.34 "at the end of the game, take score into account instead of team size by this amount (beware: values over 0.5 mean that a x:0 score imbalance will cause ALL new players to prefer the losing team at the end, despite numbers)"
-set g_tdm_teams 2 "how many teams are in team deathmatch (set by mapinfo)"
-seta g_tdm_teams_override 0 "how many teams are in team deathmatch"
-set g_tdm_team_spawns 0 "when 1, a map can define team spawnpoints for TDM"
set g_changeteam_banned 0 "not allowed to change team"
set g_changeteam_fragtransfer 0 "% of frags you get to keep when you change teams (rounded down)"
set sv_teamnagger 1 "enable a nag message when the teams are unbalanced"
-// dm
-set g_dm 1 "Deathmatch: killing any other player is one frag, player with most frags wins"
-set gamecfg 1 // "deathmatch"
-
-// ctf
-set g_ctf 0 "Capture The Flag: take the enemy flag and bring it to yours at your base to score"
-set g_ctf_flag_returntime 30
-set g_ctf_flagcarrier_selfdamage 1
-set g_ctf_flagcarrier_selfforce 1
-set g_ctf_fullbrightflags 0
-set g_ctf_dynamiclights 0
-set g_ctf_allow_drop 1 "dropping allows circumventing carrierkill score, so enable this with care!"
-set g_ctf_reverse 0 "if enabled, flags positions are switched: you have to capture the enemy's flag from your own base by bringing it to your own flag in the enemy base"
-set g_balance_ctf_delay_collect 1.0
-set g_balance_ctf_damageforcescale 1
-
-set g_ctf_shield_max_ratio 0 "shield at most this percentage of a team from the enemy flag (try: 0.4 for 40%)"
-set g_ctf_shield_min_negscore 20 "shield the player from the flag if he's got this negative amount of points or less"
-set g_ctf_shield_force 100 "push force of the shield"
-
-// fun for server admins
-set g_ctf_flag_red_model "models/ctf/flags.md3"
-set g_ctf_flag_red_skin 0
-set g_ctf_flag_blue_model "models/ctf/flags.md3"
-set g_ctf_flag_blue_skin 1
-set g_ctf_flag_glowtrails 0
-set g_ctf_flag_pickup_effects 1
-set g_ctf_flag_capture_effects 1
-set g_ctf_captimerecord_always 0 "if enabled, assisted CTF records (with other players on the server) are recorded too"
-
-// runematch
-set g_runematch 0 "Runematch: pick up and hold the runes, special items that give you points, a special power (rune) and a disadvantage (curse)"
-set g_runematch_pointrate 5
-set g_runematch_fixedspawns 1 "use fixed runematch spawns if available"
-set g_runematch_pointamt 1
-set g_runematch_shuffletime 30 "how often runes change position"
-set g_runematch_respawntime 15 "how soon after being dropped to respawn"
-set g_runematch_frags_killedby_runeholder 4
-set g_runematch_frags_killed_runeholder 5
-set g_runematch_frags_norune 0
-set g_runematch_drop_runes_max 2 "only drop up to 2 runes, the rest should respawn"
-set g_runematch_allow_same 0 "allow matching rune-curse pairs"
-set g_runematch_rune_alpha 0.78
-set g_runematch_rune_effects 544 "EF_ADDITIVE + EF_FULLBRIGHT = 544"
-set g_runematch_rune_glow_size 0
-set g_runematch_rune_glow_color 0
-set g_runematch_rune_color_strength 1.0
-// strength/weakness
-set g_balance_rune_strength_damage 2.0
-set g_balance_rune_strength_force 1.5
-set g_balance_curse_weak_damage 0.5
-set g_balance_curse_weak_force 0.6
-set g_balance_rune_strength_combo_damage 0.9
-set g_balance_rune_strength_combo_force 1.0
-// defense/vulner
-set g_balance_rune_defense_takedamage 0.5
-set g_balance_curse_vulner_takedamage 2.0
-set g_balance_rune_defense_combo_takedamage 1.0
-// vampire/empathy
-set g_balance_rune_vampire_absorb 0.4
-set g_balance_curse_empathy_takedamage -0.4
-set g_balance_rune_vampire_combo_absorb -0.1
-set g_balance_rune_vampire_maxhealth 500
-set g_balance_curse_empathy_minhealth 20
-set g_balance_rune_vampire_combo_minhealth 40
-// regen/venom
-set g_balance_rune_regen_hpmod 1.75
-set g_balance_curse_venom_hpmod 0.6
-set g_balance_rune_regen_combo_hpmod 0.9
-set g_balance_rune_regen_regenrate 3.0
-set g_balance_curse_venom_rotrate 3.0
-set g_balance_rune_regen_combo_regenrate 0.5
-set g_balance_rune_regen_combo_rotrate 1.5
-set g_balance_rune_regen_limitmod 1
-set g_balance_curse_venom_limitmod 1
-set g_balance_rune_regen_combo_limitmod 1
-// speed/slow
-set g_balance_rune_speed_atkrate 0.66
-set g_balance_curse_slow_atkrate 1.5
-set g_balance_rune_speed_combo_atkrate 1.2
-set g_balance_rune_speed_highspeed 1.5
-set g_balance_curse_slow_highspeed 0.6
-set g_balance_rune_speed_combo_highspeed 0.9
-
-// domination
-set g_domination 0 "Domination: capture and hold control points to gain points"
-set g_domination_default_teams 2 "default number of teams for maps that aren't domination-specific"
-seta g_domination_teams_override 0 "use a specific number of teams in domination games (minimum 2), disables dom_team entities"
-set g_domination_disable_frags 0 "players can't get frags normally, only get points from kills"
-set g_domination_point_amt 0 "override: how many points to get per ping"
-set g_domination_point_fullbright 0 "domination point fullbright"
-set g_domination_point_rate 0 "override: how often to give those points"
-set g_domination_point_capturetime 0.1 "how long it takes to capture a point (given no interference)"
-set g_domination_point_glow 0 "domination point glow (warning, slow)"
-//set g_domination_balance_team_points 1 "# of points received is based on team sizes"
-
-// last man standing
-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_regenerate 0
-set g_lms_campcheck_interval 10
-set g_lms_campcheck_message "^1Don't camp!"
-set g_lms_campcheck_damage 100
-set g_lms_campcheck_distance 1800
-set g_lms_last_join 3 "if g_lms_join_anytime is false, new players can only join if the worst active player has more than (fraglimit - g_lms_last_join) lives"
-set g_lms_join_anytime 1 "if true, new players can join, but get same amount of lives as the worst player"
-
-// arena
-set g_arena 0 "Arena: many one-on-one rounds are played to find the winner"
-set g_arena_maxspawned 2 "maximum number of players to spawn at once (the rest is spectating, waiting for their turn)"
-set g_arena_roundbased 1 "if disabled, the next player will spawn as soon as someone dies"
-set g_arena_warmup 5 "time, newly spawned players have to prepare themselves in round based matches"
-
-// ca
-set g_ca 0 "Clan Arena: Played in rounds, once you're dead you're out! The team with survivors wins the round."
-set g_ca_point_limit 10 "point limit 10 is standard for clan arena"
-set g_ca_point_leadlimit 0
-set g_ca_spectate_enemies 0 "Allow spectating enemy player by dead player during clan arena games."
-set g_ca_warmup 10 "how long the players will have time to run around the map before the round starts"
-
-// onslaught
-set g_onslaught 0 "Onslaught: take control points towards the enemy generator and then destroy it"
-set g_onslaught_gen_health 2500
-set g_onslaught_cp_health 1000
-set g_onslaught_cp_buildhealth 100
-set g_onslaught_cp_buildtime 5
-set g_onslaught_cp_regen 20
-
-// assault
-set g_assault 0 "Assault: attack the enemy base as fast as you can, then defend the base against the enemy for that time to win"
-
-// race
-set g_race 0 "Race: be faster than your opponents"
-set g_race_qualifying_timelimit 0
-set g_race_qualifying_timelimit_override -1
-set g_race_teams 0 "when 2, 3, or 4, the race is played as a team game (the team members can add up their laps)"
-
-// cts
-set g_cts 0 "CTS: complete the stage"
-
-// nexball
-set g_nexball 0 "Nexball: Basketball and Soccer go Xonotic"
-
-set g_nexball_basketball_effects_default 8 "default: dim light. The original version used 1024 (fire) but it gives bad performance"
-set g_balance_nexball_primary_speed 1000 "launching speed"
-set g_balance_nexball_primary_refire 0.7 "launching refire"
-set g_balance_nexball_primary_animtime 0.3 "launching animtime"
-set g_balance_nexball_secondary_animtime 0.3 "launching animtime"
-set g_balance_nexball_secondary_speed 3000 "stealing projectile speed"
-set g_balance_nexball_secondary_lifetime 0.15 "stealing projectile lifetime"
-set g_balance_nexball_secondary_force 500 "stealing projectile force"
-set g_balance_nexball_secondary_refire 0.6 "stealing projectile refire"
-set g_balance_nexball_secondary_animtime 0.3 "stealing projectile animtime"
-
-// -1: MrBougo's first try, not very playable but working...
-// The ball gets the player's velocity * 1.5 + a vertical boost
-// 0: Revenant style
-// Player's velocity + a boost where he's looking at + a boost
-// perpendicularly to the first boost, that is upwards relatively
-// to the view angle
-// 1: MrBougo's modded Rev style 1
-// The 2nd Rev boost is always vertical
-// 2: MrBougo's modded Rev style 2
-// The 1st Rev boost is always horizontal
-// The 2nd Rev boost is always vertical
-set g_nexball_football_physics 2 "0: Revenant's original movement, 1: 0 but half independant of aiming height, 2: 1 fully independant, -1: first recode try"
-set g_nexball_basketball_bouncefactor 0.6 "velocity loss when the ball bounces"
-set g_nexball_basketball_bouncestop 0.075 "speed at which the ball stops when it hits the ground (multiplied by sv_gravity)"
-set g_nexball_football_bouncefactor 0.6 "velocity loss when the ball bounces"
-set g_nexball_football_bouncestop 0.075 "speed at which the ball stops when it hits the ground (multiplied by sv_gravity)"
-
-set g_nexball_football_boost_forward 100 "forward velocity boost when the ball is touched"
-set g_nexball_football_boost_up 200 "vertical velocity boost when the ball is touched"
-
-set g_nexball_basketball_delay_hold 20 "time before a player who caught the ball loses it (anti-ballcamp)"
-set g_nexball_basketball_delay_hold_forteam 60 "time before a ball reset when a team holds the ball for too long"
-set g_nexball_basketball_teamsteal 1 "1 to allow players to steal from teammates, 0 to disallow"
-
-set g_nexball_basketball_carrier_highspeed 0.8 "speed multiplier for the ballcarrier"
-
-set g_nexball_meter_period 1 "time to make a full cycle on the power meter"
-set g_nexball_basketball_meter 1 "use the power meter for basketball"
-set g_nexball_basketball_meter_minpower 0.5 "minimal multiplier to the launching speed when using the power meter"
-set g_nexball_basketball_meter_maxpower 1.2 "maximal multiplier to the launching speed when using the power meter"
-
-set g_nexball_delay_goal 3 "delay between a goal and a ball reset"
-set g_nexball_delay_idle 10 "maximal idle time before a reset"
-set g_nexball_delay_start 3 "time the ball stands on its spawn before being released"
-set g_nexball_delay_collect 0.5 "time before the same player can catch the ball he launched"
-
-set g_nexball_sound_bounce 1 "bouncing sound (0: off)"
-
-set g_nexball_basketball_trail 1 "1 to leave a trail"
-set g_nexball_football_trail 0 "1 to leave a trail"
-set g_nexball_trail_color 254 "1-256 for different colors (Quake palette, 254 is white)"
-
-set g_nexball_radar_showallplayers 1 "1: show every player and the ball on the radar 0: only show teammates and the ball on the radar"
-
set g_bloodloss 0 "amount of health below which blood loss occurs"
set g_footsteps 1 "serverside footstep sounds"
seta cl_announcer default "name of the announcer you wish to use from data/sound/announcer"
seta cl_announcer_antispam 2 "number of seconds before an announcement of the same sound can be played again"
seta cl_announcer_maptime 3 "play announcer sound telling you the remaining maptime - 0: do not play at all, 1: play at one minute, 2: play at five minutes, 3: play both"
-seta cl_notify_carried_items "3" "notify you of carried items when you obtain them (e.g. flags in CTF) - 0: disabled, 1: notify of taken items, 2: notify of picking up dropped items, 3: notify of both"
// startmap_dm is used when running with the -listen or -dedicated commandline options
set serverconfig server.cfg
// when sv_maxidle is not 0, assume spectators are idle too
set sv_maxidle_spectatorsareidle 0
-// CTF capture limit placeholder cvar
-set capturelimit 0
-
// these entities are not referenced by anything directly, they just represent
// teams and are found by find() when needed
prvm_leaktest_ignore_classnames "ctf_team dom_team tdm_team"
set g_mapinfo_settemp_acl "+*" "ACL for mapinfo setting cvars"
-// hooks
-alias _cl_hook_gamestart "set _cl_hook_gametype $1; _cl_hook_gamestart_stage2"
-alias _cl_hook_gamestart_stage2 "cl_hook_gamestart_all; cl_hook_gamestart_${_cl_hook_gametype}"
-alias cl_hook_gamestart_all
-alias cl_hook_gamestart_nop //is only called when CSQC unloads before knowing the gametype, very unlikely
-alias cl_hook_gamestart_dm
-alias cl_hook_gamestart_tdm
-alias cl_hook_gamestart_dom
-alias cl_hook_gamestart_ctf
-alias cl_hook_gamestart_rune
-alias cl_hook_gamestart_lms
-alias cl_hook_gamestart_arena
-alias cl_hook_gamestart_ca
-alias cl_hook_gamestart_kh
-alias cl_hook_gamestart_ons
-alias cl_hook_gamestart_as
-alias cl_hook_gamestart_rc
-alias cl_hook_gamestart_nb
-alias cl_hook_gamestart_cts
-alias cl_hook_gamestart_ka
-alias cl_hook_gamestart_ft
-alias cl_hook_gameend
-alias cl_hook_activeweapon
-
-alias _sv_hook_gamestart "set _sv_hook_gametype $1; _sv_hook_gamestart_stage2"
-alias _sv_hook_gamestart_stage2 "sv_hook_gamestart_all; sv_hook_gamestart_${_sv_hook_gametype}"
-alias sv_hook_gamestart_all
-alias sv_hook_gamestart_dm
-alias sv_hook_gamestart_tdm
-alias sv_hook_gamestart_dom
-alias sv_hook_gamestart_ctf
-alias sv_hook_gamestart_rune
-alias sv_hook_gamestart_lms
-alias sv_hook_gamestart_arena
-alias sv_hook_gamestart_ca
-alias sv_hook_gamestart_kh
-alias sv_hook_gamestart_ons
-alias sv_hook_gamestart_as
-alias sv_hook_gamestart_rc
-alias sv_hook_gamestart_nb
-alias sv_hook_gamestart_cts
-alias sv_hook_gamestart_ka
-alias sv_hook_gamestart_ft
-alias sv_hook_gamerestart
-alias sv_hook_gameend
-
seta cl_casings_maxcount 100 "maximum amount of shell casings (must be at least 1)"
seta cl_gibs_maxcount 100 "maximum amount of gibs (must be at least 1)"
seta cl_vehicle_spiderbot_cross_alpha 0.6
// other config files
exec mutator_new_toys.cfg // run BEFORE balance to make sure balance wins
exec balanceXonotic.cfg
-exec ctfscoring-ai.cfg
exec effects-normal.cfg
exec physicsX.cfg
exec turrets.cfg
exec vehicles.cfg
exec crosshairs.cfg
+exec gamemodes.cfg
// load console command aliases and settings
exec commands.cfg
originjitter 80 80 80
sizeincrease -10
airfriction 0.04
-gravity -0.2
\ No newline at end of file
+gravity -0.2
+
+// redflag_touch -- effects for touching the red flag
+// used nowhere in code
+effect redflag_touch
+count 35
+type spark
+tex 40 40
+color 0xFF0000 0x970000
+size 1 3
+alpha 0 256 556
+gravity 1
+bounce 1.5
+originjitter 1 1 1
+velocityjitter 300 300 300
+velocitymultiplier 0.5
+airfriction 3
+
+// blueflag_touch -- effects for touching the blue flag
+// used nowhere in code
+effect blueflag_touch
+count 35
+type spark
+tex 40 40
+color 0x0000FF 0x000097
+size 1 3
+alpha 0 256 556
+gravity 1
+bounce 1.5
+originjitter 1 1 1
+velocityjitter 300 300 300
+velocitymultiplier 0.5
+airfriction 3
+
+// red_pass
+// used nowhere in code
+effect red_pass
+trailspacing 64
+color 0xFF0000 0x970000
+size 2 2
+tex 32 32
+alpha 64 128 64
+airfriction 5
+sizeincrease 2
+type static
+// ============== DRIFTING SMOKE
+effect red_pass
+trailspacing 12
+color 0xFF0000 0x970000
+size 1 1
+tex 0 8
+alpha 32 64 32
+airfriction 9
+sizeincrease 8
+velocityjitter 64 64 64
+type static
+// ============== BRIGHT CORE
+effect red_pass
+trailspacing 12
+color 0xFF0000 0x970000
+size 4 4
+//tex 48 55
+alpha 256 256 1280
+type static
+
+// blue_pass
+// used nowhere in code
+effect blue_pass
+trailspacing 64
+color 0x0000FF 0x000097
+size 2 2
+tex 32 32
+alpha 64 128 64
+airfriction 5
+sizeincrease 2
+type static
+// ============== DRIFTING SMOKE
+effect blue_pass
+trailspacing 12
+color 0x0000FF 0x000097
+size 1 1
+tex 0 8
+alpha 32 64 32
+airfriction 9
+sizeincrease 8
+velocityjitter 64 64 64
+type static
+// ============== BRIGHT CORE
+effect blue_pass
+trailspacing 12
+color 0x0000FF 0x000097
+size 4 4
+//tex 48 55
+alpha 256 256 1280
+type static
+
--- /dev/null
+// ==================================
+// Master config for core game modes
+// ==================================
+
+
+
+
+// ========
+// common
+// ========
+
+// hooks
+alias _cl_hook_gamestart "set _cl_hook_gametype $1; _cl_hook_gamestart_stage2"
+alias _cl_hook_gamestart_stage2 "cl_hook_gamestart_all; cl_hook_gamestart_${_cl_hook_gametype}"
+alias cl_hook_gamestart_all
+alias cl_hook_gamestart_nop //is only called when CSQC unloads before knowing the gametype, very unlikely
+alias cl_hook_gamestart_dm
+alias cl_hook_gamestart_tdm
+alias cl_hook_gamestart_dom
+alias cl_hook_gamestart_ctf
+alias cl_hook_gamestart_rune
+alias cl_hook_gamestart_lms
+alias cl_hook_gamestart_arena
+alias cl_hook_gamestart_ca
+alias cl_hook_gamestart_kh
+alias cl_hook_gamestart_ons
+alias cl_hook_gamestart_as
+alias cl_hook_gamestart_rc
+alias cl_hook_gamestart_nb
+alias cl_hook_gamestart_cts
+alias cl_hook_gamestart_ka
+alias cl_hook_gamestart_ft
+alias cl_hook_gameend
+alias cl_hook_activeweapon
+
+alias _sv_hook_gamestart "set _sv_hook_gametype $1; _sv_hook_gamestart_stage2"
+alias _sv_hook_gamestart_stage2 "sv_hook_gamestart_all; sv_hook_gamestart_${_sv_hook_gametype}"
+alias sv_hook_gamestart_all
+alias sv_hook_gamestart_dm
+alias sv_hook_gamestart_tdm
+alias sv_hook_gamestart_dom
+alias sv_hook_gamestart_ctf
+alias sv_hook_gamestart_rune
+alias sv_hook_gamestart_lms
+alias sv_hook_gamestart_arena
+alias sv_hook_gamestart_ca
+alias sv_hook_gamestart_kh
+alias sv_hook_gamestart_ons
+alias sv_hook_gamestart_as
+alias sv_hook_gamestart_rc
+alias sv_hook_gamestart_nb
+alias sv_hook_gamestart_cts
+alias sv_hook_gamestart_ka
+alias sv_hook_gamestart_ft
+alias sv_hook_gamerestart
+alias sv_hook_gameend
+
+// say aliases
+alias asay_ctf_flagcarrier "say_team flag carrier at %y"
+alias asay_ctf_haveflag "say_team (%l) have the flag"
+alias asay_willgo "say_team will go to %y"
+alias asay_support "say_team (%l) need help, %h%%"
+alias asay_killed "say_team got killed at %d"
+alias asay_noammo "say_team (%l) need %W for %w"
+alias asay_drop "say_team (%l) dropped %w ; impulse 17"
+
+
+set leadlimit 0
+set leadlimit_and_fraglimit 0 "if set, leadlimit is ANDed with fraglimit (otherwise ORed)"
+
+// this means that timelimit can be overidden globally and fraglimit can be overidden for each game mode: DM/TDM, Domination, CTF, and Runematch.
+seta timelimit_override -1 "Time limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta fraglimit_override -1 "Frag limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta leadlimit_override -1 "Lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta capturelimit_override -1 "Capture limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta captureleadlimit_override -1 "Capture llead imit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_arena_point_limit -1 "Arena point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_arena_point_leadlimit -1 "Arena point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_domination_point_limit -1 "Domination point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_domination_point_leadlimit -1 "Domination point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_runematch_point_limit -1 "Runematch point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_runematch_point_leadlimit -1 "Runematch point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_keyhunt_point_limit -1 "Keyhunt point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_keyhunt_point_leadlimit -1 "Keyhunt point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_race_laps_limit -1 "Race laps limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_nexball_goallimit -1 "Nexball goal limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_nexball_safepass_maxdist 5000 // Max distance to allow save fassping (0 to turn off safe passing)
+seta g_nexball_safepass_turnrate 0.1 // How fast the safe-pass ball can habge direction
+seta g_nexball_safepass_holdtime 0.75 // How long to remeber last teammate you pointed at
+seta g_nexball_viewmodel_scale 0.25 // How large the ball for the carrier
+seta g_nexball_viewmodel_offset "8 8 0" // Where the ball is located on carrier "forward right up"
+seta g_nexball_tackling 1 // Allow ball theft?
+
+seta g_ctf_ignore_frags 0 "1: regular frags give no points"
+
+
+
+set g_freezetag 0 "Freeze Tag: Freeze the opposing team(s) to win, unfreeze teammates by standing next to them"
+seta g_freezetag_warmup 5 "Time players get to run around before the round starts"
+seta g_freezetag_point_limit -1 "Freeze Tag point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_freezetag_point_leadlimit -1 "Freeze Tag point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_freezetag_revive_speed 0.4 "Speed for reviving a frozen teammate"
+seta g_freezetag_revive_clearspeed 1.6 "Speed at which reviving progress gets lost when out of range"
+seta g_freezetag_revive_extra_size 100 "Distance in qu that you can stand from a frozen teammate to keep reviving him"
+seta g_freezetag_frozen_force 0.6 "How much to multiply the force on a frozen player with"
+
+// when variables are set to 0, they take over the global setting...
+// to force disable delay or waves, set them to 0.125
+set g_ctf_respawn_delay 0
+set g_ctf_respawn_waves 0
+set g_ctf_weapon_stay 0
+set g_dm_respawn_delay 0
+set g_dm_respawn_waves 0
+set g_dm_weapon_stay 0
+set g_dom_respawn_delay 0
+set g_dom_respawn_waves 0
+set g_dom_weapon_stay 0
+set g_lms_respawn_delay 0
+set g_lms_respawn_waves 0
+set g_lms_weapon_stay 0
+set g_rune_respawn_delay 0
+set g_rune_respawn_waves 0
+set g_rune_weapon_stay 0
+set g_tdm_respawn_delay 0
+set g_tdm_respawn_waves 0
+set g_tdm_weapon_stay 0
+set g_ka_respawn_delay 0
+set g_ka_respawn_waves 0
+set g_ka_weapon_stay 0
+set g_kh_respawn_delay 0
+set g_kh_respawn_waves 0
+set g_kh_weapon_stay 0
+set g_arena_respawn_delay 0
+set g_arena_respawn_waves 0
+set g_arena_weapon_stay 0
+set g_ca_respawn_delay 0
+set g_ca_respawn_waves 0
+set g_ca_weapon_stay 0
+set g_ca_damage2score_multiplier 0.01
+set g_ca_round_timelimit 180
+set g_nb_respawn_delay 0
+set g_nb_respawn_waves 0
+set g_nb_weapon_stay 0
+set g_as_respawn_delay 0
+set g_as_respawn_waves 0
+set g_as_weapon_stay 0
+set g_ons_respawn_delay 0
+set g_ons_respawn_waves 0
+set g_ons_weapon_stay 0
+set g_rc_respawn_waves 0
+set g_rc_respawn_delay 0
+set g_rc_weapon_stay 0
+set g_cts_respawn_waves 0
+set g_cts_respawn_delay 0
+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_weapon_stay 2
+set g_ft_respawn_waves 0
+set g_ft_respawn_delay 0
+set g_ft_weapon_stay 0
+
+set g_tdm_teams 2 "how many teams are in team deathmatch (set by mapinfo)"
+seta g_tdm_teams_override 0 "how many teams are in team deathmatch"
+set g_tdm_team_spawns 0 "when 1, a map can define team spawnpoints for TDM"
+
+// dm
+set g_dm 1 "Deathmatch: killing any other player is one frag, player with most frags wins"
+set gamecfg 1 // "deathmatch"
+
+// ctf
+set g_ctf 0 "Capture The Flag: take the enemy flag and bring it to yours at your base to score"
+set g_ctf_flag_return_time 15
+set g_ctf_flag_return_dropped 100
+set g_ctf_flag_return_damage 0
+set g_ctf_flag_return_when_unreachable 1 "automatically return the flag if it falls into lava/slime/trigger hurt"
+set g_ctf_flagcarrier_auto_helpme_when_damaged 50
+set g_ctf_flagcarrier_allow_vehicle_carry 1
+set g_ctf_flagcarrier_selfdamagefactor 1
+set g_ctf_flagcarrier_selfforcefactor 1
+set g_ctf_flagcarrier_damagefactor 1
+set g_ctf_flagcarrier_forcefactor 1
+set g_ctf_flagcarrier_waypointforenemy_stalemate 60 "show the enemy flagcarrier location after both teams have held the flags for this amount of time"
+set g_ctf_flagcarrier_waypointforenemy_spotting 1 "show the enemy flagcarrier location if a team mate presses +use to spot them"
+set g_ctf_dropped_capture_radius 100 "allow dropped flags to be automatically captured by base flags if the dropped flag is within this radius of it"
+set g_ctf_fullbrightflags 0
+set g_ctf_dynamiclights 0
+set g_ctf_flag_damageforcescale 2
+set g_ctf_portalteleport 0 "allow flag carriers to go through portals made in portal gun without dropping the flag"
+set g_ctf_reverse 0 "if enabled, flags positions are switched: you have to capture the enemy's flag from your own base by bringing it to your own flag in the enemy base"
+set g_ctf_flag_collect_delay 1
+set g_ctf_flag_health 0
+set g_ctf_flag_dropped_waypoint 2 "show dropped flag waypointsprite when a flag is lost. 1 = team only, 2 = for all players"
+set g_ctf_flag_dropped_floatinwater 200 "move upwards while in water at this velocity"
+set g_ctf_flag_pickup_verbosename 0 "show the name of the person who picked up the flag too"
+set g_ctf_drop 1 "dropping allows circumventing carrierkill score, so enable this with care!"
+set g_ctf_drop_strengthmultiplier 2 "multiplier for velocity when you have the strength... essentially, throw the flag REALLY hard when you have the strength :D"
+set g_ctf_drop_velocity 500 "how fast or far a player can throw the flag"
+set g_ctf_pass 1 "allow passing of flags to nearby team mates"
+set g_ctf_pass_radius 500 "maximum radius that you can pass to a team mate in"
+set g_ctf_pass_wait 2 "delay in seconds between how often players can pass the flag (antispam, essentially)"
+set g_ctf_pass_request 1 "allow players to request the flag carrier to pass the flag to them"
+set g_ctf_pass_turnrate 50 "how well the flag follows the best direction to its target while passing"
+set g_ctf_pass_timelimit 2 "how long a flag can stay trying to pass before it gives up and just becomes dropped"
+set g_ctf_pass_velocity 750 "how fast or far a player can pass the flag"
+set g_ctf_allow_vehicle_touch 1 "allow flags to be picked up/captured/returned from inside a vehicle
+
+set g_ctf_shield_max_ratio 0 "shield at most this percentage of a team from the enemy flag (try: 0.4 for 40%)"
+set g_ctf_shield_min_negscore 20 "shield the player from the flag if he's got this negative amount of points or less"
+set g_ctf_shield_force 100 "push force of the shield"
+
+// fun for server admins
+set g_ctf_flag_red_model "models/ctf/flags.md3"
+set g_ctf_flag_red_skin 0
+set g_ctf_flag_blue_model "models/ctf/flags.md3"
+set g_ctf_flag_blue_skin 1
+set g_ctf_flag_glowtrails 0
+set g_ctf_flag_pickup_effects 1
+set g_ctf_flag_capture_effects 1
+set g_ctf_captimerecord_always 0 "if enabled, assisted CTF records (with other players on the server) are recorded too"
+
+// CTF capture limit placeholder cvar
+set capturelimit 0
+exec ctfscoring-samual.cfg
+
+// runematch
+set g_runematch 0 "Runematch: pick up and hold the runes, special items that give you points, a special power (rune) and a disadvantage (curse)"
+set g_runematch_pointrate 5
+set g_runematch_fixedspawns 1 "use fixed runematch spawns if available"
+set g_runematch_pointamt 1
+set g_runematch_shuffletime 30 "how often runes change position"
+set g_runematch_respawntime 15 "how soon after being dropped to respawn"
+set g_runematch_frags_killedby_runeholder 4
+set g_runematch_frags_killed_runeholder 5
+set g_runematch_frags_norune 0
+set g_runematch_drop_runes_max 2 "only drop up to 2 runes, the rest should respawn"
+set g_runematch_allow_same 0 "allow matching rune-curse pairs"
+set g_runematch_rune_alpha 0.78
+set g_runematch_rune_effects 544 "EF_ADDITIVE + EF_FULLBRIGHT = 544"
+set g_runematch_rune_glow_size 0
+set g_runematch_rune_glow_color 0
+set g_runematch_rune_color_strength 1.0
+// strength/weakness
+set g_balance_rune_strength_damage 2.0
+set g_balance_rune_strength_force 1.5
+set g_balance_curse_weak_damage 0.5
+set g_balance_curse_weak_force 0.6
+set g_balance_rune_strength_combo_damage 0.9
+set g_balance_rune_strength_combo_force 1.0
+// defense/vulner
+set g_balance_rune_defense_takedamage 0.5
+set g_balance_curse_vulner_takedamage 2.0
+set g_balance_rune_defense_combo_takedamage 1.0
+// vampire/empathy
+set g_balance_rune_vampire_absorb 0.4
+set g_balance_curse_empathy_takedamage -0.4
+set g_balance_rune_vampire_combo_absorb -0.1
+set g_balance_rune_vampire_maxhealth 500
+set g_balance_curse_empathy_minhealth 20
+set g_balance_rune_vampire_combo_minhealth 40
+// regen/venom
+set g_balance_rune_regen_hpmod 1.75
+set g_balance_curse_venom_hpmod 0.6
+set g_balance_rune_regen_combo_hpmod 0.9
+set g_balance_rune_regen_regenrate 3.0
+set g_balance_curse_venom_rotrate 3.0
+set g_balance_rune_regen_combo_regenrate 0.5
+set g_balance_rune_regen_combo_rotrate 1.5
+set g_balance_rune_regen_limitmod 1
+set g_balance_curse_venom_limitmod 1
+set g_balance_rune_regen_combo_limitmod 1
+// speed/slow
+set g_balance_rune_speed_atkrate 0.66
+set g_balance_curse_slow_atkrate 1.5
+set g_balance_rune_speed_combo_atkrate 1.2
+set g_balance_rune_speed_highspeed 1.5
+set g_balance_curse_slow_highspeed 0.6
+set g_balance_rune_speed_combo_highspeed 0.9
+
+// domination
+set g_domination 0 "Domination: capture and hold control points to gain points"
+set g_domination_default_teams 2 "default number of teams for maps that aren't domination-specific"
+seta g_domination_teams_override 0 "use a specific number of teams in domination games (minimum 2), disables dom_team entities"
+set g_domination_disable_frags 0 "players can't get frags normally, only get points from kills"
+set g_domination_point_amt 0 "override: how many points to get per ping"
+set g_domination_point_fullbright 0 "domination point fullbright"
+set g_domination_point_rate 0 "override: how often to give those points"
+set g_domination_point_capturetime 0.1 "how long it takes to capture a point (given no interference)"
+set g_domination_point_glow 0 "domination point glow (warning, slow)"
+//set g_domination_balance_team_points 1 "# of points received is based on team sizes"
+
+// last man standing
+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_regenerate 0
+set g_lms_campcheck_interval 10
+set g_lms_campcheck_message "^1Don't camp!"
+set g_lms_campcheck_damage 100
+set g_lms_campcheck_distance 1800
+set g_lms_last_join 3 "if g_lms_join_anytime is false, new players can only join if the worst active player has more than (fraglimit - g_lms_last_join) lives"
+set g_lms_join_anytime 1 "if true, new players can join, but get same amount of lives as the worst player"
+
+// arena
+set g_arena 0 "Arena: many one-on-one rounds are played to find the winner"
+set g_arena_maxspawned 2 "maximum number of players to spawn at once (the rest is spectating, waiting for their turn)"
+set g_arena_roundbased 1 "if disabled, the next player will spawn as soon as someone dies"
+set g_arena_warmup 5 "time, newly spawned players have to prepare themselves in round based matches"
+
+// ca
+set g_ca 0 "Clan Arena: Played in rounds, once you're dead you're out! The team with survivors wins the round."
+set g_ca_point_limit 10 "point limit 10 is standard for clan arena"
+set g_ca_point_leadlimit 0
+set g_ca_spectate_enemies 0 "Allow spectating enemy player by dead player during clan arena games."
+set g_ca_warmup 10 "how long the players will have time to run around the map before the round starts"
+
+// onslaught
+set g_onslaught 0 "Onslaught: take control points towards the enemy generator and then destroy it"
+set g_onslaught_gen_health 2500
+set g_onslaught_cp_health 1000
+set g_onslaught_cp_buildhealth 100
+set g_onslaught_cp_buildtime 5
+set g_onslaught_cp_regen 20
+
+// assault
+set g_assault 0 "Assault: attack the enemy base as fast as you can, then defend the base against the enemy for that time to win"
+
+// race
+set g_race 0 "Race: be faster than your opponents"
+set g_race_qualifying_timelimit 0
+set g_race_qualifying_timelimit_override -1
+set g_race_teams 0 "when 2, 3, or 4, the race is played as a team game (the team members can add up their laps)"
+
+// cts
+set g_cts 0 "CTS: complete the stage"
+
+// nexball
+set g_nexball 0 "Nexball: Basketball and Soccer go Xonotic"
+
+set g_nexball_basketball_effects_default 8 "default: dim light. The original version used 1024 (fire) but it gives bad performance"
+set g_balance_nexball_primary_speed 1000 "launching speed"
+set g_balance_nexball_primary_refire 0.7 "launching refire"
+set g_balance_nexball_primary_animtime 0.3 "launching animtime"
+set g_balance_nexball_secondary_animtime 0.3 "launching animtime"
+set g_balance_nexball_secondary_speed 3000 "stealing projectile speed"
+set g_balance_nexball_secondary_lifetime 0.15 "stealing projectile lifetime"
+set g_balance_nexball_secondary_force 500 "stealing projectile force"
+set g_balance_nexball_secondary_refire 0.6 "stealing projectile refire"
+set g_balance_nexball_secondary_animtime 0.3 "stealing projectile animtime"
+
+// -1: MrBougo's first try, not very playable but working...
+// The ball gets the player's velocity * 1.5 + a vertical boost
+// 0: Revenant style
+// Player's velocity + a boost where he's looking at + a boost
+// perpendicularly to the first boost, that is upwards relatively
+// to the view angle
+// 1: MrBougo's modded Rev style 1
+// The 2nd Rev boost is always vertical
+// 2: MrBougo's modded Rev style 2
+// The 1st Rev boost is always horizontal
+// The 2nd Rev boost is always vertical
+set g_nexball_football_physics 2 "0: Revenant's original movement, 1: 0 but half independant of aiming height, 2: 1 fully independant, -1: first recode try"
+set g_nexball_basketball_bouncefactor 0.6 "velocity loss when the ball bounces"
+set g_nexball_basketball_bouncestop 0.075 "speed at which the ball stops when it hits the ground (multiplied by sv_gravity)"
+set g_nexball_football_bouncefactor 0.6 "velocity loss when the ball bounces"
+set g_nexball_football_bouncestop 0.075 "speed at which the ball stops when it hits the ground (multiplied by sv_gravity)"
+
+set g_nexball_football_boost_forward 100 "forward velocity boost when the ball is touched"
+set g_nexball_football_boost_up 200 "vertical velocity boost when the ball is touched"
+
+set g_nexball_basketball_delay_hold 20 "time before a player who caught the ball loses it (anti-ballcamp)"
+set g_nexball_basketball_delay_hold_forteam 60 "time before a ball reset when a team holds the ball for too long"
+set g_nexball_basketball_teamsteal 1 "1 to allow players to steal from teammates, 0 to disallow"
+
+set g_nexball_basketball_carrier_highspeed 0.8 "speed multiplier for the ballcarrier"
+
+set g_nexball_meter_period 1 "time to make a full cycle on the power meter"
+set g_nexball_basketball_meter 1 "use the power meter for basketball"
+set g_nexball_basketball_meter_minpower 0.5 "minimal multiplier to the launching speed when using the power meter"
+set g_nexball_basketball_meter_maxpower 1.2 "maximal multiplier to the launching speed when using the power meter"
+
+set g_nexball_delay_goal 3 "delay between a goal and a ball reset"
+set g_nexball_delay_idle 10 "maximal idle time before a reset"
+set g_nexball_delay_start 3 "time the ball stands on its spawn before being released"
+set g_nexball_delay_collect 0.5 "time before the same player can catch the ball he launched"
+
+set g_nexball_sound_bounce 1 "bouncing sound (0: off)"
+
+set g_nexball_basketball_trail 1 "1 to leave a trail"
+set g_nexball_football_trail 0 "1 to leave a trail"
+set g_nexball_trail_color 254 "1-256 for different colors (Quake palette, 254 is white)"
+
+set g_nexball_radar_showallplayers 1 "1: show every player and the ball on the radar 0: only show teammates and the ball on the radar"
}
}
-float redflag_prev;
-float blueflag_prev;
-void carrierAnnouncer() {
- float stat_items, redflag, blueflag;
- float pickup;
- string item;
-
- if not(autocvar_cl_notify_carried_items)
- return;
-
- stat_items = getstati(STAT_ITEMS);
-
- redflag = (stat_items/IT_RED_FLAG_TAKEN) & 3;
- blueflag = (stat_items/IT_BLUE_FLAG_TAKEN) & 3;
-
- if (redflag == 3 && redflag != redflag_prev) {
- item = _("^1RED^7 flag");
- pickup = (redflag_prev == 2);
- }
-
- if (blueflag == 3 && blueflag != blueflag_prev) {
- item = _("^4BLUE^7 flag");
- pickup = (blueflag_prev == 2);
- }
-
- if (item)
- {
- if (pickup) {
- if (autocvar_cl_notify_carried_items & 2)
- centerprint_hud(sprintf(_("You picked up the %s!"), item));
- }
- else {
- if (autocvar_cl_notify_carried_items & 1)
- centerprint_hud(sprintf(_("You got the %s!"), item));
- }
- }
-
- blueflag_prev = blueflag;
- redflag_prev = redflag;
-}
-
void Announcer()
{
Announcer_Gamestart();
Announcer_Time();
- carrierAnnouncer();
}
void Announcer_Precache ()
float autocvar_cl_hidewaypoints;
float autocvar_cl_lockview;
float autocvar_cl_nogibs;
-float autocvar_cl_notify_carried_items;
float autocvar_cl_particlegibs;
float autocvar_cl_particles_oldnexbeam;
float autocvar_cl_particles_quality;
case "bckills": return CTX(_("SCO^bckills"));
case "bctime": return CTX(_("SCO^bctime"));
case "caps": return CTX(_("SCO^caps"));
+ case "captime": return CTX(_("SCO^captime"));
case "deaths": return CTX(_("SCO^deaths"));
case "destroyed": return CTX(_("SCO^destroyed"));
case "drops": return CTX(_("SCO^drops"));
print(_("^3kd^7 The kill-death ratio\n"));
print(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
print(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
+ print(_("^3captime^7 Time of fastest cap (CTF)\n"));
print(_("^3fckills^7 Number of flag carrier kills\n"));
print(_("^3returns^7 Number of flag returns\n"));
print(_("^3drops^7 Number of flag drops\n"));
case "as-defend": return _("Defend");
case "bluebase": return _("Blue base");
case "danger": return _("DANGER");
+ case "enemyflagcarrier": return _("Enemy carrier");
case "flagcarrier": return _("Flag carrier");
case "flagdropped": return _("Dropped flag");
case "helpme": return _("Help me!");
float IT_INVINCIBLE = 16384;
float IT_HEALTH = 32768;
// union:
- // for items:
- float IT_KEY1 = 131072;
- float IT_KEY2 = 262144;
- // for players:
- float IT_RED_FLAG_TAKEN = 32768;
- float IT_RED_FLAG_LOST = 65536;
- float IT_RED_FLAG_CARRING = 98304;
- float IT_BLUE_FLAG_TAKEN = 131072;
- float IT_BLUE_FLAG_LOST = 262144;
- float IT_BLUE_FLAG_CARRING = 393216;
+ // for items:
+ float IT_KEY1 = 131072;
+ float IT_KEY2 = 262144;
+ // for players:
+ float IT_RED_FLAG_TAKEN = 32768;
+ float IT_RED_FLAG_LOST = 65536;
+ float IT_RED_FLAG_CARRYING = 98304;
+ float IT_BLUE_FLAG_TAKEN = 131072;
+ float IT_BLUE_FLAG_LOST = 262144;
+ float IT_BLUE_FLAG_CARRYING = 393216;
// end
float IT_5HP = 524288;
float IT_25HP = 1048576;
--- /dev/null
+#define FLAG_MIN (PL_MIN + '0 0 -13')
+#define FLAG_MAX (PL_MAX + '0 0 -13')
+
+.entity basewaypoint;
+.entity sprite;
+entity ctf_worldflaglist; // CTF flags in the map
+.entity ctf_worldflagnext;
+.float dropperid;
+.float ctf_droptime;
+
+.float next_take_time; // the next time a player can pick up a flag (time + blah)
+ /// I used this, in part, to fix the looping score bug. - avirox
+//float FLAGSCORE_PICKUP = 1;
+//float FLAGSCORE_RETURN = 5; // returned by owner team
+//float FLAGSCORE_RETURNROGUE = 10; // returned by rogue team
+//float FLAGSCORE_CAPTURE = 5;
+
+#define FLAG_CARRY_POS '-15 0 7'
+
+.float ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
+
+float captureshield_min_negscore; // punish at -20 points
+float captureshield_max_ratio; // punish at most 30% of each team
+float captureshield_force; // push force of the shield
+
+float ctf_captureshield_shielded(entity p)
+{
+ float s, se;
+ entity e;
+ float players_worseeq, players_total;
+
+ if(captureshield_max_ratio <= 0)
+ return FALSE;
+
+ s = PlayerScore_Add(p, SP_SCORE, 0);
+ if(s >= -captureshield_min_negscore)
+ return FALSE;
+
+ players_total = players_worseeq = 0;
+ FOR_EACH_PLAYER(e)
+ {
+ if(e.team != p.team)
+ continue;
+ se = PlayerScore_Add(e, SP_SCORE, 0);
+ if(se <= s)
+ ++players_worseeq;
+ ++players_total;
+ }
+
+ // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
+ // use this rule here
+
+ if(players_worseeq >= players_total * captureshield_max_ratio)
+ return FALSE;
+
+ return TRUE;
+}
+
+void ctf_captureshield_update(entity p, float dir)
+{
+ float should;
+ if(dir == p.ctf_captureshielded) // 0: shield only, 1: unshield only
+ {
+ should = ctf_captureshield_shielded(p);
+ if(should != dir)
+ {
+ if(should)
+ {
+ Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
+ // TODO csqc notifier for this
+ }
+ else
+ {
+ Send_CSQC_Centerprint_Generic(p, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
+ // TODO csqc notifier for this
+ }
+ p.ctf_captureshielded = should;
+ }
+ }
+}
+
+float ctf_captureshield_customize()
+{
+ if not(other.ctf_captureshielded)
+ return FALSE;
+ if(self.team == other.team)
+ return FALSE;
+ return TRUE;
+}
+
+.float ctf_captureshield_touch_msgtime;
+void ctf_captureshield_touch()
+{
+ if not(other.ctf_captureshielded)
+ return;
+ if(self.team == other.team)
+ return;
+ vector mymid;
+ vector othermid;
+ mymid = (self.absmin + self.absmax) * 0.5;
+ othermid = (other.absmin + other.absmax) * 0.5;
+ Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * captureshield_force);
+ if (time - other.ctf_captureshield_touch_msgtime > 2)
+ Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
+ other.ctf_captureshield_touch_msgtime = time;
+}
+
+void ctf_flag_spawnstuff()
+{
+ entity e;
+ e = spawn();
+ e.enemy = self;
+ e.team = self.team;
+ e.touch = ctf_captureshield_touch;
+ e.customizeentityforclient = ctf_captureshield_customize;
+ e.classname = "ctf_captureshield";
+ e.effects = EF_ADDITIVE;
+ e.movetype = MOVETYPE_NOCLIP;
+ e.solid = SOLID_TRIGGER;
+ e.avelocity = '7 0 11';
+ setorigin(e, self.origin);
+ setmodel(e, "models/ctf/shield.md3");
+ e.scale = 0.5;
+ setsize(e, e.scale * e.mins, e.scale * e.maxs);
+
+ waypoint_spawnforitem_force(self, self.origin);
+ self.nearestwaypointtimeout = 0; // activate waypointing again
+ self.basewaypoint = self.nearestwaypoint;
+
+ if(self.team == COLOR_TEAM1)
+ WaypointSprite_SpawnFixed("redbase", self.origin + '0 0 61', self, sprite, RADARICON_FLAG, colormapPaletteColor(COLOR_TEAM1 - 1, FALSE));
+ else
+ WaypointSprite_SpawnFixed("bluebase", self.origin + '0 0 61', self, sprite, RADARICON_FLAG, colormapPaletteColor(COLOR_TEAM2 - 1, FALSE));
+}
+
+float ctf_score_value(string parameter)
+{
+ return cvar(strcat("g_ctf_personal", parameter));
+}
+
+void FakeTimeLimit(entity e, float t)
+{
+ msg_entity = e;
+ WriteByte(MSG_ONE, 3); // svc_updatestat
+ WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
+ if(t < 0)
+ WriteCoord(MSG_ONE, autocvar_timelimit);
+ else
+ WriteCoord(MSG_ONE, (t + 1) / 60);
+}
+
+float flagcaptimerecord;
+.float flagpickuptime;
+//.float iscommander;
+//.float ctf_state;
+
+void() FlagThink;
+void() FlagTouch;
+
+void place_flag()
+{
+ if(self.classname != "item_flag_team")
+ {
+ backtrace("PlaceFlag a non-flag");
+ return;
+ }
+
+ setattachment(self, world, "");
+ self.mdl = self.model;
+ self.flags = FL_ITEM | FL_NOTARGET;
+ self.solid = SOLID_TRIGGER;
+ self.movetype = MOVETYPE_NONE;
+ self.velocity = '0 0 0';
+ self.origin_z = self.origin_z + 6;
+ self.think = FlagThink;
+ self.touch = FlagTouch;
+ self.nextthink = time + 0.1;
+ self.cnt = FLAG_BASE;
+ self.mangle = self.angles;
+ self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
+ //self.effects = self.effects | EF_DIMLIGHT;
+ if(self.noalign)
+ {
+ self.dropped_origin = self.origin;
+ }
+ else
+ {
+ droptofloor();
+ self.movetype = MOVETYPE_TOSS;
+ }
+
+ InitializeEntity(self, ctf_flag_spawnstuff, INITPRIO_SETLOCATION);
+}
+
+void LogCTF(string mode, float flagteam, entity actor)
+{
+ string s;
+ if(!autocvar_sv_eventlog)
+ return;
+ s = strcat(":ctf:", mode);
+ s = strcat(s, ":", ftos(flagteam));
+ if(actor != world)
+ s = strcat(s, ":", ftos(actor.playerid));
+ GameLogEcho(s);
+}
+
+void RegenFlag(entity e)
+{
+ if(e.classname != "item_flag_team")
+ {
+ backtrace("RegenFlag a non-flag");
+ return;
+ }
+
+ if(e.waypointsprite_attachedforcarrier)
+ WaypointSprite_DetachCarrier(e);
+
+ setattachment(e, world, "");
+ e.damageforcescale = 0;
+ e.takedamage = DAMAGE_NO;
+ e.movetype = MOVETYPE_NONE;
+ if(!e.noalign)
+ e.movetype = MOVETYPE_TOSS;
+ e.velocity = '0 0 0';
+ e.solid = SOLID_TRIGGER;
+ // TODO: play a sound here
+ setorigin(e, e.dropped_origin);
+ e.angles = e.mangle;
+ e.cnt = FLAG_BASE;
+ e.owner = world;
+ e.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND and any other junk
+}
+
+void ReturnFlag(entity e)
+{
+ if(e.classname != "item_flag_team")
+ {
+ backtrace("ReturnFlag a non-flag");
+ return;
+ }
+
+ if (e.owner)
+ if (e.owner.flagcarried == e)
+ {
+ WaypointSprite_DetachCarrier(e.owner);
+ e.owner.flagcarried = world;
+
+ if(e.speedrunning)
+ FakeTimeLimit(e.owner, -1);
+ }
+ e.owner = world;
+ RegenFlag(e);
+}
+
+void DropFlag(entity e, entity penalty_receiver, entity attacker)
+{
+ entity p;
+
+ if(e.classname != "item_flag_team")
+ {
+ backtrace("DropFlag a non-flag");
+ return;
+ }
+
+ if(e.speedrunning)
+ {
+ ReturnFlag(e);
+ return;
+ }
+
+ if (!e.owner)
+ {
+ dprint("FLAG: drop - no owner?!?!\n");
+ return;
+ }
+ p = e.owner;
+ if (p.flagcarried != e)
+ {
+ dprint("FLAG: drop - owner is not carrying this flag??\n");
+ return;
+ }
+ //bprint(p.netname, "^7 lost the ", e.netname, "\n");
+ Send_KillNotification (p.netname, e.netname, "", INFO_LOSTFLAG, MSG_INFO);
+
+ if(penalty_receiver)
+ UpdateFrags(penalty_receiver, -ctf_score_value("penalty_suicidedrop"));
+ else
+ UpdateFrags(p, -ctf_score_value("penalty_drop"));
+ PlayerScore_Add(p, SP_CTF_DROPS, +1);
+ ctf_captureshield_update(p, 0); // shield only
+ e.playerid = attacker.playerid;
+ e.ctf_droptime = time;
+ WaypointSprite_Spawn("flagdropped", 0, 0, e, '0 0 1' * 61, world, COLOR_TEAM1 + COLOR_TEAM2 - e.team, e, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAG, '0 1 1');
+ WaypointSprite_Ping(e.waypointsprite_attachedforcarrier);
+
+ if(p.waypointsprite_attachedforcarrier)
+ {
+ WaypointSprite_DetachCarrier(p);
+ }
+ else
+ {
+ bprint("\{1}^1Flag carrier had no flag sprite?!?\n");
+ backtrace("Flag carrier had no flag sprite?!?");
+ }
+ LogCTF("dropped", p.team, p);
+ sound (p, CH_TRIGGER, self.noise4, VOL_BASE, ATTN_NONE);
+
+ setattachment(e, world, "");
+ e.damageforcescale = autocvar_g_balance_ctf_damageforcescale;
+ e.takedamage = DAMAGE_YES;
+
+ if (p.flagcarried == e)
+ p.flagcarried = world;
+ e.owner = world;
+
+ e.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND and any other junk
+ e.solid = SOLID_TRIGGER;
+ e.movetype = MOVETYPE_TOSS;
+ // setsize(e, '-16 -16 0', '16 16 74');
+ setorigin(e, p.origin - '0 0 24' + '0 0 37');
+ e.cnt = FLAG_DROPPED;
+ e.velocity = '0 0 300';
+ e.pain_finished = time + autocvar_g_ctf_flag_returntime;//30;
+
+ trace_startsolid = FALSE;
+ tracebox(e.origin, e.mins, e.maxs, e.origin, TRUE, e);
+ if(trace_startsolid)
+ dprint("FLAG FALLTHROUGH will happen SOON\n");
+}
+
+void FlagThink()
+{
+ entity e;
+
+ self.nextthink = time + 0.1;
+
+ // sorry, we have to reset the flag size if it got squished by something
+ if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX)
+ {
+ // if we can grow back, grow back
+ tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
+ if(!trace_startsolid)
+ setsize(self, FLAG_MIN, FLAG_MAX);
+ }
+
+ if(self == ctf_worldflaglist) // only for the first flag
+ {
+ FOR_EACH_CLIENT(e)
+ ctf_captureshield_update(e, 1); // release shield only
+ }
+
+ if(self.speedrunning)
+ if(self.cnt == FLAG_CARRY)
+ {
+ if(self.owner)
+ if(flagcaptimerecord)
+ if(time >= self.flagpickuptime + flagcaptimerecord)
+ {
+ bprint("The ", self.netname, " became impatient after ", ftos_decimals(flagcaptimerecord, 2), " seconds and returned itself\n");
+
+ sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
+ self.owner.impulse = 141; // returning!
+
+ e = self;
+ self = self.owner;
+ ReturnFlag(e);
+ ImpulseCommands();
+ self = e;
+ return;
+ }
+ }
+
+ if (self.cnt == FLAG_BASE)
+ return;
+
+ if (self.cnt == FLAG_DROPPED)
+ {
+ // flag fallthrough? FIXME remove this if bug is really fixed now
+ if(self.origin_z < -131072)
+ {
+ dprint("FLAG FALLTHROUGH just happened\n");
+ self.pain_finished = 0;
+ }
+ setattachment(self, world, "");
+ if (time > self.pain_finished)
+ {
+ bprint("The ", self.netname, " has returned to base\n");
+ sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
+ LogCTF("returned", self.team, world);
+ ReturnFlag(self);
+ }
+ return;
+ }
+
+ e = self.owner;
+ if (e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
+ {
+ dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
+ DropFlag(self, world, world);
+ return;
+ }
+}
+
+float ctf_usekey()
+{
+ if(self.flagcarried)
+ {
+ DropFlag(self.flagcarried, self, world);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void flag_cap_ring_spawn(vector org)
+{
+ shockwave_spawn("models/ctf/shockwavetransring.md3", org - '0 0 15', -0.8, 0, 1);
+}
+
+// TODO add FlagDamage, replace weird hurttrigger handling inside trigger_hurt code by it
+void FlagTouch()
+{
+ if(gameover) return;
+
+ float t;
+ entity player;
+ string s, s0, h0, h1;
+
+ if (self.cnt == FLAG_CARRY)
+ return;
+
+ if (self.cnt == FLAG_DROPPED)
+ {
+ if(ITEM_TOUCH_NEEDKILL())
+ {
+ self.pain_finished = 0; // return immediately
+ return;
+ }
+ }
+
+ if (other.classname != "player")
+ return;
+ if (other.health < 1) // ignore dead players
+ return;
+
+ if (self.cnt == FLAG_BASE)
+ if (other.team == self.team)
+ if (other.flagcarried) // he's got a flag
+ if (other.flagcarried.team != self.team) // capture
+ {
+ if (other.flagcarried == world)
+ {
+ return;
+ }
+ if(autocvar_g_ctf_captimerecord_always || player_count - currentbots <= 1) // at most one human
+ {
+ t = time - other.flagcarried.flagpickuptime;
+ s = ftos_decimals(t, 2);
+ s0 = ftos_decimals(flagcaptimerecord, 2);
+ h0 = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
+ h1 = other.netname;
+ if(h0 == h1)
+ h0 = "their";
+ else
+ h0 = strcat(h0, "^7's"); // h0: display text for previous netname
+ if (flagcaptimerecord == 0)
+ {
+ s = strcat(" in ", s, " seconds");
+ flagcaptimerecord = t;
+ db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
+ db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
+ write_recordmarker(other, time - t, t);
+ }
+ else if (t < flagcaptimerecord)
+ {
+ s = strcat(" in ", s, " seconds, breaking ", h0, " previous record of ", s0, " seconds");
+ flagcaptimerecord = t;
+ db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
+ db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
+ write_recordmarker(other, time - t, t);
+ }
+ else
+ {
+ s = strcat(" in ", s, " seconds, failing to break ", h0, " record of ", s0, " seconds");
+ }
+ }
+ else
+ s = "";
+
+ Send_KillNotification (other.netname, other.flagcarried.netname, s, INFO_CAPTUREFLAG, MSG_INFO);
+
+ PlayerTeamScore_Add(other, SP_CTF_CAPS, ST_CTF_CAPS, 1);
+ LogCTF("capture", other.flagcarried.team, other);
+ // give credit to the individual player
+ UpdateFrags(other, ctf_score_value("score_capture"));
+
+ if (autocvar_g_ctf_flag_capture_effects) {
+ if (other.team == COLOR_TEAM1) { // red team scores effect
+ pointparticles(particleeffectnum("red_ground_quake"), self.origin, '0 0 0', 1);
+ flag_cap_ring_spawn(self.origin);
+ }
+ if (other.team == COLOR_TEAM2) { // blue team scores effect
+ pointparticles(particleeffectnum("blue_ground_quake"), self.origin, '0 0 0', 1);
+ flag_cap_ring_spawn(self.origin);
+ }
+ }
+
+ sound (other, CH_TRIGGER, self.noise2, VOL_BASE, ATTN_NONE);
+ WaypointSprite_DetachCarrier(other);
+ if(self.speedrunning)
+ FakeTimeLimit(other, -1);
+ RegenFlag (other.flagcarried);
+ other.flagcarried = world;
+ other.next_take_time = time + 1;
+ }
+ if (self.cnt == FLAG_BASE)
+ if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2) // only red and blue team can steal flags
+ if (other.team != self.team)
+ if (!other.flagcarried)
+ if (!other.ctf_captureshielded)
+ {
+ if(MUTATOR_CALLHOOK(ItemTouch))
+ return;
+
+ if (other.next_take_time > time)
+ return;
+
+ if (autocvar_g_ctf_flag_pickup_effects) // pickup effect
+ pointparticles(particleeffectnum("smoke_ring"), 0.5 * (self.absmin + self.absmax), '0 0 0', 1);
+
+ // pick up
+ self.flagpickuptime = time; // used for timing runs
+ self.speedrunning = other.speedrunning; // if speedrunning, flag will self-return and teleport the owner back after the record
+ if(other.speedrunning)
+ if(flagcaptimerecord)
+ FakeTimeLimit(other, time + flagcaptimerecord);
+ self.solid = SOLID_NOT;
+ setorigin(self, self.origin); // relink
+ self.owner = other;
+ other.flagcarried = self;
+ self.cnt = FLAG_CARRY;
+ self.angles = '0 0 0';
+ //bprint(other.netname, "^7 got the ", self.netname, "\n");
+ Send_KillNotification (other.netname, self.netname, "", INFO_GOTFLAG, MSG_INFO);
+ UpdateFrags(other, ctf_score_value("score_pickup_base"));
+ self.dropperid = other.playerid;
+ PlayerScore_Add(other, SP_CTF_PICKUPS, 1);
+ LogCTF("steal", self.team, other);
+ sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NONE);
+
+ FOR_EACH_PLAYER(player)
+ if(player.team == self.team)
+ centerprint(player, "The enemy got your flag! Retrieve it!");
+
+ self.movetype = MOVETYPE_NONE;
+ setorigin(self, FLAG_CARRY_POS);
+ setattachment(self, other, "");
+ WaypointSprite_AttachCarrier("flagcarrier", other, RADARICON_FLAGCARRIER, '1 1 0');
+ WaypointSprite_Ping(self.sprite);
+
+ return;
+ }
+
+ if (self.cnt == FLAG_DROPPED)
+ {
+ self.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND and any other junk
+ if (other.team == self.team || (other.team != COLOR_TEAM1 && other.team != COLOR_TEAM2))
+ {
+ // return flag
+ Send_KillNotification (other.netname, self.netname, "", INFO_RETURNFLAG, MSG_INFO);
+ //bprint(other.netname, "^7 returned the ", self.netname, "\n");
+
+ // punish the player who last had it
+ FOR_EACH_PLAYER(player)
+ if(player.playerid == self.dropperid)
+ {
+ PlayerScore_Add(player, SP_SCORE, -ctf_score_value("penalty_returned"));
+ ctf_captureshield_update(player, 0); // shield only
+ }
+
+ // punish the team who was last carrying it
+ if(self.team == COLOR_TEAM1)
+ TeamScore_AddToTeam(COLOR_TEAM2, ST_SCORE, -ctf_score_value("penalty_returned"));
+ else
+ TeamScore_AddToTeam(COLOR_TEAM1, ST_SCORE, -ctf_score_value("penalty_returned"));
+
+ // reward the player who returned it
+ if(other.playerid == self.playerid) // is this the guy who killed the FC last?
+ {
+ if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2)
+ UpdateFrags(other, ctf_score_value("score_return_by_killer"));
+ else
+ UpdateFrags(other, ctf_score_value("score_return_rogue_by_killer"));
+ }
+ else
+ {
+ if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2)
+ UpdateFrags(other, ctf_score_value("score_return"));
+ else
+ UpdateFrags(other, ctf_score_value("score_return_rogue"));
+ }
+ PlayerScore_Add(other, SP_CTF_RETURNS, 1);
+ LogCTF("return", self.team, other);
+ sound (other, CH_TRIGGER, self.noise1, VOL_BASE, ATTN_NONE);
+ ReturnFlag(self);
+ }
+ else if (!other.flagcarried && (other.playerid != self.dropperid || time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect))
+ {
+ if(self.waypointsprite_attachedforcarrier)
+ WaypointSprite_DetachCarrier(self);
+
+ if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
+ pointparticles(particleeffectnum("smoke_ring"), 0.5 * (self.absmin + self.absmax), '0 0 0', 1);
+
+ // pick up
+ self.solid = SOLID_NOT;
+ setorigin(self, self.origin); // relink
+ self.owner = other;
+ other.flagcarried = self;
+ self.cnt = FLAG_CARRY;
+ Send_KillNotification (other.netname, self.netname, "", INFO_PICKUPFLAG, MSG_INFO);
+ //bprint(other.netname, "^7 picked up the ", self.netname, "\n");
+
+ float f;
+ f = bound(0, (self.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1);
+ //print("factor is ", ftos(f), "\n");
+ f = ctf_score_value("score_pickup_dropped_late") * (1-f)
+ + ctf_score_value("score_pickup_dropped_early") * f;
+ f = floor(f + 0.5);
+ self.dropperid = other.playerid;
+ //print("score is ", ftos(f), "\n");
+
+ UpdateFrags(other, f);
+ PlayerScore_Add(other, SP_CTF_PICKUPS, 1);
+ LogCTF("pickup", self.team, other);
+ sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NONE);
+
+ FOR_EACH_PLAYER(player)
+ if(player.team == self.team)
+ centerprint(player, "The enemy got your flag! Retrieve it!");
+
+ self.movetype = MOVETYPE_NONE; // flag must have MOVETYPE_NONE here, otherwise it will drop through the floor...
+ setorigin(self, FLAG_CARRY_POS);
+ setattachment(self, other, "");
+ self.damageforcescale = 0;
+ self.takedamage = DAMAGE_NO;
+ WaypointSprite_AttachCarrier("flagcarrier", other, RADARICON_FLAGCARRIER, '1 1 0');
+ }
+ }
+}
+
+/*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
+CTF Starting point for a player
+in team one (Red).
+
+Keys:
+"angle"
+ viewing angle when spawning
+*/
+void spawnfunc_info_player_team1()
+{
+ if(g_assault)
+ {
+ remove(self);
+ return;
+ }
+ self.team = COLOR_TEAM1; // red
+ spawnfunc_info_player_deathmatch();
+}
+//self.team = 4;self.classname = "info_player_start";spawnfunc_info_player_start();}
+
+/*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
+CTF Starting point for a player in
+team two (Blue).
+
+Keys:
+"angle"
+ viewing angle when spawning
+*/
+void spawnfunc_info_player_team2()
+{
+ if(g_assault)
+ {
+ remove(self);
+ return;
+ }
+ self.team = COLOR_TEAM2; // blue
+ spawnfunc_info_player_deathmatch();
+}
+//self.team = 13;self.classname = "info_player_start";spawnfunc_info_player_start();}
+
+/*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
+CTF Starting point for a player in
+team three (Yellow).
+
+Keys:
+"angle"
+ viewing angle when spawning
+*/
+void spawnfunc_info_player_team3()
+{
+ if(g_assault)
+ {
+ remove(self);
+ return;
+ }
+ self.team = COLOR_TEAM3; // yellow
+ spawnfunc_info_player_deathmatch();
+}
+
+
+/*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
+CTF Starting point for a player in
+team four (Magenta).
+
+Keys:
+"angle"
+ viewing angle when spawning
+*/
+void spawnfunc_info_player_team4()
+{
+ if(g_assault)
+ {
+ remove(self);
+ return;
+ }
+ self.team = COLOR_TEAM4; // purple
+ spawnfunc_info_player_deathmatch();
+}
+
+void item_flag_reset()
+{
+ DropFlag(self, world, world);
+ if(self.waypointsprite_attachedforcarrier)
+ WaypointSprite_DetachCarrier(self);
+ ReturnFlag(self);
+}
+
+void item_flag_postspawn()
+{ // Check CTF Item Flag Post Spawn
+
+ // Flag Glow Trail Support
+ if(autocvar_g_ctf_flag_glowtrails)
+ { // Provide Flag Glow Trail
+ if(self.team == COLOR_TEAM1)
+ // Red
+ self.glow_color = 251;
+ else
+ if(self.team == COLOR_TEAM2)
+ // Blue
+ self.glow_color = 210;
+
+ self.glow_size = 25;
+ self.glow_trail = 1;
+ }
+}
+
+/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team one (Red).
+Multiple are allowed.
+
+Keys:
+"angle"
+ Angle the flag will point
+(minus 90 degrees)
+"model"
+ model to use, note this needs red and blue as skins 0 and 1
+ (default models/ctf/flag.md3)
+"noise"
+ sound played when flag is picked up
+ (default ctf/take.wav)
+"noise1"
+ sound played when flag is returned by a teammate
+ (default ctf/return.wav)
+"noise2"
+ sound played when flag is captured
+ (default ctf/redcapture.wav)
+"noise3"
+ sound played when flag is lost in the field and respawns itself
+ (default ctf/respawn.wav)
+*/
+
+void spawnfunc_item_flag_team2();
+void spawnfunc_item_flag_team1()
+{
+ if (!g_ctf)
+ {
+ remove(self);
+ return;
+ }
+
+ if (g_ctf_reverse)
+ {
+ float old_g_ctf_reverse = g_ctf_reverse;
+ g_ctf_reverse = 0; // avoid an endless loop
+ spawnfunc_item_flag_team2();
+ g_ctf_reverse = old_g_ctf_reverse;
+ return;
+ }
+
+ // link flag into ctf_worldflaglist
+ self.ctf_worldflagnext = ctf_worldflaglist;
+ ctf_worldflaglist = self;
+
+ self.classname = "item_flag_team";
+ self.team = COLOR_TEAM1; // color 4 team (red)
+ self.items = IT_KEY2; // gold key (redish enough)
+ self.netname = "^1RED^7 flag";
+ self.target = "###item###";
+ self.skin = autocvar_g_ctf_flag_red_skin;
+ if(self.spawnflags & 1)
+ self.noalign = 1;
+ if (!self.model)
+ self.model = autocvar_g_ctf_flag_red_model;
+ if (!self.noise)
+ self.noise = "ctf/red_taken.wav";
+ if (!self.noise1)
+ self.noise1 = "ctf/red_returned.wav";
+ if (!self.noise2)
+ self.noise2 = "ctf/red_capture.wav"; // blue team scores by capturing the red flag
+ if (!self.noise3)
+ self.noise3 = "ctf/flag_respawn.wav";
+ if (!self.noise4)
+ self.noise4 = "ctf/red_dropped.wav";
+ precache_model (self.model);
+ setmodel (self, self.model); // precision set below
+ precache_sound (self.noise);
+ precache_sound (self.noise1);
+ precache_sound (self.noise2);
+ precache_sound (self.noise3);
+ precache_sound (self.noise4);
+ //setsize(self, '-16 -16 -37', '16 16 37');
+ setsize(self, FLAG_MIN, FLAG_MAX);
+ setorigin(self, self.origin + '0 0 37');
+ self.nextthink = time + 0.2; // start after doors etc
+ self.think = place_flag;
+
+ if(!self.scale)
+ self.scale = 0.6;
+ //if(!self.glow_size)
+ // self.glow_size = 50;
+
+ self.effects = self.effects | EF_LOWPRECISION;
+ if(autocvar_g_ctf_fullbrightflags)
+ self.effects |= EF_FULLBRIGHT;
+ if(autocvar_g_ctf_dynamiclights)
+ self.effects |= EF_RED;
+
+ // From Spidflisk
+ item_flag_postspawn();
+
+ precache_model("models/ctf/shield.md3");
+ precache_model("models/ctf/shockwavetransring.md3");
+
+ self.reset = item_flag_reset;
+}
+
+/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -24) (48 48 64)
+CTF flag for team two (Blue).
+Multiple are allowed.
+
+Keys:
+"angle"
+ Angle the flag will point
+(minus 90 degrees)
+"model"
+ model to use, note this needs red and blue as skins 0 and 1
+ (default models/ctf/flag.md3)
+"noise"
+ sound played when flag is picked up
+ (default ctf/take.wav)
+"noise1"
+ sound played when flag is returned by a teammate
+ (default ctf/return.wav)
+"noise2"
+ sound played when flag is captured
+ (default ctf/bluecapture.wav)
+"noise3"
+ sound played when flag is lost in the field and respawns itself
+ (default ctf/respawn.wav)
+*/
+
+void spawnfunc_item_flag_team2()
+{
+ if (!g_ctf)
+ {
+ remove(self);
+ return;
+ }
+
+ if (g_ctf_reverse)
+ {
+ float old_g_ctf_reverse = g_ctf_reverse;
+ g_ctf_reverse = 0; // avoid an endless loop
+ spawnfunc_item_flag_team1();
+ g_ctf_reverse = old_g_ctf_reverse;
+ return;
+ }
+
+ // link flag into ctf_worldflaglist
+ self.ctf_worldflagnext = ctf_worldflaglist;
+ ctf_worldflaglist = self;
+
+ self.classname = "item_flag_team";
+ self.team = COLOR_TEAM2; // color 13 team (blue)
+ self.items = IT_KEY1; // silver key (bluish enough)
+ self.netname = "^4BLUE^7 flag";
+ self.target = "###item###";
+ self.skin = autocvar_g_ctf_flag_blue_skin;
+ if(self.spawnflags & 1)
+ self.noalign = 1;
+ if (!self.model)
+ self.model = autocvar_g_ctf_flag_blue_model;
+ if (!self.noise)
+ self.noise = "ctf/blue_taken.wav";
+ if (!self.noise1)
+ self.noise1 = "ctf/blue_returned.wav";
+ if (!self.noise2)
+ self.noise2 = "ctf/blue_capture.wav"; // blue team scores by capturing the red flag
+ if (!self.noise3)
+ self.noise3 = "ctf/flag_respawn.wav";
+ if (!self.noise4)
+ self.noise4 = "ctf/blue_dropped.wav";
+ precache_model (self.model);
+ setmodel (self, self.model); // precision set below
+ precache_sound (self.noise);
+ precache_sound (self.noise1);
+ precache_sound (self.noise2);
+ precache_sound (self.noise3);
+ precache_sound (self.noise4);
+ //setsize(self, '-16 -16 -37', '16 16 37');
+ setsize(self, FLAG_MIN, FLAG_MAX);
+ setorigin(self, self.origin + '0 0 37');
+ self.nextthink = time + 0.2; // start after doors etc
+ self.think = place_flag;
+
+ if(!self.scale)
+ self.scale = 0.6;
+ //if(!self.glow_size)
+ // self.glow_size = 50;
+
+ self.effects = self.effects | EF_LOWPRECISION;
+ if(autocvar_g_ctf_fullbrightflags)
+ self.effects |= EF_FULLBRIGHT;
+ if(autocvar_g_ctf_dynamiclights)
+ self.effects |= EF_BLUE;
+
+ // From Spidflisk
+ item_flag_postspawn();
+
+ precache_model("models/ctf/shield.md3");
+ precache_model("models/ctf/shockwavetransring.md3");
+
+ self.reset = item_flag_reset;
+}
+
+
+/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
+Team declaration for CTF gameplay, this allows you to decide what team
+names and control point models are used in your map.
+
+Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike
+domination, you don't need to make a blank one too.
+
+Keys:
+"netname"
+ Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)
+"cnt"
+ Scoreboard color of the team (for example 4 is red and 13 is blue)
+
+*/
+
+void spawnfunc_ctf_team()
+{
+ if (!g_ctf)
+ {
+ remove(self);
+ return;
+ }
+ self.classname = "ctf_team";
+ self.team = self.cnt + 1;
+}
+
+// code from here on is just to support maps that don't have control point and team entities
+void ctf_spawnteam (string teamname, float teamcolor)
+{
+ entity oldself;
+ oldself = self;
+ self = spawn();
+ self.classname = "ctf_team";
+ self.netname = teamname;
+ self.cnt = teamcolor;
+
+ spawnfunc_ctf_team();
+
+ self = oldself;
+}
+
+// spawn some default teams if the map is not set up for ctf
+void ctf_spawnteams()
+{
+ float numteams;
+
+ numteams = 2;//cvar("g_ctf_default_teams");
+
+ ctf_spawnteam("Red", COLOR_TEAM1 - 1);
+ ctf_spawnteam("Blue", COLOR_TEAM2 - 1);
+}
+
+void ctf_delayedinit()
+{
+ // if no teams are found, spawn defaults
+ if (find(world, classname, "ctf_team") == world)
+ ctf_spawnteams();
+
+ ScoreRules_ctf();
+}
+
+void ctf_init()
+{
+ InitializeEntity(world, ctf_delayedinit, INITPRIO_GAMETYPE);
+ flagcaptimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
+
+ captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
+ captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
+ captureshield_force = autocvar_g_ctf_shield_force;
+}
+
+void ctf_setstatus2(entity flag, float shift)
+{
+ if (flag.cnt == FLAG_CARRY)
+ if (flag.owner == self)
+ self.items |= shift * 3;
+ else
+ self.items |= shift * 1;
+ else if (flag.cnt == FLAG_DROPPED)
+ self.items |= shift * 2;
+ else
+ {
+ // no status bits
+ }
+}
+
+void ctf_setstatus()
+{
+ self.items &~= IT_RED_FLAG_TAKEN;
+ self.items &~= IT_RED_FLAG_LOST;
+ self.items &~= IT_BLUE_FLAG_TAKEN;
+ self.items &~= IT_BLUE_FLAG_LOST;
+ self.items &~= IT_CTF_SHIELDED;
+
+ entity flag;
+ float redflags, blueflags;
+
+ if(self.ctf_captureshielded)
+ self.items |= IT_CTF_SHIELDED;
+
+ redflags = 0;
+ blueflags = 0;
+
+ for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
+ {
+ if(flag.items & IT_KEY2) // blue
+ ++redflags;
+ else if(flag.items & IT_KEY1) // red
+ ++blueflags;
+ }
+
+ // blinking magic: if there is more than one flag, show one of these in a clever way
+ if(redflags)
+ redflags = mod(floor(time * redflags * 0.75), redflags);
+ if(blueflags)
+ blueflags = mod(floor(time * blueflags * 0.75), blueflags);
+
+ for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
+ {
+ if(flag.items & IT_KEY2) // blue
+ {
+ if(--redflags == -1) // happens exactly once (redflags is in 0..count-1, and will --'ed count times)
+ ctf_setstatus2(flag, IT_RED_FLAG_TAKEN);
+ }
+ else if(flag.items & IT_KEY1) // red
+ {
+ if(--blueflags == -1) // happens exactly once
+ ctf_setstatus2(flag, IT_BLUE_FLAG_TAKEN);
+ }
+ }
+}
+/*
+entity ctf_team_has_commander(float cteam)
+{
+ entity pl;
+ if(cteam != COLOR_TEAM1 || cteam != COLOR_TEAM2)
+ return world;
+
+ FOR_EACH_REALPLAYER(pl) {
+ if(pl.team == cteam && pl.iscommander) {
+ return pl;
+ }
+ }
+ return world;
+}
+
+void ctf_setstate(entity e, float st)
+{
+ e.ctf_state = st;
+ ++e.version;
+}
+
+void ctf_new_commander(float cteam)
+{
+ entity pl, plmax;
+
+ plmax = world;
+ FOR_EACH_REALPLAYER(pl) {
+ if(pl.team == cteam) {
+ if(pl.iscommander) { // don't reassign if alreay there
+ return;
+ }
+ if(plmax == world || plmax.frags < pl.frags) <<<<<<<<<<<<<<<<< BROKEN in new scoring system
+ plmax = pl;
+ }
+ }
+ if(plmax == world) {
+ bprint(strcat(ColoredTeamName(cteam), " Team has no Commander!\n"));
+ return;
+ }
+
+ plmax.iscommander = TRUE;
+ ctf_setstate(plmax, 3);
+ sprint(plmax, "^3You're the commander now!\n");
+ centerprint(plmax, "^3You're the commander now!\n");
+}
+
+void ctf_clientconnect()
+{
+ self.iscommander = FALSE;
+
+ if(!self.team || self.classname != "player") {
+ ctf_setstate(self, -1);
+ } else
+ ctf_setstate(self, 0);
+
+ self.team_saved = self.team;
+
+ if(self.team != 0 && self.classname == "player" && !ctf_team_has_commander(self.team)) {
+ ctf_new_commander(self.team);
+ }
+}
+
+void ctf_playerchanged()
+{
+ if(!self.team || self.classname != "player") {
+ ctf_setstate(self, -1);
+ } else if(self.ctf_state < 0 && self.classname == "player") {
+ ctf_setstate(self, 0);
+ }
+
+ if(self.iscommander &&
+ (self.classname != "player" || self.team != self.team_saved)
+ )
+ {
+ self.iscommander = FALSE;
+ if(self.classname == "player")
+ ctf_setstate(self, 0);
+ else
+ ctf_setstate(self, -1);
+ ctf_new_commander(self.team_saved);
+ }
+
+ self.team_saved = self.team;
+
+ ctf_new_commander(self.team);
+}
+
+void ctf_clientdisconnect()
+{
+ if(self.iscommander)
+ {
+ ctf_new_commander(self.team);
+ }
+}
+
+entity GetPlayer(string);
+float ctf_clientcommand()
+{
+ entity e;
+ if(argv(0) == "order") {
+ if(!g_ctf) {
+ sprint(self, "This command is not supported in this gamemode.\n");
+ return TRUE;
+ }
+ if(!self.iscommander) {
+ sprint(self, "^1You are not the commander!\n");
+ return TRUE;
+ }
+ if(argv(2) == "") {
+ sprint(self, "Usage: order #player status - (playernumber as in status)\n");
+ return TRUE;
+ }
+ e = GetPlayer(argv(1));
+ if(e == world) {
+ sprint(self, "Invalid player.\nUsage: order #player status - (playernumber as in status)\n");
+ return TRUE;
+ }
+ if(e.team != self.team) {
+ sprint(self, "^3You can only give orders to your own team!\n");
+ return TRUE;
+ }
+ if(argv(2) == "attack") {
+ sprint(self, strcat("Ordering ", e.netname, " to attack!\n"));
+ sprint(e, "^1Attack!\n");
+ centerprint(e, "^7You've been ordered to^9\n^1Attack!\n");
+ ctf_setstate(e, 1);
+ } else if(argv(2) == "defend") {
+ sprint(self, strcat("Ordering ", e.netname, " to defend!\n"));
+ sprint(e, "^Defend!\n");
+ centerprint(e, "^7You've been ordered to^9\n^2Defend!\n");
+ ctf_setstate(e, 2);
+ } else {
+ sprint(self, "^7Invalid command, use ^3attack^7, or ^3defend^7.\n");
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+*/
// spread think times so they don't all happen at same time
self.nextthink = self.nextthink + random()*0.5 + 0.1;
self.iscreature = TRUE;
+ self.teleportable = TELEPORT_NORMAL;
self.damagedbycontents = TRUE;
force_retouch = 2; // mainly to detect teleports
}
}
self.iscreature = TRUE;
+ self.teleportable = TELEPORT_NORMAL;
self.damagedbycontents = TRUE;
force_retouch = 2; // mainly to detect teleports
}
}
self.iscreature = TRUE;
+ self.teleportable = TELEPORT_NORMAL;
self.damagedbycontents = TRUE;
force_retouch = 2; // mainly to detect teleports
.float teamtime;
+.float nb_dropperid;
+.float nb_droptime;
+
void nb_delayedinit();
void nb_init() // Called early (worldspawn stage)
{
ball.owner = ball.pusher = plyr; //"owner" is set to the player carrying, "pusher" to the last player who touched it
ball.team = plyr.team;
plyr.ballcarried = ball;
- ball.dropperid = plyr.playerid;
+ ball.nb_dropperid = plyr.playerid;
plyr.effects |= g_nexball_basketball_effects_default;
ball.effects &~= g_nexball_basketball_effects_default;
ball.flags &~= FL_ONGROUND;
ball.scale = ball_scale;
ball.velocity = vel;
- ball.ctf_droptime = time;
+ ball.nb_droptime = time;
ball.touch = basketball_touch;
ball.think = ResetBall;
ball.nextthink = min(time + g_nexball_delay_idle, ball.teamtime);
football_touch();
return;
}
- if (!self.cnt && other.classname == "player" && (other.playerid != self.dropperid || time > self.ctf_droptime + autocvar_g_nexball_delay_collect)) {
+ if (!self.cnt && other.classname == "player" && (other.playerid != self.nb_dropperid || time > self.nb_droptime + autocvar_g_nexball_delay_collect)) {
if (other.health <= 0)
return;
LogNB("caught", other);
float autocvar_g_balance_crylink_secondary_spread;
float autocvar_g_balance_crylink_reload_ammo;
float autocvar_g_balance_crylink_reload_time;
-float autocvar_g_balance_ctf_damageforcescale;
-float autocvar_g_balance_ctf_delay_collect;
float autocvar_g_balance_curse_empathy_minhealth;
float autocvar_g_balance_curse_empathy_takedamage;
float autocvar_g_balance_curse_slow_atkrate;
float autocvar_g_chat_flood_spl_tell;
float autocvar_g_chat_nospectators;
float autocvar_g_chat_teamcolors;
+float autocvar_g_ctf_allow_vehicle_touch;
+float autocvar_g_ctf_drop;
+float autocvar_g_ctf_drop_strengthmultiplier;
+float autocvar_g_ctf_drop_velocity;
+float autocvar_g_ctf_portalteleport;
+float autocvar_g_ctf_pass;
+float autocvar_g_ctf_pass_radius;
+float autocvar_g_ctf_pass_wait;
+float autocvar_g_ctf_pass_request;
+float autocvar_g_ctf_pass_turnrate;
+float autocvar_g_ctf_pass_timelimit;
+float autocvar_g_ctf_pass_velocity;
float autocvar_g_ctf_captimerecord_always;
float autocvar_g_ctf_dynamiclights;
string autocvar_g_ctf_flag_blue_model;
float autocvar_g_ctf_flag_blue_skin;
float autocvar_g_ctf_flag_capture_effects;
+float autocvar_g_ctf_flag_collect_delay;
+float autocvar_g_ctf_flag_damageforcescale;
+float autocvar_g_ctf_flag_dropped_waypoint;
+float autocvar_g_ctf_flag_dropped_floatinwater;
float autocvar_g_ctf_flag_glowtrails;
+float autocvar_g_ctf_flag_health;
float autocvar_g_ctf_flag_pickup_effects;
+float autocvar_g_ctf_flag_pickup_verbosename;
string autocvar_g_ctf_flag_red_model;
float autocvar_g_ctf_flag_red_skin;
-float autocvar_g_ctf_flag_returntime;
-float autocvar_g_ctf_flagcarrier_selfdamage;
-float autocvar_g_ctf_flagcarrier_selfforce;
+float autocvar_g_ctf_flag_return_time;
+float autocvar_g_ctf_flag_return_when_unreachable;
+float autocvar_g_ctf_flag_return_damage;
+float autocvar_g_ctf_flag_return_dropped;
+float autocvar_g_ctf_flagcarrier_auto_helpme_when_damaged;
+float autocvar_g_ctf_flagcarrier_allow_vehicle_carry;
+float autocvar_g_ctf_flagcarrier_selfdamagefactor;
+float autocvar_g_ctf_flagcarrier_selfforcefactor;
+float autocvar_g_ctf_flagcarrier_damagefactor;
+float autocvar_g_ctf_flagcarrier_forcefactor;
+float autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate;
+float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
float autocvar_g_ctf_fullbrightflags;
float autocvar_g_ctf_ignore_frags;
+float autocvar_g_ctf_score_capture;
+float autocvar_g_ctf_score_capture_assist;
+float autocvar_g_ctf_score_kill;
+float autocvar_g_ctf_score_penalty_drop;
+float autocvar_g_ctf_score_penalty_suicidedrop;
+float autocvar_g_ctf_score_penalty_returned;
+float autocvar_g_ctf_score_pickup_base;
+float autocvar_g_ctf_score_pickup_dropped_early;
+float autocvar_g_ctf_score_pickup_dropped_late;
+float autocvar_g_ctf_score_return;
float autocvar_g_ctf_shield_force;
float autocvar_g_ctf_shield_max_ratio;
float autocvar_g_ctf_shield_min_negscore;
+float autocvar_g_ctf_reverse;
+float autocvar_g_ctf_dropped_capture_radius;
float autocvar_g_cts_finish_kill_delay;
float autocvar_g_cts_selfdamage;
float autocvar_g_debug_bot_commands;
.float havocbot_cantfindflag;
.float havocbot_role_timeout;
.entity ctf_worldflagnext;
-.entity basewaypoint;
+.entity bot_basewaypoint;
entity ctf_worldflaglist;
vector havocbot_ctf_middlepoint;
if not(head)
return;
- navigation_routerating(head.basewaypoint, ratingscale, 10000);
+ navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
}
void havocbot_goalrating_ctf_enemyflag(float ratingscale)
if not(head)
return;
- navigation_routerating(head.basewaypoint, ratingscale, 10000);
+ navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
}
void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
mf = havocbot_ctf_find_flag(self);
- if(mf.cnt == FLAG_BASE)
+ if(mf.ctf_status == FLAG_BASE)
return;
if(mf.tag_entity)
while (head)
{
// flag is out in the field
- if(head.cnt != FLAG_BASE)
+ if(head.ctf_status != FLAG_BASE)
if(head.tag_entity==world) // dropped
{
if(radius)
// If enemy flag is back on the base switch to previous role
ef = havocbot_ctf_find_enemy_flag(self);
- if(ef.cnt==FLAG_BASE)
+ if(ef.ctf_status==FLAG_BASE)
{
self.havocbot_role = self.havocbot_previous_role;
self.havocbot_role_timeout = 0;
// If the flag carrier reached the base switch to defense
mf = havocbot_ctf_find_flag(self);
- if(mf.cnt!=FLAG_BASE)
+ if(mf.ctf_status!=FLAG_BASE)
if(vlen(ef.origin - mf.dropped_origin) < 300)
{
havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
ef = havocbot_ctf_find_enemy_flag(self);
// Own flag stolen
- if(mf.cnt!=FLAG_BASE)
+ if(mf.ctf_status!=FLAG_BASE)
{
if(mf.tag_entity)
pos = mf.tag_entity.origin;
}
// Escort flag carrier
- if(ef.cnt!=FLAG_BASE)
+ if(ef.ctf_status!=FLAG_BASE)
{
if(ef.tag_entity)
pos = ef.tag_entity.origin;
// If flag is back on the base switch to previous role
mf = havocbot_ctf_find_flag(self);
- if(mf.cnt==FLAG_BASE)
+ if(mf.ctf_status==FLAG_BASE)
{
havocbot_ctf_reset_role(self);
return;
}
mf = havocbot_ctf_find_flag(self);
- if(mf.cnt!=FLAG_BASE)
+ if(mf.ctf_status!=FLAG_BASE)
{
havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
return;
// If own flag was captured
mf = havocbot_ctf_find_flag(self);
- if(mf.cnt!=FLAG_BASE)
+ if(mf.ctf_status!=FLAG_BASE)
{
havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
return;
ef = havocbot_ctf_find_enemy_flag(bot);
// Retrieve stolen flag
- if(mf.cnt!=FLAG_BASE)
+ if(mf.ctf_status!=FLAG_BASE)
{
havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
return;
}
// If enemy flag is taken go to the middle to intercept pursuers
- if(ef.cnt!=FLAG_BASE)
+ if(ef.ctf_status!=FLAG_BASE)
{
havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
return;
self.oldvelocity = self.velocity = self.personal.velocity;
self.angles = self.personal.v_angle;
self.fixangle = TRUE;
- if(self.flagcarried)
- {
- bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
- ReturnFlag(self.flagcarried);
- }
- }
- if(g_ctf)
- {
- self.ammo_rockets = 999;
- self.ammo_nails = 999;
- self.ammo_cells = 999;
- self.ammo_shells = 999;
- self.ammo_fuel = 999;
- self.health = start_health;
- self.armorvalue = start_armorvalue;
- WEPSET_OR_EA(self.personal, weaponsInMap);
- self.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn;
- self.pauserothealth_finished = time + autocvar_g_balance_pause_health_rot_spawn;
- self.pauserotfuel_finished = time + autocvar_g_balance_pause_fuel_rot_spawn;
- self.pauseregen_finished = time + autocvar_g_balance_pause_health_regen_spawn;
- self.strength_finished = 0;
- self.invincible_finished = 0;
- }
- else
- {
- self.ammo_rockets = self.personal.ammo_rockets;
- self.ammo_nails = self.personal.ammo_nails;
- self.ammo_cells = self.personal.ammo_cells;
- self.ammo_shells = self.personal.ammo_shells;
- self.ammo_fuel = self.personal.ammo_fuel;
- self.health = self.personal.health;
- self.armorvalue = self.personal.armorvalue;
- WEPSET_COPY_EE(self, self.personal);
- self.items = self.personal.items;
- self.pauserotarmor_finished = time + self.personal.pauserotarmor_finished - self.personal.teleport_time;
- self.pauserothealth_finished = time + self.personal.pauserothealth_finished - self.personal.teleport_time;
- self.pauserotfuel_finished = time + self.personal.pauserotfuel_finished - self.personal.teleport_time;
- self.pauseregen_finished = time + self.personal.pauseregen_finished - self.personal.teleport_time;
- self.strength_finished = time + self.personal.strength_finished - self.personal.teleport_time;
- self.invincible_finished = time + self.personal.invincible_finished - self.personal.teleport_time;
+
+ MUTATOR_CALLHOOK(AbortSpeedrun);
}
+
+ self.ammo_rockets = self.personal.ammo_rockets;
+ self.ammo_nails = self.personal.ammo_nails;
+ self.ammo_cells = self.personal.ammo_cells;
+ self.ammo_shells = self.personal.ammo_shells;
+ self.ammo_fuel = self.personal.ammo_fuel;
+ self.health = self.personal.health;
+ self.armorvalue = self.personal.armorvalue;
+ WEPSET_COPY_EE(self, self.personal);
+ self.items = self.personal.items;
+ self.pauserotarmor_finished = time + self.personal.pauserotarmor_finished - self.personal.teleport_time;
+ self.pauserothealth_finished = time + self.personal.pauserothealth_finished - self.personal.teleport_time;
+ self.pauserotfuel_finished = time + self.personal.pauserotfuel_finished - self.personal.teleport_time;
+ self.pauseregen_finished = time + self.personal.pauseregen_finished - self.personal.teleport_time;
+ self.strength_finished = time + self.personal.strength_finished - self.personal.teleport_time;
+ self.invincible_finished = time + self.personal.invincible_finished - self.personal.teleport_time;
+
DID_CHEAT();
break;
}
if(self.vehicle)
vehicles_exit(VHEF_RELESE);
- if(self.flagcarried)
- DropFlag(self.flagcarried, world, world);
-
WaypointSprite_PlayerDead();
if not(g_ca) // don't reset teams when moving a ca player to the spectators
self.classname = "observer";
self.iscreature = FALSE;
+ self.teleportable = TELEPORT_SIMPLE;
self.damagedbycontents = FALSE;
self.health = -666;
self.takedamage = DAMAGE_NO;
Called when a client spawns in the server
=============
*/
-//void() ctf_playerchanged;
void PutClientInServer (void)
{
self.classname = "player";
self.wasplayer = TRUE;
self.iscreature = TRUE;
+ self.teleportable = TELEPORT_NORMAL;
self.damagedbycontents = TRUE;
self.movetype = MOVETYPE_WALK;
self.solid = SOLID_SLIDEBOX;
} else if(self.classname == "observer") {
PutObserverInServer ();
}
-
- //if(g_ctf)
- // ctf_playerchanged();
}
.float ebouncefactor, ebouncestop; // electro's values
Called when a client connects to the server
=============
*/
-//void ctf_clientconnect();
string ColoredTeamName(float t);
void DecodeLevelParms (void);
//void dom_player_join_team(entity pl);
if(g_arena)
Spawnqueue_Insert(self);
}
- /*else if(g_ctf)
- {
- ctf_clientconnect();
- }*/
attach_entcs();
Portal_ClearAll(self);
RemoveGrapplingHook(self);
- if(self.flagcarried)
- DropFlag(self.flagcarried, world, world);
// Here, everything has been done that requires this player to be a client.
self.flags |= FL_CLIENT | FL_NOTARGET;
}
-float ctf_usekey();
void PlayerUseKey()
{
if(self.classname != "player")
}
// a use key was pressed; call handlers
- if(ctf_usekey())
- return;
-
MUTATOR_CALLHOOK(PlayerUseKey);
}
=============
*/
.float usekeypressed;
-void() ctf_setstatus;
void() nexball_setstatus;
.float items_added;
void PlayerPreThink (void)
if(frametime)
player_anim();
- if(g_ctf)
- ctf_setstatus();
-
if(g_nexball)
nexball_setstatus();
case 33:
if(self.deadflag == DEAD_NO && teamplay)
{
- wp = WaypointSprite_Attach("helpme", TRUE, RADARICON_HELPME, '1 0.5 0');
- if(!wp)
- WaypointSprite_HelpMePing(self.waypointsprite_attachedforcarrier);
- else
- WaypointSprite_Ping(wp);
+ if not(MUTATOR_CALLHOOK(HelpMePing))
+ {
+ wp = WaypointSprite_Attach("helpme", TRUE, RADARICON_HELPME, '1 0.5 0');
+ if(!wp)
+ WaypointSprite_HelpMePing(self.waypointsprite_attachedforcarrier);
+ else
+ WaypointSprite_Ping(wp);
+ }
sprint(self, "HELP ME attached\n");
}
break;
self.lip = oldself.lip;
self.colormap = oldself.colormap;
self.iscreature = oldself.iscreature;
+ self.teleportable = oldself.teleportable;
self.damagedbycontents = oldself.damagedbycontents;
self.angles = oldself.angles;
self.avelocity = oldself.avelocity;
RemoveGrapplingHook(self);
- if(self.flagcarried)
- {
- if(attacker.classname != "player")
- DropFlag(self.flagcarried, self, attacker); // penalty for flag loss by suicide
- else if(attacker.team == self.team)
- DropFlag(self.flagcarried, attacker, attacker); // penalty for flag loss by suicide/teamkill
- else
- DropFlag(self.flagcarried, world, attacker);
- }
Portal_ClearAllLater(self);
if(clienttype(self) == CLIENTTYPE_REAL)
return output;
}
-// switch between sprint and print depending on whether the reciever is the server or a player
+// switch between sprint and print depending on whether the receiver is the server or a player
void print_to(entity to, string input)
{
if(to)
if(successful)
bprint("Successfully sent message '", admin_message, "' to ", successful, ".\n");
else
- print("No players given (", original_targets, ") could recieve the message.\n");
+ print("No players given (", original_targets, ") could receive the message.\n");
return;
}
float TE_BEAM = 13; // grappling hook
-// CTF
-float FLAG_BASE = 1;
-float FLAG_CARRY = 2;
-float FLAG_DROPPED = 3;
-
float COLOR_TEAM1 = 5; // red
float COLOR_TEAM2 = 14; // blue
float COLOR_TEAM3 = 13; // yellow
+++ /dev/null
-#define FLAG_MIN (PL_MIN + '0 0 -13')
-#define FLAG_MAX (PL_MAX + '0 0 -13')
-
-.entity basewaypoint;
-.entity sprite;
-entity ctf_worldflaglist; // CTF flags in the map
-.entity ctf_worldflagnext;
-.float dropperid;
-.float ctf_droptime;
-
-.float next_take_time; // the next time a player can pick up a flag (time + blah)
- /// I used this, in part, to fix the looping score bug. - avirox
-//float FLAGSCORE_PICKUP = 1;
-//float FLAGSCORE_RETURN = 5; // returned by owner team
-//float FLAGSCORE_RETURNROGUE = 10; // returned by rogue team
-//float FLAGSCORE_CAPTURE = 5;
-
-#define FLAG_CARRY_POS '-15 0 7'
-
-.float ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
-
-float captureshield_min_negscore; // punish at -20 points
-float captureshield_max_ratio; // punish at most 30% of each team
-float captureshield_force; // push force of the shield
-
-float ctf_captureshield_shielded(entity p)
-{
- float s, se;
- entity e;
- float players_worseeq, players_total;
-
- if(captureshield_max_ratio <= 0)
- return FALSE;
-
- s = PlayerScore_Add(p, SP_SCORE, 0);
- if(s >= -captureshield_min_negscore)
- return FALSE;
-
- players_total = players_worseeq = 0;
- FOR_EACH_PLAYER(e)
- {
- if(e.team != p.team)
- continue;
- se = PlayerScore_Add(e, SP_SCORE, 0);
- if(se <= s)
- ++players_worseeq;
- ++players_total;
- }
-
- // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
- // use this rule here
-
- if(players_worseeq >= players_total * captureshield_max_ratio)
- return FALSE;
-
- return TRUE;
-}
-
-void ctf_captureshield_update(entity p, float dir)
-{
- float should;
- if(dir == p.ctf_captureshielded) // 0: shield only, 1: unshield only
- {
- should = ctf_captureshield_shielded(p);
- if(should != dir)
- {
- if(should)
- {
- Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
- // TODO csqc notifier for this
- }
- else
- {
- Send_CSQC_Centerprint_Generic(p, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
- // TODO csqc notifier for this
- }
- p.ctf_captureshielded = should;
- }
- }
-}
-
-float ctf_captureshield_customize()
-{
- if not(other.ctf_captureshielded)
- return FALSE;
- if(self.team == other.team)
- return FALSE;
- return TRUE;
-}
-
-.float ctf_captureshield_touch_msgtime;
-void ctf_captureshield_touch()
-{
- if not(other.ctf_captureshielded)
- return;
- if(self.team == other.team)
- return;
- vector mymid;
- vector othermid;
- mymid = (self.absmin + self.absmax) * 0.5;
- othermid = (other.absmin + other.absmax) * 0.5;
- Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * captureshield_force);
- if (time - other.ctf_captureshield_touch_msgtime > 2)
- Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
- other.ctf_captureshield_touch_msgtime = time;
-}
-
-void ctf_flag_spawnstuff()
-{
- entity e;
- e = spawn();
- e.enemy = self;
- e.team = self.team;
- e.touch = ctf_captureshield_touch;
- e.customizeentityforclient = ctf_captureshield_customize;
- e.classname = "ctf_captureshield";
- e.effects = EF_ADDITIVE;
- e.movetype = MOVETYPE_NOCLIP;
- e.solid = SOLID_TRIGGER;
- e.avelocity = '7 0 11';
- setorigin(e, self.origin);
- setmodel(e, "models/ctf/shield.md3");
- e.scale = 0.5;
- setsize(e, e.scale * e.mins, e.scale * e.maxs);
-
- waypoint_spawnforitem_force(self, self.origin);
- self.nearestwaypointtimeout = 0; // activate waypointing again
- self.basewaypoint = self.nearestwaypoint;
-
- if(self.team == COLOR_TEAM1)
- WaypointSprite_SpawnFixed("redbase", self.origin + '0 0 61', self, sprite, RADARICON_FLAG, colormapPaletteColor(COLOR_TEAM1 - 1, FALSE));
- else
- WaypointSprite_SpawnFixed("bluebase", self.origin + '0 0 61', self, sprite, RADARICON_FLAG, colormapPaletteColor(COLOR_TEAM2 - 1, FALSE));
-}
-
-float ctf_score_value(string parameter)
-{
- return cvar(strcat("g_ctf_personal", parameter));
-}
-
-void FakeTimeLimit(entity e, float t)
-{
- msg_entity = e;
- WriteByte(MSG_ONE, 3); // svc_updatestat
- WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
- if(t < 0)
- WriteCoord(MSG_ONE, autocvar_timelimit);
- else
- WriteCoord(MSG_ONE, (t + 1) / 60);
-}
-
-float flagcaptimerecord;
-.float flagpickuptime;
-//.float iscommander;
-//.float ctf_state;
-
-void() FlagThink;
-void() FlagTouch;
-
-void place_flag()
-{
- if(self.classname != "item_flag_team")
- {
- backtrace("PlaceFlag a non-flag");
- return;
- }
-
- setattachment(self, world, "");
- self.mdl = self.model;
- self.flags = FL_ITEM | FL_NOTARGET;
- self.solid = SOLID_TRIGGER;
- self.movetype = MOVETYPE_NONE;
- self.velocity = '0 0 0';
- self.origin_z = self.origin_z + 6;
- self.think = FlagThink;
- self.touch = FlagTouch;
- self.nextthink = time + 0.1;
- self.cnt = FLAG_BASE;
- self.mangle = self.angles;
- self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
- //self.effects = self.effects | EF_DIMLIGHT;
- if(self.noalign)
- {
- self.dropped_origin = self.origin;
- }
- else
- {
- droptofloor();
- self.movetype = MOVETYPE_TOSS;
- }
-
- InitializeEntity(self, ctf_flag_spawnstuff, INITPRIO_SETLOCATION);
-}
-
-void LogCTF(string mode, float flagteam, entity actor)
-{
- string s;
- if(!autocvar_sv_eventlog)
- return;
- s = strcat(":ctf:", mode);
- s = strcat(s, ":", ftos(flagteam));
- if(actor != world)
- s = strcat(s, ":", ftos(actor.playerid));
- GameLogEcho(s);
-}
-
-void RegenFlag(entity e)
-{
- if(e.classname != "item_flag_team")
- {
- backtrace("RegenFlag a non-flag");
- return;
- }
-
- if(e.waypointsprite_attachedforcarrier)
- WaypointSprite_DetachCarrier(e);
-
- setattachment(e, world, "");
- e.damageforcescale = 0;
- e.takedamage = DAMAGE_NO;
- e.movetype = MOVETYPE_NONE;
- if(!e.noalign)
- e.movetype = MOVETYPE_TOSS;
- e.velocity = '0 0 0';
- e.solid = SOLID_TRIGGER;
- // TODO: play a sound here
- setorigin(e, e.dropped_origin);
- e.angles = e.mangle;
- e.cnt = FLAG_BASE;
- e.owner = world;
- e.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND and any other junk
-}
-
-void ReturnFlag(entity e)
-{
- if(e.classname != "item_flag_team")
- {
- backtrace("ReturnFlag a non-flag");
- return;
- }
-
- if (e.owner)
- if (e.owner.flagcarried == e)
- {
- WaypointSprite_DetachCarrier(e.owner);
- e.owner.flagcarried = world;
-
- if(e.speedrunning)
- FakeTimeLimit(e.owner, -1);
- }
- e.owner = world;
- RegenFlag(e);
-}
-
-void DropFlag(entity e, entity penalty_receiver, entity attacker)
-{
- entity p;
-
- if(e.classname != "item_flag_team")
- {
- backtrace("DropFlag a non-flag");
- return;
- }
-
- if(e.speedrunning)
- {
- ReturnFlag(e);
- return;
- }
-
- if (!e.owner)
- {
- dprint("FLAG: drop - no owner?!?!\n");
- return;
- }
- p = e.owner;
- if (p.flagcarried != e)
- {
- dprint("FLAG: drop - owner is not carrying this flag??\n");
- return;
- }
- //bprint(p.netname, "^7 lost the ", e.netname, "\n");
- Send_KillNotification (p.netname, e.netname, "", INFO_LOSTFLAG, MSG_INFO);
-
- if(penalty_receiver)
- UpdateFrags(penalty_receiver, -ctf_score_value("penalty_suicidedrop"));
- else
- UpdateFrags(p, -ctf_score_value("penalty_drop"));
- PlayerScore_Add(p, SP_CTF_DROPS, +1);
- ctf_captureshield_update(p, 0); // shield only
- e.playerid = attacker.playerid;
- e.ctf_droptime = time;
- WaypointSprite_Spawn("flagdropped", 0, 0, e, '0 0 1' * 61, world, COLOR_TEAM1 + COLOR_TEAM2 - e.team, e, waypointsprite_attachedforcarrier, FALSE, RADARICON_FLAG, '0 1 1');
- WaypointSprite_Ping(e.waypointsprite_attachedforcarrier);
-
- if(p.waypointsprite_attachedforcarrier)
- {
- WaypointSprite_DetachCarrier(p);
- }
- else
- {
- bprint("\{1}^1Flag carrier had no flag sprite?!?\n");
- backtrace("Flag carrier had no flag sprite?!?");
- }
- LogCTF("dropped", p.team, p);
- sound (p, CH_TRIGGER, self.noise4, VOL_BASE, ATTN_NONE);
-
- setattachment(e, world, "");
- e.damageforcescale = autocvar_g_balance_ctf_damageforcescale;
- e.takedamage = DAMAGE_YES;
-
- if (p.flagcarried == e)
- p.flagcarried = world;
- e.owner = world;
-
- e.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND and any other junk
- e.solid = SOLID_TRIGGER;
- e.movetype = MOVETYPE_TOSS;
- // setsize(e, '-16 -16 0', '16 16 74');
- setorigin(e, p.origin - '0 0 24' + '0 0 37');
- e.cnt = FLAG_DROPPED;
- e.velocity = '0 0 300';
- e.pain_finished = time + autocvar_g_ctf_flag_returntime;//30;
-
- trace_startsolid = FALSE;
- tracebox(e.origin, e.mins, e.maxs, e.origin, TRUE, e);
- if(trace_startsolid)
- dprint("FLAG FALLTHROUGH will happen SOON\n");
-}
-
-void FlagThink()
-{
- entity e;
-
- self.nextthink = time + 0.1;
-
- // sorry, we have to reset the flag size if it got squished by something
- if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX)
- {
- // if we can grow back, grow back
- tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
- if(!trace_startsolid)
- setsize(self, FLAG_MIN, FLAG_MAX);
- }
-
- if(self == ctf_worldflaglist) // only for the first flag
- {
- FOR_EACH_CLIENT(e)
- ctf_captureshield_update(e, 1); // release shield only
- }
-
- if(self.speedrunning)
- if(self.cnt == FLAG_CARRY)
- {
- if(self.owner)
- if(flagcaptimerecord)
- if(time >= self.flagpickuptime + flagcaptimerecord)
- {
- bprint("The ", self.netname, " became impatient after ", ftos_decimals(flagcaptimerecord, 2), " seconds and returned itself\n");
-
- sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
- self.owner.impulse = 141; // returning!
-
- e = self;
- self = self.owner;
- ReturnFlag(e);
- ImpulseCommands();
- self = e;
- return;
- }
- }
-
- if (self.cnt == FLAG_BASE)
- return;
-
- if (self.cnt == FLAG_DROPPED)
- {
- // flag fallthrough? FIXME remove this if bug is really fixed now
- if(self.origin_z < -131072)
- {
- dprint("FLAG FALLTHROUGH just happened\n");
- self.pain_finished = 0;
- }
- setattachment(self, world, "");
- if (time > self.pain_finished)
- {
- bprint("The ", self.netname, " has returned to base\n");
- sound (self, CH_TRIGGER, self.noise3, VOL_BASE, ATTN_NONE);
- LogCTF("returned", self.team, world);
- ReturnFlag(self);
- }
- return;
- }
-
- e = self.owner;
- if (e.classname != "player" || (e.deadflag) || (e.flagcarried != self))
- {
- dprint("CANNOT HAPPEN - player dead and STILL had a flag!\n");
- DropFlag(self, world, world);
- return;
- }
-}
-
-float ctf_usekey()
-{
- if(self.flagcarried)
- {
- DropFlag(self.flagcarried, self, world);
- return TRUE;
- }
- return FALSE;
-}
-
-void flag_cap_ring_spawn(vector org)
-{
- shockwave_spawn("models/ctf/shockwavetransring.md3", org - '0 0 15', -0.8, 0, 1);
-}
-
-// TODO add FlagDamage, replace weird hurttrigger handling inside trigger_hurt code by it
-void FlagTouch()
-{
- if(gameover) return;
-
- float t;
- entity player;
- string s, s0, h0, h1;
-
- if (self.cnt == FLAG_CARRY)
- return;
-
- if (self.cnt == FLAG_DROPPED)
- {
- if(ITEM_TOUCH_NEEDKILL())
- {
- self.pain_finished = 0; // return immediately
- return;
- }
- }
-
- if (other.classname != "player")
- return;
- if (other.health < 1) // ignore dead players
- return;
-
- if (self.cnt == FLAG_BASE)
- if (other.team == self.team)
- if (other.flagcarried) // he's got a flag
- if (other.flagcarried.team != self.team) // capture
- {
- if (other.flagcarried == world)
- {
- return;
- }
- if(autocvar_g_ctf_captimerecord_always || player_count - currentbots <= 1) // at most one human
- {
- t = time - other.flagcarried.flagpickuptime;
- s = ftos_decimals(t, 2);
- s0 = ftos_decimals(flagcaptimerecord, 2);
- h0 = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
- h1 = other.netname;
- if(h0 == h1)
- h0 = "their";
- else
- h0 = strcat(h0, "^7's"); // h0: display text for previous netname
- if (flagcaptimerecord == 0)
- {
- s = strcat(" in ", s, " seconds");
- flagcaptimerecord = t;
- db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
- db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
- write_recordmarker(other, time - t, t);
- }
- else if (t < flagcaptimerecord)
- {
- s = strcat(" in ", s, " seconds, breaking ", h0, " previous record of ", s0, " seconds");
- flagcaptimerecord = t;
- db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(t));
- db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), h1);
- write_recordmarker(other, time - t, t);
- }
- else
- {
- s = strcat(" in ", s, " seconds, failing to break ", h0, " record of ", s0, " seconds");
- }
- }
- else
- s = "";
-
- Send_KillNotification (other.netname, other.flagcarried.netname, s, INFO_CAPTUREFLAG, MSG_INFO);
-
- PlayerTeamScore_Add(other, SP_CTF_CAPS, ST_CTF_CAPS, 1);
- LogCTF("capture", other.flagcarried.team, other);
- // give credit to the individual player
- UpdateFrags(other, ctf_score_value("score_capture"));
-
- if (autocvar_g_ctf_flag_capture_effects) {
- if (other.team == COLOR_TEAM1) { // red team scores effect
- pointparticles(particleeffectnum("red_ground_quake"), self.origin, '0 0 0', 1);
- flag_cap_ring_spawn(self.origin);
- }
- if (other.team == COLOR_TEAM2) { // blue team scores effect
- pointparticles(particleeffectnum("blue_ground_quake"), self.origin, '0 0 0', 1);
- flag_cap_ring_spawn(self.origin);
- }
- }
-
- sound (other, CH_TRIGGER, self.noise2, VOL_BASE, ATTN_NONE);
- WaypointSprite_DetachCarrier(other);
- if(self.speedrunning)
- FakeTimeLimit(other, -1);
- RegenFlag (other.flagcarried);
- other.flagcarried = world;
- other.next_take_time = time + 1;
- }
- if (self.cnt == FLAG_BASE)
- if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2) // only red and blue team can steal flags
- if (other.team != self.team)
- if (!other.flagcarried)
- if (!other.ctf_captureshielded)
- {
- if(MUTATOR_CALLHOOK(ItemTouch))
- return;
-
- if (other.next_take_time > time)
- return;
-
- if (autocvar_g_ctf_flag_pickup_effects) // pickup effect
- pointparticles(particleeffectnum("smoke_ring"), 0.5 * (self.absmin + self.absmax), '0 0 0', 1);
-
- // pick up
- self.flagpickuptime = time; // used for timing runs
- self.speedrunning = other.speedrunning; // if speedrunning, flag will self-return and teleport the owner back after the record
- if(other.speedrunning)
- if(flagcaptimerecord)
- FakeTimeLimit(other, time + flagcaptimerecord);
- self.solid = SOLID_NOT;
- setorigin(self, self.origin); // relink
- self.owner = other;
- other.flagcarried = self;
- self.cnt = FLAG_CARRY;
- self.angles = '0 0 0';
- //bprint(other.netname, "^7 got the ", self.netname, "\n");
- Send_KillNotification (other.netname, self.netname, "", INFO_GOTFLAG, MSG_INFO);
- UpdateFrags(other, ctf_score_value("score_pickup_base"));
- self.dropperid = other.playerid;
- PlayerScore_Add(other, SP_CTF_PICKUPS, 1);
- LogCTF("steal", self.team, other);
- sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NONE);
-
- FOR_EACH_PLAYER(player)
- if(player.team == self.team)
- centerprint(player, "The enemy got your flag! Retrieve it!");
-
- self.movetype = MOVETYPE_NONE;
- setorigin(self, FLAG_CARRY_POS);
- setattachment(self, other, "");
- WaypointSprite_AttachCarrier("flagcarrier", other, RADARICON_FLAGCARRIER, '1 1 0');
- WaypointSprite_Ping(self.sprite);
-
- return;
- }
-
- if (self.cnt == FLAG_DROPPED)
- {
- self.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND and any other junk
- if (other.team == self.team || (other.team != COLOR_TEAM1 && other.team != COLOR_TEAM2))
- {
- // return flag
- Send_KillNotification (other.netname, self.netname, "", INFO_RETURNFLAG, MSG_INFO);
- //bprint(other.netname, "^7 returned the ", self.netname, "\n");
-
- // punish the player who last had it
- FOR_EACH_PLAYER(player)
- if(player.playerid == self.dropperid)
- {
- PlayerScore_Add(player, SP_SCORE, -ctf_score_value("penalty_returned"));
- ctf_captureshield_update(player, 0); // shield only
- }
-
- // punish the team who was last carrying it
- if(self.team == COLOR_TEAM1)
- TeamScore_AddToTeam(COLOR_TEAM2, ST_SCORE, -ctf_score_value("penalty_returned"));
- else
- TeamScore_AddToTeam(COLOR_TEAM1, ST_SCORE, -ctf_score_value("penalty_returned"));
-
- // reward the player who returned it
- if(other.playerid == self.playerid) // is this the guy who killed the FC last?
- {
- if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2)
- UpdateFrags(other, ctf_score_value("score_return_by_killer"));
- else
- UpdateFrags(other, ctf_score_value("score_return_rogue_by_killer"));
- }
- else
- {
- if (other.team == COLOR_TEAM1 || other.team == COLOR_TEAM2)
- UpdateFrags(other, ctf_score_value("score_return"));
- else
- UpdateFrags(other, ctf_score_value("score_return_rogue"));
- }
- PlayerScore_Add(other, SP_CTF_RETURNS, 1);
- LogCTF("return", self.team, other);
- sound (other, CH_TRIGGER, self.noise1, VOL_BASE, ATTN_NONE);
- ReturnFlag(self);
- }
- else if (!other.flagcarried && (other.playerid != self.dropperid || time > self.ctf_droptime + autocvar_g_balance_ctf_delay_collect))
- {
- if(self.waypointsprite_attachedforcarrier)
- WaypointSprite_DetachCarrier(self);
-
- if (autocvar_g_ctf_flag_pickup_effects) // field pickup effect
- pointparticles(particleeffectnum("smoke_ring"), 0.5 * (self.absmin + self.absmax), '0 0 0', 1);
-
- // pick up
- self.solid = SOLID_NOT;
- setorigin(self, self.origin); // relink
- self.owner = other;
- other.flagcarried = self;
- self.cnt = FLAG_CARRY;
- Send_KillNotification (other.netname, self.netname, "", INFO_PICKUPFLAG, MSG_INFO);
- //bprint(other.netname, "^7 picked up the ", self.netname, "\n");
-
- float f;
- f = bound(0, (self.pain_finished - time) / autocvar_g_ctf_flag_returntime, 1);
- //print("factor is ", ftos(f), "\n");
- f = ctf_score_value("score_pickup_dropped_late") * (1-f)
- + ctf_score_value("score_pickup_dropped_early") * f;
- f = floor(f + 0.5);
- self.dropperid = other.playerid;
- //print("score is ", ftos(f), "\n");
-
- UpdateFrags(other, f);
- PlayerScore_Add(other, SP_CTF_PICKUPS, 1);
- LogCTF("pickup", self.team, other);
- sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTN_NONE);
-
- FOR_EACH_PLAYER(player)
- if(player.team == self.team)
- centerprint(player, "The enemy got your flag! Retrieve it!");
-
- self.movetype = MOVETYPE_NONE; // flag must have MOVETYPE_NONE here, otherwise it will drop through the floor...
- setorigin(self, FLAG_CARRY_POS);
- setattachment(self, other, "");
- self.damageforcescale = 0;
- self.takedamage = DAMAGE_NO;
- WaypointSprite_AttachCarrier("flagcarrier", other, RADARICON_FLAGCARRIER, '1 1 0');
- }
- }
-}
-
-/*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
-CTF Starting point for a player
-in team one (Red).
-
-Keys:
-"angle"
- viewing angle when spawning
-*/
-void spawnfunc_info_player_team1()
-{
- if(g_assault)
- {
- remove(self);
- return;
- }
- self.team = COLOR_TEAM1; // red
- spawnfunc_info_player_deathmatch();
-}
-//self.team = 4;self.classname = "info_player_start";spawnfunc_info_player_start();}
-
-/*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
-CTF Starting point for a player in
-team two (Blue).
-
-Keys:
-"angle"
- viewing angle when spawning
-*/
-void spawnfunc_info_player_team2()
-{
- if(g_assault)
- {
- remove(self);
- return;
- }
- self.team = COLOR_TEAM2; // blue
- spawnfunc_info_player_deathmatch();
-}
-//self.team = 13;self.classname = "info_player_start";spawnfunc_info_player_start();}
-
-/*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
-CTF Starting point for a player in
-team three (Yellow).
-
-Keys:
-"angle"
- viewing angle when spawning
-*/
-void spawnfunc_info_player_team3()
-{
- if(g_assault)
- {
- remove(self);
- return;
- }
- self.team = COLOR_TEAM3; // yellow
- spawnfunc_info_player_deathmatch();
-}
-
-
-/*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
-CTF Starting point for a player in
-team four (Magenta).
-
-Keys:
-"angle"
- viewing angle when spawning
-*/
-void spawnfunc_info_player_team4()
-{
- if(g_assault)
- {
- remove(self);
- return;
- }
- self.team = COLOR_TEAM4; // purple
- spawnfunc_info_player_deathmatch();
-}
-
-void item_flag_reset()
-{
- DropFlag(self, world, world);
- if(self.waypointsprite_attachedforcarrier)
- WaypointSprite_DetachCarrier(self);
- ReturnFlag(self);
-}
-
-void item_flag_postspawn()
-{ // Check CTF Item Flag Post Spawn
-
- // Flag Glow Trail Support
- if(autocvar_g_ctf_flag_glowtrails)
- { // Provide Flag Glow Trail
- if(self.team == COLOR_TEAM1)
- // Red
- self.glow_color = 251;
- else
- if(self.team == COLOR_TEAM2)
- // Blue
- self.glow_color = 210;
-
- self.glow_size = 25;
- self.glow_trail = 1;
- }
-}
-
-/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
-CTF flag for team one (Red).
-Multiple are allowed.
-
-Keys:
-"angle"
- Angle the flag will point
-(minus 90 degrees)
-"model"
- model to use, note this needs red and blue as skins 0 and 1
- (default models/ctf/flag.md3)
-"noise"
- sound played when flag is picked up
- (default ctf/take.wav)
-"noise1"
- sound played when flag is returned by a teammate
- (default ctf/return.wav)
-"noise2"
- sound played when flag is captured
- (default ctf/redcapture.wav)
-"noise3"
- sound played when flag is lost in the field and respawns itself
- (default ctf/respawn.wav)
-*/
-
-void spawnfunc_item_flag_team2();
-void spawnfunc_item_flag_team1()
-{
- if (!g_ctf)
- {
- remove(self);
- return;
- }
-
- if (g_ctf_reverse)
- {
- float old_g_ctf_reverse = g_ctf_reverse;
- g_ctf_reverse = 0; // avoid an endless loop
- spawnfunc_item_flag_team2();
- g_ctf_reverse = old_g_ctf_reverse;
- return;
- }
-
- // link flag into ctf_worldflaglist
- self.ctf_worldflagnext = ctf_worldflaglist;
- ctf_worldflaglist = self;
-
- self.classname = "item_flag_team";
- self.team = COLOR_TEAM1; // color 4 team (red)
- self.items = IT_KEY2; // gold key (redish enough)
- self.netname = "^1RED^7 flag";
- self.target = "###item###";
- self.skin = autocvar_g_ctf_flag_red_skin;
- if(self.spawnflags & 1)
- self.noalign = 1;
- if (!self.model)
- self.model = autocvar_g_ctf_flag_red_model;
- if (!self.noise)
- self.noise = "ctf/red_taken.wav";
- if (!self.noise1)
- self.noise1 = "ctf/red_returned.wav";
- if (!self.noise2)
- self.noise2 = "ctf/red_capture.wav"; // blue team scores by capturing the red flag
- if (!self.noise3)
- self.noise3 = "ctf/flag_respawn.wav";
- if (!self.noise4)
- self.noise4 = "ctf/red_dropped.wav";
- precache_model (self.model);
- setmodel (self, self.model); // precision set below
- precache_sound (self.noise);
- precache_sound (self.noise1);
- precache_sound (self.noise2);
- precache_sound (self.noise3);
- precache_sound (self.noise4);
- //setsize(self, '-16 -16 -37', '16 16 37');
- setsize(self, FLAG_MIN, FLAG_MAX);
- setorigin(self, self.origin + '0 0 37');
- self.nextthink = time + 0.2; // start after doors etc
- self.think = place_flag;
-
- if(!self.scale)
- self.scale = 0.6;
- //if(!self.glow_size)
- // self.glow_size = 50;
-
- self.effects = self.effects | EF_LOWPRECISION;
- if(autocvar_g_ctf_fullbrightflags)
- self.effects |= EF_FULLBRIGHT;
- if(autocvar_g_ctf_dynamiclights)
- self.effects |= EF_RED;
-
- // From Spidflisk
- item_flag_postspawn();
-
- precache_model("models/ctf/shield.md3");
- precache_model("models/ctf/shockwavetransring.md3");
-
- self.reset = item_flag_reset;
-}
-
-/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -24) (48 48 64)
-CTF flag for team two (Blue).
-Multiple are allowed.
-
-Keys:
-"angle"
- Angle the flag will point
-(minus 90 degrees)
-"model"
- model to use, note this needs red and blue as skins 0 and 1
- (default models/ctf/flag.md3)
-"noise"
- sound played when flag is picked up
- (default ctf/take.wav)
-"noise1"
- sound played when flag is returned by a teammate
- (default ctf/return.wav)
-"noise2"
- sound played when flag is captured
- (default ctf/bluecapture.wav)
-"noise3"
- sound played when flag is lost in the field and respawns itself
- (default ctf/respawn.wav)
-*/
-
-void spawnfunc_item_flag_team2()
-{
- if (!g_ctf)
- {
- remove(self);
- return;
- }
-
- if (g_ctf_reverse)
- {
- float old_g_ctf_reverse = g_ctf_reverse;
- g_ctf_reverse = 0; // avoid an endless loop
- spawnfunc_item_flag_team1();
- g_ctf_reverse = old_g_ctf_reverse;
- return;
- }
-
- // link flag into ctf_worldflaglist
- self.ctf_worldflagnext = ctf_worldflaglist;
- ctf_worldflaglist = self;
-
- self.classname = "item_flag_team";
- self.team = COLOR_TEAM2; // color 13 team (blue)
- self.items = IT_KEY1; // silver key (bluish enough)
- self.netname = "^4BLUE^7 flag";
- self.target = "###item###";
- self.skin = autocvar_g_ctf_flag_blue_skin;
- if(self.spawnflags & 1)
- self.noalign = 1;
- if (!self.model)
- self.model = autocvar_g_ctf_flag_blue_model;
- if (!self.noise)
- self.noise = "ctf/blue_taken.wav";
- if (!self.noise1)
- self.noise1 = "ctf/blue_returned.wav";
- if (!self.noise2)
- self.noise2 = "ctf/blue_capture.wav"; // blue team scores by capturing the red flag
- if (!self.noise3)
- self.noise3 = "ctf/flag_respawn.wav";
- if (!self.noise4)
- self.noise4 = "ctf/blue_dropped.wav";
- precache_model (self.model);
- setmodel (self, self.model); // precision set below
- precache_sound (self.noise);
- precache_sound (self.noise1);
- precache_sound (self.noise2);
- precache_sound (self.noise3);
- precache_sound (self.noise4);
- //setsize(self, '-16 -16 -37', '16 16 37');
- setsize(self, FLAG_MIN, FLAG_MAX);
- setorigin(self, self.origin + '0 0 37');
- self.nextthink = time + 0.2; // start after doors etc
- self.think = place_flag;
-
- if(!self.scale)
- self.scale = 0.6;
- //if(!self.glow_size)
- // self.glow_size = 50;
-
- self.effects = self.effects | EF_LOWPRECISION;
- if(autocvar_g_ctf_fullbrightflags)
- self.effects |= EF_FULLBRIGHT;
- if(autocvar_g_ctf_dynamiclights)
- self.effects |= EF_BLUE;
-
- // From Spidflisk
- item_flag_postspawn();
-
- precache_model("models/ctf/shield.md3");
- precache_model("models/ctf/shockwavetransring.md3");
-
- self.reset = item_flag_reset;
-}
-
-
-/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
-Team declaration for CTF gameplay, this allows you to decide what team
-names and control point models are used in your map.
-
-Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike
-domination, you don't need to make a blank one too.
-
-Keys:
-"netname"
- Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)
-"cnt"
- Scoreboard color of the team (for example 4 is red and 13 is blue)
-
-*/
-
-void spawnfunc_ctf_team()
-{
- if (!g_ctf)
- {
- remove(self);
- return;
- }
- self.classname = "ctf_team";
- self.team = self.cnt + 1;
-}
-
-// code from here on is just to support maps that don't have control point and team entities
-void ctf_spawnteam (string teamname, float teamcolor)
-{
- entity oldself;
- oldself = self;
- self = spawn();
- self.classname = "ctf_team";
- self.netname = teamname;
- self.cnt = teamcolor;
-
- spawnfunc_ctf_team();
-
- self = oldself;
-}
-
-// spawn some default teams if the map is not set up for ctf
-void ctf_spawnteams()
-{
- float numteams;
-
- numteams = 2;//cvar("g_ctf_default_teams");
-
- ctf_spawnteam("Red", COLOR_TEAM1 - 1);
- ctf_spawnteam("Blue", COLOR_TEAM2 - 1);
-}
-
-void ctf_delayedinit()
-{
- // if no teams are found, spawn defaults
- if (find(world, classname, "ctf_team") == world)
- ctf_spawnteams();
-
- ScoreRules_ctf();
-}
-
-void ctf_init()
-{
- InitializeEntity(world, ctf_delayedinit, INITPRIO_GAMETYPE);
- flagcaptimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
-
- captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
- captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
- captureshield_force = autocvar_g_ctf_shield_force;
-}
-
-void ctf_setstatus2(entity flag, float shift)
-{
- if (flag.cnt == FLAG_CARRY)
- if (flag.owner == self)
- self.items |= shift * 3;
- else
- self.items |= shift * 1;
- else if (flag.cnt == FLAG_DROPPED)
- self.items |= shift * 2;
- else
- {
- // no status bits
- }
-}
-
-void ctf_setstatus()
-{
- self.items &~= IT_RED_FLAG_TAKEN;
- self.items &~= IT_RED_FLAG_LOST;
- self.items &~= IT_BLUE_FLAG_TAKEN;
- self.items &~= IT_BLUE_FLAG_LOST;
- self.items &~= IT_CTF_SHIELDED;
-
- entity flag;
- float redflags, blueflags;
-
- if(self.ctf_captureshielded)
- self.items |= IT_CTF_SHIELDED;
-
- redflags = 0;
- blueflags = 0;
-
- for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
- {
- if(flag.items & IT_KEY2) // blue
- ++redflags;
- else if(flag.items & IT_KEY1) // red
- ++blueflags;
- }
-
- // blinking magic: if there is more than one flag, show one of these in a clever way
- if(redflags)
- redflags = mod(floor(time * redflags * 0.75), redflags);
- if(blueflags)
- blueflags = mod(floor(time * blueflags * 0.75), blueflags);
-
- for (flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) if(flag.cnt != FLAG_BASE)
- {
- if(flag.items & IT_KEY2) // blue
- {
- if(--redflags == -1) // happens exactly once (redflags is in 0..count-1, and will --'ed count times)
- ctf_setstatus2(flag, IT_RED_FLAG_TAKEN);
- }
- else if(flag.items & IT_KEY1) // red
- {
- if(--blueflags == -1) // happens exactly once
- ctf_setstatus2(flag, IT_BLUE_FLAG_TAKEN);
- }
- }
-}
-/*
-entity ctf_team_has_commander(float cteam)
-{
- entity pl;
- if(cteam != COLOR_TEAM1 || cteam != COLOR_TEAM2)
- return world;
-
- FOR_EACH_REALPLAYER(pl) {
- if(pl.team == cteam && pl.iscommander) {
- return pl;
- }
- }
- return world;
-}
-
-void ctf_setstate(entity e, float st)
-{
- e.ctf_state = st;
- ++e.version;
-}
-
-void ctf_new_commander(float cteam)
-{
- entity pl, plmax;
-
- plmax = world;
- FOR_EACH_REALPLAYER(pl) {
- if(pl.team == cteam) {
- if(pl.iscommander) { // don't reassign if alreay there
- return;
- }
- if(plmax == world || plmax.frags < pl.frags) <<<<<<<<<<<<<<<<< BROKEN in new scoring system
- plmax = pl;
- }
- }
- if(plmax == world) {
- bprint(strcat(ColoredTeamName(cteam), " Team has no Commander!\n"));
- return;
- }
-
- plmax.iscommander = TRUE;
- ctf_setstate(plmax, 3);
- sprint(plmax, "^3You're the commander now!\n");
- centerprint(plmax, "^3You're the commander now!\n");
-}
-
-void ctf_clientconnect()
-{
- self.iscommander = FALSE;
-
- if(!self.team || self.classname != "player") {
- ctf_setstate(self, -1);
- } else
- ctf_setstate(self, 0);
-
- self.team_saved = self.team;
-
- if(self.team != 0 && self.classname == "player" && !ctf_team_has_commander(self.team)) {
- ctf_new_commander(self.team);
- }
-}
-
-void ctf_playerchanged()
-{
- if(!self.team || self.classname != "player") {
- ctf_setstate(self, -1);
- } else if(self.ctf_state < 0 && self.classname == "player") {
- ctf_setstate(self, 0);
- }
-
- if(self.iscommander &&
- (self.classname != "player" || self.team != self.team_saved)
- )
- {
- self.iscommander = FALSE;
- if(self.classname == "player")
- ctf_setstate(self, 0);
- else
- ctf_setstate(self, -1);
- ctf_new_commander(self.team_saved);
- }
-
- self.team_saved = self.team;
-
- ctf_new_commander(self.team);
-}
-
-void ctf_clientdisconnect()
-{
- if(self.iscommander)
- {
- ctf_new_commander(self.team);
- }
-}
-
-entity GetPlayer(string);
-float ctf_clientcommand()
-{
- entity e;
- if(argv(0) == "order") {
- if(!g_ctf) {
- sprint(self, "This command is not supported in this gamemode.\n");
- return TRUE;
- }
- if(!self.iscommander) {
- sprint(self, "^1You are not the commander!\n");
- return TRUE;
- }
- if(argv(2) == "") {
- sprint(self, "Usage: order #player status - (playernumber as in status)\n");
- return TRUE;
- }
- e = GetPlayer(argv(1));
- if(e == world) {
- sprint(self, "Invalid player.\nUsage: order #player status - (playernumber as in status)\n");
- return TRUE;
- }
- if(e.team != self.team) {
- sprint(self, "^3You can only give orders to your own team!\n");
- return TRUE;
- }
- if(argv(2) == "attack") {
- sprint(self, strcat("Ordering ", e.netname, " to attack!\n"));
- sprint(e, "^1Attack!\n");
- centerprint(e, "^7You've been ordered to^9\n^1Attack!\n");
- ctf_setstate(e, 1);
- } else if(argv(2) == "defend") {
- sprint(self, strcat("Ordering ", e.netname, " to defend!\n"));
- sprint(e, "^Defend!\n");
- centerprint(e, "^7You've been ordered to^9\n^2Defend!\n");
- ctf_setstate(e, 2);
- } else {
- sprint(self, "^7Invalid command, use ^3attack^7, or ^3defend^7.\n");
- }
- return TRUE;
- }
- return FALSE;
-}
-*/
// Globals
-float ctf_score_value(string parameter);
-
float g_cloaked, g_footsteps, g_jump_grunt, g_grappling_hook, g_midair, g_minstagib, g_pinata, g_norecoil, g_minstagib_invis_alpha, g_bloodloss;
float g_warmup_limit;
float g_warmup_allguns;
float g_warmup_allow_timeout;
-float g_ctf_ignore_frags;
-float g_ctf_reverse;
float g_race_qualifying;
float inWarmupStage;
float g_pickup_respawntime_weapon;
.float watersound_finished;
.float iscreature;
.float damagedbycontents;
+.float damagedbytriggers;
+.float pushable;
+.float teleportable;
.vector oldvelocity;
.float pauseregen_finished;
float startitem_failed;
-void DropFlag(entity flag, entity penalty_receiver, entity attacker);
void DropAllRunes(entity pl);
string matchid;
.float hitplotfh;
-.string noise4;
.float last_pickup;
}
f = 0;
}
- else if(g_ctf)
- {
- if(g_ctf_ignore_frags)
- f = 0;
- }
}
attacker.totalfrags += f;
Send_KillNotification(a, s, msg, deathtype, MSG_KILL);
- if(g_ctf && targ.flagcarried)
- {
- UpdateFrags(attacker, ctf_score_value("score_kill"));
- PlayerScore_Add(attacker, SP_CTF_FCKILLS, 1);
- GiveFrags(attacker, targ, 0, deathtype); // for logging
- }
- else
- GiveFrags(attacker, targ, 1, deathtype);
+ GiveFrags(attacker, targ, 1, deathtype);
if (targ.killcount > 2) {
Send_KillNotification(s, ftos(targ.killcount), a, KILL_END_SPREE, MSG_SPREE);
damage = damage * autocvar_g_balance_selfdamagepercent; // Partial damage if the attacker hits himself
}
- // CTF: reduce damage/force
- if(g_ctf)
- if(targ == attacker)
- if(targ.flagcarried)
- {
- damage = damage * autocvar_g_ctf_flagcarrier_selfdamage;
- force = force * autocvar_g_ctf_flagcarrier_selfforce;
- }
-
if(g_runematch)
{
// apply strength rune
Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
}
}
+ else if(other.damagedbytriggers)
+ {
+ if(other.takedamage)
+ {
+ EXACTTRIGGER_TOUCH;
+ Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+ }
+ }
else
{
if (!other.owner)
{
- if (other.items & IT_KEY1 || other.items & IT_KEY2) // reset flag
- {
- EXACTTRIGGER_TOUCH;
- other.pain_finished = min(other.pain_finished, time + 2);
- }
- else if (other.classname == "rune") // reset runes
+ if (other.classname == "rune") // reset runes
{
EXACTTRIGGER_TOUCH;
other.nextthink = min(other.nextthink, time + 1);
g_bloodloss = cvar("g_bloodloss");
sv_maxidle = cvar("sv_maxidle");
sv_maxidle_spectatorsareidle = cvar("sv_maxidle_spectatorsareidle");
- g_ctf_reverse = cvar("g_ctf_reverse");
sv_autotaunt = cvar("sv_autotaunt");
sv_taunt = cvar("sv_taunt");
{
if(e.iscreature)
return TRUE;
+ if(e.pushable)
+ return TRUE;
switch(e.classname)
{
case "body":
// IN+OUT
string ret_string;
+MUTATOR_HOOKABLE(PortalTeleport);
+ // called whenever a player goes through a portal gun teleport
+ // allows you to strip a player of an item if they go through the teleporter to help prevent cheating
+ // INPUT
+ entity self;
+
+MUTATOR_HOOKABLE(HelpMePing);
+ // called whenever a player uses impulse 33 (help me) in cl_impulse.qc
+ // normally help me ping uses self.waypointsprite_attachedforcarrier,
+ // but if your mutator uses something different then you can handle it
+ // in a special manner using this hook
+ // INPUT
+ entity self; // the player who pressed impulse 33
+
+MUTATOR_HOOKABLE(VehicleEnter);
+ // called when a player enters a vehicle
+ // allows mutators to set special settings in this event
+ // INPUT
+ entity vh_player; // player
+ entity vh_vehicle; // vehicle
+
+MUTATOR_HOOKABLE(VehicleExit);
+ // called when a player exits a vehicle
+ // allows mutators to set special settings in this event
+ // INPUT
+ entity vh_player; // player
+ entity vh_vehicle; // vehicle
+
+MUTATOR_HOOKABLE(AbortSpeedrun);
+ // called when a speedrun is aborted and the player is teleported back to start position
+ // INPUT
+ entity self; // player
+
MUTATOR_HOOKABLE(ItemTouch);
// called at when a item is touched. Called early, can edit item properties.
entity self; // item
--- /dev/null
+// ================================================================
+// Official capture the flag game mode coding, reworked by Samual
+// Last updated: September, 2012
+// ================================================================
+
+void ctf_FakeTimeLimit(entity e, float t)
+{
+ msg_entity = e;
+ WriteByte(MSG_ONE, 3); // svc_updatestat
+ WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
+ if(t < 0)
+ WriteCoord(MSG_ONE, autocvar_timelimit);
+ else
+ WriteCoord(MSG_ONE, (t + 1) / 60);
+}
+
+void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
+{
+ if(autocvar_sv_eventlog)
+ GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
+}
+
+string ctf_CaptureRecord(entity flag, entity player)
+{
+ float cap_time, cap_record, success;
+ string cap_message, refername;
+
+ if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots))
+ {
+ cap_record = ctf_captimerecord;
+ cap_time = (time - flag.ctf_pickuptime);
+
+ refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
+ refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
+
+ if(!ctf_captimerecord)
+ { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
+ else if(cap_time < cap_record)
+ { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
+ else
+ { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
+
+ if(success)
+ {
+ ctf_captimerecord = cap_time;
+ db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
+ db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
+ write_recordmarker(player, (time - cap_time), cap_time);
+ }
+ }
+
+ return cap_message;
+}
+
+void ctf_FlagcarrierWaypoints(entity player)
+{
+ WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
+ WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
+ WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
+ WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
+}
+
+
+// =======================
+// CaptureShield Functions
+// =======================
+
+float ctf_CaptureShield_CheckStatus(entity p)
+{
+ float s, se;
+ entity e;
+ float players_worseeq, players_total;
+
+ if(ctf_captureshield_max_ratio <= 0)
+ return FALSE;
+
+ s = PlayerScore_Add(p, SP_SCORE, 0);
+ if(s >= -ctf_captureshield_min_negscore)
+ return FALSE;
+
+ players_total = players_worseeq = 0;
+ FOR_EACH_PLAYER(e)
+ {
+ if(IsDifferentTeam(e, p))
+ continue;
+ se = PlayerScore_Add(e, SP_SCORE, 0);
+ if(se <= s)
+ ++players_worseeq;
+ ++players_total;
+ }
+
+ // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
+ // use this rule here
+
+ if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
+ return FALSE;
+
+ return TRUE;
+}
+
+void ctf_CaptureShield_Update(entity player, float wanted_status)
+{
+ float updated_status = ctf_CaptureShield_CheckStatus(player);
+ if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
+ {
+ if(updated_status) // TODO csqc notifier for this // Samual: How?
+ Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Make some defensive scores before trying again.", 5, 0);
+ else
+ Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
+
+ player.ctf_captureshielded = updated_status;
+ }
+}
+
+float ctf_CaptureShield_Customize()
+{
+ if(!other.ctf_captureshielded) { return FALSE; }
+ if(!IsDifferentTeam(self, other)) { return FALSE; }
+
+ return TRUE;
+}
+
+void ctf_CaptureShield_Touch()
+{
+ if(!other.ctf_captureshielded) { return; }
+ if(!IsDifferentTeam(self, other)) { return; }
+
+ vector mymid = (self.absmin + self.absmax) * 0.5;
+ vector othermid = (other.absmin + other.absmax) * 0.5;
+
+ Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
+ Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
+}
+
+void ctf_CaptureShield_Spawn(entity flag)
+{
+ entity shield = spawn();
+
+ shield.enemy = self;
+ shield.team = self.team;
+ shield.touch = ctf_CaptureShield_Touch;
+ shield.customizeentityforclient = ctf_CaptureShield_Customize;
+ shield.classname = "ctf_captureshield";
+ shield.effects = EF_ADDITIVE;
+ shield.movetype = MOVETYPE_NOCLIP;
+ shield.solid = SOLID_TRIGGER;
+ shield.avelocity = '7 0 11';
+ shield.scale = 0.5;
+
+ setorigin(shield, self.origin);
+ setmodel(shield, "models/ctf/shield.md3");
+ setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
+}
+
+
+// ====================
+// Drop/Pass/Throw Code
+// ====================
+
+void ctf_Handle_Drop(entity flag, entity player, float droptype)
+{
+ // declarations
+ player = (player ? player : flag.pass_sender);
+
+ // main
+ flag.movetype = MOVETYPE_TOSS;
+ flag.takedamage = DAMAGE_YES;
+ flag.health = flag.max_flag_health;
+ flag.ctf_droptime = time;
+ flag.ctf_dropper = player;
+ flag.ctf_status = FLAG_DROPPED;
+
+ // messages and sounds
+ Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
+ sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
+ ctf_EventLog("dropped", player.team, player);
+
+ // scoring
+ PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
+ PlayerScore_Add(player, SP_CTF_DROPS, 1);
+
+ // waypoints
+ if(autocvar_g_ctf_flag_dropped_waypoint)
+ WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
+
+ if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
+ {
+ WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
+ WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
+ }
+
+ player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+
+ if(droptype == DROP_PASS)
+ {
+ flag.pass_sender = world;
+ flag.pass_target = world;
+ }
+}
+
+void ctf_Handle_Retrieve(entity flag, entity player)
+{
+ entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
+ entity sender = flag.pass_sender;
+
+ // transfer flag to player
+ flag.owner = player;
+ flag.owner.flagcarried = flag;
+
+ // reset flag
+ setattachment(flag, player, "");
+ setorigin(flag, FLAG_CARRY_OFFSET);
+ flag.movetype = MOVETYPE_NONE;
+ flag.takedamage = DAMAGE_NO;
+ flag.solid = SOLID_NOT;
+ flag.ctf_status = FLAG_CARRY;
+
+ // messages and sounds
+ sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
+ ctf_EventLog("receive", flag.team, player);
+
+ FOR_EACH_REALPLAYER(tmp_player)
+ {
+ if(tmp_player == sender)
+ centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
+ else if(tmp_player == player)
+ centerprint(tmp_player, strcat("You received the ", flag.netname, " from ", sender.netname));
+ else if(!IsDifferentTeam(tmp_player, sender))
+ centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
+ }
+
+ // create new waypoint
+ ctf_FlagcarrierWaypoints(player);
+
+ sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
+ player.throw_antispam = sender.throw_antispam;
+
+ flag.pass_sender = world;
+ flag.pass_target = world;
+}
+
+void ctf_Handle_Throw(entity player, entity receiver, float droptype)
+{
+ entity flag = player.flagcarried;
+ vector targ_origin, flag_velocity;
+
+ if(!flag) { return; }
+ if((droptype == DROP_PASS) && !receiver) { return; }
+
+ if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
+
+ // reset the flag
+ setattachment(flag, world, "");
+ setorigin(flag, player.origin + FLAG_DROP_OFFSET);
+ flag.owner.flagcarried = world;
+ flag.owner = world;
+ flag.solid = SOLID_TRIGGER;
+ flag.ctf_dropper = player;
+ flag.ctf_droptime = time;
+
+ flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
+
+ switch(droptype)
+ {
+ case DROP_PASS:
+ {
+ WarpZone_RefSys_Copy(flag, receiver);
+ targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax)));
+ flag.velocity = (normalize(targ_origin - player.origin) * autocvar_g_ctf_pass_velocity);
+ break;
+ }
+
+ case DROP_THROW:
+ {
+ makevectors((player.v_angle_y * '0 1 0') + (player.v_angle_x * '0.5 0 0'));
+ flag_velocity = ('0 0 200' + ((v_forward * autocvar_g_ctf_drop_velocity) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_drop_strengthmultiplier : 1)));
+ flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
+ break;
+ }
+
+ case DROP_RESET:
+ {
+ flag.velocity = '0 0 0'; // do nothing
+ break;
+ }
+
+ default:
+ case DROP_NORMAL:
+ {
+ flag.velocity = W_CalculateProjectileVelocity(player.velocity, ('0 0 200' + ('0 100 0' * crandom()) + ('100 0 0' * crandom())), FALSE);
+ break;
+ }
+ }
+
+ switch(droptype)
+ {
+ case DROP_PASS:
+ {
+ // main
+ flag.movetype = MOVETYPE_FLY;
+ flag.takedamage = DAMAGE_NO;
+ flag.pass_sender = player;
+ flag.pass_target = receiver;
+ flag.ctf_status = FLAG_PASSING;
+
+ // other
+ sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
+ WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), targ_origin, player.origin);
+ ctf_EventLog("pass", flag.team, player);
+ break;
+ }
+
+ case DROP_RESET:
+ {
+ // do nothing
+ break;
+ }
+
+ default:
+ case DROP_THROW:
+ case DROP_NORMAL:
+ {
+ ctf_Handle_Drop(flag, player, droptype);
+ break;
+ }
+ }
+
+ // kill old waypointsprite
+ WaypointSprite_Ping(player.wps_flagcarrier);
+ WaypointSprite_Kill(player.wps_flagcarrier);
+
+ if(player.wps_enemyflagcarrier)
+ WaypointSprite_Kill(player.wps_enemyflagcarrier);
+
+ // captureshield
+ ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
+}
+
+
+// ==============
+// Event Handlers
+// ==============
+
+void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
+{
+ entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
+ entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
+ float old_time, new_time;
+
+ if not(player) { return; } // without someone to give the reward to, we can't possibly cap
+
+ // messages and sounds
+ Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
+ sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
+
+ switch(capturetype)
+ {
+ case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
+ case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
+ default: break;
+ }
+
+ // scoring
+ PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
+ PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
+
+ old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
+ new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
+ if(!old_time || new_time < old_time)
+ PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
+
+ // effects
+ if(autocvar_g_ctf_flag_capture_effects)
+ {
+ pointparticles(particleeffectnum((player.team == COLOR_TEAM1) ? "red_ground_quake" : "blue_ground_quake"), flag.origin, '0 0 0', 1);
+ shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
+ }
+
+ // other
+ if(capturetype == CAPTURE_NORMAL)
+ {
+ WaypointSprite_Kill(player.wps_flagcarrier);
+ if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
+
+ if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
+ { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
+ }
+
+ // reset the flag
+ player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
+ ctf_RespawnFlag(enemy_flag);
+}
+
+void ctf_Handle_Return(entity flag, entity player)
+{
+ // messages and sounds
+ //centerprint(player, strcat("You returned the ", flag.netname));
+ Send_KillNotification(player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
+ sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
+ ctf_EventLog("return", flag.team, player);
+
+ // scoring
+ PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
+ PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
+
+ TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
+
+ if(flag.ctf_dropper)
+ {
+ PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
+ ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
+ flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
+ }
+
+ // reset the flag
+ ctf_RespawnFlag(flag);
+}
+
+void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
+{
+ // declarations
+ entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
+ string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
+ float pickup_dropped_score; // used to calculate dropped pickup score
+
+ // attach the flag to the player
+ flag.owner = player;
+ player.flagcarried = flag;
+ setattachment(flag, player, "");
+ setorigin(flag, FLAG_CARRY_OFFSET);
+
+ // flag setup
+ flag.movetype = MOVETYPE_NONE;
+ flag.takedamage = DAMAGE_NO;
+ flag.solid = SOLID_NOT;
+ flag.angles = '0 0 0';
+ flag.ctf_status = FLAG_CARRY;
+
+ switch(pickuptype)
+ {
+ case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
+ case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
+ default: break;
+ }
+
+ // messages and sounds
+ Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
+ sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
+ verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
+
+ FOR_EACH_REALPLAYER(tmp_player)
+ {
+ if(tmp_player == player)
+ centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
+ else if(!IsDifferentTeam(tmp_player, player))
+ centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
+ else if(!IsDifferentTeam(tmp_player, flag))
+ centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
+ }
+
+ switch(pickuptype)
+ {
+ case PICKUP_BASE: ctf_EventLog("steal", flag.team, player); break;
+ case PICKUP_DROPPED: ctf_EventLog("pickup", flag.team, player); break;
+ default: break;
+ }
+
+ // scoring
+ PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
+ switch(pickuptype)
+ {
+ case PICKUP_BASE:
+ {
+ PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
+ break;
+ }
+
+ case PICKUP_DROPPED:
+ {
+ pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
+ pickup_dropped_score = floor((autocvar_g_ctf_score_pickup_dropped_late * (1 - pickup_dropped_score) + autocvar_g_ctf_score_pickup_dropped_early * pickup_dropped_score) + 0.5);
+ print("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
+ PlayerTeamScore_AddScore(player, pickup_dropped_score);
+ break;
+ }
+
+ default: break;
+ }
+
+ // speedrunning
+ if(pickuptype == PICKUP_BASE)
+ {
+ flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
+ if((player.speedrunning) && (ctf_captimerecord))
+ ctf_FakeTimeLimit(player, time + ctf_captimerecord);
+ }
+
+ // effects
+ if(autocvar_g_ctf_flag_pickup_effects)
+ pointparticles(particleeffectnum("smoke_ring"), 0.5 * (flag.absmin + flag.absmax), '0 0 0', 1);
+
+ // waypoints
+ if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
+ ctf_FlagcarrierWaypoints(player);
+ WaypointSprite_Ping(player.wps_flagcarrier);
+}
+
+
+// ===================
+// Main Flag Functions
+// ===================
+
+void ctf_CheckFlagReturn(entity flag, float returntype)
+{
+ if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
+
+ if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
+ {
+ switch(returntype)
+ {
+ case RETURN_DROPPED: bprint("The ", flag.netname, " was dropped in the base and returned itself\n"); break;
+ case RETURN_DAMAGE: bprint("The ", flag.netname, " was destroyed and returned to base\n"); break;
+ case RETURN_SPEEDRUN: bprint("The ", flag.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); break;
+ case RETURN_NEEDKILL: bprint("The ", flag.netname, " fell somewhere it couldn't be reached and returned to base\n"); break;
+
+ default:
+ case RETURN_TIMEOUT:
+ { bprint("The ", flag.netname, " has returned to base\n"); break; }
+ }
+ sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
+ ctf_EventLog("returned", flag.team, world);
+ ctf_RespawnFlag(flag);
+ }
+}
+
+void ctf_CheckStalemate(void)
+{
+ // declarations
+ float stale_red_flags, stale_blue_flags;
+ entity tmp_entity;
+
+ entity ctf_staleflaglist; // reset the list, we need to build the list each time this function runs
+
+ // build list of stale flags
+ for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+ {
+ if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
+ if(tmp_entity.ctf_status != FLAG_BASE)
+ if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
+ {
+ tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
+ ctf_staleflaglist = tmp_entity;
+
+ switch(tmp_entity.team)
+ {
+ case COLOR_TEAM1: ++stale_red_flags; break;
+ case COLOR_TEAM2: ++stale_blue_flags; break;
+ }
+ }
+ }
+
+ if(stale_red_flags && stale_blue_flags)
+ ctf_stalemate = TRUE;
+ else if(!stale_red_flags && !stale_blue_flags)
+ ctf_stalemate = FALSE;
+
+ // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
+ if(ctf_stalemate)
+ {
+ for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
+ {
+ if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
+ WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
+ }
+
+ if not(wpforenemy_announced)
+ {
+ FOR_EACH_REALPLAYER(tmp_entity)
+ if(tmp_entity.flagcarried)
+ centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
+ else
+ centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
+
+ wpforenemy_announced = TRUE;
+ }
+ }
+}
+
+void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if(ITEM_DAMAGE_NEEDKILL(deathtype))
+ {
+ // automatically kill the flag and return it
+ self.health = 0;
+ ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+ return;
+ }
+ if(autocvar_g_ctf_flag_return_damage)
+ {
+ // reduce health and check if it should be returned
+ self.health = self.health - damage;
+ ctf_CheckFlagReturn(self, RETURN_DAMAGE);
+ return;
+ }
+}
+
+void ctf_FlagThink()
+{
+ // declarations
+ entity tmp_entity;
+
+ self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
+
+ // captureshield
+ if(self == ctf_worldflaglist) // only for the first flag
+ FOR_EACH_CLIENT(tmp_entity)
+ ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
+
+ // sanity checks
+ if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
+ dprint("wtf the flag got squashed?\n");
+ tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
+ if(!trace_startsolid) // can we resize it without getting stuck?
+ setsize(self, FLAG_MIN, FLAG_MAX); }
+
+ switch(self.ctf_status) // reset flag angles in case warpzones adjust it
+ {
+ case FLAG_DROPPED:
+ case FLAG_PASSING:
+ {
+ self.angles = '0 0 0';
+ break;
+ }
+
+ default: break;
+ }
+
+ // main think method
+ switch(self.ctf_status)
+ {
+ case FLAG_BASE:
+ {
+ if(autocvar_g_ctf_dropped_capture_radius)
+ {
+ for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+ if(tmp_entity.ctf_status == FLAG_DROPPED)
+ if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
+ ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
+ }
+ return;
+ }
+
+ case FLAG_DROPPED:
+ {
+ if(autocvar_g_ctf_flag_dropped_floatinwater)
+ {
+ vector midpoint = ((self.absmin + self.absmax) * 0.5);
+ if(pointcontents(midpoint) == CONTENT_WATER)
+ {
+ self.velocity = self.velocity * 0.5;
+
+ if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
+ { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
+ else
+ { self.movetype = MOVETYPE_FLY; }
+ }
+ else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
+ }
+ if(autocvar_g_ctf_flag_return_dropped)
+ {
+ if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
+ {
+ self.health = 0;
+ ctf_CheckFlagReturn(self, RETURN_DROPPED);
+ return;
+ }
+ }
+ if(autocvar_g_ctf_flag_return_time)
+ {
+ self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
+ ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
+ return;
+ }
+ return;
+ }
+
+ case FLAG_CARRY:
+ {
+ if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
+ {
+ self.health = 0;
+ ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
+
+ tmp_entity = self;
+ self = self.owner;
+ self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
+ ImpulseCommands();
+ self = tmp_entity;
+ }
+ if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
+ {
+ if(time >= wpforenemy_nextthink)
+ {
+ ctf_CheckStalemate();
+ wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
+ }
+ }
+ return;
+ }
+
+ case FLAG_PASSING: // todo make work with warpzones
+ {
+ vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
+ vector old_targ_origin = targ_origin;
+ targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin);
+ WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
+
+ print(strcat("self: ", vtos(self.origin), ", old: ", vtos(old_targ_origin), " (", ftos(vlen(self.origin - old_targ_origin)), "qu)"), ", transformed: ", vtos(targ_origin), " (", ftos(vlen(self.origin - targ_origin)), "qu)", ".\n");
+
+ if((self.pass_target.deadflag != DEAD_NO)
+ || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
+ || ((trace_fraction < 1) && (trace_ent != self.pass_target))
+ || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
+ {
+ ctf_Handle_Drop(self, world, DROP_PASS);
+ }
+ else // still a viable target, go for it
+ {
+ vector desired_direction = normalize(targ_origin - self.origin);
+ vector current_direction = normalize(self.velocity);
+
+ self.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity);
+ }
+ return;
+ }
+
+ default: // this should never happen
+ {
+ dprint("ctf_FlagThink(): Flag exists with no status?\n");
+ return;
+ }
+ }
+}
+
+void ctf_FlagTouch()
+{
+ if(gameover) { return; }
+
+ entity toucher = other;
+
+ // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
+ if(ITEM_TOUCH_NEEDKILL())
+ {
+ self.health = 0;
+ ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+ return;
+ }
+
+ // special touch behaviors
+ if(toucher.vehicle_flags & VHF_ISVEHICLE)
+ {
+ if(autocvar_g_ctf_allow_vehicle_touch)
+ toucher = toucher.owner; // the player is actually the vehicle owner, not other
+ else
+ return; // do nothing
+ }
+ else if(toucher.classname != "player") // The flag just touched an object, most likely the world
+ {
+ if(time > self.wait) // if we haven't in a while, play a sound/effect
+ {
+ pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
+ sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
+ self.wait = time + FLAG_TOUCHRATE;
+ }
+ return;
+ }
+ else if(toucher.deadflag != DEAD_NO) { return; }
+
+ switch(self.ctf_status)
+ {
+ case FLAG_BASE:
+ {
+ if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
+ ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
+ else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
+ ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
+ break;
+ }
+
+ case FLAG_DROPPED:
+ {
+ if(!IsDifferentTeam(toucher, self))
+ ctf_Handle_Return(self, toucher); // toucher just returned his own flag
+ else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
+ ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
+ break;
+ }
+
+ case FLAG_CARRY:
+ {
+ dprint("Someone touched a flag even though it was being carried?\n");
+ break;
+ }
+
+ case FLAG_PASSING:
+ {
+ if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
+ {
+ if(IsDifferentTeam(toucher, self.pass_sender))
+ ctf_Handle_Return(self, toucher);
+ else
+ ctf_Handle_Retrieve(self, toucher);
+ }
+ break;
+ }
+ }
+}
+
+void ctf_RespawnFlag(entity flag)
+{
+ // reset the player (if there is one)
+ if((flag.owner) && (flag.owner.flagcarried == flag))
+ {
+ if(flag.owner.wps_enemyflagcarrier)
+ WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
+
+ WaypointSprite_Kill(flag.wps_flagcarrier);
+
+ flag.owner.flagcarried = world;
+
+ if(flag.speedrunning)
+ ctf_FakeTimeLimit(flag.owner, -1);
+ }
+
+ if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
+ { WaypointSprite_Kill(flag.wps_flagdropped); }
+
+ // reset the flag
+ setattachment(flag, world, "");
+ setorigin(flag, flag.ctf_spawnorigin);
+
+ flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
+ flag.takedamage = DAMAGE_NO;
+ flag.health = flag.max_flag_health;
+ flag.solid = SOLID_TRIGGER;
+ flag.velocity = '0 0 0';
+ flag.angles = flag.mangle;
+ flag.flags = FL_ITEM | FL_NOTARGET;
+
+ flag.ctf_status = FLAG_BASE;
+ flag.owner = world;
+ flag.pass_sender = world;
+ flag.pass_target = world;
+ flag.ctf_dropper = world;
+ flag.ctf_pickuptime = 0;
+ flag.ctf_droptime = 0;
+
+ wpforenemy_announced = FALSE;
+}
+
+void ctf_Reset()
+{
+ if(self.owner)
+ if(self.owner.classname == "player")
+ ctf_Handle_Throw(self.owner, world, DROP_RESET);
+
+ ctf_RespawnFlag(self);
+}
+
+void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
+{
+ // bot waypoints
+ waypoint_spawnforitem_force(self, self.origin);
+ self.nearestwaypointtimeout = 0; // activate waypointing again
+ self.bot_basewaypoint = self.nearestwaypoint;
+
+ // waypointsprites
+ WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
+ WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
+
+ // captureshield setup
+ ctf_CaptureShield_Spawn(self);
+}
+
+void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
+{
+ // declarations
+ teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1.
+ self = flag; // for later usage with droptofloor()
+
+ // main setup
+ flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
+ ctf_worldflaglist = flag;
+
+ setattachment(flag, world, "");
+
+ flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
+ flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
+ flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
+ flag.classname = "item_flag_team";
+ flag.target = "###item###"; // wut?
+ flag.flags = FL_ITEM | FL_NOTARGET;
+ flag.solid = SOLID_TRIGGER;
+ flag.takedamage = DAMAGE_NO;
+ flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
+ flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
+ flag.health = flag.max_flag_health;
+ flag.event_damage = ctf_FlagDamage;
+ flag.pushable = TRUE;
+ flag.teleportable = TELEPORT_NORMAL;
+ flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
+ flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
+ flag.velocity = '0 0 0';
+ flag.mangle = flag.angles;
+ flag.reset = ctf_Reset;
+ flag.touch = ctf_FlagTouch;
+ flag.think = ctf_FlagThink;
+ flag.nextthink = time + FLAG_THINKRATE;
+ flag.ctf_status = FLAG_BASE;
+
+ if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
+ if(!flag.scale) { flag.scale = FLAG_SCALE; }
+ if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
+ if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
+ if(!flag.passeffect) { flag.passeffect = ((!teamnumber) ? "red_pass" : "blue_pass"); } // invert the team number of the flag to pass as enemy team color
+
+ // sound
+ if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
+ if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
+ if(!flag.snd_flag_capture) { flag.snd_flag_capture = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
+ if(!flag.snd_flag_respawn) { flag.snd_flag_respawn = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
+ if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
+ if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
+ if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
+
+ // precache
+ precache_sound(flag.snd_flag_taken);
+ precache_sound(flag.snd_flag_returned);
+ precache_sound(flag.snd_flag_capture);
+ precache_sound(flag.snd_flag_respawn);
+ precache_sound(flag.snd_flag_dropped);
+ precache_sound(flag.snd_flag_touch);
+ precache_sound(flag.snd_flag_pass);
+ precache_model(flag.model);
+ precache_model("models/ctf/shield.md3");
+ precache_model("models/ctf/shockwavetransring.md3");
+
+ // appearence
+ setmodel(flag, flag.model); // precision set below
+ setsize(flag, FLAG_MIN, FLAG_MAX);
+ setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
+
+ if(autocvar_g_ctf_flag_glowtrails)
+ {
+ flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
+ flag.glow_size = 25;
+ flag.glow_trail = 1;
+ }
+
+ flag.effects |= EF_LOWPRECISION;
+ if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
+ if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
+
+ // flag placement
+ if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
+ {
+ flag.dropped_origin = flag.origin;
+ flag.noalign = TRUE;
+ flag.movetype = MOVETYPE_NONE;
+ }
+ else // drop to floor, automatically find a platform and set that as spawn origin
+ {
+ flag.noalign = FALSE;
+ self = flag;
+ droptofloor();
+ flag.movetype = MOVETYPE_TOSS;
+ }
+
+ InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
+{
+ entity flag;
+
+ // initially clear items so they can be set as necessary later.
+ self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
+ | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
+
+ // scan through all the flags and notify the client about them
+ for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+ {
+ switch(flag.ctf_status)
+ {
+ case FLAG_PASSING:
+ case FLAG_CARRY:
+ {
+ if((flag.owner == self) || (flag.pass_sender == self))
+ self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
+ else
+ self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
+ break;
+ }
+ case FLAG_DROPPED:
+ {
+ self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
+ break;
+ }
+ }
+ }
+
+ // item for stopping players from capturing the flag too often
+ if(self.ctf_captureshielded)
+ self.items |= IT_CTF_SHIELDED;
+
+ // update the health of the flag carrier waypointsprite
+ if(self.wps_flagcarrier)
+ WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
+{
+ if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
+ {
+ if(frag_target == frag_attacker) // damage done to yourself
+ {
+ frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
+ frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
+ }
+ else // damage done to everyone else
+ {
+ frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
+ frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
+ }
+ }
+ else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
+ {
+ if(autocvar_g_ctf_flagcarrier_auto_helpme_when_damaged > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
+ WaypointSprite_HelpMePing(frag_target.wps_flagcarrier); // TODO: only do this if there is a significant loss of health?
+ }
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
+{
+ if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
+ {
+ PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
+ PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
+ }
+
+ if(frag_target.flagcarried)
+ { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
+{
+ frag_score = 0;
+ return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
+}
+
+MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
+{
+ if(self.flagcarried)
+ { ctf_Handle_Throw(self, world, DROP_NORMAL); }
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
+{
+ if(self.flagcarried)
+ if(!autocvar_g_ctf_portalteleport)
+ { ctf_Handle_Throw(self, world, DROP_NORMAL); }
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
+{
+ if(gameover) { return FALSE; }
+
+ entity player = self;
+
+ if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
+ {
+ // pass the flag to a team mate
+ if(autocvar_g_ctf_pass)
+ {
+ entity head, closest_target;
+ head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
+
+ while(head) // find the closest acceptable target to pass to
+ {
+ if(head.classname == "player" && head.deadflag == DEAD_NO)
+ if(head != player && !IsDifferentTeam(head, player))
+ if(!head.speedrunning && (!head.vehicle || autocvar_g_ctf_allow_vehicle_touch))
+ {
+ if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
+ {
+ if(clienttype(head) == CLIENTTYPE_BOT)
+ {
+ centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
+ ctf_Handle_Throw(head, player, DROP_PASS);
+ }
+ else
+ {
+ centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
+ centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
+ }
+ player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+ return TRUE;
+ }
+ else if(player.flagcarried)
+ {
+ if(closest_target)
+ {
+ if(vlen(player.origin - WarpZone_UnTransformOrigin(head, head.origin)) < vlen(player.origin - WarpZone_UnTransformOrigin(closest_target, closest_target.origin)))
+ { closest_target = head; }
+ }
+ else { closest_target = head; }
+ }
+ }
+ head = head.chain;
+ }
+
+ if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
+ }
+
+ // throw the flag in front of you
+ if(autocvar_g_ctf_drop && player.flagcarried)
+ { ctf_Handle_Throw(player, world, DROP_THROW); return TRUE; }
+ }
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
+{
+ if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
+ {
+ WaypointSprite_HelpMePing(self.wps_flagcarrier);
+ }
+ else // create a normal help me waypointsprite
+ {
+ WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0');
+ WaypointSprite_Ping(self.wps_helpme);
+ }
+
+ return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
+{
+ if(vh_player.flagcarried)
+ {
+ if(!autocvar_g_ctf_flagcarrier_allow_vehicle_carry)
+ {
+ ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
+ }
+ else
+ {
+ setattachment(vh_player.flagcarried, vh_vehicle, "");
+ setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
+ vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
+ //vh_player.flagcarried.angles = '0 0 0';
+ }
+ }
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
+{
+ if(vh_player.flagcarried)
+ {
+ setattachment(vh_player.flagcarried, vh_player, "");
+ setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
+ vh_player.flagcarried.scale = FLAG_SCALE;
+ vh_player.flagcarried.angles = '0 0 0';
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
+{
+ if(self.flagcarried)
+ {
+ bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
+ ctf_RespawnFlag(self);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
+{
+ entity flag; // temporary entity for the search method
+
+ for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+ {
+ switch(flag.ctf_status)
+ {
+ case FLAG_DROPPED:
+ case FLAG_PASSING:
+ {
+ // lock the flag, game is over
+ flag.movetype = MOVETYPE_NONE;
+ flag.takedamage = DAMAGE_NO;
+ flag.solid = SOLID_NOT;
+ flag.nextthink = FALSE; // stop thinking
+
+ print("stopping the ", flag.netname, " from moving.\n");
+ break;
+ }
+
+ default:
+ case FLAG_BASE:
+ case FLAG_CARRY:
+ {
+ // do nothing for these flags
+ break;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+
+// ==========
+// Spawnfuncs
+// ==========
+
+/*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
+CTF Starting point for a player in team one (Red).
+Keys: "angle" viewing angle when spawning. */
+void spawnfunc_info_player_team1()
+{
+ if(g_assault) { remove(self); return; }
+
+ self.team = COLOR_TEAM1; // red
+ spawnfunc_info_player_deathmatch();
+}
+
+
+/*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
+CTF Starting point for a player in team two (Blue).
+Keys: "angle" viewing angle when spawning. */
+void spawnfunc_info_player_team2()
+{
+ if(g_assault) { remove(self); return; }
+
+ self.team = COLOR_TEAM2; // blue
+ spawnfunc_info_player_deathmatch();
+}
+
+/*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
+CTF Starting point for a player in team three (Yellow).
+Keys: "angle" viewing angle when spawning. */
+void spawnfunc_info_player_team3()
+{
+ if(g_assault) { remove(self); return; }
+
+ self.team = COLOR_TEAM3; // yellow
+ spawnfunc_info_player_deathmatch();
+}
+
+
+/*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
+CTF Starting point for a player in team four (Purple).
+Keys: "angle" viewing angle when spawning. */
+void spawnfunc_info_player_team4()
+{
+ if(g_assault) { remove(self); return; }
+
+ self.team = COLOR_TEAM4; // purple
+ spawnfunc_info_player_deathmatch();
+}
+
+/*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team one (Red).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red and blue as skins 0 and 1...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+void spawnfunc_item_flag_team1()
+{
+ if(!g_ctf) { remove(self); return; }
+
+ ctf_FlagSetup(1, self); // 1 = red
+}
+
+/*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team two (Blue).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red and blue as skins 0 and 1...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+void spawnfunc_item_flag_team2()
+{
+ if(!g_ctf) { remove(self); return; }
+
+ ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
+}
+
+/*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
+Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
+Note: If you use spawnfunc_ctf_team entities you must define at least 2! However, unlike domination, you don't need to make a blank one too.
+Keys:
+"netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
+"cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
+void spawnfunc_ctf_team()
+{
+ if(!g_ctf) { remove(self); return; }
+
+ self.classname = "ctf_team";
+ self.team = self.cnt + 1;
+}
+
+
+// ==============
+// Initialization
+// ==============
+
+// code from here on is just to support maps that don't have flag and team entities
+void ctf_SpawnTeam (string teamname, float teamcolor)
+{
+ entity oldself;
+ oldself = self;
+ self = spawn();
+ self.classname = "ctf_team";
+ self.netname = teamname;
+ self.cnt = teamcolor;
+
+ spawnfunc_ctf_team();
+
+ self = oldself;
+}
+
+void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
+{
+ // if no teams are found, spawn defaults
+ if(find(world, classname, "ctf_team") == world)
+ {
+ print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
+ ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
+ ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
+ }
+
+ ScoreRules_ctf();
+}
+
+void ctf_Initialize()
+{
+ ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
+
+ ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
+ ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
+ ctf_captureshield_force = autocvar_g_ctf_shield_force;
+
+ InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
+}
+
+
+MUTATOR_DEFINITION(gamemode_ctf)
+{
+ MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
+ MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
+ MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
+ MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
+ MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
+ MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
+ MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
+
+ MUTATOR_ONADD
+ {
+ if(time > 1) // game loads at time 1
+ error("This is a game type and it cannot be added at runtime.");
+ g_ctf = 1;
+ ctf_Initialize();
+ }
+
+ MUTATOR_ONREMOVE
+ {
+ g_ctf = 0;
+ error("This is a game type and it cannot be removed at runtime.");
+ }
+
+ return 0;
+}
--- /dev/null
+// these are needed since mutators are compiled last
+
+// used in cheats.qc
+void ctf_RespawnFlag(entity flag)
+
+// used in t_quake3.qc
+void spawnfunc_info_player_team1();
+void spawnfunc_info_player_team2();
+void spawnfunc_info_player_team3();
+void spawnfunc_info_player_team4();
+void spawnfunc_item_flag_team1();
+void spawnfunc_item_flag_team2();
+void spawnfunc_ctf_team();
+
+// flag constants // for most of these, there is just one question to be asked: WHYYYYY?
+#define FLAG_MIN (PL_MIN + '0 0 -13')
+#define FLAG_MAX (PL_MAX + '0 0 -13')
+
+#define FLAG_SCALE 0.6
+
+#define FLAG_THINKRATE 0.2
+#define FLAG_TOUCHRATE 0.5
+#define WPFE_THINKRATE 0.5
+
+#define FLAG_DROP_OFFSET ('0 0 32')
+#define FLAG_CARRY_OFFSET ('-16 0 8')
+#define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_z - 13))
+#define FLAG_WAYPOINT_OFFSET ('0 0 64')
+#define FLAG_FLOAT_OFFSET ('0 0 32')
+
+#define VEHICLE_FLAG_OFFSET ('0 0 96')
+#define VEHICLE_FLAG_SCALE 1.0
+
+// waypoint colors
+#define WPCOLOR_ENEMYFC(t) (colormapPaletteColor(t - 1, FALSE) * 0.75)
+#define WPCOLOR_FLAGCARRIER(t) ('0.8 0.8 0')
+#define WPCOLOR_DROPPEDFLAG(t) (('0.25 0.25 0.25' + colormapPaletteColor(t - 1, FALSE)) * 0.5)
+
+// sounds
+#define snd_flag_taken noise
+#define snd_flag_returned noise1
+#define snd_flag_capture noise2
+#define snd_flag_respawn noise3
+.string snd_flag_dropped;
+.string snd_flag_touch;
+.string snd_flag_pass;
+
+// effects
+.string toucheffect;
+.string passeffect;
+
+// list of flags on the map
+entity ctf_worldflaglist;
+.entity ctf_worldflagnext;
+.entity ctf_staleflagnext;
+
+// waypoint sprites
+.entity bot_basewaypoint; // flag waypointsprite
+.entity wps_helpme;
+.entity wps_flagbase;
+.entity wps_flagcarrier;
+.entity wps_flagdropped;
+.entity wps_enemyflagcarrier;
+float wpforenemy_announced;
+float wpforenemy_nextthink;
+
+// statuses
+#define FLAG_BASE 1
+#define FLAG_DROPPED 2
+#define FLAG_CARRY 3
+#define FLAG_PASSING 4
+
+#define DROP_NORMAL 1
+#define DROP_THROW 2
+#define DROP_PASS 3
+#define DROP_RESET 4
+
+#define PICKUP_BASE 1
+#define PICKUP_DROPPED 2
+
+#define CAPTURE_NORMAL 1
+#define CAPTURE_DROPPED 2
+
+#define RETURN_TIMEOUT 1
+#define RETURN_DROPPED 2
+#define RETURN_DAMAGE 3
+#define RETURN_SPEEDRUN 4
+#define RETURN_NEEDKILL 5
+
+// flag properties
+#define ctf_spawnorigin dropped_origin
+float ctf_stalemate; // indicates that a stalemate is active
+float ctf_captimerecord; // record time for capturing the flag
+.float ctf_pickuptime;
+.float ctf_droptime;
+.float ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
+.entity ctf_dropper; // don't allow spam of dropping the flag
+.float max_flag_health;
+.float next_take_time;
+
+// passing properties
+.entity pass_sender;
+.entity pass_target;
+.float throw_antispam;
+
+// CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
+.float ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
+float ctf_captureshield_min_negscore; // punish at -20 points
+float ctf_captureshield_max_ratio; // punish at most 30% of each team
+float ctf_captureshield_force; // push force of the shield
ball.owner = ball.pusher = plyr; //"owner" is set to the player carrying, "pusher" to the last player who touched it
ball.team = plyr.team;
plyr.ballcarried = ball;
- ball.dropperid = plyr.playerid;
+ ball.nb_dropper = plyr;
plyr.effects |= autocvar_g_nexball_basketball_effects_default;
ball.effects &~= autocvar_g_nexball_basketball_effects_default;
ball.flags &~= FL_ONGROUND;
ball.scale = ball_scale;
ball.velocity = vel;
- ball.ctf_droptime = time;
+ ball.nb_droptime = time;
ball.touch = basketball_touch;
ball.think = ResetBall;
ball.nextthink = min(time + autocvar_g_nexball_delay_idle, ball.teamtime);
football_touch();
return;
}
- if(!self.cnt && other.classname == "player" && (other.playerid != self.dropperid || time > self.ctf_droptime + autocvar_g_nexball_delay_collect))
+ if(!self.cnt && other.classname == "player" && (other != self.nb_dropper || time > self.nb_droptime + autocvar_g_nexball_delay_collect))
{
if(other.health <= 0)
return;
float ball_scale;
float nb_teams;
-.float teamtime;
\ No newline at end of file
+.entity nb_dropper;
+.float nb_droptime;
+
+.float teamtime;
MUTATOR_DECLARATION(gamemode_keyhunt);
MUTATOR_DECLARATION(gamemode_freezetag);
MUTATOR_DECLARATION(gamemode_keepaway);
+MUTATOR_DECLARATION(gamemode_ctf);
MUTATOR_DECLARATION(gamemode_nexball);
MUTATOR_DECLARATION(gamemode_onslaught);
// factor -1 allows chaining portals, but may be weird
player.right_vector = -1 * AnglesTransform_Apply(transform, player.right_vector);
- if(player.flagcarried)
- DropFlag(player.flagcarried, player, world);
+ entity oldself = self;
+ self = player;
+ MUTATOR_CALLHOOK(PortalTeleport);
+ player = self;
+ self = oldself;
if not(teleporter.enemy)
{
mutators/base.qh
mutators/mutators.qh
+mutators/gamemode_ctf.qh
mutators/gamemode_keyhunt.qh // TODO fix this
mutators/gamemode_nexball.qh
mutators/mutator_dodging.qh
t_plats.qc
antilag.qc
-ctf.qc
+//ctf.qc
domination.qc
//mode_onslaught.qc
//nexball.qc
../common/explosion_equation.qc
mutators/base.qc
-mutators/gamemode_nexball.qc
-mutators/gamemode_keyhunt.qc
+mutators/gamemode_ctf.qc
mutators/gamemode_freezetag.qc
+mutators/gamemode_keyhunt.qc
mutators/gamemode_keepaway.qc
+mutators/gamemode_nexball.qc
mutators/gamemode_onslaught.qc
mutators/mutator_invincibleproj.qc
mutators/mutator_new_toys.qc
// g_ctf
#define ST_CTF_CAPS 1
#define SP_CTF_CAPS 4
-#define SP_CTF_PICKUPS 5
-#define SP_CTF_DROPS 6
-#define SP_CTF_FCKILLS 7
-#define SP_CTF_RETURNS 8
+#define SP_CTF_CAPTIME 5
+#define SP_CTF_PICKUPS 6
+#define SP_CTF_DROPS 7
+#define SP_CTF_FCKILLS 8
+#define SP_CTF_RETURNS 9
void ScoreRules_ctf()
{
CheckAllowedTeams(world);
- ScoreRules_basics(2 + (c3>=0), SFL_SORT_PRIO_PRIMARY, 0, TRUE); // NOTE this assumes that the rogue team is team 3
+ ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
+ ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
#define TELEPORT_FLAGS_WARPZONE 0
#define TELEPORT_FLAGS_PORTAL (TELEPORT_FLAG_SOUND | TELEPORT_FLAG_PARTICLES | TELEPORT_FLAG_TDEATH | TELEPORT_FLAG_FORCE_TDEATH)
#define TELEPORT_FLAGS_TELEPORTER (TELEPORT_FLAG_SOUND | TELEPORT_FLAG_PARTICLES | TELEPORT_FLAG_TDEATH)
+
+// types for .teleportable entity setting
+#define TELEPORT_NORMAL 1 // play sounds/effects etc
+#define TELEPORT_SIMPLE 2 // only do teleport, nothing special
+
void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity, vector telefragmin, vector telefragmax, float tflags)
{
entity telefragger;
makevectors (to_angles);
- if(player.classname == "player") // don't play sounds or show particles for anything that isn't a player, maybe change later to block only observers
+ if(player.teleportable == TELEPORT_NORMAL) // don't play sounds or show particles for anything that isn't a player, maybe change later to block only observers
{
if(self.pushltime < time) // only show one teleport effect per teleporter per 0.2 seconds, for better fps
{
if (self.active != ACTIVE_ACTIVE)
return;
- if not(other.iscreature)
- return;
-
- // for gameplay: vehicles can't teleport
- if (other.vehicle_flags & VHF_ISVEHICLE)
+ if not(other.teleportable)
return;
- if(other.vehicle)
- return;
-
- if(other.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
- return;
+ if(other.vehicle)
+ if(!other.vehicle.teleportable)
+ return;
+
+ if(other.turrcaps_flags & TFL_TURRCAPS_ISTURRET)
+ return;
- if (other.deadflag != DEAD_NO)
+ if(other.deadflag != DEAD_NO)
return;
if(self.team)
}
void dom_init();
-void ctf_init();
void runematch_init();
void tdm_init();
void entcs_init();
if(g_ctf)
{
ActivateTeamplay();
- g_ctf_ignore_frags = autocvar_g_ctf_ignore_frags;
fraglimit_override = autocvar_capturelimit_override;
leadlimit_override = autocvar_captureleadlimit_override;
- ctf_init();
+ MUTATOR_ADD(gamemode_ctf);
have_team_spawns = -1; // request team spawns
}
if(self.deadflag == DEAD_NO)
Damage(self, self, self, 100000, DEATH_TEAMCHANGE, self.origin, '0 0 0');
}
- //ctf_playerchanged();
}
void ShufflePlayerOutOfTeam (float source_team)
self.target_select_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS;
self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS;
self.iscreature = TRUE;
+ self.teleportable = TELEPORT_NORMAL;
self.damagedbycontents = TRUE;
self.movetype = MOVETYPE_WALK;
self.solid = SOLID_SLIDEBOX;
self.target_select_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS;
self.target_validate_flags = TFL_TARGETSELECT_PLAYERS | TFL_TARGETSELECT_RANGELIMTS | TFL_TARGETSELECT_TEAMCHECK | TFL_TARGETSELECT_LOS;
self.iscreature = TRUE;
+ self.teleportable = TELEPORT_NORMAL;
self.damagedbycontents = TRUE;
self.movetype = MOVETYPE_WALK;
self.solid = SOLID_SLIDEBOX;
self.hud = HUD_NORMAL;
self.switchweapon = self.vehicle.switchweapon;
- if(self.flagcarried)
- {
- self.flagcarried.scale = 0.6;
- setattachment(self.flagcarried, self, "");
- setorigin(self.flagcarried, FLAG_CARRY_POS);
- }
+ vh_player = self;
+ vh_vehicle = self.vehicle;
+ MUTATOR_CALLHOOK(VehicleExit);
+ self = vh_player;
+ self.vehicle = vh_vehicle;
self.vehicle.vehicle_hudmodel.viewmodelforclient = self.vehicle;
CSQCVehicleSetup(other, other.hud);
- if(other.flagcarried)
- {
- if(!autocvar_g_vehicles_allow_flagcarry)
- DropFlag(other.flagcarried, world, world);
- else
- {
- other.flagcarried.scale = 1;
- setattachment(other.flagcarried, self, "");
- setorigin(other.flagcarried, '0 0 1' * self.maxs_z);
- }
- }
+ vh_player = other;
+ vh_vehicle = _gun;
+ MUTATOR_CALLHOOK(VehicleEnter);
+ other = vh_player;
+ _gun = vh_vehicle;
return TRUE;
}
float autocvar_g_vehicles_crush_force;
float autocvar_g_vehicles_delayspawn;
float autocvar_g_vehicles_delayspawn_jitter;
-float autocvar_g_vehicles_allow_flagcarry;
var float autocvar_g_vehicles_nex_damagerate = 0.5;
var float autocvar_g_vehicles_uzi_damagerate = 0.5;
self.touch = vehicles_touch;
self.event_damage = vehicles_damage;
self.iscreature = TRUE;
+ self.teleportable = FALSE; // no teleporting for vehicles, too buggy
self.damagedbycontents = TRUE;
self.movetype = MOVETYPE_WALK;
self.solid = SOLID_SLIDEBOX;
vehicles_clearrturn();
CSQCVehicleSetup(self.owner, self.hud);
-
- if(other.flagcarried)
- {
- if(!autocvar_g_vehicles_allow_flagcarry)
- DropFlag(other.flagcarried, world, world);
- else
- {
- other.flagcarried.scale = 1;
- setattachment(other.flagcarried, self, "");
- setorigin(other.flagcarried, self.maxs_z * '0 0 1');
- }
- }
+
+ vh_player = other;
+ vh_vehicle = self;
+ MUTATOR_CALLHOOK(VehicleEnter);
+ other = vh_player;
+ self = vh_vehicle;
self.vehicle_enter();
antilag_clear(other);
_player.hud = HUD_NORMAL;
_player.switchweapon = _vehicle.switchweapon;
- if(_player.flagcarried)
- {
- _player.flagcarried.scale = 0.6;
- setattachment(_player.flagcarried, _player, "");
- setorigin(_player.flagcarried, FLAG_CARRY_POS);
- }
-
CSQCVehicleSetup(_player, HUD_NORMAL);
}
_vehicle.flags |= FL_NOTARGET;
if(!teamplay)
_vehicle.team = 0;
else
- _vehicle.team = _vehicle.tur_head.team;
+
+ vh_player = _player;
+ vh_vehicle = _vehicle;
+ MUTATOR_CALLHOOK(VehicleExit);
+ _player = vh_player;
+ _vehicle = vh_vehicle;
+
+ _vehicle.team = _vehicle.tur_head.team;
sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTN_NORM);
_vehicle.vehicle_hudmodel.viewmodelforclient = _vehicle;
self.takedamage = DAMAGE_AIM;
self.bot_attack = TRUE;
self.iscreature = TRUE;
+ self.teleportable = FALSE; // no teleporting for vehicles, too buggy
self.damagedbycontents = TRUE;
self.hud = vhud;
self.tur_health = _max_health;
return _predict_pos;
}
-*/
\ No newline at end of file
+*/
set g_vehicles_delayspawn 1
set g_vehicles_delayspawn_jitter 10
-set g_vehicles_allow_flagcarry 1
set g_vehicles_nex_damagerate 0.5
set g_vehicles_uzi_damagerate 0.65
set g_vehicles_rifle_damagerate 1
set g_vehicles_minstanex_damagerate 0.001
-set g_vehicles_tag_damagerate 2
\ No newline at end of file
+set g_vehicles_tag_damagerate 2
+