]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge remote-tracking branch 'origin/samual/mutator_ctf'
authorSamual Lenks <samual@xonotic.org>
Mon, 10 Sep 2012 23:10:25 +0000 (19:10 -0400)
committerSamual Lenks <samual@xonotic.org>
Mon, 10 Sep 2012 23:10:25 +0000 (19:10 -0400)
64 files changed:
ctfscoring-ai.cfg
ctfscoring-alien.cfg [deleted file]
ctfscoring-alpha.cfg [deleted file]
ctfscoring-div0.cfg
ctfscoring-nex242.cfg
ctfscoring-samual.cfg [new file with mode: 0644]
ctfscoring-z-lowdeposit.cfg [deleted file]
ctfscoring-z.cfg [deleted file]
defaultXonotic.cfg
effectinfo.txt
gamemodes.cfg [new file with mode: 0644]
qcsrc/client/announcer.qc
qcsrc/client/autocvars.qh
qcsrc/client/scoreboard.qc
qcsrc/client/waypointsprites.qc
qcsrc/common/items.qh
qcsrc/server/attic/bot/havocbot/role_ctf.qc [new file with mode: 0644]
qcsrc/server/attic/bot/havocbot/role_freezetag.qc [new file with mode: 0644]
qcsrc/server/attic/bot/havocbot/role_keepaway.qc [new file with mode: 0644]
qcsrc/server/attic/ctf.qc [new file with mode: 0644]
qcsrc/server/attic/monsters/m_monsters.qc
qcsrc/server/attic/nexball.qc
qcsrc/server/autocvars.qh
qcsrc/server/bot/havocbot/havocbot.qc
qcsrc/server/bot/havocbot/havocbot.qh
qcsrc/server/bot/havocbot/role_ctf.qc [deleted file]
qcsrc/server/bot/havocbot/role_freezetag.qc [deleted file]
qcsrc/server/bot/havocbot/role_keepaway.qc [deleted file]
qcsrc/server/bot/havocbot/roles.qc
qcsrc/server/cheats.qc
qcsrc/server/cl_client.qc
qcsrc/server/cl_impulse.qc
qcsrc/server/cl_player.qc
qcsrc/server/command/common.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/constants.qh
qcsrc/server/ctf.qc [deleted file]
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_triggers.qc
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/base.qh
qcsrc/server/mutators/gamemode_ctf.qc [new file with mode: 0644]
qcsrc/server/mutators/gamemode_ctf.qh [new file with mode: 0644]
qcsrc/server/mutators/gamemode_freezetag.qc
qcsrc/server/mutators/gamemode_keepaway.qc
qcsrc/server/mutators/gamemode_keepaway.qh [new file with mode: 0644]
qcsrc/server/mutators/gamemode_nexball.qc
qcsrc/server/mutators/gamemode_nexball.qh
qcsrc/server/mutators/mutators.qh
qcsrc/server/portals.qc
qcsrc/server/progs.src
qcsrc/server/scores_rules.qc
qcsrc/server/t_quake3.qc
qcsrc/server/t_teleporters.qc
qcsrc/server/teamplay.qc
qcsrc/server/tturrets/units/unit_ewheel.qc
qcsrc/server/tturrets/units/unit_walker.qc
qcsrc/server/vehicles/bumblebee.qc
qcsrc/server/vehicles/vehicles.qc
sound/ctf/pass.wav [new file with mode: 0644]
sound/ctf/touch.wav [new file with mode: 0644]
vehicles.cfg

index 711373647864160c111a36aec9e7295409e1a6bd..284aff7c5b00ce286893dea912bffc3fcb718d28 100644 (file)
@@ -1,19 +1,10 @@
-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
diff --git a/ctfscoring-alien.cfg b/ctfscoring-alien.cfg
deleted file mode 100644 (file)
index dd5758d..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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
diff --git a/ctfscoring-alpha.cfg b/ctfscoring-alpha.cfg
deleted file mode 100644 (file)
index c15cee8..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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
index 33c8ab1b72bd9f7d7b7c0da352db959719a74a43..f361d189523a5a5a894b7f9080ed8794ac8fa545 100644 (file)
@@ -1,19 +1,10 @@
-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
index 861ead4613d165ecef7681adb90dc87f691dc2bb..7461dffc003347e7ba5da83b0e2273fbe8d8476a 100644 (file)
@@ -1,19 +1,10 @@
-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
diff --git a/ctfscoring-samual.cfg b/ctfscoring-samual.cfg
new file mode 100644 (file)
index 0000000..9bc87c2
--- /dev/null
@@ -0,0 +1,10 @@
+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
diff --git a/ctfscoring-z-lowdeposit.cfg b/ctfscoring-z-lowdeposit.cfg
deleted file mode 100644 (file)
index c75b306..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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
diff --git a/ctfscoring-z.cfg b/ctfscoring-z.cfg
deleted file mode 100644 (file)
index e45bdd9..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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
index 57448790dc6e2996ac91857d7c4313bd05eb8af9..1f71855687ae404b7081d1d021dce4cb13cf38c9 100644 (file)
@@ -31,15 +31,6 @@ seta cl_startcount 0 "how many times the client has been run"
 
 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
@@ -463,45 +454,6 @@ set sv_dodging_height_threshold 10 "the maximum height above ground where to all
 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"
@@ -509,60 +461,6 @@ set g_spawn_near_teammate_distance 640 "max distance to consider a spawn to be n
 // 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"
@@ -585,212 +483,11 @@ seta g_teamdamage_resetspeed 20  "for teamplay 4: how fast player's teamdamage co
 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"
@@ -879,7 +576,6 @@ sv_sound_watersplash ""
 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
@@ -1169,51 +865,6 @@ seta cl_hidewaypoints 0 "disable static waypoints, only show team waypoints"
 seta g_waypointsprites_turrets 1 "disable turret waypoints"
 seta g_waypointsprites_turrets_maxdist 4000 "max distace for turret sprites"
 
-// key hunt
-set g_keyhunt 0 "Key Hunt: collect all keys from the enemies and bring them together to score"
-set g_balance_keyhunt_delay_return 60
-set g_balance_keyhunt_delay_round 5
-set g_balance_keyhunt_delay_tracking 10
-set g_balance_keyhunt_delay_fadeout 2
-set g_balance_keyhunt_delay_collect 1.5
-set g_balance_keyhunt_maxdist 150
-set g_balance_keyhunt_score_collect 3
-set g_balance_keyhunt_score_carrierfrag 2
-set g_balance_keyhunt_score_capture 100
-set g_balance_keyhunt_score_push 60
-set g_balance_keyhunt_score_destroyed 50
-set g_balance_keyhunt_score_destroyed_ownfactor 1
-set g_balance_keyhunt_dropvelocity 300
-set g_balance_keyhunt_throwvelocity 400
-set g_balance_keyhunt_protecttime 0.8
-set g_balance_keyhunt_damageforcescale 1
-seta g_keyhunt_teams_override 0
-set g_keyhunt_teams 0
-
-// keepaway
-set g_keepaway 0 "game mode which focuses around a ball, look at g_keepaway_win_mode for further details"
-set g_keepaway_score_bckill 1 "enable scoring points (y/n) for ball carrier kills (value is how many points to award)"
-set g_keepaway_score_killac 1 "amount of points to give when you kill someone while you have the ball"
-set g_keepaway_score_timeinterval 1 "amount of time it takes between intervals for timepoints to be added to the score"
-set g_keepaway_score_timepoints 0 "points to add to score per timeinterval, 0 for no points"
-set g_keepaway_ballcarrier_effects 8 "Add together the numbers you want: EF_ADDITIVE (32) / EF_NODEPTHTEST (8192) / EF_DIMLIGHT (8)"
-set g_keepaway_ballcarrier_highspeed 1 "speed multiplier done to the person holding the ball (recommended when used with some mutators)"
-set g_keepaway_ballcarrier_damage      1       "damage multiplier while holding the ball"
-set g_keepaway_ballcarrier_force       1       "force multiplier while holding the ball"
-set g_keepaway_ballcarrier_selfdamage  1       "self damage multiplier while holding the ball"
-set g_keepaway_ballcarrier_selfforce   1       "self force multiplier while holding the ball"
-set g_keepaway_noncarrier_warn 1       "warn players when they kill without holding the ball"
-set g_keepaway_noncarrier_damage       1       "damage done to other players if both you and they don't have the ball"
-set g_keepaway_noncarrier_force        1       "force done to other players if both you and they don't have the ball"
-set g_keepaway_noncarrier_selfdamage   1       "self damage if you don't have the ball"
-set g_keepaway_noncarrier_selfforce    1       "self force if you don't have the ball"
-set g_keepawayball_effects 0 "Add together the numbers you want: EF_ADDITIVE (32) / EF_NODEPTHTEST (8192) / EF_DIMLIGHT (8)"
-set g_keepawayball_trail_color 254     "particle trail color from player/ball"
-set g_keepawayball_damageforcescale    3 "Scale of force which is applied to the ball by weapons/explosions/etc"
-set g_keepawayball_respawntime 10      "if no one picks up the ball, how long to wait until the ball respawns"
-seta g_keepaway_teams_override 0
-set g_keepaway_teams 0
-
 // so it can be stuffcmd-ed still
 set cl_gravity 800     "but ignored anyway"
 
@@ -1436,9 +1087,6 @@ set sv_maxidle 0
 // 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"
@@ -1604,52 +1252,6 @@ seta cl_modeldetailreduction 1   "the higher, the less detailed certain map models
 
 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
@@ -1935,12 +1537,12 @@ scr_loadingscreen_scale_limit 2
 // 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
index 38413d3c622aa32af70926abdb2e1b8649458327..d346a30efbe930804ce535b3fe9b24b31e686fb0 100644 (file)
@@ -775,7 +775,7 @@ effect teleport
 count 500
 type spark
 tex 64 64
-color 0xff8400 0xff2a00
+color 0x807aff 0x4463d5
 size 1 1
 alpha 0 256 100
 stretchfactor 2
@@ -793,7 +793,7 @@ tex 65 65
 size 150 150
 alpha 190 190 180
 sizeincrease -80
-color 0xff8400 0xff2a00
+color 0x807aff 0x4463d5
 
 
 
@@ -7636,4 +7636,145 @@ velocityjitter 10 10 10
 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
+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
+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
+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
+effect blue_pass
+trailspacing 12
+color 0x0000FF 0x000097
+size 4 4
+//tex 48 55
+alpha 256 256 1280
+type static
+
+// red_cap -- red team capture effect
+effect red_cap
+count 500
+type spark
+tex 64 64
+color 0xFF0000 0x970000
+size 1 1
+alpha 0 256 100
+stretchfactor 2
+//gravity 1
+bounce 1.5
+originjitter 1 1 1
+velocityjitter 1000 1000 1500
+velocitymultiplier 0.5
+airfriction 2
+stretchfactor 0.6
+effect red_cap
+countabsolute 1
+type smoke
+tex 65 65
+size 150 150
+alpha 190 190 180
+sizeincrease -80
+color 0xFF0000 0x970000
+
+// blue_cap -- blue team capture effect
+effect blue_cap
+count 500
+type spark
+tex 64 64
+color 0x0000FF 0x000097
+size 1 1
+alpha 0 256 100
+stretchfactor 2
+//gravity 1
+bounce 1.5
+originjitter 1 1 1
+velocityjitter 1000 1000 1500
+velocitymultiplier 0.5
+airfriction 2
+stretchfactor 0.6
+effect blue_cap
+countabsolute 1
+type smoke
+tex 65 65
+size 150 150
+alpha 190 190 180
+sizeincrease -80
+color 0x0000FF 0x000097
+
diff --git a/gamemodes.cfg b/gamemodes.cfg
new file mode 100644 (file)
index 0000000..258b63c
--- /dev/null
@@ -0,0 +1,458 @@
+// ===================================
+//  Master config for core game modes
+// ===================================
+
+// global gametype setting (1 = deathmatch)
+set gamecfg 1
+
+// 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"
+
+
+// =================
+//  gamestart 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
+
+
+// ===========
+//  leadlimit
+// ===========
+// this means that timelimit can be overidden globally and fraglimit can be overidden for each game mode: DM/TDM, Domination, CTF, and Runematch.
+set leadlimit 0
+set leadlimit_and_fraglimit 0 "if set, leadlimit is ANDed with fraglimit (otherwise ORed)"
+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)"
+
+
+// =================================
+//  respawn delay/waves/weapon_stay
+// =================================
+// when variables are set to anything other than 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_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_weapon_stay 2
+set g_ft_respawn_waves 0
+set g_ft_respawn_delay 0
+set g_ft_weapon_stay 0
+
+
+// =======
+//  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"
+
+
+// =========
+//  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"
+
+
+// ============
+//  clan arena
+// ============
+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"
+set g_ca_damage2score_multiplier 0.01
+set g_ca_round_timelimit 180
+
+
+// ==================
+//  capture the flag
+// ==================
+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 100
+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_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 0 "allow passing of flags to nearby team mates... WARNING: UNSTABLE, does not work correctly with warpzones! Disabled by default until fixed."
+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 0 "allow flags to be picked up/captured/returned without even leaving the vehicle"
+set g_ctf_allow_vehicle_carry 1 "allow players to hold flags 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"
+
+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 1
+set g_ctf_fullbrightflags 0
+set g_ctf_dynamiclights 0
+set g_ctf_captimerecord_always 0 "always show capture time information when someone captures the flag"
+
+seta g_ctf_ignore_frags 0      "1: regular frags give no points"
+exec ctfscoring-samual.cfg
+
+
+// ====================
+//  complete the stage
+// ====================
+set g_cts 0 "CTS: complete the stage"
+set g_cts_selfdamage 1 "0 = disable all selfdamage and falldamage in cts"
+set g_cts_finish_kill_delay 10 "prevent cheating by running back to the start line, and starting out with more speed than otherwise possible"
+
+
+// ==========================
+//  deathmatch (ffa or team)
+// ==========================
+set g_dm 1 "Deathmatch: killing any other player is one frag, player with most frags wins"
+set g_tdm_teams 2 "how many teams are in team deathmatch (set by mapinfo)"
+set g_tdm_team_spawns 0 "when 1, a map can define team spawnpoints for TDM"
+seta g_tdm_teams_override 0    "how many teams are in team deathmatch"
+
+
+// ============
+//  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"
+
+
+// ===========
+//  freezetag
+// ===========
+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"
+
+
+// ==========
+//  keepaway
+// ==========
+set g_keepaway 0 "game mode which focuses around a ball, look at g_keepaway_win_mode for further details"
+set g_keepaway_score_bckill 1 "enable scoring points (y/n) for ball carrier kills (value is how many points to award)"
+set g_keepaway_score_killac 1 "amount of points to give when you kill someone while you have the ball"
+set g_keepaway_score_timeinterval 1 "amount of time it takes between intervals for timepoints to be added to the score"
+set g_keepaway_score_timepoints 0 "points to add to score per timeinterval, 0 for no points"
+set g_keepaway_ballcarrier_effects 8 "Add together the numbers you want: EF_ADDITIVE (32) / EF_NODEPTHTEST (8192) / EF_DIMLIGHT (8)"
+set g_keepaway_ballcarrier_highspeed 1 "speed multiplier done to the person holding the ball (recommended when used with some mutators)"
+set g_keepaway_ballcarrier_damage      1       "damage multiplier while holding the ball"
+set g_keepaway_ballcarrier_force       1       "force multiplier while holding the ball"
+set g_keepaway_ballcarrier_selfdamage  1       "self damage multiplier while holding the ball"
+set g_keepaway_ballcarrier_selfforce   1       "self force multiplier while holding the ball"
+set g_keepaway_noncarrier_warn 1       "warn players when they kill without holding the ball"
+set g_keepaway_noncarrier_damage       1       "damage done to other players if both you and they don't have the ball"
+set g_keepaway_noncarrier_force        1       "force done to other players if both you and they don't have the ball"
+set g_keepaway_noncarrier_selfdamage   1       "self damage if you don't have the ball"
+set g_keepaway_noncarrier_selfforce    1       "self force if you don't have the ball"
+set g_keepawayball_effects 0 "Add together the numbers you want: EF_ADDITIVE (32) / EF_NODEPTHTEST (8192) / EF_DIMLIGHT (8)"
+set g_keepawayball_trail_color 254     "particle trail color from player/ball"
+set g_keepawayball_damageforcescale    3 "Scale of force which is applied to the ball by weapons/explosions/etc"
+set g_keepawayball_respawntime 10      "if no one picks up the ball, how long to wait until the ball respawns"
+seta g_keepaway_teams_override 0
+set g_keepaway_teams 0
+
+
+// ==========
+//  key hunt
+// ==========
+set g_keyhunt 0 "Key Hunt: collect all keys from the enemies and bring them together to score"
+set g_balance_keyhunt_delay_return 60
+set g_balance_keyhunt_delay_round 5
+set g_balance_keyhunt_delay_tracking 10
+set g_balance_keyhunt_delay_fadeout 2
+set g_balance_keyhunt_delay_collect 1.5
+set g_balance_keyhunt_maxdist 150
+set g_balance_keyhunt_score_collect 3
+set g_balance_keyhunt_score_carrierfrag 2
+set g_balance_keyhunt_score_capture 100
+set g_balance_keyhunt_score_push 60
+set g_balance_keyhunt_score_destroyed 50
+set g_balance_keyhunt_score_destroyed_ownfactor 1
+set g_balance_keyhunt_dropvelocity 300
+set g_balance_keyhunt_throwvelocity 400
+set g_balance_keyhunt_protecttime 0.8
+set g_balance_keyhunt_damageforcescale 1
+seta g_keyhunt_teams_override 0
+set g_keyhunt_teams 0
+
+
+// ===================
+//  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"
+
+
+// =========
+//  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"
+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"
+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?"
+
+
+// ===========
+//  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
+
+
+// ======
+//  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)"
+
+
+// ===========
+//  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
+
index 3e109e78052036fd77f33f88b5138e12844f4418..db1ae41336d5e1eb54d30c437975b7323b330739 100644 (file)
@@ -134,52 +134,10 @@ void Announcer_Time()
        }
 }
 
-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 () 
index dd482a8f252a73de57d9a51ee3976df07e4a5662..ee402287283d690259ff9bffcc6e89e56d94a555 100644 (file)
@@ -54,7 +54,6 @@ float autocvar_cl_gunalign;
 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;
index 69a296796108022624ef2b5a7bb65c4fb9a9634d..48a7217fc703a08b3b7f7fdfab3bc58efea36c2e 100644 (file)
@@ -19,6 +19,7 @@ string TranslateScoresLabel(string l)
                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"));
@@ -254,6 +255,7 @@ void Cmd_HUD_Help()
        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"));
index 513c66eb8b9ca96623a1eb7c55ccab0de2e38103..d3d57aba9e8cbcd52ad7606466f912dc394032a9 100644 (file)
@@ -256,6 +256,7 @@ string spritelookuptext(string s)
                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!");
index 33fa21f7eeef5fb96bb0986971ce5223f27d0979..c20715d5852c3cbf09eae967424bf50d2a6754b2 100644 (file)
@@ -31,16 +31,16 @@ float   IT_STRENGTH                  = 8192;
 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;
diff --git a/qcsrc/server/attic/bot/havocbot/role_ctf.qc b/qcsrc/server/attic/bot/havocbot/role_ctf.qc
new file mode 100644 (file)
index 0000000..0946f43
--- /dev/null
@@ -0,0 +1,684 @@
+#define HAVOCBOT_CTF_ROLE_NONE                 0
+#define HAVOCBOT_CTF_ROLE_DEFENSE      2
+#define HAVOCBOT_CTF_ROLE_MIDDLE       4
+#define HAVOCBOT_CTF_ROLE_OFFENSE      8
+#define HAVOCBOT_CTF_ROLE_CARRIER      16
+#define HAVOCBOT_CTF_ROLE_RETRIEVER    32
+#define HAVOCBOT_CTF_ROLE_ESCORT       64
+
+.void() havocbot_role;
+.void() havocbot_previous_role;
+
+void() havocbot_role_ctf_middle;
+void() havocbot_role_ctf_defense;
+void() havocbot_role_ctf_offense;
+void() havocbot_role_ctf_carrier;
+void() havocbot_role_ctf_retriever;
+void() havocbot_role_ctf_escort;
+
+void(entity bot) havocbot_ctf_reset_role;
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
+
+.float havocbot_cantfindflag;
+.float havocbot_role_timeout;
+.entity ctf_worldflagnext;
+.entity bot_basewaypoint;
+
+entity ctf_worldflaglist;
+vector havocbot_ctf_middlepoint;
+float havocbot_ctf_middlepoint_radius;
+
+entity havocbot_ctf_find_flag(entity bot)
+{
+       entity f;
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               if (bot.team == f.team)
+                       return f;
+               f = f.ctf_worldflagnext;
+       }
+       return world;
+}
+
+entity havocbot_ctf_find_enemy_flag(entity bot)
+{
+       entity f;
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               if (bot.team != f.team)
+                       return f;
+               f = f.ctf_worldflagnext;
+       }
+       return world;
+}
+
+float havocbot_ctf_teamcount(entity bot, vector org, float radius)
+{
+       if not(teamplay)
+               return 0;
+
+       float c = 0;
+       entity head;
+
+       FOR_EACH_PLAYER(head)
+       {
+               if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
+                       continue;
+
+               if(vlen(head.origin - org) < radius)
+                       ++c;
+       }
+
+       return c;
+}
+
+void havocbot_goalrating_ctf_ourflag(float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (self.team == head.team)
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (head)
+               navigation_routerating(head, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_ourbase(float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (self.team == head.team)
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if not(head)
+               return;
+
+       navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemyflag(float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (self.team != head.team)
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (head)
+               navigation_routerating(head, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemybase(float ratingscale)
+{
+       if not(bot_waypoints_for_items)
+       {
+               havocbot_goalrating_ctf_enemyflag(ratingscale);
+               return;
+       }
+
+       entity head;
+
+       head = havocbot_ctf_find_enemy_flag(self);
+
+       if not(head)
+               return;
+
+       navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
+{
+       entity mf;
+
+       mf = havocbot_ctf_find_flag(self);
+
+       if(mf.ctf_status == FLAG_BASE)
+               return;
+
+       if(mf.tag_entity)
+               navigation_routerating(mf.tag_entity, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float radius)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               // flag is out in the field
+               if(head.ctf_status != FLAG_BASE)
+               if(head.tag_entity==world)      // dropped
+               {
+                       if(radius)
+                       {
+                               if(vlen(org-head.origin)<radius)
+                                       navigation_routerating(head, ratingscale, 10000);
+                       }
+                       else
+                               navigation_routerating(head, ratingscale, 10000);
+               }
+
+               head = head.ctf_worldflagnext;
+       }
+}
+
+void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
+{
+       entity head;
+       float t;
+       head = findchainfloat(bot_pickup, TRUE);
+       while (head)
+       {
+               // gather health and armor only
+               if (head.solid)
+               if (head.health || head.armorvalue)
+               if (vlen(head.origin - org) < sradius)
+               {
+                       // get the value of the item
+                       t = head.bot_pickupevalfunc(self, head) * 0.0001;
+                       if (t > 0)
+                               navigation_routerating(head, t * ratingscale, 500);
+               }
+               head = head.chain;
+       }
+}
+
+void havocbot_role_ctf_setrole(entity bot, float role)
+{
+       dprint(strcat(bot.netname," switched to "));
+       switch(role)
+       {
+               case HAVOCBOT_CTF_ROLE_CARRIER:
+                       dprint("carrier");
+                       bot.havocbot_role = havocbot_role_ctf_carrier;
+                       bot.havocbot_role_timeout = 0;
+                       bot.havocbot_cantfindflag = time + 10;
+                       bot.bot_strategytime = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_DEFENSE:
+                       dprint("defense");
+                       bot.havocbot_role = havocbot_role_ctf_defense;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_MIDDLE:
+                       dprint("middle");
+                       bot.havocbot_role = havocbot_role_ctf_middle;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_OFFENSE:
+                       dprint("offense");
+                       bot.havocbot_role = havocbot_role_ctf_offense;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_RETRIEVER:
+                       dprint("retriever");
+                       bot.havocbot_previous_role = bot.havocbot_role;
+                       bot.havocbot_role = havocbot_role_ctf_retriever;
+                       bot.havocbot_role_timeout = time + 10;
+                       bot.bot_strategytime = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_ESCORT:
+                       dprint("escort");
+                       bot.havocbot_previous_role = bot.havocbot_role;
+                       bot.havocbot_role = havocbot_role_ctf_escort;
+                       bot.havocbot_role_timeout = time + 30;
+                       bot.bot_strategytime = 0;
+                       break;
+       }
+       dprint("\n");
+}
+
+void havocbot_role_ctf_carrier()
+{
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried == world)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourbase(50000);
+
+               if(self.health<100)
+                       havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
+
+               navigation_goalrating_end();
+
+               if (self.navigation_hasgoals)
+                       self.havocbot_cantfindflag = time + 10;
+               else if (time > self.havocbot_cantfindflag)
+               {
+                       // Can't navigate to my own base, suicide!
+                       // TODO: drop it and wander around
+                       Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
+                       return;
+               }
+       }
+}
+
+void havocbot_role_ctf_escort()
+{
+       entity mf, ef;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If enemy flag is back on the base switch to previous role
+       ef = havocbot_ctf_find_enemy_flag(self);
+       if(ef.ctf_status==FLAG_BASE)
+       {
+               self.havocbot_role = self.havocbot_previous_role;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       // If the flag carrier reached the base switch to defense
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status!=FLAG_BASE)
+       if(vlen(ef.origin - mf.dropped_origin) < 300)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!self.havocbot_role_timeout)
+       {
+               self.havocbot_role_timeout = time + random() * 30 + 60;
+       }
+
+       // If nothing happened just switch to previous role
+       if (time > self.havocbot_role_timeout)
+       {
+               self.havocbot_role = self.havocbot_previous_role;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       // Chase the flag carrier
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_enemyflag(30000);
+               havocbot_goalrating_ctf_ourstolenflag(40000);
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_offense()
+{
+       entity mf, ef;
+       vector pos;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // Check flags
+       mf = havocbot_ctf_find_flag(self);
+       ef = havocbot_ctf_find_enemy_flag(self);
+
+       // Own flag stolen
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               if(mf.tag_entity)
+                       pos = mf.tag_entity.origin;
+               else
+                       pos = mf.origin;
+
+               // Try to get it if closer than the enemy base
+               if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
+               {
+                       havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+                       return;
+               }
+       }
+
+       // Escort flag carrier
+       if(ef.ctf_status!=FLAG_BASE)
+       {
+               if(ef.tag_entity)
+                       pos = ef.tag_entity.origin;
+               else
+                       pos = ef.origin;
+
+               if(vlen(pos-mf.dropped_origin)>700)
+               {
+                       havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
+                       return;
+               }
+       }
+
+       // About to fail, switch to middlefield
+       if(self.health<50)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 120;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourstolenflag(50000);
+               havocbot_goalrating_ctf_enemybase(20000);
+               havocbot_goalrating_items(5000, self.origin, 1000);
+               havocbot_goalrating_items(1000, self.origin, 10000);
+               navigation_goalrating_end();
+       }
+}
+
+// Retriever (temporary role):
+void havocbot_role_ctf_retriever()
+{
+       entity mf;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If flag is back on the base switch to previous role
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status==FLAG_BASE)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 20;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               float radius;
+               radius = 10000;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourstolenflag(50000);
+               havocbot_goalrating_ctf_droppedflags(40000, self.origin, radius);
+               havocbot_goalrating_ctf_enemybase(30000);
+               havocbot_goalrating_items(500, self.origin, radius);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_middle()
+{
+       entity mf;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 10;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               vector org;
+
+               org = havocbot_ctf_middlepoint;
+               org_z = self.origin_z;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourstolenflag(50000);
+               havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(2500, self.origin, 10000);
+               havocbot_goalrating_ctf_enemybase(2500);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_defense()
+{
+       entity mf;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If own flag was captured
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 30;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+       if (self.bot_strategytime < time)
+       {
+               float radius;
+               vector org;
+
+               org = mf.dropped_origin;
+               radius = havocbot_ctf_middlepoint_radius;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+
+               // if enemies are closer to our base, go there
+               entity head, closestplayer = world;
+               float distance, bestdistance = 10000;
+               FOR_EACH_PLAYER(head)
+               {
+                       if(head.deadflag!=DEAD_NO)
+                               continue;
+
+                       distance = vlen(org - head.origin);
+                       if(distance<bestdistance)
+                       {
+                               closestplayer = head;
+                               bestdistance = distance;
+                       }
+               }
+
+               if(closestplayer)
+               if(closestplayer.team!=self.team)
+               if(vlen(org - self.origin)>1000)
+               if(checkpvs(self.origin,closestplayer)||random()<0.5)
+                       havocbot_goalrating_ctf_ourbase(30000);
+
+               havocbot_goalrating_ctf_ourstolenflag(20000);
+               havocbot_goalrating_ctf_droppedflags(20000, org, radius);
+               havocbot_goalrating_enemyplayers(15000, org, radius);
+               havocbot_goalrating_items(10000, org, radius);
+               havocbot_goalrating_items(5000, self.origin, 10000);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_calculate_middlepoint()
+{
+       entity f;
+       vector s = '0 0 0';
+       vector fo = '0 0 0';
+       float n = 0;
+
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               fo = f.origin;
+               s = s + fo;
+               f = f.ctf_worldflagnext;
+       }
+       if(!n)
+               return;
+       havocbot_ctf_middlepoint = s * (1.0 / n);
+       havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
+}
+
+void havocbot_ctf_reset_role(entity bot)
+{
+       float cdefense, cmiddle, coffense;
+       entity mf, ef, head;
+       float c;
+
+       if(bot.deadflag != DEAD_NO)
+               return;
+
+       if(vlen(havocbot_ctf_middlepoint)==0)
+               havocbot_calculate_middlepoint();
+
+       // Check ctf flags
+       if (bot.flagcarried)
+       {
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       mf = havocbot_ctf_find_flag(bot);
+       ef = havocbot_ctf_find_enemy_flag(bot);
+
+       // Retrieve stolen flag
+       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.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
+               return;
+       }
+
+       // if there is only me on the team switch to offense
+       c = 0;
+       FOR_EACH_PLAYER(head)
+       if(head.team==bot.team)
+               ++c;
+
+       if(c==1)
+       {
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
+               return;
+       }
+
+       // Evaluate best position to take
+       // Count mates on middle position
+       cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
+
+       // Count mates on defense position
+       cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
+
+       // Count mates on offense position
+       coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
+
+       if(cdefense<=coffense)
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
+       else if(coffense<=cmiddle)
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
+       else
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
+}
+
+void havocbot_chooserole_ctf()
+{
+       havocbot_ctf_reset_role(self);
+}
diff --git a/qcsrc/server/attic/bot/havocbot/role_freezetag.qc b/qcsrc/server/attic/bot/havocbot/role_freezetag.qc
new file mode 100644 (file)
index 0000000..4e5669e
--- /dev/null
@@ -0,0 +1,109 @@
+void() havocbot_role_ft_freeing;
+void() havocbot_role_ft_offense;
+
+void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius)
+{
+       entity head;
+       float distance;
+
+       FOR_EACH_PLAYER(head)
+       {
+               if ((head != self) && (head.team == self.team))
+               {
+                       if (head.freezetag_frozen)
+                       {
+                               distance = vlen(head.origin - org);
+                               if (distance > sradius)
+                                       continue;
+                               navigation_routerating(head, ratingscale, 2000);
+                       }
+                       else
+                       {
+                               // If teamate is not frozen still seek them out as fight better
+                               // in a group.
+                               navigation_routerating(head, ratingscale/3, 2000);
+                       }
+               }
+       }
+}
+
+void havocbot_role_ft_offense()
+{
+       entity head;
+       float unfrozen;
+
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + random() * 10 + 20;
+
+       // Count how many players on team are unfrozen.
+       unfrozen = 0;
+       FOR_EACH_PLAYER(head)
+       {
+               if ((head.team == self.team) && (!head.freezetag_frozen))
+                       unfrozen++;
+       }
+
+       // If only one left on team or if role has timed out then start trying to free players.
+       if (((unfrozen == 0) && (!self.freezetag_frozen)) || (time > self.havocbot_role_timeout))
+       {
+               dprint("changing role to freeing\n");
+               self.havocbot_role = havocbot_role_ft_freeing;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
+               havocbot_goalrating_freeplayers(9000, self.origin, 10000);
+               //havocbot_goalrating_waypoints(1, self.origin, 1000);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ft_freeing()
+{
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + random() * 10 + 20;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               dprint("changing role to offense\n");
+               self.havocbot_role = havocbot_role_ft_offense;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(8000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(10000, self.origin, 10000);
+               havocbot_goalrating_freeplayers(20000, self.origin, 10000);
+               //havocbot_goalrating_waypoints(1, self.origin, 1000);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_chooserole_ft()
+{
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (random() < 0.5)
+               self.havocbot_role = havocbot_role_ft_freeing;
+       else
+               self.havocbot_role = havocbot_role_ft_offense;
+}
diff --git a/qcsrc/server/attic/bot/havocbot/role_keepaway.qc b/qcsrc/server/attic/bot/havocbot/role_keepaway.qc
new file mode 100644 (file)
index 0000000..ede6208
--- /dev/null
@@ -0,0 +1,82 @@
+void() havocbot_role_ka_carrier;
+void() havocbot_role_ka_collector;
+void() havocbot_chooserole_ka;
+
+entity ka_ball;
+
+// Keepaway
+// If you don't have the ball, get it; if you do, kill people.
+
+void havocbot_goalrating_ball(float ratingscale, vector org)
+{
+       float t;
+       entity ball_owner;
+       ball_owner = ka_ball.owner;
+
+       if (ball_owner == self)
+               return;
+
+       // If ball is carried by player then hunt them down.
+       if (ball_owner)
+       {
+               t = (self.health + self.armorvalue) / (ball_owner.health + ball_owner.armorvalue);
+               navigation_routerating(ball_owner, t * ratingscale, 2000);
+       }
+
+       // Ball has been dropped so collect.
+       navigation_routerating(ka_ball, ratingscale, 2000);
+}
+
+void havocbot_role_ka_carrier()
+{
+       if (self.deadflag != DEAD_NO)
+               return;
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
+               //havocbot_goalrating_waypoints(1, self.origin, 1000);
+               navigation_goalrating_end();
+       }
+
+       if (!self.ballcarried)
+       {
+               self.havocbot_role = havocbot_role_ka_collector;
+               self.bot_strategytime = 0;
+       }
+}
+
+void havocbot_role_ka_collector()
+{
+       if (self.deadflag != DEAD_NO)
+               return;
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(1000, self.origin, 10000);
+               havocbot_goalrating_ball(20000, self.origin);
+               navigation_goalrating_end();
+       }
+
+       if (self.ballcarried)
+       {
+               self.havocbot_role = havocbot_role_ka_carrier;
+               self.bot_strategytime = 0;
+       }
+}
+
+void havocbot_chooserole_ka()
+{
+       if (self.ballcarried)
+               self.havocbot_role = havocbot_role_ka_carrier;
+       else
+               self.havocbot_role = havocbot_role_ka_collector;
+}
diff --git a/qcsrc/server/attic/ctf.qc b/qcsrc/server/attic/ctf.qc
new file mode 100644 (file)
index 0000000..d65d866
--- /dev/null
@@ -0,0 +1,1226 @@
+#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;
+}
+*/
index 3e160d97b543e89e84fe01a53084dced6aa4139b..b450f761e31af442b2c084763e97fd44ae5faf84 100644 (file)
@@ -259,6 +259,7 @@ void() walkmonster_start_go =
 // 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
@@ -353,6 +354,7 @@ void() flymonster_start_go =
                }
        }
        self.iscreature = TRUE;
+       self.teleportable = TELEPORT_NORMAL;
        self.damagedbycontents = TRUE;
 
        force_retouch = 2; // mainly to detect teleports
@@ -442,6 +444,7 @@ void() swimmonster_start_go =
                }
        }
        self.iscreature = TRUE;
+       self.teleportable = TELEPORT_NORMAL;
        self.damagedbycontents = TRUE;
 
        force_retouch = 2; // mainly to detect teleports
index a068a33a54e236017194c3ec5ac05e5caa0b9df1..d3f4b55f4a49acf42693df6e04df24942a1b99f3 100644 (file)
@@ -28,6 +28,9 @@ float nb_teams;
 
 .float teamtime;
 
+.float nb_dropperid;
+.float nb_droptime;
+
 void nb_delayedinit();
 void nb_init() // Called early (worldspawn stage)
 {
@@ -155,7 +158,7 @@ void GiveBall (entity plyr, entity ball)
        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;
@@ -188,7 +191,7 @@ void DropBall (entity ball, vector org, vector vel)
        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);
@@ -302,7 +305,7 @@ void basketball_touch (void)
                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);
index 12a343eb726b6fac85a0ff52693bc9bec340b8d2..a3687f9592ff828fcbea2e81519c7b0a223bbe81 100644 (file)
@@ -179,8 +179,6 @@ float autocvar_g_balance_crylink_secondary_speed;
 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;
@@ -762,23 +760,60 @@ float autocvar_g_chat_flood_spl_team;
 float autocvar_g_chat_flood_spl_tell;
 float autocvar_g_chat_nospectators;
 float autocvar_g_chat_teamcolors;
+float autocvar_g_ctf_allow_vehicle_carry;
+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_pickup_effects;
+float autocvar_g_ctf_flag_health;
+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_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;
index e03cbac66a5937baa08cea8a4cd3575a6675c057..1b9178b1774b7632c2359463a9fd777833771326 100644 (file)
@@ -1,9 +1,6 @@
 #include "havocbot.qh"
-#include "role_ctf.qc"
 #include "role_onslaught.qc"
 #include "role_keyhunt.qc"
-#include "role_freezetag.qc"
-#include "role_keepaway.qc"
 #include "role_assault.qc"
 #include "roles.qc"
 
index be28962912aaf5e6dc662ead1683b8d43fb06e0f..de94691822e902631b6bbe732162d5bb22270d92 100644 (file)
@@ -19,6 +19,7 @@
 .float havocbot_personal_waypoint_failcounter;
 .float havocbot_chooseenemy_finished;
 .float havocbot_stickenemy;
+.float havocbot_role_timeout;
 
 .entity ignoregoal;
 .entity bot_lastseengoal;
@@ -47,6 +48,10 @@ float havocbot_moveto_refresh_route();
 vector havocbot_dodge();
 
 .void() havocbot_role;
+.void() havocbot_previous_role;
+
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
+void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
 
 /*
  * Imports
diff --git a/qcsrc/server/bot/havocbot/role_ctf.qc b/qcsrc/server/bot/havocbot/role_ctf.qc
deleted file mode 100644 (file)
index 178ee0d..0000000
+++ /dev/null
@@ -1,684 +0,0 @@
-#define HAVOCBOT_CTF_ROLE_NONE                 0
-#define HAVOCBOT_CTF_ROLE_DEFENSE      2
-#define HAVOCBOT_CTF_ROLE_MIDDLE       4
-#define HAVOCBOT_CTF_ROLE_OFFENSE      8
-#define HAVOCBOT_CTF_ROLE_CARRIER      16
-#define HAVOCBOT_CTF_ROLE_RETRIEVER    32
-#define HAVOCBOT_CTF_ROLE_ESCORT       64
-
-.void() havocbot_role;
-.void() havocbot_previous_role;
-
-void() havocbot_role_ctf_middle;
-void() havocbot_role_ctf_defense;
-void() havocbot_role_ctf_offense;
-void() havocbot_role_ctf_carrier;
-void() havocbot_role_ctf_retriever;
-void() havocbot_role_ctf_escort;
-
-void(entity bot) havocbot_ctf_reset_role;
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
-void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
-
-.float havocbot_cantfindflag;
-.float havocbot_role_timeout;
-.entity ctf_worldflagnext;
-.entity basewaypoint;
-
-entity ctf_worldflaglist;
-vector havocbot_ctf_middlepoint;
-float havocbot_ctf_middlepoint_radius;
-
-entity havocbot_ctf_find_flag(entity bot)
-{
-       entity f;
-       f = ctf_worldflaglist;
-       while (f)
-       {
-               if (bot.team == f.team)
-                       return f;
-               f = f.ctf_worldflagnext;
-       }
-       return world;
-}
-
-entity havocbot_ctf_find_enemy_flag(entity bot)
-{
-       entity f;
-       f = ctf_worldflaglist;
-       while (f)
-       {
-               if (bot.team != f.team)
-                       return f;
-               f = f.ctf_worldflagnext;
-       }
-       return world;
-}
-
-float havocbot_ctf_teamcount(entity bot, vector org, float radius)
-{
-       if not(teamplay)
-               return 0;
-
-       float c = 0;
-       entity head;
-
-       FOR_EACH_PLAYER(head)
-       {
-               if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
-                       continue;
-
-               if(vlen(head.origin - org) < radius)
-                       ++c;
-       }
-
-       return c;
-}
-
-void havocbot_goalrating_ctf_ourflag(float ratingscale)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if (self.team == head.team)
-                       break;
-               head = head.ctf_worldflagnext;
-       }
-       if (head)
-               navigation_routerating(head, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_ourbase(float ratingscale)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if (self.team == head.team)
-                       break;
-               head = head.ctf_worldflagnext;
-       }
-       if not(head)
-               return;
-
-       navigation_routerating(head.basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemyflag(float ratingscale)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               if (self.team != head.team)
-                       break;
-               head = head.ctf_worldflagnext;
-       }
-       if (head)
-               navigation_routerating(head, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_enemybase(float ratingscale)
-{
-       if not(bot_waypoints_for_items)
-       {
-               havocbot_goalrating_ctf_enemyflag(ratingscale);
-               return;
-       }
-
-       entity head;
-
-       head = havocbot_ctf_find_enemy_flag(self);
-
-       if not(head)
-               return;
-
-       navigation_routerating(head.basewaypoint, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
-{
-       entity mf;
-
-       mf = havocbot_ctf_find_flag(self);
-
-       if(mf.cnt == FLAG_BASE)
-               return;
-
-       if(mf.tag_entity)
-               navigation_routerating(mf.tag_entity, ratingscale, 10000);
-}
-
-void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float radius)
-{
-       entity head;
-       head = ctf_worldflaglist;
-       while (head)
-       {
-               // flag is out in the field
-               if(head.cnt != FLAG_BASE)
-               if(head.tag_entity==world)      // dropped
-               {
-                       if(radius)
-                       {
-                               if(vlen(org-head.origin)<radius)
-                                       navigation_routerating(head, ratingscale, 10000);
-                       }
-                       else
-                               navigation_routerating(head, ratingscale, 10000);
-               }
-
-               head = head.ctf_worldflagnext;
-       }
-}
-
-void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
-{
-       entity head;
-       float t;
-       head = findchainfloat(bot_pickup, TRUE);
-       while (head)
-       {
-               // gather health and armor only
-               if (head.solid)
-               if (head.health || head.armorvalue)
-               if (vlen(head.origin - org) < sradius)
-               {
-                       // get the value of the item
-                       t = head.bot_pickupevalfunc(self, head) * 0.0001;
-                       if (t > 0)
-                               navigation_routerating(head, t * ratingscale, 500);
-               }
-               head = head.chain;
-       }
-}
-
-void havocbot_role_ctf_setrole(entity bot, float role)
-{
-       dprint(strcat(bot.netname," switched to "));
-       switch(role)
-       {
-               case HAVOCBOT_CTF_ROLE_CARRIER:
-                       dprint("carrier");
-                       bot.havocbot_role = havocbot_role_ctf_carrier;
-                       bot.havocbot_role_timeout = 0;
-                       bot.havocbot_cantfindflag = time + 10;
-                       bot.bot_strategytime = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_DEFENSE:
-                       dprint("defense");
-                       bot.havocbot_role = havocbot_role_ctf_defense;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_MIDDLE:
-                       dprint("middle");
-                       bot.havocbot_role = havocbot_role_ctf_middle;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_OFFENSE:
-                       dprint("offense");
-                       bot.havocbot_role = havocbot_role_ctf_offense;
-                       bot.havocbot_role_timeout = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_RETRIEVER:
-                       dprint("retriever");
-                       bot.havocbot_previous_role = bot.havocbot_role;
-                       bot.havocbot_role = havocbot_role_ctf_retriever;
-                       bot.havocbot_role_timeout = time + 10;
-                       bot.bot_strategytime = 0;
-                       break;
-               case HAVOCBOT_CTF_ROLE_ESCORT:
-                       dprint("escort");
-                       bot.havocbot_previous_role = bot.havocbot_role;
-                       bot.havocbot_role = havocbot_role_ctf_escort;
-                       bot.havocbot_role_timeout = time + 30;
-                       bot.bot_strategytime = 0;
-                       break;
-       }
-       dprint("\n");
-}
-
-void havocbot_role_ctf_carrier()
-{
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried == world)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
-               navigation_goalrating_start();
-               havocbot_goalrating_ctf_ourbase(50000);
-
-               if(self.health<100)
-                       havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
-
-               navigation_goalrating_end();
-
-               if (self.navigation_hasgoals)
-                       self.havocbot_cantfindflag = time + 10;
-               else if (time > self.havocbot_cantfindflag)
-               {
-                       // Can't navigate to my own base, suicide!
-                       // TODO: drop it and wander around
-                       Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
-                       return;
-               }
-       }
-}
-
-void havocbot_role_ctf_escort()
-{
-       entity mf, ef;
-
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If enemy flag is back on the base switch to previous role
-       ef = havocbot_ctf_find_enemy_flag(self);
-       if(ef.cnt==FLAG_BASE)
-       {
-               self.havocbot_role = self.havocbot_previous_role;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       // If the flag carrier reached the base switch to defense
-       mf = havocbot_ctf_find_flag(self);
-       if(mf.cnt!=FLAG_BASE)
-       if(vlen(ef.origin - mf.dropped_origin) < 300)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!self.havocbot_role_timeout)
-       {
-               self.havocbot_role_timeout = time + random() * 30 + 60;
-       }
-
-       // If nothing happened just switch to previous role
-       if (time > self.havocbot_role_timeout)
-       {
-               self.havocbot_role = self.havocbot_previous_role;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       // Chase the flag carrier
-       if (self.bot_strategytime < time)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-               havocbot_goalrating_ctf_enemyflag(30000);
-               havocbot_goalrating_ctf_ourstolenflag(40000);
-               havocbot_goalrating_items(10000, self.origin, 10000);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_ctf_offense()
-{
-       entity mf, ef;
-       vector pos;
-
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // Check flags
-       mf = havocbot_ctf_find_flag(self);
-       ef = havocbot_ctf_find_enemy_flag(self);
-
-       // Own flag stolen
-       if(mf.cnt!=FLAG_BASE)
-       {
-               if(mf.tag_entity)
-                       pos = mf.tag_entity.origin;
-               else
-                       pos = mf.origin;
-
-               // Try to get it if closer than the enemy base
-               if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
-               {
-                       havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
-                       return;
-               }
-       }
-
-       // Escort flag carrier
-       if(ef.cnt!=FLAG_BASE)
-       {
-               if(ef.tag_entity)
-                       pos = ef.tag_entity.origin;
-               else
-                       pos = ef.origin;
-
-               if(vlen(pos-mf.dropped_origin)>700)
-               {
-                       havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
-                       return;
-               }
-       }
-
-       // About to fail, switch to middlefield
-       if(self.health<50)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
-               return;
-       }
-
-       // Set the role timeout if necessary
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 120;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-               havocbot_goalrating_ctf_ourstolenflag(50000);
-               havocbot_goalrating_ctf_enemybase(20000);
-               havocbot_goalrating_items(5000, self.origin, 1000);
-               havocbot_goalrating_items(1000, self.origin, 10000);
-               navigation_goalrating_end();
-       }
-}
-
-// Retriever (temporary role):
-void havocbot_role_ctf_retriever()
-{
-       entity mf;
-
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If flag is back on the base switch to previous role
-       mf = havocbot_ctf_find_flag(self);
-       if(mf.cnt==FLAG_BASE)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 20;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               float radius;
-               radius = 10000;
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-               havocbot_goalrating_ctf_ourstolenflag(50000);
-               havocbot_goalrating_ctf_droppedflags(40000, self.origin, radius);
-               havocbot_goalrating_ctf_enemybase(30000);
-               havocbot_goalrating_items(500, self.origin, radius);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_ctf_middle()
-{
-       entity mf;
-
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       mf = havocbot_ctf_find_flag(self);
-       if(mf.cnt!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
-               return;
-       }
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 10;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.bot_strategytime < time)
-       {
-               vector org;
-
-               org = havocbot_ctf_middlepoint;
-               org_z = self.origin_z;
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-               havocbot_goalrating_ctf_ourstolenflag(50000);
-               havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
-               havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(2500, self.origin, 10000);
-               havocbot_goalrating_ctf_enemybase(2500);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_ctf_defense()
-{
-       entity mf;
-
-       if(self.deadflag != DEAD_NO)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-
-       if (self.flagcarried)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       // If own flag was captured
-       mf = havocbot_ctf_find_flag(self);
-       if(mf.cnt!=FLAG_BASE)
-       {
-               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
-               return;
-       }
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + 30;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               havocbot_ctf_reset_role(self);
-               return;
-       }
-       if (self.bot_strategytime < time)
-       {
-               float radius;
-               vector org;
-
-               org = mf.dropped_origin;
-               radius = havocbot_ctf_middlepoint_radius;
-
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-               navigation_goalrating_start();
-
-               // if enemies are closer to our base, go there
-               entity head, closestplayer = world;
-               float distance, bestdistance = 10000;
-               FOR_EACH_PLAYER(head)
-               {
-                       if(head.deadflag!=DEAD_NO)
-                               continue;
-
-                       distance = vlen(org - head.origin);
-                       if(distance<bestdistance)
-                       {
-                               closestplayer = head;
-                               bestdistance = distance;
-                       }
-               }
-
-               if(closestplayer)
-               if(closestplayer.team!=self.team)
-               if(vlen(org - self.origin)>1000)
-               if(checkpvs(self.origin,closestplayer)||random()<0.5)
-                       havocbot_goalrating_ctf_ourbase(30000);
-
-               havocbot_goalrating_ctf_ourstolenflag(20000);
-               havocbot_goalrating_ctf_droppedflags(20000, org, radius);
-               havocbot_goalrating_enemyplayers(15000, org, radius);
-               havocbot_goalrating_items(10000, org, radius);
-               havocbot_goalrating_items(5000, self.origin, 10000);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_calculate_middlepoint()
-{
-       entity f;
-       vector s = '0 0 0';
-       vector fo = '0 0 0';
-       float n = 0;
-
-       f = ctf_worldflaglist;
-       while (f)
-       {
-               fo = f.origin;
-               s = s + fo;
-               f = f.ctf_worldflagnext;
-       }
-       if(!n)
-               return;
-       havocbot_ctf_middlepoint = s * (1.0 / n);
-       havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
-}
-
-void havocbot_ctf_reset_role(entity bot)
-{
-       float cdefense, cmiddle, coffense;
-       entity mf, ef, head;
-       float c;
-
-       if(bot.deadflag != DEAD_NO)
-               return;
-
-       if(vlen(havocbot_ctf_middlepoint)==0)
-               havocbot_calculate_middlepoint();
-
-       // Check ctf flags
-       if (bot.flagcarried)
-       {
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
-               return;
-       }
-
-       mf = havocbot_ctf_find_flag(bot);
-       ef = havocbot_ctf_find_enemy_flag(bot);
-
-       // Retrieve stolen flag
-       if(mf.cnt!=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)
-       {
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
-               return;
-       }
-
-       // if there is only me on the team switch to offense
-       c = 0;
-       FOR_EACH_PLAYER(head)
-       if(head.team==bot.team)
-               ++c;
-
-       if(c==1)
-       {
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
-               return;
-       }
-
-       // Evaluate best position to take
-       // Count mates on middle position
-       cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
-
-       // Count mates on defense position
-       cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
-
-       // Count mates on offense position
-       coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
-
-       if(cdefense<=coffense)
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
-       else if(coffense<=cmiddle)
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
-       else
-               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
-}
-
-void havocbot_chooserole_ctf()
-{
-       havocbot_ctf_reset_role(self);
-}
diff --git a/qcsrc/server/bot/havocbot/role_freezetag.qc b/qcsrc/server/bot/havocbot/role_freezetag.qc
deleted file mode 100644 (file)
index 4e5669e..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-void() havocbot_role_ft_freeing;
-void() havocbot_role_ft_offense;
-
-void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius)
-{
-       entity head;
-       float distance;
-
-       FOR_EACH_PLAYER(head)
-       {
-               if ((head != self) && (head.team == self.team))
-               {
-                       if (head.freezetag_frozen)
-                       {
-                               distance = vlen(head.origin - org);
-                               if (distance > sradius)
-                                       continue;
-                               navigation_routerating(head, ratingscale, 2000);
-                       }
-                       else
-                       {
-                               // If teamate is not frozen still seek them out as fight better
-                               // in a group.
-                               navigation_routerating(head, ratingscale/3, 2000);
-                       }
-               }
-       }
-}
-
-void havocbot_role_ft_offense()
-{
-       entity head;
-       float unfrozen;
-
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + random() * 10 + 20;
-
-       // Count how many players on team are unfrozen.
-       unfrozen = 0;
-       FOR_EACH_PLAYER(head)
-       {
-               if ((head.team == self.team) && (!head.freezetag_frozen))
-                       unfrozen++;
-       }
-
-       // If only one left on team or if role has timed out then start trying to free players.
-       if (((unfrozen == 0) && (!self.freezetag_frozen)) || (time > self.havocbot_role_timeout))
-       {
-               dprint("changing role to freeing\n");
-               self.havocbot_role = havocbot_role_ft_freeing;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (time > self.bot_strategytime)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
-               navigation_goalrating_start();
-               havocbot_goalrating_items(10000, self.origin, 10000);
-               havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
-               havocbot_goalrating_freeplayers(9000, self.origin, 10000);
-               //havocbot_goalrating_waypoints(1, self.origin, 1000);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_role_ft_freeing()
-{
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if (!self.havocbot_role_timeout)
-               self.havocbot_role_timeout = time + random() * 10 + 20;
-
-       if (time > self.havocbot_role_timeout)
-       {
-               dprint("changing role to offense\n");
-               self.havocbot_role = havocbot_role_ft_offense;
-               self.havocbot_role_timeout = 0;
-               return;
-       }
-
-       if (time > self.bot_strategytime)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
-               navigation_goalrating_start();
-               havocbot_goalrating_items(8000, self.origin, 10000);
-               havocbot_goalrating_enemyplayers(10000, self.origin, 10000);
-               havocbot_goalrating_freeplayers(20000, self.origin, 10000);
-               //havocbot_goalrating_waypoints(1, self.origin, 1000);
-               navigation_goalrating_end();
-       }
-}
-
-void havocbot_chooserole_ft()
-{
-       if(self.deadflag != DEAD_NO)
-               return;
-
-       if (random() < 0.5)
-               self.havocbot_role = havocbot_role_ft_freeing;
-       else
-               self.havocbot_role = havocbot_role_ft_offense;
-}
diff --git a/qcsrc/server/bot/havocbot/role_keepaway.qc b/qcsrc/server/bot/havocbot/role_keepaway.qc
deleted file mode 100644 (file)
index ede6208..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-void() havocbot_role_ka_carrier;
-void() havocbot_role_ka_collector;
-void() havocbot_chooserole_ka;
-
-entity ka_ball;
-
-// Keepaway
-// If you don't have the ball, get it; if you do, kill people.
-
-void havocbot_goalrating_ball(float ratingscale, vector org)
-{
-       float t;
-       entity ball_owner;
-       ball_owner = ka_ball.owner;
-
-       if (ball_owner == self)
-               return;
-
-       // If ball is carried by player then hunt them down.
-       if (ball_owner)
-       {
-               t = (self.health + self.armorvalue) / (ball_owner.health + ball_owner.armorvalue);
-               navigation_routerating(ball_owner, t * ratingscale, 2000);
-       }
-
-       // Ball has been dropped so collect.
-       navigation_routerating(ka_ball, ratingscale, 2000);
-}
-
-void havocbot_role_ka_carrier()
-{
-       if (self.deadflag != DEAD_NO)
-               return;
-
-       if (time > self.bot_strategytime)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
-               navigation_goalrating_start();
-               havocbot_goalrating_items(10000, self.origin, 10000);
-               havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
-               //havocbot_goalrating_waypoints(1, self.origin, 1000);
-               navigation_goalrating_end();
-       }
-
-       if (!self.ballcarried)
-       {
-               self.havocbot_role = havocbot_role_ka_collector;
-               self.bot_strategytime = 0;
-       }
-}
-
-void havocbot_role_ka_collector()
-{
-       if (self.deadflag != DEAD_NO)
-               return;
-
-       if (time > self.bot_strategytime)
-       {
-               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
-
-               navigation_goalrating_start();
-               havocbot_goalrating_items(10000, self.origin, 10000);
-               havocbot_goalrating_enemyplayers(1000, self.origin, 10000);
-               havocbot_goalrating_ball(20000, self.origin);
-               navigation_goalrating_end();
-       }
-
-       if (self.ballcarried)
-       {
-               self.havocbot_role = havocbot_role_ka_carrier;
-               self.bot_strategytime = 0;
-       }
-}
-
-void havocbot_chooserole_ka()
-{
-       if (self.ballcarried)
-               self.havocbot_role = havocbot_role_ka_carrier;
-       else
-               self.havocbot_role = havocbot_role_ka_collector;
-}
index ac8ddd161b05c749a1e94bf79b29799bf2674c95..ad3bb2367418f611d13bb67725c70cd416d7831f 100644 (file)
@@ -293,8 +293,8 @@ void havocbot_chooserole()
 {
        dprint("choosing a role...\n");
        self.bot_strategytime = 0;
-       if (g_ctf)
-               havocbot_chooserole_ctf();
+       if (MUTATOR_CALLHOOK(HavocBot_ChooseRule))
+               return;
        else if (g_domination)
                havocbot_chooserole_dom();
        else if (g_keyhunt)
@@ -303,10 +303,6 @@ void havocbot_chooserole()
                havocbot_chooserole_race();
        else if (g_onslaught)
                havocbot_chooserole_ons();
-       else if (g_keepaway)
-               havocbot_chooserole_ka();
-       else if (g_freezetag)
-               havocbot_chooserole_ft();
        else if (g_assault)
                havocbot_chooserole_ast();
        else // assume anything else is deathmatch
index 2f6a660cb203f4debee868d307f05fd4c018e4d4..8377eb465a167d5ebb328b499ebd8fe620f60022 100644 (file)
@@ -191,47 +191,26 @@ float CheatImpulse(float i)
                                        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;
                        }
index 2be7889e99985c75da72c912dc597d5bad063766..9d6bcf798d8b3026e6fb542731491923ba99b715 100644 (file)
@@ -417,9 +417,6 @@ void PutObserverInServer (void)
        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
@@ -448,6 +445,7 @@ void PutObserverInServer (void)
        
        self.classname = "observer";
        self.iscreature = FALSE;
+       self.teleportable = TELEPORT_SIMPLE;
        self.damagedbycontents = FALSE;
        self.health = -666;
        self.takedamage = DAMAGE_NO;
@@ -641,7 +639,6 @@ PutClientInServer
 Called when a client spawns in the server
 =============
 */
-//void() ctf_playerchanged;
 
 void PutClientInServer (void)
 {
@@ -698,6 +695,7 @@ 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;
@@ -925,9 +923,6 @@ void PutClientInServer (void)
        } else if(self.classname == "observer") {
                PutObserverInServer ();
        }
-
-       //if(g_ctf)
-       //      ctf_playerchanged();
 }
 
 .float ebouncefactor, ebouncestop; // electro's values
@@ -1340,7 +1335,6 @@ ClientConnect
 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);
@@ -1512,10 +1506,6 @@ void ClientConnect (void)
                if(g_arena)
                        Spawnqueue_Insert(self);
        }
-       /*else if(g_ctf)
-       {
-               ctf_clientconnect();
-       }*/
 
        attach_entcs();
 
@@ -1653,8 +1643,6 @@ void ClientDisconnect (void)
        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.
 
@@ -2535,7 +2523,6 @@ void SpectatorThink()
        self.flags |= FL_CLIENT | FL_NOTARGET;
 }
 
-float ctf_usekey();
 void PlayerUseKey()
 {
        if(self.classname != "player")
@@ -2548,9 +2535,6 @@ void PlayerUseKey()
        }
        
        // a use key was pressed; call handlers
-       if(ctf_usekey())
-               return;
-
        MUTATOR_CALLHOOK(PlayerUseKey);
 }
 
@@ -2564,7 +2548,6 @@ Called every frame for each client before the physics are run
 =============
 */
 .float usekeypressed;
-void() ctf_setstatus;
 void() nexball_setstatus;
 .float items_added;
 void PlayerPreThink (void)
@@ -2863,9 +2846,6 @@ void PlayerPreThink (void)
                if(frametime)
                        player_anim();
 
-               if(g_ctf)
-                       ctf_setstatus();
-
                if(g_nexball)
                        nexball_setstatus();
                
index 987e07c46f228b892e9741c3c4b75e00a5571968..529567a3b407cc7118590aa59c451fb3444798ac 100644 (file)
@@ -165,11 +165,14 @@ void ImpulseCommands (void)
                        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;
index 1e8ea301ea0b3384b222cd5798b7270abf54b9a4..3e560bd2452a71020d339d7ed22830a76b212ec8 100644 (file)
@@ -152,6 +152,7 @@ void CopyBody(float keepvelocity)
        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;
@@ -704,15 +705,6 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht
 
                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)
index 13e667ad87123b72c7a282f3633d6940ec1abbd1..eae71b045c82845688d653b999d705f07854ac68 100644 (file)
@@ -146,7 +146,7 @@ float GetFilteredNumber(string input)
        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)
index 4699049d2deece5103cd4fd67b94be28d1085083..f3d6d5999ff6ed65f76674f5903b93f2e642ee3e 100644 (file)
@@ -118,7 +118,7 @@ void GameCommand_adminmsg(float request, float argc)
                                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;
                        }
index b95c7261366b099902f2bb67ac4730661c024db7..aec1e3256b020878305579b179dfeea42a18e67e 100644 (file)
@@ -142,11 +142,6 @@ float      MSG_ENTITY                              = 5; // csqc
 
 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
diff --git a/qcsrc/server/ctf.qc b/qcsrc/server/ctf.qc
deleted file mode 100644 (file)
index d65d866..0000000
+++ /dev/null
@@ -1,1226 +0,0 @@
-#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;
-}
-*/
index 8564e04e9d3c7cb7624287acb8343486a46e989c..9068fa75bf53e95400df5e635e8551faa3ae3a2c 100644 (file)
@@ -16,14 +16,10 @@ noref float require_spawnfunc_prefix; // if this float exists, only functions wi
 
 // 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;
@@ -201,6 +197,9 @@ void setanim(entity e, vector anim, float looping, float override, float restart
 .float watersound_finished;
 .float iscreature;
 .float damagedbycontents;
+.float damagedbytriggers;
+.float pushable;
+.float teleportable;
 .vector oldvelocity;
 
 .float pauseregen_finished;
@@ -343,7 +342,6 @@ string gamemode_name;
 
 float startitem_failed;
 
-void DropFlag(entity flag, entity penalty_receiver, entity attacker);
 void DropAllRunes(entity pl);
 
 
@@ -570,7 +568,6 @@ float servertime, serverprevtime, serverframetime;
 
 string matchid;
 .float hitplotfh;
-.string noise4;
 
 .float last_pickup;
 
index 7b02f4e5ecdc9a2fe0446c49782089aa4b89dd43..046e0f5137981e426082c31a524140697f4c72cb 100644 (file)
@@ -219,11 +219,6 @@ void GiveFrags (entity attacker, entity targ, float f, float deathtype)
                        }
                        f = 0;
                }
-               else if(g_ctf)
-               {
-                       if(g_ctf_ignore_frags)
-                               f = 0;
-               }
        }
 
        attacker.totalfrags += f;
@@ -443,14 +438,7 @@ void Obituary (entity attacker, entity inflictor, entity targ, float deathtype)
 
                                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);
@@ -764,15 +752,6 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                                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
index 6115b4218a8f45834d4477f3684a0168e5c17f6f..20ddc655170cfbc483ff0b41fe22ccec1e2c2284 100644 (file)
@@ -466,16 +466,19 @@ void trigger_hurt_touch()
                        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);
index 224f6ce259763962b12b88230dbe4176eef34d14..6efe273bd3c41c1ffd9918033fcf478397677a01 100644 (file)
@@ -295,9 +295,8 @@ void cvar_changes_init()
                BADCVAR("g_balance_kill_delay");
                BADCVAR("g_ca_point_leadlimit");
                BADCVAR("g_ctf_captimerecord_always");
-               BADCVAR("g_ctf_flag_capture_effects");
                BADCVAR("g_ctf_flag_glowtrails");
-               BADCVAR("g_ctf_flag_pickup_effects");
+               BADCVAR("g_ctf_flag_pickup_verbosename");
                BADCVAR("g_domination_point_leadlimit");
                BADCVAR("g_forced_respawn");
                BADCVAR("g_keyhunt_point_leadlimit");
index bb0d5280356957aba68f7b696362db8a94680fa8..726f2b50316799555901a7e31f97733703588634 100644 (file)
@@ -1183,7 +1183,6 @@ void readlevelcvars(void)
        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");
 
@@ -2798,6 +2797,8 @@ float isPushable(entity e)
 {
        if(e.iscreature)
                return TRUE;
+       if(e.pushable)
+               return TRUE;
        switch(e.classname)
        {
                case "body":
index e46d5b6728244e42a67bc2c837543ed175b5f969..29a8e4e0b5602d9fa0f645a2b5de7a1e4bb437d0 100644 (file)
@@ -213,6 +213,39 @@ MUTATOR_HOOKABLE(SetWeaponreplace);
        // 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
@@ -221,3 +254,6 @@ MUTATOR_HOOKABLE(ItemTouch);
 MUTATOR_HOOKABLE(ClientConnect);
        // called at when a player connect
        entity self;    // player
+
+MUTATOR_HOOKABLE(HavocBot_ChooseRule);
+       entity self;
diff --git a/qcsrc/server/mutators/gamemode_ctf.qc b/qcsrc/server/mutators/gamemode_ctf.qc
new file mode 100644 (file)
index 0000000..2459215
--- /dev/null
@@ -0,0 +1,2110 @@
+// ================================================================
+//  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
+       pointparticles(particleeffectnum(flag.capeffect), 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);
+                       dprint("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
+       pointparticles(particleeffectnum(flag.toucheffect), player.origin, '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;
+               }
+       }
+}
+
+.float last_respawn;
+void ctf_RespawnFlag(entity flag)
+{
+       // check for flag respawn being called twice in a row
+       if(flag.last_respawn > time - 0.5)
+               { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
+
+       flag.last_respawn = time;
+       
+       // 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"); }
+       if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
+       
+       // 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);
+}
+
+
+// ================
+// Bot player logic
+// ================
+
+// NOTE: LEGACY CODE, needs to be re-written!
+
+void havocbot_calculate_middlepoint()
+{
+       entity f;
+       vector s = '0 0 0';
+       vector fo = '0 0 0';
+       float n = 0;
+
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               fo = f.origin;
+               s = s + fo;
+               f = f.ctf_worldflagnext;
+       }
+       if(!n)
+               return;
+       havocbot_ctf_middlepoint = s * (1.0 / n);
+       havocbot_ctf_middlepoint_radius  = vlen(fo - havocbot_ctf_middlepoint);
+}
+
+
+entity havocbot_ctf_find_flag(entity bot)
+{
+       entity f;
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               if (bot.team == f.team)
+                       return f;
+               f = f.ctf_worldflagnext;
+       }
+       return world;
+}
+
+entity havocbot_ctf_find_enemy_flag(entity bot)
+{
+       entity f;
+       f = ctf_worldflaglist;
+       while (f)
+       {
+               if (bot.team != f.team)
+                       return f;
+               f = f.ctf_worldflagnext;
+       }
+       return world;
+}
+
+float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
+{
+       if not(teamplay)
+               return 0;
+
+       float c = 0;
+       entity head;
+
+       FOR_EACH_PLAYER(head)
+       {
+               if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
+                       continue;
+
+               if(vlen(head.origin - org) < tc_radius)
+                       ++c;
+       }
+
+       return c;
+}
+
+void havocbot_goalrating_ctf_ourflag(float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (self.team == head.team)
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (head)
+               navigation_routerating(head, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_ourbase(float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (self.team == head.team)
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if not(head)
+               return;
+
+       navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemyflag(float ratingscale)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               if (self.team != head.team)
+                       break;
+               head = head.ctf_worldflagnext;
+       }
+       if (head)
+               navigation_routerating(head, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_enemybase(float ratingscale)
+{
+       if not(bot_waypoints_for_items)
+       {
+               havocbot_goalrating_ctf_enemyflag(ratingscale);
+               return;
+       }
+
+       entity head;
+
+       head = havocbot_ctf_find_enemy_flag(self);
+
+       if not(head)
+               return;
+
+       navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
+{
+       entity mf;
+
+       mf = havocbot_ctf_find_flag(self);
+
+       if(mf.ctf_status == FLAG_BASE)
+               return;
+
+       if(mf.tag_entity)
+               navigation_routerating(mf.tag_entity, ratingscale, 10000);
+}
+
+void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
+{
+       entity head;
+       head = ctf_worldflaglist;
+       while (head)
+       {
+               // flag is out in the field
+               if(head.ctf_status != FLAG_BASE)
+               if(head.tag_entity==world)      // dropped
+               {
+                       if(df_radius)
+                       {
+                               if(vlen(org-head.origin)<df_radius)
+                                       navigation_routerating(head, ratingscale, 10000);
+                       }
+                       else
+                               navigation_routerating(head, ratingscale, 10000);
+               }
+
+               head = head.ctf_worldflagnext;
+       }
+}
+
+void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
+{
+       entity head;
+       float t;
+       head = findchainfloat(bot_pickup, TRUE);
+       while (head)
+       {
+               // gather health and armor only
+               if (head.solid)
+               if (head.health || head.armorvalue)
+               if (vlen(head.origin - org) < sradius)
+               {
+                       // get the value of the item
+                       t = head.bot_pickupevalfunc(self, head) * 0.0001;
+                       if (t > 0)
+                               navigation_routerating(head, t * ratingscale, 500);
+               }
+               head = head.chain;
+       }
+}
+
+void havocbot_ctf_reset_role(entity bot)
+{
+       float cdefense, cmiddle, coffense;
+       entity mf, ef, head;
+       float c;
+
+       if(bot.deadflag != DEAD_NO)
+               return;
+
+       if(vlen(havocbot_ctf_middlepoint)==0)
+               havocbot_calculate_middlepoint();
+
+       // Check ctf flags
+       if (bot.flagcarried)
+       {
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       mf = havocbot_ctf_find_flag(bot);
+       ef = havocbot_ctf_find_enemy_flag(bot);
+
+       // Retrieve stolen flag
+       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.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
+               return;
+       }
+
+       // if there is only me on the team switch to offense
+       c = 0;
+       FOR_EACH_PLAYER(head)
+       if(head.team==bot.team)
+               ++c;
+
+       if(c==1)
+       {
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
+               return;
+       }
+
+       // Evaluate best position to take
+       // Count mates on middle position
+       cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
+
+       // Count mates on defense position
+       cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
+
+       // Count mates on offense position
+       coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
+
+       if(cdefense<=coffense)
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
+       else if(coffense<=cmiddle)
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
+       else
+               havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
+}
+
+void havocbot_role_ctf_carrier()
+{
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried == world)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourbase(50000);
+
+               if(self.health<100)
+                       havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
+
+               navigation_goalrating_end();
+
+               if (self.navigation_hasgoals)
+                       self.havocbot_cantfindflag = time + 10;
+               else if (time > self.havocbot_cantfindflag)
+               {
+                       // Can't navigate to my own base, suicide!
+                       // TODO: drop it and wander around
+                       Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
+                       return;
+               }
+       }
+}
+
+void havocbot_role_ctf_escort()
+{
+       entity mf, ef;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If enemy flag is back on the base switch to previous role
+       ef = havocbot_ctf_find_enemy_flag(self);
+       if(ef.ctf_status==FLAG_BASE)
+       {
+               self.havocbot_role = self.havocbot_previous_role;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       // If the flag carrier reached the base switch to defense
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status!=FLAG_BASE)
+       if(vlen(ef.origin - mf.dropped_origin) < 300)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!self.havocbot_role_timeout)
+       {
+               self.havocbot_role_timeout = time + random() * 30 + 60;
+       }
+
+       // If nothing happened just switch to previous role
+       if (time > self.havocbot_role_timeout)
+       {
+               self.havocbot_role = self.havocbot_previous_role;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       // Chase the flag carrier
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_enemyflag(30000);
+               havocbot_goalrating_ctf_ourstolenflag(40000);
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_offense()
+{
+       entity mf, ef;
+       vector pos;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // Check flags
+       mf = havocbot_ctf_find_flag(self);
+       ef = havocbot_ctf_find_enemy_flag(self);
+
+       // Own flag stolen
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               if(mf.tag_entity)
+                       pos = mf.tag_entity.origin;
+               else
+                       pos = mf.origin;
+
+               // Try to get it if closer than the enemy base
+               if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
+               {
+                       havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+                       return;
+               }
+       }
+
+       // Escort flag carrier
+       if(ef.ctf_status!=FLAG_BASE)
+       {
+               if(ef.tag_entity)
+                       pos = ef.tag_entity.origin;
+               else
+                       pos = ef.origin;
+
+               if(vlen(pos-mf.dropped_origin)>700)
+               {
+                       havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
+                       return;
+               }
+       }
+
+       // About to fail, switch to middlefield
+       if(self.health<50)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
+               return;
+       }
+
+       // Set the role timeout if necessary
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 120;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourstolenflag(50000);
+               havocbot_goalrating_ctf_enemybase(20000);
+               havocbot_goalrating_items(5000, self.origin, 1000);
+               havocbot_goalrating_items(1000, self.origin, 10000);
+               navigation_goalrating_end();
+       }
+}
+
+// Retriever (temporary role):
+void havocbot_role_ctf_retriever()
+{
+       entity mf;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If flag is back on the base switch to previous role
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status==FLAG_BASE)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 20;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               float rt_radius;
+               rt_radius = 10000;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourstolenflag(50000);
+               havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
+               havocbot_goalrating_ctf_enemybase(30000);
+               havocbot_goalrating_items(500, self.origin, rt_radius);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_middle()
+{
+       entity mf;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 10;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.bot_strategytime < time)
+       {
+               vector org;
+
+               org = havocbot_ctf_middlepoint;
+               org_z = self.origin_z;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+               havocbot_goalrating_ctf_ourstolenflag(50000);
+               havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(2500, self.origin, 10000);
+               havocbot_goalrating_ctf_enemybase(2500);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_defense()
+{
+       entity mf;
+
+       if(self.deadflag != DEAD_NO)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+
+       if (self.flagcarried)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
+               return;
+       }
+
+       // If own flag was captured
+       mf = havocbot_ctf_find_flag(self);
+       if(mf.ctf_status!=FLAG_BASE)
+       {
+               havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
+               return;
+       }
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + 30;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               havocbot_ctf_reset_role(self);
+               return;
+       }
+       if (self.bot_strategytime < time)
+       {
+               float mp_radius;
+               vector org;
+
+               org = mf.dropped_origin;
+               mp_radius = havocbot_ctf_middlepoint_radius;
+
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+               navigation_goalrating_start();
+
+               // if enemies are closer to our base, go there
+               entity head, closestplayer = world;
+               float distance, bestdistance = 10000;
+               FOR_EACH_PLAYER(head)
+               {
+                       if(head.deadflag!=DEAD_NO)
+                               continue;
+
+                       distance = vlen(org - head.origin);
+                       if(distance<bestdistance)
+                       {
+                               closestplayer = head;
+                               bestdistance = distance;
+                       }
+               }
+
+               if(closestplayer)
+               if(closestplayer.team!=self.team)
+               if(vlen(org - self.origin)>1000)
+               if(checkpvs(self.origin,closestplayer)||random()<0.5)
+                       havocbot_goalrating_ctf_ourbase(30000);
+
+               havocbot_goalrating_ctf_ourstolenflag(20000);
+               havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
+               havocbot_goalrating_enemyplayers(15000, org, mp_radius);
+               havocbot_goalrating_items(10000, org, mp_radius);
+               havocbot_goalrating_items(5000, self.origin, 10000);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ctf_setrole(entity bot, float role)
+{
+       dprint(strcat(bot.netname," switched to "));
+       switch(role)
+       {
+               case HAVOCBOT_CTF_ROLE_CARRIER:
+                       dprint("carrier");
+                       bot.havocbot_role = havocbot_role_ctf_carrier;
+                       bot.havocbot_role_timeout = 0;
+                       bot.havocbot_cantfindflag = time + 10;
+                       bot.bot_strategytime = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_DEFENSE:
+                       dprint("defense");
+                       bot.havocbot_role = havocbot_role_ctf_defense;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_MIDDLE:
+                       dprint("middle");
+                       bot.havocbot_role = havocbot_role_ctf_middle;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_OFFENSE:
+                       dprint("offense");
+                       bot.havocbot_role = havocbot_role_ctf_offense;
+                       bot.havocbot_role_timeout = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_RETRIEVER:
+                       dprint("retriever");
+                       bot.havocbot_previous_role = bot.havocbot_role;
+                       bot.havocbot_role = havocbot_role_ctf_retriever;
+                       bot.havocbot_role_timeout = time + 10;
+                       bot.bot_strategytime = 0;
+                       break;
+               case HAVOCBOT_CTF_ROLE_ESCORT:
+                       dprint("escort");
+                       bot.havocbot_previous_role = bot.havocbot_role;
+                       bot.havocbot_role = havocbot_role_ctf_escort;
+                       bot.havocbot_role_timeout = time + 30;
+                       bot.bot_strategytime = 0;
+                       break;
+       }
+       dprint("\n");
+}
+
+
+// ==============
+// 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);
+       }
+       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(MUTATOR_RETURNVALUE || 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_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 TRUE;
+       }
+               
+       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;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_BotRoles)
+{
+       havocbot_ctf_reset_role(self);
+       return TRUE;
+}
+
+
+// ==========
+// 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;
+}
+
+// compatibility for quake maps
+void spawnfunc_team_CTF_redflag()    { spawnfunc_item_flag_team1();    }
+void spawnfunc_team_CTF_blueflag()   { spawnfunc_item_flag_team2();    }
+void spawnfunc_team_CTF_redplayer()  { spawnfunc_info_player_team1();  }
+void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2();  }
+void spawnfunc_team_CTF_redspawn()   { spawnfunc_info_player_team1();  }
+void spawnfunc_team_CTF_bluespawn()  { spawnfunc_info_player_team2();  }
+
+
+// ==============
+// Initialization
+// ==============
+
+// scoreboard setup
+void ctf_ScoreRules()
+{
+       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);
+       ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
+       ScoreRules_basics_end();
+}
+
+// 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);
+       }
+       
+       ctf_ScoreRules();
+}
+
+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_HOOK(HavocBot_ChooseRule, ctf_BotRoles, 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;
+}
diff --git a/qcsrc/server/mutators/gamemode_ctf.qh b/qcsrc/server/mutators/gamemode_ctf.qh
new file mode 100644 (file)
index 0000000..baf55eb
--- /dev/null
@@ -0,0 +1,127 @@
+// these are needed since mutators are compiled last
+
+// used in cheats.qc
+void ctf_RespawnFlag(entity flag)
+
+// score rule declarations
+#define ST_CTF_CAPS 1
+#define SP_CTF_CAPS 4
+#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
+
+// 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;
+.string capeffect;
+
+// 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
+
+// bot player logic
+#define HAVOCBOT_CTF_ROLE_NONE 0
+#define HAVOCBOT_CTF_ROLE_DEFENSE 2
+#define HAVOCBOT_CTF_ROLE_MIDDLE 4
+#define HAVOCBOT_CTF_ROLE_OFFENSE 8
+#define HAVOCBOT_CTF_ROLE_CARRIER 16
+#define HAVOCBOT_CTF_ROLE_RETRIEVER 32
+#define HAVOCBOT_CTF_ROLE_ESCORT 64
+
+.float havocbot_cantfindflag;
+
+vector havocbot_ctf_middlepoint;
+float havocbot_ctf_middlepoint_radius;
+
+void havocbot_role_ctf_setrole(entity bot, float role);
index df75dfa122e2ab965be708f4b42ae3d8fbad03cb..aedfd6364c942ef5f1d967b38d0dac04fa8d202f 100644 (file)
@@ -129,6 +129,115 @@ void freezetag_Unfreeze(entity attacker)
                WaypointSprite_Kill(self.waypointsprite_attached);
 }
 
+
+// ================
+// Bot player logic
+// ================
+
+void() havocbot_role_ft_freeing;
+void() havocbot_role_ft_offense;
+
+void havocbot_goalrating_freeplayers(float ratingscale, vector org, float sradius)
+{
+       entity head;
+       float distance;
+
+       FOR_EACH_PLAYER(head)
+       {
+               if ((head != self) && (head.team == self.team))
+               {
+                       if (head.freezetag_frozen)
+                       {
+                               distance = vlen(head.origin - org);
+                               if (distance > sradius)
+                                       continue;
+                               navigation_routerating(head, ratingscale, 2000);
+                       }
+                       else
+                       {
+                               // If teamate is not frozen still seek them out as fight better
+                               // in a group.
+                               navigation_routerating(head, ratingscale/3, 2000);
+                       }
+               }
+       }
+}
+
+void havocbot_role_ft_offense()
+{
+       entity head;
+       float unfrozen;
+
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + random() * 10 + 20;
+
+       // Count how many players on team are unfrozen.
+       unfrozen = 0;
+       FOR_EACH_PLAYER(head)
+       {
+               if ((head.team == self.team) && (!head.freezetag_frozen))
+                       unfrozen++;
+       }
+
+       // If only one left on team or if role has timed out then start trying to free players.
+       if (((unfrozen == 0) && (!self.freezetag_frozen)) || (time > self.havocbot_role_timeout))
+       {
+               dprint("changing role to freeing\n");
+               self.havocbot_role = havocbot_role_ft_freeing;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
+               havocbot_goalrating_freeplayers(9000, self.origin, 10000);
+               //havocbot_goalrating_waypoints(1, self.origin, 1000);
+               navigation_goalrating_end();
+       }
+}
+
+void havocbot_role_ft_freeing()
+{
+       if(self.deadflag != DEAD_NO)
+               return;
+
+       if (!self.havocbot_role_timeout)
+               self.havocbot_role_timeout = time + random() * 10 + 20;
+
+       if (time > self.havocbot_role_timeout)
+       {
+               dprint("changing role to offense\n");
+               self.havocbot_role = havocbot_role_ft_offense;
+               self.havocbot_role_timeout = 0;
+               return;
+       }
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(8000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(10000, self.origin, 10000);
+               havocbot_goalrating_freeplayers(20000, self.origin, 10000);
+               //havocbot_goalrating_waypoints(1, self.origin, 1000);
+               navigation_goalrating_end();
+       }
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
 MUTATOR_HOOKFUNCTION(freezetag_RemovePlayer)
 {
        if(self.freezetag_frozen == 0 && self.health >= 1)
@@ -334,6 +443,19 @@ MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon)
        return 0;
 }
 
+MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
+{
+       if not(self.deadflag)
+       {
+               if (random() < 0.5)
+                       self.havocbot_role = havocbot_role_ft_freeing;
+               else
+                       self.havocbot_role = havocbot_role_ft_offense;
+       }
+       
+       return TRUE;
+}
+
 MUTATOR_DEFINITION(gamemode_freezetag)
 {
        MUTATOR_HOOK(MakePlayerObserver, freezetag_RemovePlayer, CBC_ORDER_ANY);
@@ -345,6 +467,7 @@ MUTATOR_DEFINITION(gamemode_freezetag)
        MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST);
        MUTATOR_HOOK(PlayerDamage_Calculate, freezetag_PlayerDamage_Calculate, CBC_ORDER_ANY);
        MUTATOR_HOOK(ForbidThrowCurrentWeapon, freezetag_ForbidThrowCurrentWeapon, CBC_ORDER_FIRST); //first, last or any? dunno.
+       MUTATOR_HOOK(HavocBot_ChooseRule, freezetag_BotRoles, CBC_ORDER_ANY);
 
        MUTATOR_ONADD
        {
index 1f6a82b84b7c9475a29f5d1fdfcd21aa5d624720..07c96671c67e28b0eddc529b6e83bb58aa79d369 100644 (file)
@@ -1,61 +1,23 @@
-void ka_SpawnBall(void);
-void ka_TouchEvent(void);
-void ka_RespawnBall(void);
-void ka_DropEvent(entity);
-void ka_TimeScoring(void);
-void ka_EventLog(string, entity);
+// ===========================================================
+//  Keepaway game mode coding, written by Samual and Diabolik
+//  Last updated: September, 2012
+// ===========================================================
 
-entity ka_ball;
-
-float ka_ballcarrier_waypointsprite_visible_for_player(entity);
-
-void ka_Initialize() // run at the start of a match, initiates game mode
+float ka_ballcarrier_waypointsprite_visible_for_player(entity e) // runs on waypoints which are attached to ballcarriers, updates once per frame 
 {
-       if(!g_keepaway)
-               return;
+       if(e.ballcarried)
+               if(other.classname == "spectator") 
+                       return FALSE; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
                
-       precache_sound("keepaway/pickedup.wav");
-       precache_sound("keepaway/dropped.wav");
-       precache_sound("keepaway/respawn.wav");
-       precache_sound("keepaway/touch.wav");
-
-       ScoreRules_keepaway();
-       ka_SpawnBall();
-}
-
-void ka_Reset() // used to clear the ballcarrier whenever the match switches from warmup to normal
-{
-       if(self.owner)
-               if(self.owner.classname == "player")
-                       ka_DropEvent(self.owner);
+       // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup
 
-       ka_RespawnBall();
+       return TRUE;
 }
 
-void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
+void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
 {
-       if(!g_keepaway) { return; }
-       
-       entity e;
-       e = spawn();
-       e.model = "models/orbs/orbblue.md3";    
-       precache_model(e.model);
-       setmodel(e, e.model);
-       setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
-       e.classname = "keepawayball";
-       e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
-       e.takedamage = DAMAGE_YES;
-       e.solid = SOLID_TRIGGER;
-       e.movetype = MOVETYPE_BOUNCE;
-       e.glow_color = autocvar_g_keepawayball_trail_color;
-       e.glow_trail = TRUE;
-       e.flags = FL_ITEM;
-       e.reset = ka_Reset;
-       e.touch = ka_TouchEvent;
-       e.owner = world;
-       ka_ball = e;
-
-       InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So. 
+       if(autocvar_sv_eventlog)
+               GameLogEcho(strcat(":ka:", mode, ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
 }
 
 void ka_RespawnBall() // runs whenever the ball needs to be relocated
@@ -87,6 +49,18 @@ void ka_RespawnBall() // runs whenever the ball needs to be relocated
        }
 }
 
+void ka_TimeScoring()
+{
+       if(self.owner.ballcarried)
+       { // add points for holding the ball after a certain amount of time
+               if(autocvar_g_keepaway_score_timepoints)
+                       PlayerScore_Add(self.owner, SP_SCORE, autocvar_g_keepaway_score_timepoints);
+                       
+               PlayerScore_Add(self.owner, SP_KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
+               self.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+       }
+}
+
 void ka_TouchEvent() // runs any time that the ball comes in contact with something
 {
        if(gameover) { return; }
@@ -185,35 +159,90 @@ void ka_DropEvent(entity plyr) // runs any time that a player is supposed to los
        WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
 }
 
-float ka_ballcarrier_waypointsprite_visible_for_player(entity e) // runs on waypoints which are attached to ballcarriers, updates once per frame 
+void ka_Reset() // used to clear the ballcarrier whenever the match switches from warmup to normal
 {
-       if(e.ballcarried)
-               if(other.classname == "spectator") 
-                       return FALSE; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
-               
-       // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup
+       if((self.owner) && (self.owner.classname == "player"))
+               ka_DropEvent(self.owner);
 
-       return TRUE;
+       ka_RespawnBall();
 }
 
-void ka_TimeScoring()
+
+// ================
+// Bot player logic
+// ================
+
+void havocbot_goalrating_ball(float ratingscale, vector org)
 {
-       if(self.owner.ballcarried)
-       { // add points for holding the ball after a certain amount of time
-               if(autocvar_g_keepaway_score_timepoints)
-                       PlayerScore_Add(self.owner, SP_SCORE, autocvar_g_keepaway_score_timepoints);
-                       
-               PlayerScore_Add(self.owner, SP_KEEPAWAY_BCTIME, (autocvar_g_keepaway_score_timeinterval / 1)); // interval is divided by 1 so that time always shows "seconds"
-               self.nextthink = time + autocvar_g_keepaway_score_timeinterval;
+       float t;
+       entity ball_owner;
+       ball_owner = ka_ball.owner;
+
+       if (ball_owner == self)
+               return;
+
+       // If ball is carried by player then hunt them down.
+       if (ball_owner)
+       {
+               t = (self.health + self.armorvalue) / (ball_owner.health + ball_owner.armorvalue);
+               navigation_routerating(ball_owner, t * ratingscale, 2000);
        }
+
+       // Ball has been dropped so collect.
+       navigation_routerating(ka_ball, ratingscale, 2000);
 }
 
-void ka_EventLog(string mode, entity actor) // use an alias for easy changing and quick editing later
+void havocbot_role_ka_carrier()
 {
-       if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":ka:", mode, ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
+       if (self.deadflag != DEAD_NO)
+               return;
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(20000, self.origin, 10000);
+               //havocbot_goalrating_waypoints(1, self.origin, 1000);
+               navigation_goalrating_end();
+       }
+
+       if (!self.ballcarried)
+       {
+               self.havocbot_role = havocbot_role_ka_collector;
+               self.bot_strategytime = 0;
+       }
+}
+
+void havocbot_role_ka_collector()
+{
+       if (self.deadflag != DEAD_NO)
+               return;
+
+       if (time > self.bot_strategytime)
+       {
+               self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
+
+               navigation_goalrating_start();
+               havocbot_goalrating_items(10000, self.origin, 10000);
+               havocbot_goalrating_enemyplayers(1000, self.origin, 10000);
+               havocbot_goalrating_ball(20000, self.origin);
+               navigation_goalrating_end();
+       }
+
+       if (self.ballcarried)
+       {
+               self.havocbot_role = havocbot_role_ka_carrier;
+               self.bot_strategytime = 0;
+       }
 }
 
+
+// ==============
+// Hook Functions
+// ==============
+
 MUTATOR_HOOKFUNCTION(ka_Scoring)
 {
        if((frag_attacker != frag_target) && (frag_attacker.classname == "player"))
@@ -314,6 +343,70 @@ MUTATOR_HOOKFUNCTION(ka_PlayerPowerups)
        return 0;
 }
 
+MUTATOR_HOOKFUNCTION(ka_BotRoles)
+{
+       if (self.ballcarried)
+               self.havocbot_role = havocbot_role_ka_carrier;
+       else
+               self.havocbot_role = havocbot_role_ka_collector;
+       return TRUE;
+}
+
+
+// ==============
+// Initialization
+// ==============
+
+void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
+{
+       if(!g_keepaway) { return; }
+       
+       entity e;
+       e = spawn();
+       e.model = "models/orbs/orbblue.md3";    
+       precache_model(e.model);
+       setmodel(e, e.model);
+       setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+       e.classname = "keepawayball";
+       e.damageforcescale = autocvar_g_keepawayball_damageforcescale;
+       e.takedamage = DAMAGE_YES;
+       e.solid = SOLID_TRIGGER;
+       e.movetype = MOVETYPE_BOUNCE;
+       e.glow_color = autocvar_g_keepawayball_trail_color;
+       e.glow_trail = TRUE;
+       e.flags = FL_ITEM;
+       e.reset = ka_Reset;
+       e.touch = ka_TouchEvent;
+       e.owner = world;
+       ka_ball = e;
+
+       InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So. 
+}
+
+void ka_ScoreRules()
+{
+       ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, 0, TRUE); // SFL_SORT_PRIO_PRIMARY
+       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_PICKUPS,                     "pickups",              0);
+       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_CARRIERKILLS,        "bckills",              0);
+       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_BCTIME,                      "bctime",               SFL_SORT_PRIO_SECONDARY);
+       ScoreRules_basics_end();
+}
+
+void ka_Initialize() // run at the start of a match, initiates game mode
+{
+       if(!g_keepaway)
+               return;
+               
+       precache_sound("keepaway/pickedup.wav");
+       precache_sound("keepaway/dropped.wav");
+       precache_sound("keepaway/respawn.wav");
+       precache_sound("keepaway/touch.wav");
+
+       ka_ScoreRules();
+       ka_SpawnBall();
+}
+
+
 MUTATOR_DEFINITION(gamemode_keepaway)
 {
        MUTATOR_HOOK(MakePlayerObserver, ka_RemovePlayer, CBC_ORDER_ANY);
@@ -324,6 +417,7 @@ MUTATOR_DEFINITION(gamemode_keepaway)
        MUTATOR_HOOK(PlayerDamage_Calculate, ka_PlayerDamage, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerPowerups, ka_PlayerPowerups, CBC_ORDER_ANY);
        MUTATOR_HOOK(PlayerUseKey, ka_PlayerUseKey, CBC_ORDER_ANY);
+       MUTATOR_HOOK(HavocBot_ChooseRule, ka_BotRoles, CBC_ORDER_ANY);
 
        MUTATOR_ONADD
        {
diff --git a/qcsrc/server/mutators/gamemode_keepaway.qh b/qcsrc/server/mutators/gamemode_keepaway.qh
new file mode 100644 (file)
index 0000000..062fc9e
--- /dev/null
@@ -0,0 +1,10 @@
+// these are needed since mutators are compiled last
+
+entity ka_ball;
+
+#define SP_KEEPAWAY_PICKUPS 4
+#define SP_KEEPAWAY_CARRIERKILLS 5
+#define SP_KEEPAWAY_BCTIME 6
+
+void() havocbot_role_ka_carrier;
+void() havocbot_role_ka_collector;
index 76d48775b3302fad99248ebe18cb5fbeed2a1284..808d1a29b35229d56931b9645642a524d95e833b 100644 (file)
@@ -117,7 +117,7 @@ void GiveBall(entity plyr, entity ball)
        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;
@@ -160,7 +160,7 @@ void DropBall(entity ball, vector org, vector vel)
        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);
@@ -287,7 +287,7 @@ void basketball_touch(void)
                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;
index 545ec96d75295764b09b55c6368cbf95f633a71c..4b0b7eaa15c67a3dd686ce2e00474803730684a8 100644 (file)
@@ -27,4 +27,7 @@ float balls;
 float ball_scale;
 float nb_teams;
 
-.float teamtime;
\ No newline at end of file
+.entity nb_dropper;
+.float nb_droptime;
+
+.float teamtime;
index 61c2b896ae50d1d036ebe71ba942d99dd9afd276..2ac6094d339d578d174da5ec3ed06eed91fcad20 100644 (file)
@@ -1,6 +1,7 @@
 MUTATOR_DECLARATION(gamemode_keyhunt);
 MUTATOR_DECLARATION(gamemode_freezetag);
 MUTATOR_DECLARATION(gamemode_keepaway);
+MUTATOR_DECLARATION(gamemode_ctf);
 MUTATOR_DECLARATION(gamemode_nexball);
 MUTATOR_DECLARATION(gamemode_onslaught);
 
index 76af253dca5c0f3bd3b5785134f9c378687fdf8c..75dae14bf5f28d666e9beb91e820e912389a1a54 100644 (file)
@@ -154,8 +154,11 @@ float Portal_TeleportPlayer(entity teleporter, entity player)
        // 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)
        {
index b58f5d53216631dc990389cb1fd54558146b0335..028519372f025ae24aa923d472ba0a6593de86b7 100644 (file)
@@ -29,7 +29,9 @@ defs.qh               // Should rename this, it has fields and globals
 
 mutators/base.qh
 mutators/mutators.qh
+mutators/gamemode_ctf.qh
 mutators/gamemode_keyhunt.qh // TODO fix this
+mutators/gamemode_keepaway.qh
 mutators/gamemode_nexball.qh 
 mutators/mutator_dodging.qh
 
@@ -138,7 +140,7 @@ cl_client.qc
 t_plats.qc
 antilag.qc
 
-ctf.qc
+//ctf.qc
 domination.qc
 //mode_onslaught.qc
 //nexball.qc
@@ -205,10 +207,11 @@ playerstats.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
index 806e2450792d356ff0c1298bcbdc53fefd614780..c4021fc397ea3d2cf28c67e004bb9afdde68e1aa 100644 (file)
@@ -44,26 +44,6 @@ void ScoreRules_generic()
        ScoreRules_basics_end();
 }
 
-// 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
-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
-       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_PICKUPS,  "pickups",   0);
-       ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS,  "fckills",   0);
-       ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS,  "returns",   0);
-       ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS,    "drops",     SFL_LOWER_IS_BETTER);
-       ScoreRules_basics_end();
-}
-
 // g_domination
 #define ST_DOM_TICKS 1
 #define SP_DOM_TICKS 4
@@ -169,19 +149,6 @@ void ScoreRules_nexball(float teams)
        ScoreRules_basics_end();
 }
 
-// Keep Away stuff
-#define SP_KEEPAWAY_PICKUPS 4
-#define SP_KEEPAWAY_CARRIERKILLS 5
-#define SP_KEEPAWAY_BCTIME 6
-void ScoreRules_keepaway()
-{
-       ScoreRules_basics(0, SFL_SORT_PRIO_PRIMARY, 0, TRUE); // SFL_SORT_PRIO_PRIMARY
-       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_PICKUPS,                     "pickups",              0);
-       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_CARRIERKILLS,        "bckills",              0);
-       ScoreInfo_SetLabel_PlayerScore(SP_KEEPAWAY_BCTIME,                      "bctime",                       SFL_SORT_PRIO_SECONDARY);
-       ScoreRules_basics_end();
-}
-
 // FreezeTag stuff
 #define SP_FREEZETAG_REVIVALS 4
 void ScoreRules_freezetag()
index 82b5f4457a9b8c5aeea05b5e185085a72fb5160b..7f8cb82921ee3ae2a3b068ac331bbff891a13949 100644 (file)
@@ -122,12 +122,8 @@ void spawnfunc_target_give()
 //void spawnfunc_item_health_mega()  /* handled in t_items.qc */
 //void spawnfunc_item_invis()        /* not supported */
 //void spawnfunc_item_regen()        /* not supported */
-void spawnfunc_team_CTF_redflag()    { spawnfunc_item_flag_team1();    }
-void spawnfunc_team_CTF_blueflag()   { spawnfunc_item_flag_team2();    }
-void spawnfunc_team_CTF_redplayer()  { spawnfunc_info_player_team1();  }
-void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2();  }
-void spawnfunc_team_CTF_redspawn()   { spawnfunc_info_player_team1();  }
-void spawnfunc_team_CTF_bluespawn()  { spawnfunc_info_player_team2();  }
+
+// CTF spawnfuncs handled in mutators/gamemode_ctf.qc now
 
 void spawnfunc_item_flight()         { spawnfunc_item_jetpack();       }
 
index 25dc837f953a514ce0ad9918b02d6d0bec92adc2..2cfdd8a7720f5bf665d22ed5d7916da177375887 100644 (file)
@@ -75,6 +75,11 @@ void spawn_tdeath(vector v0, entity e, vector v)
 #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;
@@ -87,7 +92,7 @@ void TeleportPlayer(entity teleporter, entity player, vector to, vector to_angle
 
        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
                {
@@ -197,20 +202,17 @@ void Teleport_Touch (void)
        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)
index c245a30a32995bc8f2a7560ebbc1a77ee504061a..917aa11bcc3c586a97cea50d3d9bc3c02cf1fb78 100644 (file)
@@ -53,7 +53,6 @@ string TeamNoName(float t)
 }
 
 void dom_init();
-void ctf_init();
 void runematch_init();
 void tdm_init();
 void entcs_init();
@@ -142,10 +141,9 @@ void InitGameplayMode()
        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
        }
 
@@ -911,7 +909,6 @@ void SV_ChangeTeam(float _color)
                if(self.deadflag == DEAD_NO)
                        Damage(self, self, self, 100000, DEATH_TEAMCHANGE, self.origin, '0 0 0');
        }
-       //ctf_playerchanged();
 }
 
 void ShufflePlayerOutOfTeam (float source_team)
index 767c346620547b0dd565f58f093f46b675b1f8f8..31b984e391767845be993b5faecb3bc4779f1ae5 100644 (file)
@@ -255,6 +255,7 @@ void turret_ewheel_dinit()
     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;
index f47003f4bb19d0d581ed529e8d4c71246dba2a28..599eb5776968117caabdc4eed6b1b1b9610a23a0 100644 (file)
@@ -593,6 +593,7 @@ void turret_walker_dinit()
     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;
index 0f984129fe589e406439e67aa846ccd36df14318..4b0f1d13a4b5b8976eda70b150523fccebd86724 100644 (file)
@@ -242,12 +242,11 @@ void bumb_gunner_exit(float _exitflag)
        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;
 
@@ -331,17 +330,11 @@ float bumb_gunner_enter()
 
        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;
 }
index f66c2b925278575cb08235a72394a572aab208de..16a339e09f40380dec3e1f84cfb5512d63252d5f 100644 (file)
@@ -2,7 +2,6 @@ float autocvar_g_vehicles_crush_dmg;
 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;
@@ -472,6 +471,7 @@ void vehicles_spawn()
     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;
@@ -658,18 +658,12 @@ void vehicles_enter()
     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);
@@ -793,13 +787,6 @@ void vehicles_exit(float eject)
         _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;
@@ -812,7 +799,14 @@ void vehicles_exit(float eject)
     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;   
@@ -1265,6 +1259,7 @@ float vehicle_initialize(string  net_name,
     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;
@@ -1410,4 +1405,4 @@ vector predict_target(entity _targ, vector _from, float _shot_speed)
 
     return _predict_pos;
 }
-*/
\ No newline at end of file
+*/
diff --git a/sound/ctf/pass.wav b/sound/ctf/pass.wav
new file mode 100644 (file)
index 0000000..0be02d5
Binary files /dev/null and b/sound/ctf/pass.wav differ
diff --git a/sound/ctf/touch.wav b/sound/ctf/touch.wav
new file mode 100644 (file)
index 0000000..2ab908b
Binary files /dev/null and b/sound/ctf/touch.wav differ
index 2a350bf89414cc10aea84603b51ef295d71d17f8..4030c64885d7945d728e9608a309c2b742e8e063 100644 (file)
@@ -17,10 +17,10 @@ set cl_vehicles_hudscale 0.5way
 
 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
+