X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fmutators%2Fgamemode_ctf.qc;h=afa466d36267a81029ea31fd101c40d3d53fc0f6;hb=ebbefc97022610c6d20140d3e16ba0c3e494fa1a;hp=0e1cb6a713ab07d16bb96e98248ddfd9a66cf0d7;hpb=0c46e9930dd6bfe75ec02470be65190150d1ffbc;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/mutators/gamemode_ctf.qc b/qcsrc/server/mutators/gamemode_ctf.qc index 0e1cb6a71..afa466d36 100644 --- a/qcsrc/server/mutators/gamemode_ctf.qc +++ b/qcsrc/server/mutators/gamemode_ctf.qc @@ -17,7 +17,8 @@ void ctf_FakeTimeLimit(entity e, float t) 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))) : ""))); + GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : ""))); + //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""))); } void ctf_CaptureRecord(entity flag, entity player) @@ -27,11 +28,13 @@ void ctf_CaptureRecord(entity flag, entity player) string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname")); // notify about shit - if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); } + if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); } + else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); } else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); } // write that shit in the database + if(!ctf_oneflag) // but not in 1-flag mode if((!ctf_captimerecord) || (cap_time < cap_record)) { ctf_captimerecord = cap_time; @@ -173,6 +176,7 @@ void ctf_CaptureShield_Update(entity player, float wanted_status) float ctf_CaptureShield_Customize() { + if(self.enemy.active != ACTIVE_ACTIVE) { return TRUE; } if(!other.ctf_captureshielded) { return FALSE; } if(CTF_SAMETEAM(self, other)) { return FALSE; } @@ -181,6 +185,17 @@ float ctf_CaptureShield_Customize() void ctf_CaptureShield_Touch() { + if(self.enemy.active != ACTIVE_ACTIVE) + { + 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); + if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_INACTIVE); } + + return; + } + if(!other.ctf_captureshielded) { return; } if(CTF_SAMETEAM(self, other)) { return; } @@ -188,7 +203,7 @@ void ctf_CaptureShield_Touch() vector othermid = (other.absmin + other.absmax) * 0.5; Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force); - Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); + if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); } } void ctf_CaptureShield_Spawn(entity flag) @@ -231,7 +246,7 @@ void ctf_Handle_Drop(entity flag, entity player, float droptype) flag.ctf_status = FLAG_DROPPED; // messages and sounds - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_LOST_), player.netname); + 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); ctf_EventLog("dropped", player.team, player); @@ -269,8 +284,17 @@ void ctf_Handle_Retrieve(entity flag, entity player) flag.owner.flagcarried = flag; // reset flag - setattachment(flag, player, ""); - setorigin(flag, FLAG_CARRY_OFFSET); + if(player.vehicle) + { + setattachment(flag, player.vehicle, ""); + setorigin(flag, VEHICLE_FLAG_OFFSET); + flag.scale = VEHICLE_FLAG_SCALE; + } + else + { + setattachment(flag, player, ""); + setorigin(flag, FLAG_CARRY_OFFSET); + } flag.movetype = MOVETYPE_NONE; flag.takedamage = DAMAGE_NO; flag.solid = SOLID_NOT; @@ -284,11 +308,11 @@ void ctf_Handle_Retrieve(entity flag, entity player) FOR_EACH_REALPLAYER(tmp_player) { if(tmp_player == sender) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_), player.netname); + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname); else if(tmp_player == player) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname); + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname); else if(SAME_TEAM(tmp_player, sender)) - Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname); + Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname); } // create new waypoint @@ -396,15 +420,31 @@ 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); + entity player_team_flag = world, tmp_entity; float old_time, new_time; if(!player) { return; } // without someone to give the reward to, we can't possibly cap - if(CTF_DIFFTEAM(player, flag)) { return; } + if(ctf_oneflag) + { + if(CTF_SAMETEAM(player, flag)) { return; } + } + else if(CTF_DIFFTEAM(player, flag)) { return; } + + if(ctf_oneflag) + for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext) + if(SAME_TEAM(tmp_entity, player)) + { + player_team_flag = tmp_entity; + break; + } + + player.throw_prevtime = time; + player.throw_count = 0; // messages and sounds - Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_)); + 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, ((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) { @@ -448,7 +488,7 @@ void ctf_Handle_Return(entity flag, entity player) { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name); } - else + else if(flag.team) { 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); @@ -489,8 +529,17 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype) // attach the flag to the player flag.owner = player; player.flagcarried = flag; - setattachment(flag, player, ""); - setorigin(flag, FLAG_CARRY_OFFSET); + if(player.vehicle) + { + setattachment(flag, player.vehicle, ""); + setorigin(flag, VEHICLE_FLAG_OFFSET); + flag.scale = VEHICLE_FLAG_SCALE; + } + else + { + setattachment(flag, player, ""); + setorigin(flag, FLAG_CARRY_OFFSET); + } // flag setup flag.movetype = MOVETYPE_NONE; @@ -507,13 +556,21 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype) } // messages and sounds - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_), player.netname); + Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname); if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); } - if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); } + if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); } + else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); } else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); } - Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname); - + Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname); + + if(!flag.team) + FOR_EACH_PLAYER(tmp_entity) + if(tmp_entity != player) + if(DIFF_TEAM(player, tmp_entity)) + Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); + + if(flag.team) FOR_EACH_PLAYER(tmp_entity) if(tmp_entity != player) if(CTF_SAMETEAM(flag, tmp_entity)) @@ -580,14 +637,14 @@ void ctf_CheckFlagReturn(entity flag, float returntype) { switch(returntype) { - case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break; - case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break; - case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break; - case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break; + case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break; + case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break; + case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break; + case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break; default: case RETURN_TIMEOUT: - { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; } + { 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); ctf_EventLog("returned", flag.team, world); @@ -614,7 +671,7 @@ float ctf_Stalemate_Customize() void ctf_CheckStalemate(void) { // declarations - float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0; + float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0; entity tmp_entity; entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs @@ -635,13 +692,19 @@ void ctf_CheckStalemate(void) case NUM_TEAM_2: ++stale_blue_flags; break; case NUM_TEAM_3: ++stale_yellow_flags; break; case NUM_TEAM_4: ++stale_pink_flags; break; + default: ++stale_neutral_flags; break; } } } - stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1); + if(ctf_oneflag) + stale_flags = (stale_neutral_flags >= 1); + else + stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1); - if(stale_flags == ctf_teams) + if(ctf_oneflag && stale_flags == 1) + ctf_stalemate = TRUE; + else if(stale_flags == ctf_teams) ctf_stalemate = TRUE; else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2) { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; } @@ -655,7 +718,7 @@ void ctf_CheckStalemate(void) { if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier)) { - WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team)); + WaypointSprite_Spawn(((ctf_oneflag) ? "flagcarrier" : "enemyflagcarrier"), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team)); tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize; } } @@ -674,9 +737,15 @@ void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float death { if(ITEM_DAMAGE_NEEDKILL(deathtype)) { - // automatically kill the flag and return it - self.health = 0; - ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + if(autocvar_g_ctf_flag_return_damage_delay) + { + self.ctf_flagdamaged = TRUE; + } + else + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + } return; } if(autocvar_g_ctf_flag_return_damage) @@ -704,7 +773,7 @@ void ctf_FlagThink() 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? + if(!trace_startsolid || self.noalign) // 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 @@ -759,7 +828,13 @@ void ctf_FlagThink() return; } } - if(autocvar_g_ctf_flag_return_time) + if(self.ctf_flagdamaged) + { + self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE); + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + return; + } + else 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); @@ -789,7 +864,7 @@ void ctf_FlagThink() wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check) } } - if(CTF_SAMETEAM(self, self.owner)) + if(CTF_SAMETEAM(self, self.owner) && self.team) { if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed ctf_Handle_Throw(self.owner, world, DROP_THROW); @@ -834,18 +909,24 @@ void ctf_FlagThink() void ctf_FlagTouch() { if(gameover) { return; } + if(self.active != ACTIVE_ACTIVE) { return; } - entity toucher = other; - float is_not_monster = (!(toucher.flags & FL_MONSTER)); + entity toucher = other, tmp_entity; + float is_not_monster = (!(toucher.flags & FL_MONSTER)), num_perteam = 0; // 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); + if(!autocvar_g_ctf_flag_return_damage_delay) + { + self.health = 0; + ctf_CheckFlagReturn(self, RETURN_NEEDKILL); + } return; } + FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; } + // special touch behaviors if(toucher.vehicle_flags & VHF_ISVEHICLE) { @@ -875,7 +956,14 @@ void ctf_FlagTouch() { case FLAG_BASE: { - if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster) + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster) + ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base + else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) + ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag + } + else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster) ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster) ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag @@ -884,7 +972,7 @@ void ctf_FlagTouch() case FLAG_DROPPED: { - if(CTF_SAMETEAM(toucher, self) && autocvar_g_ctf_flag_return) + if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team ctf_Handle_Return(self, toucher); // toucher just returned his own flag else if(is_not_monster && (!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 @@ -932,6 +1020,9 @@ void ctf_RespawnFlag(entity flag) ctf_FakeTimeLimit(flag.owner, -1); } + if((flag.owner) && (flag.owner.vehicle)) + flag.scale = FLAG_SCALE; + if(flag.ctf_status == FLAG_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); } @@ -955,6 +1046,9 @@ void ctf_RespawnFlag(entity flag) flag.ctf_dropper = world; flag.ctf_pickuptime = 0; flag.ctf_droptime = 0; + flag.ctf_flagdamaged = 0; + + ctf_CheckStalemate(); } void ctf_Reset() @@ -966,6 +1060,40 @@ void ctf_Reset() ctf_RespawnFlag(self); } +void ctf_Use() +{ + if(self.ctf_status != FLAG_BASE) { return; } + + entity flag; + float flag_cnt = 0; + + for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) + { + if(flag != self) + if(SAME_TEAM(flag, self)) + if(flag.active == ACTIVE_ACTIVE) + ++flag_cnt; + } + + if(self.active == ACTIVE_ACTIVE) + if(flag_cnt < 1) + { + dprint("ctf_Use: Unable to deactivate flag (not enough active flags on the map)\n"); + return; + } + + self.active = ((self.active) ? ACTIVE_NOT : ACTIVE_ACTIVE); + + if(self.active == ACTIVE_ACTIVE) + WaypointSprite_Ping(self.wps_flagbase); +} + +float ctf_FlagWaypoint_Customize() +{ + if(self.owner.active != ACTIVE_ACTIVE) { return FALSE; } + return TRUE; +} + void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup() { // bot waypoints @@ -982,10 +1110,12 @@ void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf case NUM_TEAM_2: basename = "bluebase"; break; case NUM_TEAM_3: basename = "yellowbase"; break; case NUM_TEAM_4: basename = "pinkbase"; break; + default: basename = "neutralbase"; break; } - WaypointSprite_SpawnFixed(basename, 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)); + WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1')); + WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, FALSE) : '1 1 1')); + self.wps_flagbase.customizeentityforclient = ctf_FlagWaypoint_Customize; // captureshield setup ctf_CaptureShield_Spawn(self); @@ -1002,7 +1132,7 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag setattachment(flag, world, ""); - flag.netname = ((teamnumber == NUM_TEAM_1) ? "^1RED^7 flag" : ((teamnumber == NUM_TEAM_2) ? "^4BLUE^7 flag" : ((teamnumber == NUM_TEAM_3) ? "^3YELLOW^7 flag" : "^6PINK^7 flag"))); + flag.netname = sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)); flag.team = teamnumber; flag.classname = "item_flag_team"; flag.target = "###item###"; // wut? @@ -1020,25 +1150,27 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag flag.velocity = '0 0 0'; flag.mangle = flag.angles; flag.reset = ctf_Reset; + flag.use = ctf_Use; flag.touch = ctf_FlagTouch; flag.think = ctf_FlagThink; flag.nextthink = time + FLAG_THINKRATE; flag.ctf_status = FLAG_BASE; // appearence - if(flag.model == "") { flag.model = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_model : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_model : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_model : autocvar_g_ctf_flag_pink_model))); } + if(flag.model == "") { flag.model = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_model : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_model : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_model : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_model : autocvar_g_ctf_flag_neutral_model)))); } if(!flag.scale) { flag.scale = FLAG_SCALE; } - if(!flag.skin) { flag.skin = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_skin : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_skin : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_skin : autocvar_g_ctf_flag_pink_skin))); } - if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber == NUM_TEAM_1) ? "redflag_touch" : ((teamnumber == NUM_TEAM_2) ? "blueflag_touch" : ((teamnumber == NUM_TEAM_3) ? "yellowflag_touch" : "pinkflag_touch"))); } - if(flag.passeffect == "") { flag.passeffect = ((teamnumber == NUM_TEAM_1) ? "red_pass" : ((teamnumber == NUM_TEAM_2) ? "blue_pass" : ((teamnumber == NUM_TEAM_3) ? "yellow_pass" : "pink_pass"))); } - if(flag.capeffect == "") { flag.capeffect = ((teamnumber == NUM_TEAM_1) ? "red_cap" : ((teamnumber == NUM_TEAM_2) ? "blue_cap" : ((teamnumber == NUM_TEAM_3) ? "yellow_cap" : "pink_cap"))); } + if(!flag.skin) { flag.skin = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_skin : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_skin : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_skin : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_skin : autocvar_g_ctf_flag_neutral_skin)))); } + if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber == NUM_TEAM_1) ? "redflag_touch" : ((teamnumber == NUM_TEAM_2) ? "blueflag_touch" : ((teamnumber == NUM_TEAM_3) ? "yellowflag_touch" : ((teamnumber == NUM_TEAM_4) ? "pinkflag_touch" : "neutralflag_touch")))); } + if(flag.passeffect == "") { flag.passeffect = ((teamnumber == NUM_TEAM_1) ? "red_pass" : ((teamnumber == NUM_TEAM_2) ? "blue_pass" : ((teamnumber == NUM_TEAM_3) ? "yellow_pass" : ((teamnumber == NUM_TEAM_4) ? "pink_pass" : "neutral_pass")))); } + if(flag.capeffect == "") { flag.capeffect = ((teamnumber == NUM_TEAM_1) ? "red_cap" : ((teamnumber == NUM_TEAM_2) ? "blue_cap" : ((teamnumber == NUM_TEAM_3) ? "yellow_cap" : ((teamnumber == NUM_TEAM_4) ? "pink_cap" : "")))); } // neutral flag cant be capped itself + if(!flag.active) { flag.active = ACTIVE_ACTIVE; } // sound - if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber == NUM_TEAM_1) ? "ctf/red_taken.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_taken.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_taken.wav" : "ctf/pink_taken.wav"))); } - if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber == NUM_TEAM_1) ? "ctf/red_returned.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_returned.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_returned.wav" : "ctf/pink_returned.wav"))); } - if(flag.snd_flag_capture == "") { flag.snd_flag_capture = ((teamnumber == NUM_TEAM_1) ? "ctf/red_capture.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_capture.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_capture.wav" : "ctf/pink_capture.wav"))); } - if(flag.snd_flag_dropped == "") { flag.snd_flag_dropped = ((teamnumber == NUM_TEAM_1) ? "ctf/red_dropped.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_dropped.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_dropped.wav" : "ctf/pink_dropped.wav"))); } + if(flag.snd_flag_taken == "") { flag.snd_flag_taken = ((teamnumber == NUM_TEAM_1) ? "ctf/red_taken.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_taken.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_taken.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_taken.wav" : "ctf/neutral_taken.wav")))); } + if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber == NUM_TEAM_1) ? "ctf/red_returned.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_returned.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_returned.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_returned.wav" : "")))); } // neutral flag can't be returned by players + if(flag.snd_flag_capture == "") { flag.snd_flag_capture = ((teamnumber == NUM_TEAM_1) ? "ctf/red_capture.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_capture.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_capture.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_capture.wav" : "")))); } // again can't be captured 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 == NUM_TEAM_1) ? "ctf/red_dropped.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_dropped.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_dropped.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_dropped.wav" : "ctf/neutral_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 @@ -1061,7 +1193,7 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag if(autocvar_g_ctf_flag_glowtrails) { - flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : 145))); + flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : ((teamnumber == NUM_TEAM_4) ? 145 : 254)))); flag.glow_size = 25; flag.glow_trail = 1; } @@ -1076,9 +1208,10 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag case NUM_TEAM_2: flag.effects |= EF_BLUE; break; case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break; case NUM_TEAM_4: flag.effects |= EF_RED; break; + default: flag.effects |= EF_DIMLIGHT; break; } } - + // flag placement if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location { @@ -1144,7 +1277,20 @@ entity havocbot_ctf_find_enemy_flag(entity bot) f = ctf_worldflaglist; while (f) { - if (CTF_DIFFTEAM(bot, f)) + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(bot, f)) + { + if(f.team) + { + if(bot.flagcarried) + return f; + } + else if(!bot.flagcarried) + return f; + } + } + else if (CTF_DIFFTEAM(bot, f)) return f; f = f.ctf_worldflagnext; } @@ -1161,7 +1307,7 @@ float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius) FOR_EACH_PLAYER(head) { - if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot) + if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot) continue; if(vlen(head.origin - org) < tc_radius) @@ -1207,7 +1353,20 @@ void havocbot_goalrating_ctf_enemyflag(float ratingscale) head = ctf_worldflaglist; while (head) { - if(CTF_DIFFTEAM(self, head)) + if(ctf_oneflag) + { + if(CTF_DIFFTEAM(self, head)) + { + if(head.team) + { + if(self.flagcarried) + break; + } + else if(!self.flagcarried) + break; + } + } + else if(CTF_DIFFTEAM(self, head)) break; head = head.ctf_worldflagnext; } @@ -1329,7 +1488,7 @@ void havocbot_ctf_reset_role(entity bot) // if there is only me on the team switch to offense c = 0; FOR_EACH_PLAYER(head) - if(head.team==bot.team) + if(SAME_TEAM(head, bot)) ++c; if(c==1) @@ -1375,7 +1534,10 @@ void havocbot_role_ctf_carrier() self.bot_strategytime = time + autocvar_bot_ai_strategyinterval; navigation_goalrating_start(); - havocbot_goalrating_ctf_ourbase(50000); + if(ctf_oneflag) + havocbot_goalrating_ctf_enemybase(50000); + else + havocbot_goalrating_ctf_ourbase(50000); if(self.health<100) havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000); @@ -1695,7 +1857,7 @@ void havocbot_role_ctf_defense() } if(closestplayer) - if(closestplayer.team!=self.team) + if(DIFF_TEAM(closestplayer, self)) if(vlen(org - self.origin)>1000) if(checkpvs(self.origin,closestplayer)||random()<0.5) havocbot_goalrating_ctf_ourbase(30000); @@ -1762,22 +1924,25 @@ void havocbot_role_ctf_setrole(entity bot, float role) MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink) { entity flag; + float t = 0, t2 = 0, t3 = 0; // 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_YELLOW_FLAG_CARRYING | IT_YELLOW_FLAG_TAKEN | IT_YELLOW_FLAG_LOST - | IT_PINK_FLAG_CARRYING | IT_PINK_FLAG_TAKEN | IT_PINK_FLAG_LOST - | IT_CTF_SHIELDED); + self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST + | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST + | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST + | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST + | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST + | CTF_FLAG_NEUTRAL | CTF_SHIELDED); // scan through all the flags and notify the client about them for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext) { - if(flag.team == NUM_TEAM_1) { t = IT_RED_FLAG_CARRYING; t2 = IT_RED_FLAG_TAKEN; t3 = IT_RED_FLAG_LOST; } - if(flag.team == NUM_TEAM_2) { t = IT_BLUE_FLAG_CARRYING; t2 = IT_BLUE_FLAG_TAKEN; t3 = IT_BLUE_FLAG_LOST; } - if(flag.team == NUM_TEAM_3) { t = IT_YELLOW_FLAG_CARRYING; t2 = IT_YELLOW_FLAG_TAKEN; t3 = IT_YELLOW_FLAG_LOST; } - if(flag.team == NUM_TEAM_4) { t = IT_PINK_FLAG_CARRYING; t2 = IT_PINK_FLAG_TAKEN; t3 = IT_PINK_FLAG_LOST; } + if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; } + if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; } + if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; } + if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; } + if(flag.team == 0) { t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; } switch(flag.ctf_status) { @@ -1785,14 +1950,14 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink) case FLAG_CARRY: { if((flag.owner == self) || (flag.pass_sender == self)) - self.items |= t; // carrying: self is currently carrying the flag - else - self.items |= t2; // taken: someone else is carrying the flag + self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag + else + self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag break; } case FLAG_DROPPED: { - self.items |= t3; // lost: the flag is dropped somewhere on the map + self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map break; } } @@ -1800,7 +1965,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink) // item for stopping players from capturing the flag too often if(self.ctf_captureshielded) - self.items |= IT_CTF_SHIELDED; + self.ctf_flagstatus |= CTF_SHIELDED; // update the health of the flag carrier waypointsprite if(self.wps_flagcarrier) @@ -1846,7 +2011,11 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDies) } if(frag_target.flagcarried) - { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); } + { + entity tmp_entity = frag_target.flagcarried; + ctf_Handle_Throw(frag_target, world, DROP_NORMAL); + tmp_entity.ctf_dropper = world; + } return FALSE; } @@ -2031,7 +2200,7 @@ MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun) { if(self.flagcarried) { - Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_)); + 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)); ctf_RespawnFlag(self.flagcarried); return TRUE; } @@ -2081,10 +2250,18 @@ MUTATOR_HOOKFUNCTION(ctf_BotRoles) MUTATOR_HOOKFUNCTION(ctf_GetTeamCount) { - ret_float = ctf_teams; - return 0; + //ret_float = ctf_teams; + ret_string = "ctf_team"; + return TRUE; +} + +MUTATOR_HOOKFUNCTION(ctf_SpectateCopy) +{ + self.ctf_flagstatus = other.ctf_flagstatus; + return FALSE; } + // ========== // Spawnfuncs // ========== @@ -2190,7 +2367,7 @@ void spawnfunc_item_flag_team3() } /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37) -CTF flag for team two (Pink). +CTF flag for team four (Pink). Keys: "angle" Angle the flag will point (minus 90 degrees)... "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... @@ -2207,6 +2384,24 @@ void spawnfunc_item_flag_team4() ctf_FlagSetup(NUM_TEAM_4, self); } +/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37) +CTF flag (Neutral). +Keys: +"angle" Angle the flag will point (minus 90 degrees)... +"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3... +"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_neutral() +{ + if(!g_ctf) { remove(self); return; } + + ctf_FlagSetup(0, self); +} + /*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. @@ -2273,6 +2468,7 @@ void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to { if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); } if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); } + if(tmp_entity.team == 0) { ctf_oneflag = TRUE; } } ctf_teams = bound(2, ctf_teams, 4); @@ -2299,6 +2495,8 @@ void ctf_Initialize() 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; + + addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus); InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE); } @@ -2319,8 +2517,9 @@ MUTATOR_DEFINITION(gamemode_ctf) 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_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY); MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY); + MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY); MUTATOR_ONADD {