1 #include "../../common/effects.qh"
2 #include "../../common/movetypes/movetypes.qh"
4 void ctf_FakeTimeLimit(entity e, float t)
7 WriteByte(MSG_ONE, 3); // svc_updatestat
8 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
10 WriteCoord(MSG_ONE, autocvar_timelimit);
12 WriteCoord(MSG_ONE, (t + 1) / 60);
15 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
17 if(autocvar_sv_eventlog)
18 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
19 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""), "));
22 void ctf_CaptureRecord(entity flag, entity player)
24 float cap_record = ctf_captimerecord;
25 float cap_time = (time - flag.ctf_pickuptime);
26 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
29 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
30 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)); }
31 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)); }
32 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)); }
34 // write that shit in the database
35 if(!ctf_oneflag) // but not in 1-flag mode
36 if((!ctf_captimerecord) || (cap_time < cap_record))
38 ctf_captimerecord = cap_time;
39 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
40 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
41 write_recordmarker(player, (time - cap_time), cap_time);
45 void ctf_FlagcarrierWaypoints(entity player)
47 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
48 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
49 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
50 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
53 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
55 float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis
56 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
57 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
58 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
61 if(current_height) // make sure we can actually do this arcing path
63 targpos = (to + ('0 0 1' * current_height));
64 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
65 if(trace_fraction < 1)
67 //print("normal arc line failed, trying to find new pos...");
68 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
69 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
70 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
71 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
72 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
75 else { targpos = to; }
77 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
79 vector desired_direction = normalize(targpos - from);
80 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
81 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
84 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
86 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
88 // directional tracing only
90 makevectors(passer_angle);
92 // find the closest point on the enemy to the center of the attack
93 float h; // hypotenuse, which is the distance between attacker to head
94 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
96 h = vlen(head_center - passer_center);
97 a = h * (normalize(head_center - passer_center) * v_forward);
99 vector nearest_on_line = (passer_center + a * v_forward);
100 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
102 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
103 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
105 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
110 else { return true; }
114 // =======================
115 // CaptureShield Functions
116 // =======================
118 float ctf_CaptureShield_CheckStatus(entity p)
120 float s, s2, s3, s4, se, se2, se3, se4, sr, ser;
122 float players_worseeq, players_total;
124 if(ctf_captureshield_max_ratio <= 0)
127 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
128 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
129 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
130 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
132 sr = ((s - s2) + (s3 + s4));
134 if(sr >= -ctf_captureshield_min_negscore)
137 players_total = players_worseeq = 0;
142 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
143 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
144 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
145 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
147 ser = ((se - se2) + (se3 + se4));
154 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
155 // use this rule here
157 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
163 void ctf_CaptureShield_Update(entity player, float wanted_status)
165 float updated_status = ctf_CaptureShield_CheckStatus(player);
166 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
168 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
169 player.ctf_captureshielded = updated_status;
173 float ctf_CaptureShield_Customize()
175 if(self.enemy.active != ACTIVE_ACTIVE) { return true; }
176 if(!other.ctf_captureshielded) { return false; }
177 if(CTF_SAMETEAM(self, other)) { return false; }
182 void ctf_CaptureShield_Touch()
184 if(self.enemy.active != ACTIVE_ACTIVE)
186 vector mymid = (self.absmin + self.absmax) * 0.5;
187 vector othermid = (other.absmin + other.absmax) * 0.5;
189 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
190 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_INACTIVE); }
195 if(!other.ctf_captureshielded) { return; }
196 if(CTF_SAMETEAM(self, other)) { return; }
198 vector mymid = (self.absmin + self.absmax) * 0.5;
199 vector othermid = (other.absmin + other.absmax) * 0.5;
201 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
202 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
205 void ctf_CaptureShield_Spawn(entity flag)
207 entity shield = spawn();
210 shield.team = self.team;
211 shield.touch = ctf_CaptureShield_Touch;
212 shield.customizeentityforclient = ctf_CaptureShield_Customize;
213 shield.classname = "ctf_captureshield";
214 shield.effects = EF_ADDITIVE;
215 shield.movetype = MOVETYPE_NOCLIP;
216 shield.solid = SOLID_TRIGGER;
217 shield.avelocity = '7 0 11';
220 setorigin(shield, self.origin);
221 setmodel(shield, "models/ctf/shield.md3");
222 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
226 // ====================
227 // Drop/Pass/Throw Code
228 // ====================
230 void ctf_Handle_Drop(entity flag, entity player, float droptype)
233 player = (player ? player : flag.pass_sender);
236 flag.movetype = MOVETYPE_TOSS;
237 flag.takedamage = DAMAGE_YES;
238 flag.angles = '0 0 0';
239 flag.health = flag.max_flag_health;
240 flag.ctf_droptime = time;
241 flag.ctf_dropper = player;
242 flag.ctf_status = FLAG_DROPPED;
244 // messages and sounds
245 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
246 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
247 ctf_EventLog("dropped", player.team, player);
250 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
251 PlayerScore_Add(player, SP_CTF_DROPS, 1);
254 if(autocvar_g_ctf_flag_dropped_waypoint)
255 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));
257 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
259 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
260 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
263 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
265 if(droptype == DROP_PASS)
267 flag.pass_distance = 0;
268 flag.pass_sender = world;
269 flag.pass_target = world;
273 void ctf_Handle_Retrieve(entity flag, entity player)
275 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
276 entity sender = flag.pass_sender;
278 // transfer flag to player
280 flag.owner.flagcarried = flag;
285 setattachment(flag, player.vehicle, "");
286 setorigin(flag, VEHICLE_FLAG_OFFSET);
287 flag.scale = VEHICLE_FLAG_SCALE;
291 setattachment(flag, player, "");
292 setorigin(flag, FLAG_CARRY_OFFSET);
294 flag.movetype = MOVETYPE_NONE;
295 flag.takedamage = DAMAGE_NO;
296 flag.solid = SOLID_NOT;
297 flag.angles = '0 0 0';
298 flag.ctf_status = FLAG_CARRY;
300 // messages and sounds
301 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
302 ctf_EventLog("receive", flag.team, player);
304 FOR_EACH_REALPLAYER(tmp_player)
306 if(tmp_player == sender)
307 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);
308 else if(tmp_player == player)
309 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);
310 else if(SAME_TEAM(tmp_player, sender))
311 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);
314 // create new waypoint
315 ctf_FlagcarrierWaypoints(player);
317 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
318 player.throw_antispam = sender.throw_antispam;
320 flag.pass_distance = 0;
321 flag.pass_sender = world;
322 flag.pass_target = world;
325 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
327 entity flag = player.flagcarried;
328 vector targ_origin, flag_velocity;
330 if(!flag) { return; }
331 if((droptype == DROP_PASS) && !receiver) { return; }
333 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
336 setattachment(flag, world, "");
337 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
338 flag.owner.flagcarried = world;
340 flag.solid = SOLID_TRIGGER;
341 flag.ctf_dropper = player;
342 flag.ctf_droptime = time;
344 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
351 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
352 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
353 WarpZone_RefSys_Copy(flag, receiver);
354 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
355 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
357 flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis
358 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
361 flag.movetype = MOVETYPE_FLY;
362 flag.takedamage = DAMAGE_NO;
363 flag.pass_sender = player;
364 flag.pass_target = receiver;
365 flag.ctf_status = FLAG_PASSING;
368 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
369 if(flag.passeffect == "") { Send_Effect(flag.passeffectnum, player.origin, targ_origin, 0); }
370 else { WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin); }
371 ctf_EventLog("pass", flag.team, player);
377 makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
379 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
380 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
381 ctf_Handle_Drop(flag, player, droptype);
387 flag.velocity = '0 0 0'; // do nothing
394 flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
395 ctf_Handle_Drop(flag, player, droptype);
400 // kill old waypointsprite
401 WaypointSprite_Ping(player.wps_flagcarrier);
402 WaypointSprite_Kill(player.wps_flagcarrier);
404 if(player.wps_enemyflagcarrier)
405 WaypointSprite_Kill(player.wps_enemyflagcarrier);
408 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
416 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
418 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
419 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
420 entity player_team_flag = world, tmp_entity;
421 float old_time, new_time;
423 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
424 if(CTF_DIFFTEAM(player, flag)) { return; }
427 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
428 if(SAME_TEAM(tmp_entity, player))
430 player_team_flag = tmp_entity;
434 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
436 player.throw_prevtime = time;
437 player.throw_count = 0;
439 // messages and sounds
440 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
441 ctf_CaptureRecord(enemy_flag, player);
442 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);
446 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
447 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
452 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
453 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
455 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
456 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
457 if(!old_time || new_time < old_time)
458 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
461 if(flag.capeffect == "") { Send_Effect(flag.capeffectnum, flag.origin, '0 0 0', 1); }
462 else { pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1); }
463 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
466 if(capturetype == CAPTURE_NORMAL)
468 WaypointSprite_Kill(player.wps_flagcarrier);
469 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
471 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
472 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
476 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
477 ctf_RespawnFlag(enemy_flag);
480 void ctf_Handle_Return(entity flag, entity player)
482 // messages and sounds
483 if(IS_MONSTER(player))
485 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
489 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
490 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
492 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
493 ctf_EventLog("return", flag.team, player);
496 if(IS_PLAYER(player))
498 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
499 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
501 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
504 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
508 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
509 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
510 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
514 if(player.flagcarried == flag)
515 WaypointSprite_Kill(player.wps_flagcarrier);
518 ctf_RespawnFlag(flag);
521 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
524 float pickup_dropped_score; // used to calculate dropped pickup score
525 entity tmp_entity; // temporary entity
527 // attach the flag to the player
529 player.flagcarried = flag;
532 setattachment(flag, player.vehicle, "");
533 setorigin(flag, VEHICLE_FLAG_OFFSET);
534 flag.scale = VEHICLE_FLAG_SCALE;
538 setattachment(flag, player, "");
539 setorigin(flag, FLAG_CARRY_OFFSET);
543 flag.movetype = MOVETYPE_NONE;
544 flag.takedamage = DAMAGE_NO;
545 flag.solid = SOLID_NOT;
546 flag.angles = '0 0 0';
547 flag.ctf_status = FLAG_CARRY;
551 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
552 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
556 // messages and sounds
557 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
558 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
559 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
560 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
561 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)); }
563 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);
566 FOR_EACH_PLAYER(tmp_entity)
567 if(tmp_entity != player)
568 if(DIFF_TEAM(player, tmp_entity))
569 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
572 FOR_EACH_PLAYER(tmp_entity)
573 if(tmp_entity != player)
574 if(CTF_SAMETEAM(flag, tmp_entity))
575 if(SAME_TEAM(player, tmp_entity))
576 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
578 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);
580 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
583 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
584 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
589 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
590 ctf_EventLog("steal", flag.team, player);
596 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);
597 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);
598 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
599 PlayerTeamScore_AddScore(player, pickup_dropped_score);
600 ctf_EventLog("pickup", flag.team, player);
608 if(pickuptype == PICKUP_BASE)
610 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
611 if((player.speedrunning) && (ctf_captimerecord))
612 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
616 if(flag.toucheffect == "") { Send_Effect(flag.toucheffectnum, player.origin, '0 0 0', 1); }
617 else { pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1); }
620 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
621 ctf_FlagcarrierWaypoints(player);
622 WaypointSprite_Ping(player.wps_flagcarrier);
626 // ===================
627 // Main Flag Functions
628 // ===================
630 void ctf_CheckFlagReturn(entity flag, float returntype)
632 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
634 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
636 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
640 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;
641 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;
642 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;
643 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;
647 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
649 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
650 ctf_EventLog("returned", flag.team, world);
651 ctf_RespawnFlag(flag);
656 float ctf_Stalemate_Customize()
658 // make spectators see what the player would see
660 e = WaypointSprite_getviewentity(other);
661 wp_owner = self.owner;
664 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
665 if(SAME_TEAM(wp_owner, e)) { return false; }
666 if(!IS_PLAYER(e)) { return false; }
671 void ctf_CheckStalemate(void)
674 float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
677 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
679 // build list of stale flags
680 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
682 if(autocvar_g_ctf_stalemate)
683 if(tmp_entity.ctf_status != FLAG_BASE)
684 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
686 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
687 ctf_staleflaglist = tmp_entity;
689 switch(tmp_entity.team)
691 case NUM_TEAM_1: ++stale_red_flags; break;
692 case NUM_TEAM_2: ++stale_blue_flags; break;
693 case NUM_TEAM_3: ++stale_yellow_flags; break;
694 case NUM_TEAM_4: ++stale_pink_flags; break;
695 default: ++stale_neutral_flags; break;
701 stale_flags = (stale_neutral_flags >= 1);
703 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
705 if(ctf_oneflag && stale_flags == 1)
706 ctf_stalemate = true;
707 else if(stale_flags == ctf_teams)
708 ctf_stalemate = true;
709 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
710 { ctf_stalemate = false; wpforenemy_announced = false; }
711 else if(stale_flags < ctf_teams && autocvar_g_ctf_stalemate_endcondition == 1)
712 { ctf_stalemate = false; wpforenemy_announced = false; }
714 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
717 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
719 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
721 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));
722 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
726 if (!wpforenemy_announced)
728 FOR_EACH_REALPLAYER(tmp_entity)
729 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
731 wpforenemy_announced = true;
736 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
738 self.velocity = self.velocity;
739 if(ITEM_DAMAGE_NEEDKILL(deathtype))
741 if(autocvar_g_ctf_flag_return_damage_delay)
743 self.ctf_flagdamaged = true;
748 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
752 if(autocvar_g_ctf_flag_return_damage)
754 // reduce health and check if it should be returned
755 self.health = self.health - damage;
756 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
761 void ctf_FlagUpdate()
767 if(self == ctf_worldflaglist) // only for the first flag
768 FOR_EACH_CLIENT(tmp_entity)
769 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
772 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
773 dprint("wtf the flag got squashed?\n");
774 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
775 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
776 setsize(self, FLAG_MIN, FLAG_MAX); }
778 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
782 self.angles = '0 0 0';
790 switch(self.ctf_status)
794 if(autocvar_g_ctf_dropped_capture_radius)
796 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
797 if(tmp_entity.ctf_status == FLAG_DROPPED)
798 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
799 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
800 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
807 if(autocvar_g_ctf_flag_dropped_floatinwater)
809 vector midpoint = ((self.absmin + self.absmax) * 0.5);
810 if(pointcontents(midpoint) == CONTENT_WATER)
812 self.velocity = self.velocity * 0.5;
814 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
815 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
817 { self.movetype = MOVETYPE_FLY; }
819 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
821 if(autocvar_g_ctf_flag_return_dropped)
823 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
826 ctf_CheckFlagReturn(self, RETURN_DROPPED);
830 if(self.ctf_flagdamaged)
832 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
833 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
836 else if(autocvar_g_ctf_flag_return_time)
838 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
839 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
847 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
850 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
854 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
858 if(autocvar_g_ctf_stalemate)
860 if(time >= wpforenemy_nextthink)
862 ctf_CheckStalemate();
863 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
866 if(CTF_SAMETEAM(self, self.owner) && self.team)
868 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
869 ctf_Handle_Throw(self.owner, world, DROP_THROW);
870 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried)
871 ctf_Handle_Return(self, self.owner);
878 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
879 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
880 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
882 if((self.pass_target == world)
883 || (self.pass_target.deadflag != DEAD_NO)
884 || (self.pass_target.flagcarried)
885 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
886 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
887 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
889 // give up, pass failed
890 ctf_Handle_Drop(self, world, DROP_PASS);
894 // still a viable target, go for it
895 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
900 default: // this should never happen
902 dprint("ctf_FlagThink(): Flag exists with no status?\n");
910 self.nextthink = time + FLAG_THINKRATE;
914 /*if(time >= self.ctf_thinkrate)
916 self.ctf_thinkrate = time + FLAG_THINKRATE;
920 //Movetype_Physics_NoMatchServer();
921 //Movetype_Physics_MatchTicrate(sys_frametime, 0);
926 if(gameover) { return; }
927 if(self.active != ACTIVE_ACTIVE) { return; }
928 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
930 entity toucher = other, tmp_entity;
931 float is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
933 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
934 if(ITEM_TOUCH_NEEDKILL())
936 if(!autocvar_g_ctf_flag_return_damage_delay)
939 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
941 if(!self.ctf_flagdamaged) { return; }
944 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
946 // special touch behaviors
947 if(toucher.frozen) { return; }
948 else if(IS_VEHICLE(toucher))
950 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
951 toucher = toucher.owner; // the player is actually the vehicle owner, not other
953 return; // do nothing
955 else if(IS_MONSTER(toucher))
957 if(!autocvar_g_ctf_allow_monster_touch)
958 return; // do nothing
960 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
962 if(time > self.wait) // if we haven't in a while, play a sound/effect
964 if(self.toucheffect == "") { Send_Effect(self.toucheffectnum, self.origin, '0 0 0', 1); }
965 else { pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1); }
966 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
967 self.wait = time + FLAG_TOUCHRATE;
971 else if(toucher.deadflag != DEAD_NO) { return; }
973 switch(self.ctf_status)
979 if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
980 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
981 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
982 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
984 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
985 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
986 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
987 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
993 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
994 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
995 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
996 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1002 dprint("Someone touched a flag even though it was being carried?\n");
1008 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
1010 if(DIFF_TEAM(toucher, self.pass_sender))
1011 ctf_Handle_Return(self, toucher);
1013 ctf_Handle_Retrieve(self, toucher);
1020 .float last_respawn;
1021 void ctf_RespawnFlag(entity flag)
1023 // check for flag respawn being called twice in a row
1024 if(flag.last_respawn > time - 0.5)
1025 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1027 flag.last_respawn = time;
1029 // reset the player (if there is one)
1030 if((flag.owner) && (flag.owner.flagcarried == flag))
1032 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1033 WaypointSprite_Kill(flag.wps_flagcarrier);
1035 flag.owner.flagcarried = world;
1037 if(flag.speedrunning)
1038 ctf_FakeTimeLimit(flag.owner, -1);
1041 if((flag.owner) && (flag.owner.vehicle))
1042 flag.scale = FLAG_SCALE;
1044 if(flag.ctf_status == FLAG_DROPPED)
1045 { WaypointSprite_Kill(flag.wps_flagdropped); }
1048 setattachment(flag, world, "");
1049 setorigin(flag, flag.ctf_spawnorigin);
1051 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1052 flag.takedamage = DAMAGE_NO;
1053 flag.health = flag.max_flag_health;
1054 flag.solid = SOLID_TRIGGER;
1055 flag.velocity = '0 0 0';
1056 flag.angles = flag.mangle;
1057 flag.flags = FL_ITEM | FL_NOTARGET;
1059 flag.ctf_status = FLAG_BASE;
1061 flag.pass_distance = 0;
1062 flag.pass_sender = world;
1063 flag.pass_target = world;
1064 flag.ctf_dropper = world;
1065 flag.ctf_pickuptime = 0;
1066 flag.ctf_droptime = 0;
1067 flag.ctf_flagdamaged = 0;
1069 ctf_CheckStalemate();
1075 if(IS_PLAYER(self.owner))
1076 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1078 ctf_RespawnFlag(self);
1083 if(self.ctf_status != FLAG_BASE) { return; }
1085 self.active = ((self.active) ? ACTIVE_NOT : ACTIVE_ACTIVE);
1087 if(self.active == ACTIVE_ACTIVE)
1088 WaypointSprite_Ping(self.wps_flagbase);
1091 float ctf_FlagWaypoint_Customize()
1093 if(self.owner.active != ACTIVE_ACTIVE) { return false; }
1097 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1100 waypoint_spawnforitem_force(self, self.origin);
1101 self.nearestwaypointtimeout = 0; // activate waypointing again
1102 self.bot_basewaypoint = self.nearestwaypoint;
1105 string basename = "base";
1109 case NUM_TEAM_1: basename = "redbase"; break;
1110 case NUM_TEAM_2: basename = "bluebase"; break;
1111 case NUM_TEAM_3: basename = "yellowbase"; break;
1112 case NUM_TEAM_4: basename = "pinkbase"; break;
1113 default: basename = "neutralbase"; break;
1116 WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'));
1117 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1118 self.wps_flagbase.customizeentityforclient = ctf_FlagWaypoint_Customize;
1120 // captureshield setup
1121 ctf_CaptureShield_Spawn(self);
1123 //self.move_origin = self.origin;
1124 //self.angles = self.angles;
1125 //self.velocity = self.velocity;
1128 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1131 self = flag; // for later usage with droptofloor()
1134 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1135 ctf_worldflaglist = flag;
1137 setattachment(flag, world, "");
1139 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1140 flag.team = teamnumber;
1141 flag.classname = "item_flag_team";
1142 flag.target = "###item###"; // wut?
1143 flag.flags = FL_ITEM | FL_NOTARGET;
1144 flag.solid = SOLID_TRIGGER;
1145 flag.takedamage = DAMAGE_NO;
1146 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1147 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1148 flag.health = flag.max_flag_health;
1149 flag.event_damage = ctf_FlagDamage;
1150 flag.pushable = true;
1151 flag.teleportable = TELEPORT_NORMAL;
1152 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1153 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1154 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1155 flag.velocity = '0 0 0';
1156 flag.mangle = flag.angles;
1157 flag.reset = ctf_Reset;
1159 flag.touch = ctf_FlagTouch;
1160 flag.think = ctf_FlagThink;
1161 flag.nextthink = time + FLAG_THINKRATE;
1162 flag.ctf_status = FLAG_BASE;
1165 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)))); }
1166 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1167 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)))); }
1168 if(flag.toucheffect == "") { flag.toucheffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_FLAG_RED_TOUCH : ((teamnumber == NUM_TEAM_2) ? EFFECT_FLAG_BLUE_TOUCH : ((teamnumber == NUM_TEAM_3) ? EFFECT_FLAG_YELLOW_TOUCH : ((teamnumber == NUM_TEAM_4) ? EFFECT_FLAG_PINK_TOUCH : EFFECT_FLAG_NEUTRAL_TOUCH)))); }
1169 if(flag.passeffect == "") { flag.passeffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_RED_PASS : ((teamnumber == NUM_TEAM_2) ? EFFECT_BLUE_PASS : ((teamnumber == NUM_TEAM_3) ? EFFECT_YELLOW_PASS : ((teamnumber == NUM_TEAM_4) ? EFFECT_PINK_PASS : EFFECT_NEUTRAL_PASS)))); }
1170 if(flag.capeffect == "") { flag.capeffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_RED_CAP : ((teamnumber == NUM_TEAM_2) ? EFFECT_BLUE_CAP : ((teamnumber == NUM_TEAM_3) ? EFFECT_YELLOW_CAP : ((teamnumber == NUM_TEAM_4) ? EFFECT_PINK_CAP : 0)))); } // neutral flag cant be capped itself
1171 if(!flag.active) { flag.active = ACTIVE_ACTIVE; }
1174 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")))); }
1175 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
1176 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
1177 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.
1178 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")))); }
1179 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1180 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1183 precache_sound(flag.snd_flag_taken);
1184 precache_sound(flag.snd_flag_returned);
1185 precache_sound(flag.snd_flag_capture);
1186 precache_sound(flag.snd_flag_respawn);
1187 precache_sound(flag.snd_flag_dropped);
1188 precache_sound(flag.snd_flag_touch);
1189 precache_sound(flag.snd_flag_pass);
1190 precache_model(flag.model);
1191 precache_model("models/ctf/shield.md3");
1192 precache_model("models/ctf/shockwavetransring.md3");
1195 setmodel(flag, flag.model); // precision set below
1196 setsize(flag, FLAG_MIN, FLAG_MAX);
1197 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1199 if(autocvar_g_ctf_flag_glowtrails)
1201 flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : ((teamnumber == NUM_TEAM_4) ? 145 : 254))));
1202 flag.glow_size = 25;
1203 flag.glow_trail = 1;
1206 flag.effects |= EF_LOWPRECISION;
1207 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1208 if(autocvar_g_ctf_dynamiclights)
1212 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1213 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1214 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1215 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1216 default: flag.effects |= EF_DIMLIGHT; break;
1221 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1223 flag.dropped_origin = flag.origin;
1224 flag.noalign = true;
1225 flag.movetype = MOVETYPE_NONE;
1227 else // drop to floor, automatically find a platform and set that as spawn origin
1229 flag.noalign = false;
1232 flag.movetype = MOVETYPE_TOSS;
1235 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1243 // NOTE: LEGACY CODE, needs to be re-written!
1245 void havocbot_calculate_middlepoint()
1249 vector fo = '0 0 0';
1252 f = ctf_worldflaglist;
1257 f = f.ctf_worldflagnext;
1261 havocbot_ctf_middlepoint = s * (1.0 / n);
1262 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1266 entity havocbot_ctf_find_flag(entity bot)
1269 f = ctf_worldflaglist;
1272 if (CTF_SAMETEAM(bot, f))
1274 f = f.ctf_worldflagnext;
1279 entity havocbot_ctf_find_enemy_flag(entity bot)
1282 f = ctf_worldflaglist;
1287 if(CTF_DIFFTEAM(bot, f))
1294 else if(!bot.flagcarried)
1298 else if (CTF_DIFFTEAM(bot, f))
1300 f = f.ctf_worldflagnext;
1305 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1313 FOR_EACH_PLAYER(head)
1315 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1318 if(vlen(head.origin - org) < tc_radius)
1325 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1328 head = ctf_worldflaglist;
1331 if (CTF_SAMETEAM(self, head))
1333 head = head.ctf_worldflagnext;
1336 navigation_routerating(head, ratingscale, 10000);
1339 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1342 head = ctf_worldflaglist;
1345 if (CTF_SAMETEAM(self, head))
1347 head = head.ctf_worldflagnext;
1352 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1355 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1358 head = ctf_worldflaglist;
1363 if(CTF_DIFFTEAM(self, head))
1367 if(self.flagcarried)
1370 else if(!self.flagcarried)
1374 else if(CTF_DIFFTEAM(self, head))
1376 head = head.ctf_worldflagnext;
1379 navigation_routerating(head, ratingscale, 10000);
1382 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1384 if (!bot_waypoints_for_items)
1386 havocbot_goalrating_ctf_enemyflag(ratingscale);
1392 head = havocbot_ctf_find_enemy_flag(self);
1397 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1400 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1404 mf = havocbot_ctf_find_flag(self);
1406 if(mf.ctf_status == FLAG_BASE)
1410 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1413 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1416 head = ctf_worldflaglist;
1419 // flag is out in the field
1420 if(head.ctf_status != FLAG_BASE)
1421 if(head.tag_entity==world) // dropped
1425 if(vlen(org-head.origin)<df_radius)
1426 navigation_routerating(head, ratingscale, 10000);
1429 navigation_routerating(head, ratingscale, 10000);
1432 head = head.ctf_worldflagnext;
1436 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1440 head = findchainfloat(bot_pickup, true);
1443 // gather health and armor only
1445 if (head.health || head.armorvalue)
1446 if (vlen(head.origin - org) < sradius)
1448 // get the value of the item
1449 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1451 navigation_routerating(head, t * ratingscale, 500);
1457 void havocbot_ctf_reset_role(entity bot)
1459 float cdefense, cmiddle, coffense;
1460 entity mf, ef, head;
1463 if(bot.deadflag != DEAD_NO)
1466 if(vlen(havocbot_ctf_middlepoint)==0)
1467 havocbot_calculate_middlepoint();
1470 if (bot.flagcarried)
1472 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1476 mf = havocbot_ctf_find_flag(bot);
1477 ef = havocbot_ctf_find_enemy_flag(bot);
1479 // Retrieve stolen flag
1480 if(mf.ctf_status!=FLAG_BASE)
1482 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1486 // If enemy flag is taken go to the middle to intercept pursuers
1487 if(ef.ctf_status!=FLAG_BASE)
1489 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1493 // if there is only me on the team switch to offense
1495 FOR_EACH_PLAYER(head)
1496 if(SAME_TEAM(head, bot))
1501 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1505 // Evaluate best position to take
1506 // Count mates on middle position
1507 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1509 // Count mates on defense position
1510 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1512 // Count mates on offense position
1513 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1515 if(cdefense<=coffense)
1516 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1517 else if(coffense<=cmiddle)
1518 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1520 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1523 void havocbot_role_ctf_carrier()
1525 if(self.deadflag != DEAD_NO)
1527 havocbot_ctf_reset_role(self);
1531 if (self.flagcarried == world)
1533 havocbot_ctf_reset_role(self);
1537 if (self.bot_strategytime < time)
1539 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1541 navigation_goalrating_start();
1543 havocbot_goalrating_ctf_enemybase(50000);
1545 havocbot_goalrating_ctf_ourbase(50000);
1548 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1550 navigation_goalrating_end();
1552 if (self.navigation_hasgoals)
1553 self.havocbot_cantfindflag = time + 10;
1554 else if (time > self.havocbot_cantfindflag)
1556 // Can't navigate to my own base, suicide!
1557 // TODO: drop it and wander around
1558 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1564 void havocbot_role_ctf_escort()
1568 if(self.deadflag != DEAD_NO)
1570 havocbot_ctf_reset_role(self);
1574 if (self.flagcarried)
1576 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1580 // If enemy flag is back on the base switch to previous role
1581 ef = havocbot_ctf_find_enemy_flag(self);
1582 if(ef.ctf_status==FLAG_BASE)
1584 self.havocbot_role = self.havocbot_previous_role;
1585 self.havocbot_role_timeout = 0;
1589 // If the flag carrier reached the base switch to defense
1590 mf = havocbot_ctf_find_flag(self);
1591 if(mf.ctf_status!=FLAG_BASE)
1592 if(vlen(ef.origin - mf.dropped_origin) < 300)
1594 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1598 // Set the role timeout if necessary
1599 if (!self.havocbot_role_timeout)
1601 self.havocbot_role_timeout = time + random() * 30 + 60;
1604 // If nothing happened just switch to previous role
1605 if (time > self.havocbot_role_timeout)
1607 self.havocbot_role = self.havocbot_previous_role;
1608 self.havocbot_role_timeout = 0;
1612 // Chase the flag carrier
1613 if (self.bot_strategytime < time)
1615 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1616 navigation_goalrating_start();
1617 havocbot_goalrating_ctf_enemyflag(30000);
1618 havocbot_goalrating_ctf_ourstolenflag(40000);
1619 havocbot_goalrating_items(10000, self.origin, 10000);
1620 navigation_goalrating_end();
1624 void havocbot_role_ctf_offense()
1629 if(self.deadflag != DEAD_NO)
1631 havocbot_ctf_reset_role(self);
1635 if (self.flagcarried)
1637 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1642 mf = havocbot_ctf_find_flag(self);
1643 ef = havocbot_ctf_find_enemy_flag(self);
1646 if(mf.ctf_status!=FLAG_BASE)
1649 pos = mf.tag_entity.origin;
1653 // Try to get it if closer than the enemy base
1654 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1656 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1661 // Escort flag carrier
1662 if(ef.ctf_status!=FLAG_BASE)
1665 pos = ef.tag_entity.origin;
1669 if(vlen(pos-mf.dropped_origin)>700)
1671 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1676 // About to fail, switch to middlefield
1679 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1683 // Set the role timeout if necessary
1684 if (!self.havocbot_role_timeout)
1685 self.havocbot_role_timeout = time + 120;
1687 if (time > self.havocbot_role_timeout)
1689 havocbot_ctf_reset_role(self);
1693 if (self.bot_strategytime < time)
1695 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1696 navigation_goalrating_start();
1697 havocbot_goalrating_ctf_ourstolenflag(50000);
1698 havocbot_goalrating_ctf_enemybase(20000);
1699 havocbot_goalrating_items(5000, self.origin, 1000);
1700 havocbot_goalrating_items(1000, self.origin, 10000);
1701 navigation_goalrating_end();
1705 // Retriever (temporary role):
1706 void havocbot_role_ctf_retriever()
1710 if(self.deadflag != DEAD_NO)
1712 havocbot_ctf_reset_role(self);
1716 if (self.flagcarried)
1718 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1722 // If flag is back on the base switch to previous role
1723 mf = havocbot_ctf_find_flag(self);
1724 if(mf.ctf_status==FLAG_BASE)
1726 havocbot_ctf_reset_role(self);
1730 if (!self.havocbot_role_timeout)
1731 self.havocbot_role_timeout = time + 20;
1733 if (time > self.havocbot_role_timeout)
1735 havocbot_ctf_reset_role(self);
1739 if (self.bot_strategytime < time)
1744 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1745 navigation_goalrating_start();
1746 havocbot_goalrating_ctf_ourstolenflag(50000);
1747 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1748 havocbot_goalrating_ctf_enemybase(30000);
1749 havocbot_goalrating_items(500, self.origin, rt_radius);
1750 navigation_goalrating_end();
1754 void havocbot_role_ctf_middle()
1758 if(self.deadflag != DEAD_NO)
1760 havocbot_ctf_reset_role(self);
1764 if (self.flagcarried)
1766 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1770 mf = havocbot_ctf_find_flag(self);
1771 if(mf.ctf_status!=FLAG_BASE)
1773 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1777 if (!self.havocbot_role_timeout)
1778 self.havocbot_role_timeout = time + 10;
1780 if (time > self.havocbot_role_timeout)
1782 havocbot_ctf_reset_role(self);
1786 if (self.bot_strategytime < time)
1790 org = havocbot_ctf_middlepoint;
1791 org.z = self.origin.z;
1793 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1794 navigation_goalrating_start();
1795 havocbot_goalrating_ctf_ourstolenflag(50000);
1796 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1797 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1798 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1799 havocbot_goalrating_items(2500, self.origin, 10000);
1800 havocbot_goalrating_ctf_enemybase(2500);
1801 navigation_goalrating_end();
1805 void havocbot_role_ctf_defense()
1809 if(self.deadflag != DEAD_NO)
1811 havocbot_ctf_reset_role(self);
1815 if (self.flagcarried)
1817 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1821 // If own flag was captured
1822 mf = havocbot_ctf_find_flag(self);
1823 if(mf.ctf_status!=FLAG_BASE)
1825 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1829 if (!self.havocbot_role_timeout)
1830 self.havocbot_role_timeout = time + 30;
1832 if (time > self.havocbot_role_timeout)
1834 havocbot_ctf_reset_role(self);
1837 if (self.bot_strategytime < time)
1842 org = mf.dropped_origin;
1843 mp_radius = havocbot_ctf_middlepoint_radius;
1845 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1846 navigation_goalrating_start();
1848 // if enemies are closer to our base, go there
1849 entity head, closestplayer = world;
1850 float distance, bestdistance = 10000;
1851 FOR_EACH_PLAYER(head)
1853 if(head.deadflag!=DEAD_NO)
1856 distance = vlen(org - head.origin);
1857 if(distance<bestdistance)
1859 closestplayer = head;
1860 bestdistance = distance;
1865 if(DIFF_TEAM(closestplayer, self))
1866 if(vlen(org - self.origin)>1000)
1867 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1868 havocbot_goalrating_ctf_ourbase(30000);
1870 havocbot_goalrating_ctf_ourstolenflag(20000);
1871 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1872 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1873 havocbot_goalrating_items(10000, org, mp_radius);
1874 havocbot_goalrating_items(5000, self.origin, 10000);
1875 navigation_goalrating_end();
1879 void havocbot_role_ctf_setrole(entity bot, float role)
1881 dprint(strcat(bot.netname," switched to "));
1884 case HAVOCBOT_CTF_ROLE_CARRIER:
1886 bot.havocbot_role = havocbot_role_ctf_carrier;
1887 bot.havocbot_role_timeout = 0;
1888 bot.havocbot_cantfindflag = time + 10;
1889 bot.bot_strategytime = 0;
1891 case HAVOCBOT_CTF_ROLE_DEFENSE:
1893 bot.havocbot_role = havocbot_role_ctf_defense;
1894 bot.havocbot_role_timeout = 0;
1896 case HAVOCBOT_CTF_ROLE_MIDDLE:
1898 bot.havocbot_role = havocbot_role_ctf_middle;
1899 bot.havocbot_role_timeout = 0;
1901 case HAVOCBOT_CTF_ROLE_OFFENSE:
1903 bot.havocbot_role = havocbot_role_ctf_offense;
1904 bot.havocbot_role_timeout = 0;
1906 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1907 dprint("retriever");
1908 bot.havocbot_previous_role = bot.havocbot_role;
1909 bot.havocbot_role = havocbot_role_ctf_retriever;
1910 bot.havocbot_role_timeout = time + 10;
1911 bot.bot_strategytime = 0;
1913 case HAVOCBOT_CTF_ROLE_ESCORT:
1915 bot.havocbot_previous_role = bot.havocbot_role;
1916 bot.havocbot_role = havocbot_role_ctf_escort;
1917 bot.havocbot_role_timeout = time + 30;
1918 bot.bot_strategytime = 0;
1929 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1933 float t = 0, t2 = 0, t3 = 0;
1935 // initially clear items so they can be set as necessary later.
1936 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
1937 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
1938 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
1939 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
1940 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
1941 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1943 // scan through all the flags and notify the client about them
1944 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1946 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
1947 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
1948 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
1949 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
1950 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; }
1952 switch(flag.ctf_status)
1957 if((flag.owner == self) || (flag.pass_sender == self))
1958 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1960 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1965 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1971 // item for stopping players from capturing the flag too often
1972 if(self.ctf_captureshielded)
1973 self.ctf_flagstatus |= CTF_SHIELDED;
1975 // update the health of the flag carrier waypointsprite
1976 if(self.wps_flagcarrier)
1977 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1982 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1984 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1986 if(frag_target == frag_attacker) // damage done to yourself
1988 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1989 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1991 else // damage done to everyone else
1993 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1994 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1997 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1999 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)))
2000 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2002 frag_target.wps_helpme_time = time;
2003 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2005 // todo: add notification for when flag carrier needs help?
2010 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
2012 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2014 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2015 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2018 if(frag_target.flagcarried)
2020 entity tmp_entity = frag_target.flagcarried;
2021 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2022 tmp_entity.ctf_dropper = world;
2028 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
2031 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2034 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
2036 entity flag; // temporary entity for the search method
2038 if(self.flagcarried)
2039 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2041 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2043 if(flag.pass_sender == self) { flag.pass_sender = world; }
2044 if(flag.pass_target == self) { flag.pass_target = world; }
2045 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
2051 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2053 if(self.flagcarried)
2054 if(!autocvar_g_ctf_portalteleport)
2055 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2060 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2062 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2064 entity player = self;
2066 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2068 // pass the flag to a team mate
2069 if(autocvar_g_ctf_pass)
2071 entity head, closest_target = world;
2072 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2074 while(head) // find the closest acceptable target to pass to
2076 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2077 if(head != player && SAME_TEAM(head, player))
2078 if(!head.speedrunning && !head.vehicle)
2080 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2081 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2082 vector passer_center = CENTER_OR_VIEWOFS(player);
2084 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2086 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2088 if(IS_BOT_CLIENT(head))
2090 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2091 ctf_Handle_Throw(head, player, DROP_PASS);
2095 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2096 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2098 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2101 else if(player.flagcarried)
2105 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2106 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2107 { closest_target = head; }
2109 else { closest_target = head; }
2116 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2119 // throw the flag in front of you
2120 if(autocvar_g_ctf_throw && player.flagcarried)
2122 if(player.throw_count == -1)
2124 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2126 player.throw_prevtime = time;
2127 player.throw_count = 1;
2128 ctf_Handle_Throw(player, world, DROP_THROW);
2133 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2139 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2140 else { player.throw_count += 1; }
2141 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2143 player.throw_prevtime = time;
2144 ctf_Handle_Throw(player, world, DROP_THROW);
2153 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2155 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2157 self.wps_helpme_time = time;
2158 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2160 else // create a normal help me waypointsprite
2162 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');
2163 WaypointSprite_Ping(self.wps_helpme);
2169 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2171 if(vh_player.flagcarried)
2173 vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
2175 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2177 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2181 setattachment(vh_player.flagcarried, vh_vehicle, "");
2182 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2183 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2184 //vh_player.flagcarried.angles = '0 0 0';
2192 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2194 if(vh_player.flagcarried)
2196 setattachment(vh_player.flagcarried, vh_player, "");
2197 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2198 vh_player.flagcarried.scale = FLAG_SCALE;
2199 vh_player.flagcarried.angles = '0 0 0';
2200 vh_player.flagcarried.nodrawtoclient = world;
2207 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2209 if(self.flagcarried)
2211 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));
2212 ctf_RespawnFlag(self.flagcarried);
2219 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2221 entity flag; // temporary entity for the search method
2223 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2225 switch(flag.ctf_status)
2230 // lock the flag, game is over
2231 flag.movetype = MOVETYPE_NONE;
2232 flag.takedamage = DAMAGE_NO;
2233 flag.solid = SOLID_NOT;
2234 flag.nextthink = false; // stop thinking
2236 //dprint("stopping the ", flag.netname, " from moving.\n");
2244 // do nothing for these flags
2253 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2255 havocbot_ctf_reset_role(self);
2259 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2261 //ret_float = ctf_teams;
2262 ret_string = "ctf_team";
2266 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2268 self.ctf_flagstatus = other.ctf_flagstatus;
2272 MUTATOR_HOOKFUNCTION(ctf_FormatMessage)
2274 entity bluefc = world, redfc = world, yellowfc = world, pinkfc = world, tmp_entity; // NOTE: blue = red player
2275 entity tfc = world, sfc = world;
2277 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2279 if(tmp_entity.owner)
2281 switch(tmp_entity.team)
2283 case NUM_TEAM_1: redfc = tmp_entity.owner; break;
2284 case NUM_TEAM_2: bluefc = tmp_entity.owner; break;
2285 case NUM_TEAM_3: yellowfc = tmp_entity.owner; break;
2286 case NUM_TEAM_4: pinkfc = tmp_entity.owner; break;
2289 if(SAME_TEAM(tmp_entity.owner, self)) { tfc = tmp_entity.owner; }
2290 if(SAME_TEAM(tmp_entity, self)) { sfc = tmp_entity.owner; }
2294 switch(format_escape)
2296 case "r": format_replacement = ((tfc) ? tfc.netname : "(nobody)"); break;
2297 case "R": format_replacement = ((sfc) ? sfc.netname : "(nobody)"); break;
2298 case "t": format_replacement = ((redfc) ? redfc.netname : "(nobody)"); break;
2299 case "T": format_replacement = ((bluefc) ? bluefc.netname : "(nobody)"); break;
2300 case "p": format_replacement = ((yellowfc) ? yellowfc.netname : "(nobody)"); break;
2301 case "P": format_replacement = ((pinkfc) ? pinkfc.netname : "(nobody)"); break;
2311 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2312 CTF flag for team one (Red).
2314 "angle" Angle the flag will point (minus 90 degrees)...
2315 "model" model to use, note this needs red and blue as skins 0 and 1...
2316 "noise" sound played when flag is picked up...
2317 "noise1" sound played when flag is returned by a teammate...
2318 "noise2" sound played when flag is captured...
2319 "noise3" sound played when flag is lost in the field and respawns itself...
2320 "noise4" sound played when flag is dropped by a player...
2321 "noise5" sound played when flag touches the ground... */
2322 void spawnfunc_item_flag_team1()
2324 if(!g_ctf) { remove(self); return; }
2326 ctf_FlagSetup(NUM_TEAM_1, self);
2329 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2330 CTF flag for team two (Blue).
2332 "angle" Angle the flag will point (minus 90 degrees)...
2333 "model" model to use, note this needs red and blue as skins 0 and 1...
2334 "noise" sound played when flag is picked up...
2335 "noise1" sound played when flag is returned by a teammate...
2336 "noise2" sound played when flag is captured...
2337 "noise3" sound played when flag is lost in the field and respawns itself...
2338 "noise4" sound played when flag is dropped by a player...
2339 "noise5" sound played when flag touches the ground... */
2340 void spawnfunc_item_flag_team2()
2342 if(!g_ctf) { remove(self); return; }
2344 ctf_FlagSetup(NUM_TEAM_2, self);
2347 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2348 CTF flag for team three (Yellow).
2350 "angle" Angle the flag will point (minus 90 degrees)...
2351 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2352 "noise" sound played when flag is picked up...
2353 "noise1" sound played when flag is returned by a teammate...
2354 "noise2" sound played when flag is captured...
2355 "noise3" sound played when flag is lost in the field and respawns itself...
2356 "noise4" sound played when flag is dropped by a player...
2357 "noise5" sound played when flag touches the ground... */
2358 void spawnfunc_item_flag_team3()
2360 if(!g_ctf) { remove(self); return; }
2362 ctf_FlagSetup(NUM_TEAM_3, self);
2365 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2366 CTF flag for team four (Pink).
2368 "angle" Angle the flag will point (minus 90 degrees)...
2369 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2370 "noise" sound played when flag is picked up...
2371 "noise1" sound played when flag is returned by a teammate...
2372 "noise2" sound played when flag is captured...
2373 "noise3" sound played when flag is lost in the field and respawns itself...
2374 "noise4" sound played when flag is dropped by a player...
2375 "noise5" sound played when flag touches the ground... */
2376 void spawnfunc_item_flag_team4()
2378 if(!g_ctf) { remove(self); return; }
2380 ctf_FlagSetup(NUM_TEAM_4, self);
2383 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2386 "angle" Angle the flag will point (minus 90 degrees)...
2387 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2388 "noise" sound played when flag is picked up...
2389 "noise1" sound played when flag is returned by a teammate...
2390 "noise2" sound played when flag is captured...
2391 "noise3" sound played when flag is lost in the field and respawns itself...
2392 "noise4" sound played when flag is dropped by a player...
2393 "noise5" sound played when flag touches the ground... */
2394 void spawnfunc_item_flag_neutral()
2396 if(!g_ctf) { remove(self); return; }
2397 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2399 ctf_FlagSetup(0, self);
2402 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2403 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2404 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.
2406 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2407 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2408 void spawnfunc_ctf_team()
2410 if(!g_ctf) { remove(self); return; }
2412 self.classname = "ctf_team";
2413 self.team = self.cnt + 1;
2416 // compatibility for quake maps
2417 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2418 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2419 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2420 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2421 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2422 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2424 void team_CTF_neutralflag() { spawnfunc_item_flag_neutral(); }
2425 void team_neutralobelisk() { spawnfunc_item_flag_neutral(); }
2433 void ctf_ScoreRules(float teams)
2435 CheckAllowedTeams(world);
2436 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2437 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2438 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2439 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2440 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2441 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2442 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2443 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2444 ScoreRules_basics_end();
2447 // code from here on is just to support maps that don't have flag and team entities
2448 void ctf_SpawnTeam (string teamname, float teamcolor)
2453 self.classname = "ctf_team";
2454 self.netname = teamname;
2455 self.cnt = teamcolor;
2457 spawnfunc_ctf_team();
2462 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2467 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2469 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2470 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2471 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2474 ctf_teams = bound(2, ctf_teams, 4);
2476 // if no teams are found, spawn defaults
2477 if(find(world, classname, "ctf_team") == world)
2479 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2481 for(i = 1; i <= ctf_teams; ++i)
2482 ctf_SpawnTeam(Team_ColorName(Team_NumberToTeam(i)), Team_NumberToTeam(i) - 1);
2485 ctf_ScoreRules(ctf_teams);
2488 void ctf_Initialize()
2490 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2492 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2493 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2494 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2496 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2498 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2502 MUTATOR_DEFINITION(gamemode_ctf)
2504 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2505 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2506 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2507 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2508 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2509 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2510 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2511 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2512 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2513 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2514 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2515 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2516 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2517 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2518 MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2519 MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
2520 MUTATOR_HOOK(FormatMessage, ctf_FormatMessage, CBC_ORDER_ANY);
2524 if(time > 1) // game loads at time 1
2525 error("This is a game type and it cannot be added at runtime.");
2529 MUTATOR_ONROLLBACK_OR_REMOVE
2531 // we actually cannot roll back ctf_Initialize here
2532 // BUT: we don't need to! If this gets called, adding always
2538 print("This is a game type and it cannot be removed at runtime.");