]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/mutators/gamemode_ctf.qc
Clean up a bunch of gamemode specific code
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_ctf.qc
index 6ebf651f246e3c980af4e1b31189b8bc1db99f36..016f9bd47a034ab31f651a40d9706afab771e8b5 100644 (file)
@@ -1,14 +1,82 @@
 #include "gamemode_ctf.qh"
-#include "../_all.qh"
 
 #include "gamemode.qh"
 
 #ifdef SVQC
 #include "../../common/vehicles/all.qh"
+#include "../teamplay.qh"
 #endif
 
-#include "../../warpzonelib/common.qh"
-#include "../../warpzonelib/mathlib.qh"
+#include "../../lib/warpzone/common.qh"
+
+bool autocvar_g_ctf_allow_vehicle_carry;
+bool autocvar_g_ctf_allow_vehicle_touch;
+bool autocvar_g_ctf_allow_monster_touch;
+bool autocvar_g_ctf_throw;
+float autocvar_g_ctf_throw_angle_max;
+float autocvar_g_ctf_throw_angle_min;
+int autocvar_g_ctf_throw_punish_count;
+float autocvar_g_ctf_throw_punish_delay;
+float autocvar_g_ctf_throw_punish_time;
+float autocvar_g_ctf_throw_strengthmultiplier;
+float autocvar_g_ctf_throw_velocity_forward;
+float autocvar_g_ctf_throw_velocity_up;
+float autocvar_g_ctf_drop_velocity_up;
+float autocvar_g_ctf_drop_velocity_side;
+bool autocvar_g_ctf_oneflag_reverse;
+bool autocvar_g_ctf_portalteleport;
+bool autocvar_g_ctf_pass;
+float autocvar_g_ctf_pass_arc;
+float autocvar_g_ctf_pass_arc_max;
+float autocvar_g_ctf_pass_directional_max;
+float autocvar_g_ctf_pass_directional_min;
+float autocvar_g_ctf_pass_radius;
+float autocvar_g_ctf_pass_wait;
+bool autocvar_g_ctf_pass_request;
+float autocvar_g_ctf_pass_turnrate;
+float autocvar_g_ctf_pass_timelimit;
+float autocvar_g_ctf_pass_velocity;
+bool autocvar_g_ctf_dynamiclights;
+float autocvar_g_ctf_flag_collect_delay;
+float autocvar_g_ctf_flag_damageforcescale;
+bool autocvar_g_ctf_flag_dropped_waypoint;
+bool autocvar_g_ctf_flag_dropped_floatinwater;
+bool autocvar_g_ctf_flag_glowtrails;
+int autocvar_g_ctf_flag_health;
+bool autocvar_g_ctf_flag_return;
+float autocvar_g_ctf_flag_return_carried_radius;
+float autocvar_g_ctf_flag_return_time;
+bool autocvar_g_ctf_flag_return_when_unreachable;
+float autocvar_g_ctf_flag_return_damage;
+float autocvar_g_ctf_flag_return_damage_delay;
+float autocvar_g_ctf_flag_return_dropped;
+float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
+float autocvar_g_ctf_flagcarrier_auto_helpme_time;
+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_spotting;
+bool autocvar_g_ctf_fullbrightflags;
+bool autocvar_g_ctf_ignore_frags;
+int autocvar_g_ctf_score_capture;
+int autocvar_g_ctf_score_capture_assist;
+int autocvar_g_ctf_score_kill;
+int autocvar_g_ctf_score_penalty_drop;
+int autocvar_g_ctf_score_penalty_returned;
+int autocvar_g_ctf_score_pickup_base;
+int autocvar_g_ctf_score_pickup_dropped_early;
+int autocvar_g_ctf_score_pickup_dropped_late;
+int autocvar_g_ctf_score_return;
+float autocvar_g_ctf_shield_force;
+float autocvar_g_ctf_shield_max_ratio;
+int autocvar_g_ctf_shield_min_negscore;
+bool autocvar_g_ctf_stalemate;
+int autocvar_g_ctf_stalemate_endcondition;
+float autocvar_g_ctf_stalemate_time;
+bool autocvar_g_ctf_reverse;
+float autocvar_g_ctf_dropped_capture_delay;
+float autocvar_g_ctf_dropped_capture_radius;
 
 void ctf_FakeTimeLimit(entity e, float t)
 {
@@ -54,8 +122,8 @@ void ctf_CaptureRecord(entity flag, entity player)
 void ctf_FlagcarrierWaypoints(entity player)
 {
        WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
-       WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
-       WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
+       WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
+       WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
        WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
 }
 
@@ -180,7 +248,7 @@ void ctf_CaptureShield_Update(entity player, bool wanted_status)
 }
 
 bool ctf_CaptureShield_Customize()
-{
+{SELFPARAM();
        if(!other.ctf_captureshielded) { return false; }
        if(CTF_SAMETEAM(self, other)) { return false; }
 
@@ -188,19 +256,19 @@ bool ctf_CaptureShield_Customize()
 }
 
 void ctf_CaptureShield_Touch()
-{
+{SELFPARAM();
        if(!other.ctf_captureshielded) { return; }
        if(CTF_SAMETEAM(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);
+       Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
        if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
 }
 
 void ctf_CaptureShield_Spawn(entity flag)
-{
+{SELFPARAM();
        entity shield = spawn();
 
        shield.enemy = self;
@@ -215,7 +283,7 @@ void ctf_CaptureShield_Spawn(entity flag)
        shield.scale = 0.5;
 
        setorigin(shield, self.origin);
-       setmodel(shield, "models/ctf/shield.md3");
+       setmodel(shield, MDL_CTF_SHIELD);
        setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
 }
 
@@ -240,7 +308,7 @@ void ctf_Handle_Drop(entity flag, entity player, int droptype)
 
        // messages and sounds
        Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
-       sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
+       _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
        ctf_EventLog("dropped", player.team, player);
 
        // scoring
@@ -297,7 +365,7 @@ void ctf_Handle_Retrieve(entity flag, entity player)
        flag.ctf_status = FLAG_CARRY;
 
        // messages and sounds
-       sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
+       _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
        ctf_EventLog("receive", flag.team, player);
 
        FOR_EACH_REALPLAYER(tmp_player)
@@ -364,8 +432,8 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype)
                        flag.ctf_status = FLAG_PASSING;
 
                        // other
-                       sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
-                       WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
+                       _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
+                       WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
                        ctf_EventLog("pass", flag.team, player);
                        break;
                }
@@ -437,7 +505,7 @@ void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
        // messages and sounds
        Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
        ctf_CaptureRecord(enemy_flag, player);
-       sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
+       _sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
 
        switch(capturetype)
        {
@@ -456,7 +524,7 @@ void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
                PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
 
        // effects
-       Send_Effect(flag.capeffect, flag.origin, '0 0 0', 1);
+       Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
        //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
 
        // other
@@ -486,7 +554,7 @@ void ctf_Handle_Return(entity flag, entity player)
                Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
                Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
        }
-       sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
+       _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
        ctf_EventLog("return", flag.team, player);
 
        // scoring
@@ -574,7 +642,7 @@ void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
        else
                Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
 
-       sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
+       _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
 
        // scoring
        PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
@@ -592,7 +660,7 @@ void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
                {
                        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");
+                       LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
                        PlayerTeamScore_AddScore(player, pickup_dropped_score);
                        ctf_EventLog("pickup", flag.team, player);
                        break;
@@ -610,7 +678,7 @@ void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
        }
 
        // effects
-       Send_Effect(flag.toucheffect, player.origin, '0 0 0', 1);
+       Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
 
        // waypoints
        if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
@@ -642,7 +710,7 @@ void ctf_CheckFlagReturn(entity flag, int returntype)
                                case RETURN_TIMEOUT:
                                        { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
                        }
-                       sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
+                       _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
                        ctf_EventLog("returned", flag.team, world);
                        ctf_RespawnFlag(flag);
                }
@@ -650,7 +718,7 @@ void ctf_CheckFlagReturn(entity flag, int returntype)
 }
 
 bool ctf_Stalemate_Customize()
-{
+{SELFPARAM();
        // make spectators see what the player would see
        entity e, wp_owner;
        e = WaypointSprite_getviewentity(other);
@@ -731,7 +799,7 @@ void ctf_CheckStalemate(void)
 }
 
 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
+{SELFPARAM();
        if(ITEM_DAMAGE_NEEDKILL(deathtype))
        {
                if(autocvar_g_ctf_flag_return_damage_delay)
@@ -755,7 +823,7 @@ void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathty
 }
 
 void ctf_FlagThink()
-{
+{SELFPARAM();
        // declarations
        entity tmp_entity;
 
@@ -768,7 +836,7 @@ void ctf_FlagThink()
 
        // 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");
+               LOG_TRACE("wtf the flag got squashed?\n");
                tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
                if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
                        setsize(self, FLAG_MIN, FLAG_MAX); }
@@ -847,11 +915,10 @@ void ctf_FlagThink()
                                self.health = 0;
                                ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
 
-                               tmp_entity = self;
-                               self = self.owner;
+                               setself(self.owner);
                                self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
                                ImpulseCommands();
-                               self = tmp_entity;
+                               setself(this);
                        }
                        if(autocvar_g_ctf_stalemate)
                        {
@@ -897,14 +964,14 @@ void ctf_FlagThink()
 
                default: // this should never happen
                {
-                       dprint("ctf_FlagThink(): Flag exists with no status?\n");
+                       LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
                        return;
                }
        }
 }
 
 void ctf_FlagTouch()
-{
+{SELFPARAM();
        if(gameover) { return; }
        if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
 
@@ -942,8 +1009,8 @@ void ctf_FlagTouch()
        {
                if(time > self.wait) // if we haven't in a while, play a sound/effect
                {
-                       Send_Effect(self.toucheffect, self.origin, '0 0 0', 1);
-                       sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
+                       Send_Effect_(self.toucheffect, self.origin, '0 0 0', 1);
+                       _sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
                        self.wait = time + FLAG_TOUCHRATE;
                }
                return;
@@ -979,7 +1046,7 @@ void ctf_FlagTouch()
 
                case FLAG_CARRY:
                {
-                       dprint("Someone touched a flag even though it was being carried?\n");
+                       LOG_TRACE("Someone touched a flag even though it was being carried?\n");
                        break;
                }
 
@@ -1050,7 +1117,7 @@ void ctf_RespawnFlag(entity flag)
 }
 
 void ctf_Reset()
-{
+{SELFPARAM();
        if(self.owner)
                if(IS_PLAYER(self.owner))
                        ctf_Handle_Throw(self.owner, world, DROP_RESET);
@@ -1059,7 +1126,7 @@ void ctf_Reset()
 }
 
 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
-{
+{SELFPARAM();
        // bot waypoints
        waypoint_spawnforitem_force(self, self.origin);
        self.nearestwaypointtimeout = 0; // activate waypointing again
@@ -1086,15 +1153,14 @@ void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf
 
 void set_flag_string(entity flag, .string field, string value, string teamname)
 {
-       if(flag.field == "")
-               flag.field = strzone(sprintf(value,teamname));
+       if(flag.(field) == "")
+               flag.(field) = strzone(sprintf(value,teamname));
 }
 
 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
-{
+{SELFPARAM();
        // declarations
-       string teamname = Static_Team_ColorName_Lower(teamnumber);
-       self = flag; // for later usage with droptofloor()
+       setself(flag); // for later usage with droptofloor()
 
        // main setup
        flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
@@ -1126,6 +1192,7 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
        flag.nextthink = time + FLAG_THINKRATE;
        flag.ctf_status = FLAG_BASE;
 
+       string teamname = Static_Team_ColorName_Lower(teamnumber);
        // appearence
        if(!flag.scale)                         { flag.scale = FLAG_SCALE; }
        if(flag.skin == 0)                      { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
@@ -1135,28 +1202,22 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
        set_flag_string(flag, capeffect,        "%s_cap",               teamname);
 
        // sounds
-       set_flag_string(flag, snd_flag_taken,           "ctf/%s_taken.wav",     teamname);
-       set_flag_string(flag, snd_flag_returned,        "ctf/%s_returned.wav",  teamname);
-       set_flag_string(flag, snd_flag_capture,         "ctf/%s_capture.wav",   teamname);
-       set_flag_string(flag, snd_flag_dropped,         "ctf/%s_dropped.wav",   teamname);
-       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_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);
+       flag.snd_flag_taken = SND(CTF_TAKEN(teamnumber));
+       flag.snd_flag_returned = SND(CTF_RETURNED(teamnumber));
+       flag.snd_flag_capture = SND(CTF_CAPTURE(teamnumber));
+       flag.snd_flag_dropped = SND(CTF_DROPPED(teamnumber));
+       if (flag.snd_flag_respawn == "") flag.snd_flag_respawn = SND(CTF_RESPAWN); // if there is ever a team-based sound for this, update the code to match.
        precache_sound(flag.snd_flag_respawn);
-       precache_sound(flag.snd_flag_dropped);
+       if (flag.snd_flag_touch == "") flag.snd_flag_touch = SND(CTF_TOUCH); // again has no team-based sound
        precache_sound(flag.snd_flag_touch);
+       if (flag.snd_flag_pass == "") flag.snd_flag_pass = SND(CTF_PASS); // same story here
        precache_sound(flag.snd_flag_pass);
+
+       // precache
        precache_model(flag.model);
-       precache_model("models/ctf/shield.md3");
-       precache_model("models/ctf/shockwavetransring.md3");
 
        // appearence
-       setmodel(flag, flag.model); // precision set below
+       _setmodel(flag, flag.model); // precision set below
        setsize(flag, FLAG_MIN, FLAG_MAX);
        setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
 
@@ -1198,7 +1259,7 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
        else // drop to floor, automatically find a platform and set that as spawn origin
        {
                flag.noalign = false;
-               self = flag;
+               setself(flag);
                droptofloor();
                flag.movetype = MOVETYPE_TOSS;
        }
@@ -1294,7 +1355,7 @@ int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
 }
 
 void havocbot_goalrating_ctf_ourflag(float ratingscale)
-{
+{SELFPARAM();
        entity head;
        head = ctf_worldflaglist;
        while (head)
@@ -1308,7 +1369,7 @@ void havocbot_goalrating_ctf_ourflag(float ratingscale)
 }
 
 void havocbot_goalrating_ctf_ourbase(float ratingscale)
-{
+{SELFPARAM();
        entity head;
        head = ctf_worldflaglist;
        while (head)
@@ -1324,7 +1385,7 @@ void havocbot_goalrating_ctf_ourbase(float ratingscale)
 }
 
 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
-{
+{SELFPARAM();
        entity head;
        head = ctf_worldflaglist;
        while (head)
@@ -1351,7 +1412,7 @@ void havocbot_goalrating_ctf_enemyflag(float ratingscale)
 }
 
 void havocbot_goalrating_ctf_enemybase(float ratingscale)
-{
+{SELFPARAM();
        if (!bot_waypoints_for_items)
        {
                havocbot_goalrating_ctf_enemyflag(ratingscale);
@@ -1369,7 +1430,7 @@ void havocbot_goalrating_ctf_enemybase(float ratingscale)
 }
 
 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
-{
+{SELFPARAM();
        entity mf;
 
        mf = havocbot_ctf_find_flag(self);
@@ -1405,7 +1466,7 @@ void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float d
 }
 
 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
-{
+{SELFPARAM();
        entity head;
        float t;
        head = findchainfloat(bot_pickup, true);
@@ -1492,7 +1553,7 @@ void havocbot_ctf_reset_role(entity bot)
 }
 
 void havocbot_role_ctf_carrier()
-{
+{SELFPARAM();
        if(self.deadflag != DEAD_NO)
        {
                havocbot_ctf_reset_role(self);
@@ -1526,14 +1587,14 @@ void havocbot_role_ctf_carrier()
                {
                        // 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');
+                       Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0');
                        return;
                }
        }
 }
 
 void havocbot_role_ctf_escort()
-{
+{SELFPARAM();
        entity mf, ef;
 
        if(self.deadflag != DEAD_NO)
@@ -1593,7 +1654,7 @@ void havocbot_role_ctf_escort()
 }
 
 void havocbot_role_ctf_offense()
-{
+{SELFPARAM();
        entity mf, ef;
        vector pos;
 
@@ -1675,7 +1736,7 @@ void havocbot_role_ctf_offense()
 
 // Retriever (temporary role):
 void havocbot_role_ctf_retriever()
-{
+{SELFPARAM();
        entity mf;
 
        if(self.deadflag != DEAD_NO)
@@ -1723,7 +1784,7 @@ void havocbot_role_ctf_retriever()
 }
 
 void havocbot_role_ctf_middle()
-{
+{SELFPARAM();
        entity mf;
 
        if(self.deadflag != DEAD_NO)
@@ -1774,7 +1835,7 @@ void havocbot_role_ctf_middle()
 }
 
 void havocbot_role_ctf_defense()
-{
+{SELFPARAM();
        entity mf;
 
        if(self.deadflag != DEAD_NO)
@@ -1849,47 +1910,47 @@ void havocbot_role_ctf_defense()
 
 void havocbot_role_ctf_setrole(entity bot, int role)
 {
-       dprint(strcat(bot.netname," switched to "));
+       LOG_TRACE(strcat(bot.netname," switched to "));
        switch(role)
        {
                case HAVOCBOT_CTF_ROLE_CARRIER:
-                       dprint("carrier");
+                       LOG_TRACE("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");
+                       LOG_TRACE("defense");
                        bot.havocbot_role = havocbot_role_ctf_defense;
                        bot.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_CTF_ROLE_MIDDLE:
-                       dprint("middle");
+                       LOG_TRACE("middle");
                        bot.havocbot_role = havocbot_role_ctf_middle;
                        bot.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_CTF_ROLE_OFFENSE:
-                       dprint("offense");
+                       LOG_TRACE("offense");
                        bot.havocbot_role = havocbot_role_ctf_offense;
                        bot.havocbot_role_timeout = 0;
                        break;
                case HAVOCBOT_CTF_ROLE_RETRIEVER:
-                       dprint("retriever");
+                       LOG_TRACE("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");
+                       LOG_TRACE("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");
+       LOG_TRACE("\n");
 }
 
 
@@ -1897,8 +1958,8 @@ void havocbot_role_ctf_setrole(entity bot, int role)
 // Hook Functions
 // ==============
 
-MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
-{
+MUTATOR_HOOKFUNCTION(ctfPlayerPreThink)
+{SELFPARAM();
        entity flag;
        int t = 0, t2 = 0, t3 = 0;
 
@@ -1944,12 +2005,12 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
 
        // 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, DEATH_WEAPON));
+               WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
 
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
+MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // 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
        {
@@ -1966,7 +2027,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values t
        }
        else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
        {
-               if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)))
+               if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
                if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
                {
                        frag_target.wps_helpme_time = time;
@@ -1977,7 +2038,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values t
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
+MUTATOR_HOOKFUNCTION(ctfPlayerDies)
 {
        if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
        {
@@ -1995,31 +2056,39 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
+MUTATOR_HOOKFUNCTION(ctfGiveFragsForKill)
 {
        frag_score = 0;
        return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
 }
 
-MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
+void ctf_RemovePlayer(entity player)
 {
-       entity flag; // temporary entity for the search method
-
-       if(self.flagcarried)
-               { ctf_Handle_Throw(self, world, DROP_NORMAL); }
+       if(player.flagcarried)
+               { ctf_Handle_Throw(player, world, DROP_NORMAL); }
 
-       for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+       for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
        {
-               if(flag.pass_sender == self) { flag.pass_sender = world; }
-               if(flag.pass_target == self) { flag.pass_target = world; }
-               if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
+               if(flag.pass_sender == player) { flag.pass_sender = world; }
+               if(flag.pass_target == player) { flag.pass_target = world; }
+               if(flag.ctf_dropper == player) { flag.ctf_dropper = world; }
        }
+}
 
+MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
+{SELFPARAM();
+       ctf_RemovePlayer(self);
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
-{
+MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
+{SELFPARAM();
+       ctf_RemovePlayer(self);
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
+{SELFPARAM();
        if(self.flagcarried)
        if(!autocvar_g_ctf_portalteleport)
                { ctf_Handle_Throw(self, world, DROP_NORMAL); }
@@ -2027,8 +2096,8 @@ MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
-{
+MUTATOR_HOOKFUNCTION(ctfPlayerUseKey)
+{SELFPARAM();
        if(MUTATOR_RETURNVALUE || gameover) { return false; }
 
        entity player = self;
@@ -2120,8 +2189,8 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
-{
+MUTATOR_HOOKFUNCTION(ctfHelpMePing)
+{SELFPARAM();
        if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
        {
                self.wps_helpme_time = time;
@@ -2136,7 +2205,7 @@ MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
        return true;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
+MUTATOR_HOOKFUNCTION(ctfVehicleEnter)
 {
        if(vh_player.flagcarried)
        {
@@ -2159,7 +2228,7 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
+MUTATOR_HOOKFUNCTION(ctfVehicleExit)
 {
        if(vh_player.flagcarried)
        {
@@ -2174,8 +2243,8 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
-{
+MUTATOR_HOOKFUNCTION(ctfAbortSpeedrun)
+{SELFPARAM();
        if(self.flagcarried)
        {
                Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
@@ -2186,7 +2255,7 @@ MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
+MUTATOR_HOOKFUNCTION(ctfMatchEnd)
 {
        entity flag; // temporary entity for the search method
 
@@ -2220,76 +2289,103 @@ MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
        return false;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_BotRoles)
-{
+MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
+{SELFPARAM();
        havocbot_ctf_reset_role(self);
        return true;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
+MUTATOR_HOOKFUNCTION(ctfGetTeamCount)
 {
        //ret_float = ctf_teams;
        ret_string = "ctf_team";
        return true;
 }
 
-MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
-{
+MUTATOR_HOOKFUNCTION(ctfSpectateCopy)
+{SELFPARAM();
        self.ctf_flagstatus = other.ctf_flagstatus;
        return false;
 }
 
+MUTATOR_HOOKFUNCTION(ctf, GetRecords)
+{
+       for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
+       {
+               if (MapInfo_Get_ByID(i))
+               {
+                       float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
 
-// ==========
-// Spawnfuncs
-// ==========
+                       if(!r)
+                               continue;
 
-/*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; }
+                       // TODO: uid2name
+                       string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
+                       ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
+               }
+       }
 
-       self.team = NUM_TEAM_1; // red
-       spawnfunc_info_player_deathmatch();
+       return false;
 }
 
-
-/*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()
+bool superspec_Spectate(entity _player); // TODO
+void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
+MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
 {
-       if(g_assault) { remove(self); return; }
+       if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
 
-       self.team = NUM_TEAM_2; // blue
-       spawnfunc_info_player_deathmatch();
-}
+       if(cmd_name == "followfc")
+       {
+               if(!g_ctf)
+                       return true;
 
-/*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; }
+               entity _player;
+               int _team = 0;
+               bool found = false;
 
-       self.team = NUM_TEAM_3; // yellow
-       spawnfunc_info_player_deathmatch();
-}
+               if(cmd_argc == 2)
+               {
+                       switch(argv(1))
+                       {
+                               case "red": _team = NUM_TEAM_1; break;
+                               case "blue": _team = NUM_TEAM_2; break;
+                               case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
+                               case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
+                       }
+               }
 
+               FOR_EACH_PLAYER(_player)
+               {
+                       if(_player.flagcarried && (_player.team == _team || _team == 0))
+                       {
+                               found = true;
+                               if(_team == 0 && IS_SPEC(self) && self.enemy == _player)
+                                       continue; // already spectating a fc, try to find the other fc
+                               return superspec_Spectate(_player);
+                       }
+               }
 
-/*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(!found)
+                       superspec_msg("", "", self, "No active flag carrier\n", 1);
+               return true;
+       }
+
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
 {
-       if(g_assault) { remove(self); return; }
+       if(frag_target.flagcarried)
+               ctf_Handle_Throw(frag_target, world, DROP_THROW);
 
-       self.team = NUM_TEAM_4; // purple
-       spawnfunc_info_player_deathmatch();
+       return false;
 }
 
+
+// ==========
+// Spawnfuncs
+// ==========
+
 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
 CTF flag for team one (Red).
 Keys:
@@ -2301,7 +2397,7 @@ Keys:
 "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()
+spawnfunc(item_flag_team1)
 {
        if(!g_ctf) { remove(self); return; }
 
@@ -2319,7 +2415,7 @@ Keys:
 "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()
+spawnfunc(item_flag_team2)
 {
        if(!g_ctf) { remove(self); return; }
 
@@ -2337,7 +2433,7 @@ Keys:
 "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_team3()
+spawnfunc(item_flag_team3)
 {
        if(!g_ctf) { remove(self); return; }
 
@@ -2355,7 +2451,7 @@ Keys:
 "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_team4()
+spawnfunc(item_flag_team4)
 {
        if(!g_ctf) { remove(self); return; }
 
@@ -2373,7 +2469,7 @@ Keys:
 "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_neutral()
+spawnfunc(item_flag_neutral)
 {
        if(!g_ctf) { remove(self); return; }
        if(!cvar("g_ctf_oneflag")) { remove(self); return; }
@@ -2387,7 +2483,7 @@ Note: If you use spawnfunc_ctf_team entities you must define at least 2!  Howeve
 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()
+spawnfunc(ctf_team)
 {
        if(!g_ctf) { remove(self); return; }
 
@@ -2396,15 +2492,17 @@ void spawnfunc_ctf_team()
 }
 
 // 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();  }
+spawnfunc(team_CTF_redflag)    { spawnfunc_item_flag_team1(this);    }
+spawnfunc(team_CTF_blueflag)   { spawnfunc_item_flag_team2(this);    }
+spawnfunc(info_player_team1);
+spawnfunc(team_CTF_redplayer)  { spawnfunc_info_player_team1(this);  }
+spawnfunc(team_CTF_redspawn)   { spawnfunc_info_player_team1(this);  }
+spawnfunc(info_player_team2);
+spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this);  }
+spawnfunc(team_CTF_bluespawn)  { spawnfunc_info_player_team2(this);  }
 
-void team_CTF_neutralflag()                     { spawnfunc_item_flag_neutral();  }
-void team_neutralobelisk()                      { spawnfunc_item_flag_neutral();  }
+void team_CTF_neutralflag()                     { SELFPARAM(); spawnfunc_item_flag_neutral(self);  }
+void team_neutralobelisk()                      { SELFPARAM(); spawnfunc_item_flag_neutral(self);  }
 
 
 // ==============
@@ -2429,16 +2527,11 @@ void ctf_ScoreRules(int teams)
 // code from here on is just to support maps that don't have flag and team entities
 void ctf_SpawnTeam (string teamname, int teamcolor)
 {
-       entity oldself;
-       oldself = self;
-       self = spawn();
-       self.classname = "ctf_team";
-       self.netname = teamname;
-       self.cnt = teamcolor;
-
-       spawnfunc_ctf_team();
-
-       self = oldself;
+       entity this = new(ctf_team);
+       this.netname = teamname;
+       this.cnt = teamcolor;
+       this.spawnfunc_checked = true;
+       WITH(entity, self, this, spawnfunc_ctf_team(this));
 }
 
 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
@@ -2458,7 +2551,7 @@ void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to
        // 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");
+               LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n");
                ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
                ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
                if(ctf_teams >= 3)
@@ -2483,25 +2576,11 @@ void ctf_Initialize()
        InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
 }
 
-
-MUTATOR_DEFINITION(gamemode_ctf)
+REGISTER_MUTATOR(ctf, g_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_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
-       MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
-       MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
+       ActivateTeamplay();
+       SetLimits(autocvar_capturelimit_override, -1, autocvar_captureleadlimit_override, -1);
+       have_team_spawns = -1; // request team spawns
 
        MUTATOR_ONADD
        {
@@ -2519,7 +2598,7 @@ MUTATOR_DEFINITION(gamemode_ctf)
 
        MUTATOR_ONREMOVE
        {
-               print("This is a game type and it cannot be removed at runtime.");
+               LOG_INFO("This is a game type and it cannot be removed at runtime.");
                return -1;
        }