1 // ================================================================
2 // Official capture the flag game mode coding, reworked by Samual
3 // Last updated: September, 2012
4 // ================================================================
6 void ctf_FakeTimeLimit(entity e, float t)
9 WriteByte(MSG_ONE, 3); // svc_updatestat
10 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
12 WriteCoord(MSG_ONE, autocvar_timelimit);
14 WriteCoord(MSG_ONE, (t + 1) / 60);
17 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
19 if(autocvar_sv_eventlog)
20 GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
23 string ctf_CaptureRecord(entity flag, entity player)
25 float cap_time, cap_record, success;
26 string cap_message, refername;
28 if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots))
30 cap_record = ctf_captimerecord;
31 cap_time = (time - flag.ctf_pickuptime);
33 refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
34 refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
36 if(!ctf_captimerecord)
37 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
38 else if(cap_time < cap_record)
39 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
41 { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
45 ctf_captimerecord = cap_time;
46 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
47 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
48 write_recordmarker(player, (time - cap_time), cap_time);
55 void ctf_FlagcarrierWaypoints(entity player)
57 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
58 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent) * 2);
59 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent));
60 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
63 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
65 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
67 // directional tracing only
69 makevectors(passer_angle);
71 // find the closest point on the enemy to the center of the attack
72 float ang; // angle between shotdir and h
73 float h; // hypotenuse, which is the distance between attacker to head
74 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
76 h = vlen(head_center - passer_center);
77 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
80 vector nearest_on_line = (passer_center + a * v_forward);
81 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
83 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
84 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
86 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
95 // =======================
96 // CaptureShield Functions
97 // =======================
99 float ctf_CaptureShield_CheckStatus(entity p)
103 float players_worseeq, players_total;
105 if(ctf_captureshield_max_ratio <= 0)
108 s = PlayerScore_Add(p, SP_SCORE, 0);
109 if(s >= -ctf_captureshield_min_negscore)
112 players_total = players_worseeq = 0;
115 if(IsDifferentTeam(e, p))
117 se = PlayerScore_Add(e, SP_SCORE, 0);
123 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
124 // use this rule here
126 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
132 void ctf_CaptureShield_Update(entity player, float wanted_status)
134 float updated_status = ctf_CaptureShield_CheckStatus(player);
135 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
137 if(updated_status) // TODO csqc notifier for this // Samual: How?
138 Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Make some defensive scores before trying again.", 5, 0);
140 Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
142 player.ctf_captureshielded = updated_status;
146 float ctf_CaptureShield_Customize()
148 if(!other.ctf_captureshielded) { return FALSE; }
149 if(!IsDifferentTeam(self, other)) { return FALSE; }
154 void ctf_CaptureShield_Touch()
156 if(!other.ctf_captureshielded) { return; }
157 if(!IsDifferentTeam(self, other)) { return; }
159 vector mymid = (self.absmin + self.absmax) * 0.5;
160 vector othermid = (other.absmin + other.absmax) * 0.5;
162 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
163 Send_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
166 void ctf_CaptureShield_Spawn(entity flag)
168 entity shield = spawn();
171 shield.team = self.team;
172 shield.touch = ctf_CaptureShield_Touch;
173 shield.customizeentityforclient = ctf_CaptureShield_Customize;
174 shield.classname = "ctf_captureshield";
175 shield.effects = EF_ADDITIVE;
176 shield.movetype = MOVETYPE_NOCLIP;
177 shield.solid = SOLID_TRIGGER;
178 shield.avelocity = '7 0 11';
181 setorigin(shield, self.origin);
182 setmodel(shield, "models/ctf/shield.md3");
183 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
187 // ====================
188 // Drop/Pass/Throw Code
189 // ====================
191 void ctf_Handle_Drop(entity flag, entity player, float droptype)
194 player = (player ? player : flag.pass_sender);
197 flag.movetype = MOVETYPE_TOSS;
198 flag.takedamage = DAMAGE_YES;
199 flag.health = flag.max_flag_health;
200 flag.ctf_droptime = time;
201 flag.ctf_dropper = player;
202 flag.ctf_status = FLAG_DROPPED;
204 // messages and sounds
205 Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
206 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
207 ctf_EventLog("dropped", player.team, player);
210 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
211 PlayerScore_Add(player, SP_CTF_DROPS, 1);
214 if(autocvar_g_ctf_flag_dropped_waypoint)
215 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));
217 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
219 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
220 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
223 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
225 if(droptype == DROP_PASS)
227 flag.pass_sender = world;
228 flag.pass_target = world;
232 void ctf_Handle_Retrieve(entity flag, entity player)
234 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
235 entity sender = flag.pass_sender;
237 // transfer flag to player
239 flag.owner.flagcarried = flag;
242 setattachment(flag, player, "");
243 setorigin(flag, FLAG_CARRY_OFFSET);
244 flag.movetype = MOVETYPE_NONE;
245 flag.takedamage = DAMAGE_NO;
246 flag.solid = SOLID_NOT;
247 flag.ctf_status = FLAG_CARRY;
249 // messages and sounds
250 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
251 ctf_EventLog("receive", flag.team, player);
253 FOR_EACH_REALPLAYER(tmp_player)
255 if(tmp_player == sender)
256 centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
257 else if(tmp_player == player)
258 centerprint(tmp_player, strcat("You received the ", flag.netname, " from ", sender.netname));
259 else if(!IsDifferentTeam(tmp_player, sender))
260 centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
263 // create new waypoint
264 ctf_FlagcarrierWaypoints(player);
266 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
267 player.throw_antispam = sender.throw_antispam;
269 flag.pass_sender = world;
270 flag.pass_target = world;
273 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
275 entity flag = player.flagcarried;
276 vector targ_origin, flag_velocity;
278 if(!flag) { return; }
279 if((droptype == DROP_PASS) && !receiver) { return; }
281 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
284 setattachment(flag, world, "");
285 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
286 flag.owner.flagcarried = world;
288 flag.solid = SOLID_TRIGGER;
289 flag.ctf_dropper = player;
290 flag.ctf_droptime = time;
292 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
299 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
300 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
301 WarpZone_RefSys_Copy(flag, receiver);
302 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
303 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
304 flag.velocity = (normalize(targ_origin - player.origin) * autocvar_g_ctf_pass_velocity);
307 flag.movetype = MOVETYPE_FLY;
308 flag.takedamage = DAMAGE_NO;
309 flag.pass_sender = player;
310 flag.pass_target = receiver;
311 flag.ctf_status = FLAG_PASSING;
314 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
315 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
316 ctf_EventLog("pass", flag.team, player);
322 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'));
324 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)));
325 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
326 ctf_Handle_Drop(flag, player, droptype);
332 flag.velocity = '0 0 0'; // do nothing
339 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);
340 ctf_Handle_Drop(flag, player, droptype);
345 // kill old waypointsprite
346 WaypointSprite_Ping(player.wps_flagcarrier);
347 WaypointSprite_Kill(player.wps_flagcarrier);
349 if(player.wps_enemyflagcarrier)
350 WaypointSprite_Kill(player.wps_enemyflagcarrier);
353 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
361 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
363 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
364 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
365 float old_time, new_time;
367 if not(player) { return; } // without someone to give the reward to, we can't possibly cap
369 // messages and sounds
370 Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
371 sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
375 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
376 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
381 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
382 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
384 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
385 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
386 if(!old_time || new_time < old_time)
387 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
390 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
391 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
394 if(capturetype == CAPTURE_NORMAL)
396 WaypointSprite_Kill(player.wps_flagcarrier);
397 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
399 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
400 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
404 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
405 ctf_RespawnFlag(enemy_flag);
408 void ctf_Handle_Return(entity flag, entity player)
410 // messages and sounds
411 //centerprint(player, strcat("You returned the ", flag.netname));
412 Send_KillNotification(player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
413 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
414 ctf_EventLog("return", flag.team, player);
417 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
418 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
420 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
424 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
425 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
426 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
430 ctf_RespawnFlag(flag);
433 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
436 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
437 string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
438 float pickup_dropped_score; // used to calculate dropped pickup score
440 // attach the flag to the player
442 player.flagcarried = flag;
443 setattachment(flag, player, "");
444 setorigin(flag, FLAG_CARRY_OFFSET);
447 flag.movetype = MOVETYPE_NONE;
448 flag.takedamage = DAMAGE_NO;
449 flag.solid = SOLID_NOT;
450 flag.angles = '0 0 0';
451 flag.ctf_status = FLAG_CARRY;
455 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
456 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
460 // messages and sounds
461 Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
462 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
463 verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
465 FOR_EACH_REALPLAYER(tmp_player)
467 if(tmp_player == player)
468 centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
469 else if(!IsDifferentTeam(tmp_player, player))
470 centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
471 else if(!IsDifferentTeam(tmp_player, flag))
472 centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
476 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
481 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
482 ctf_EventLog("steal", flag.team, player);
488 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);
489 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);
490 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
491 PlayerTeamScore_AddScore(player, pickup_dropped_score);
492 ctf_EventLog("pickup", flag.team, player);
500 if(pickuptype == PICKUP_BASE)
502 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
503 if((player.speedrunning) && (ctf_captimerecord))
504 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
508 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
511 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
512 ctf_FlagcarrierWaypoints(player);
513 WaypointSprite_Ping(player.wps_flagcarrier);
517 // ===================
518 // Main Flag Functions
519 // ===================
521 void ctf_CheckFlagReturn(entity flag, float returntype)
523 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
525 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
527 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
531 case RETURN_DROPPED: bprint("The ", flag.netname, " was dropped in the base and returned itself\n"); break;
532 case RETURN_DAMAGE: bprint("The ", flag.netname, " was destroyed and returned to base\n"); break;
533 case RETURN_SPEEDRUN: bprint("The ", flag.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); break;
534 case RETURN_NEEDKILL: bprint("The ", flag.netname, " fell somewhere it couldn't be reached and returned to base\n"); break;
538 { bprint("The ", flag.netname, " has returned to base\n"); break; }
540 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
541 ctf_EventLog("returned", flag.team, world);
542 ctf_RespawnFlag(flag);
547 void ctf_CheckStalemate(void)
550 float stale_red_flags, stale_blue_flags;
553 entity ctf_staleflaglist; // reset the list, we need to build the list each time this function runs
555 // build list of stale flags
556 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
558 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
559 if(tmp_entity.ctf_status != FLAG_BASE)
560 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
562 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
563 ctf_staleflaglist = tmp_entity;
565 switch(tmp_entity.team)
567 case COLOR_TEAM1: ++stale_red_flags; break;
568 case COLOR_TEAM2: ++stale_blue_flags; break;
573 if(stale_red_flags && stale_blue_flags)
574 ctf_stalemate = TRUE;
575 else if(!stale_red_flags && !stale_blue_flags)
576 ctf_stalemate = FALSE;
578 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
581 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
583 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
584 WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
587 if not(wpforenemy_announced)
589 FOR_EACH_REALPLAYER(tmp_entity)
590 if(tmp_entity.flagcarried)
591 centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
593 centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
595 wpforenemy_announced = TRUE;
600 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
602 if(ITEM_DAMAGE_NEEDKILL(deathtype))
604 // automatically kill the flag and return it
606 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
609 if(autocvar_g_ctf_flag_return_damage)
611 // reduce health and check if it should be returned
612 self.health = self.health - damage;
613 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
623 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
626 if(self == ctf_worldflaglist) // only for the first flag
627 FOR_EACH_CLIENT(tmp_entity)
628 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
631 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
632 dprint("wtf the flag got squashed?\n");
633 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
634 if(!trace_startsolid) // can we resize it without getting stuck?
635 setsize(self, FLAG_MIN, FLAG_MAX); }
637 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
642 self.angles = '0 0 0';
650 switch(self.ctf_status)
654 if(autocvar_g_ctf_dropped_capture_radius)
656 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
657 if(tmp_entity.ctf_status == FLAG_DROPPED)
658 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
659 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
666 if(autocvar_g_ctf_flag_dropped_floatinwater)
668 vector midpoint = ((self.absmin + self.absmax) * 0.5);
669 if(pointcontents(midpoint) == CONTENT_WATER)
671 self.velocity = self.velocity * 0.5;
673 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
674 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
676 { self.movetype = MOVETYPE_FLY; }
678 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
680 if(autocvar_g_ctf_flag_return_dropped)
682 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
685 ctf_CheckFlagReturn(self, RETURN_DROPPED);
689 if(autocvar_g_ctf_flag_return_time)
691 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
692 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
700 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
703 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
707 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
711 if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
713 if(time >= wpforenemy_nextthink)
715 ctf_CheckStalemate();
716 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
724 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
725 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
726 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
728 if((self.pass_target == world)
729 || (self.pass_target.deadflag != DEAD_NO)
730 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
731 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
732 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
734 ctf_Handle_Drop(self, world, DROP_PASS);
736 else // still a viable target, go for it
738 vector desired_direction = normalize(targ_origin - self.origin);
739 vector current_direction = normalize(self.velocity);
740 self.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity);
745 default: // this should never happen
747 dprint("ctf_FlagThink(): Flag exists with no status?\n");
755 if(gameover) { return; }
757 entity toucher = other;
759 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
760 if(ITEM_TOUCH_NEEDKILL())
763 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
767 // special touch behaviors
768 if(toucher.vehicle_flags & VHF_ISVEHICLE)
770 if(autocvar_g_ctf_allow_vehicle_touch)
771 toucher = toucher.owner; // the player is actually the vehicle owner, not other
773 return; // do nothing
775 else if(toucher.classname != "player") // The flag just touched an object, most likely the world
777 if(time > self.wait) // if we haven't in a while, play a sound/effect
779 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
780 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
781 self.wait = time + FLAG_TOUCHRATE;
785 else if(toucher.deadflag != DEAD_NO) { return; }
787 switch(self.ctf_status)
791 if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
792 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
793 else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
794 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
800 if(!IsDifferentTeam(toucher, self))
801 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
802 else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
803 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
809 dprint("Someone touched a flag even though it was being carried?\n");
815 if((toucher.classname == "player") && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
817 if(IsDifferentTeam(toucher, self.pass_sender))
818 ctf_Handle_Return(self, toucher);
820 ctf_Handle_Retrieve(self, toucher);
828 void ctf_RespawnFlag(entity flag)
830 // check for flag respawn being called twice in a row
831 if(flag.last_respawn > time - 0.5)
832 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
834 flag.last_respawn = time;
836 // reset the player (if there is one)
837 if((flag.owner) && (flag.owner.flagcarried == flag))
839 if(flag.owner.wps_enemyflagcarrier)
840 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
842 WaypointSprite_Kill(flag.wps_flagcarrier);
844 flag.owner.flagcarried = world;
846 if(flag.speedrunning)
847 ctf_FakeTimeLimit(flag.owner, -1);
850 if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
851 { WaypointSprite_Kill(flag.wps_flagdropped); }
854 setattachment(flag, world, "");
855 setorigin(flag, flag.ctf_spawnorigin);
857 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
858 flag.takedamage = DAMAGE_NO;
859 flag.health = flag.max_flag_health;
860 flag.solid = SOLID_TRIGGER;
861 flag.velocity = '0 0 0';
862 flag.angles = flag.mangle;
863 flag.flags = FL_ITEM | FL_NOTARGET;
865 flag.ctf_status = FLAG_BASE;
867 flag.pass_sender = world;
868 flag.pass_target = world;
869 flag.ctf_dropper = world;
870 flag.ctf_pickuptime = 0;
871 flag.ctf_droptime = 0;
873 wpforenemy_announced = FALSE;
879 if(self.owner.classname == "player")
880 ctf_Handle_Throw(self.owner, world, DROP_RESET);
882 ctf_RespawnFlag(self);
885 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
888 waypoint_spawnforitem_force(self, self.origin);
889 self.nearestwaypointtimeout = 0; // activate waypointing again
890 self.bot_basewaypoint = self.nearestwaypoint;
893 WaypointSprite_SpawnFixed(((self.team == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
894 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
896 // captureshield setup
897 ctf_CaptureShield_Spawn(self);
900 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
903 teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1.
904 self = flag; // for later usage with droptofloor()
907 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
908 ctf_worldflaglist = flag;
910 setattachment(flag, world, "");
912 flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
913 flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: color 13 team (blue)
914 flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
915 flag.classname = "item_flag_team";
916 flag.target = "###item###"; // wut?
917 flag.flags = FL_ITEM | FL_NOTARGET;
918 flag.solid = SOLID_TRIGGER;
919 flag.takedamage = DAMAGE_NO;
920 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
921 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
922 flag.health = flag.max_flag_health;
923 flag.event_damage = ctf_FlagDamage;
924 flag.pushable = TRUE;
925 flag.teleportable = TELEPORT_NORMAL;
926 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
927 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
928 flag.velocity = '0 0 0';
929 flag.mangle = flag.angles;
930 flag.reset = ctf_Reset;
931 flag.touch = ctf_FlagTouch;
932 flag.think = ctf_FlagThink;
933 flag.nextthink = time + FLAG_THINKRATE;
934 flag.ctf_status = FLAG_BASE;
936 if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
937 if(!flag.scale) { flag.scale = FLAG_SCALE; }
938 if(!flag.skin) { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
939 if(!flag.toucheffect) { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
940 if(!flag.passeffect) { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
941 if(!flag.capeffect) { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
944 if(!flag.snd_flag_taken) { flag.snd_flag_taken = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
945 if(!flag.snd_flag_returned) { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
946 if(!flag.snd_flag_capture) { flag.snd_flag_capture = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
947 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.
948 if(!flag.snd_flag_dropped) { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
949 if(!flag.snd_flag_touch) { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
950 if(!flag.snd_flag_pass) { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
953 precache_sound(flag.snd_flag_taken);
954 precache_sound(flag.snd_flag_returned);
955 precache_sound(flag.snd_flag_capture);
956 precache_sound(flag.snd_flag_respawn);
957 precache_sound(flag.snd_flag_dropped);
958 precache_sound(flag.snd_flag_touch);
959 precache_sound(flag.snd_flag_pass);
960 precache_model(flag.model);
961 precache_model("models/ctf/shield.md3");
962 precache_model("models/ctf/shockwavetransring.md3");
965 setmodel(flag, flag.model); // precision set below
966 setsize(flag, FLAG_MIN, FLAG_MAX);
967 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
969 if(autocvar_g_ctf_flag_glowtrails)
971 flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
976 flag.effects |= EF_LOWPRECISION;
977 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
978 if(autocvar_g_ctf_dynamiclights) { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
981 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
983 flag.dropped_origin = flag.origin;
985 flag.movetype = MOVETYPE_NONE;
987 else // drop to floor, automatically find a platform and set that as spawn origin
989 flag.noalign = FALSE;
992 flag.movetype = MOVETYPE_TOSS;
995 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1003 // NOTE: LEGACY CODE, needs to be re-written!
1005 void havocbot_calculate_middlepoint()
1009 vector fo = '0 0 0';
1012 f = ctf_worldflaglist;
1017 f = f.ctf_worldflagnext;
1021 havocbot_ctf_middlepoint = s * (1.0 / n);
1022 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1026 entity havocbot_ctf_find_flag(entity bot)
1029 f = ctf_worldflaglist;
1032 if (bot.team == f.team)
1034 f = f.ctf_worldflagnext;
1039 entity havocbot_ctf_find_enemy_flag(entity bot)
1042 f = ctf_worldflaglist;
1045 if (bot.team != f.team)
1047 f = f.ctf_worldflagnext;
1052 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1060 FOR_EACH_PLAYER(head)
1062 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
1065 if(vlen(head.origin - org) < tc_radius)
1072 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1075 head = ctf_worldflaglist;
1078 if (self.team == head.team)
1080 head = head.ctf_worldflagnext;
1083 navigation_routerating(head, ratingscale, 10000);
1086 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1089 head = ctf_worldflaglist;
1092 if (self.team == head.team)
1094 head = head.ctf_worldflagnext;
1099 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1102 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1105 head = ctf_worldflaglist;
1108 if (self.team != head.team)
1110 head = head.ctf_worldflagnext;
1113 navigation_routerating(head, ratingscale, 10000);
1116 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1118 if not(bot_waypoints_for_items)
1120 havocbot_goalrating_ctf_enemyflag(ratingscale);
1126 head = havocbot_ctf_find_enemy_flag(self);
1131 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1134 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1138 mf = havocbot_ctf_find_flag(self);
1140 if(mf.ctf_status == FLAG_BASE)
1144 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1147 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1150 head = ctf_worldflaglist;
1153 // flag is out in the field
1154 if(head.ctf_status != FLAG_BASE)
1155 if(head.tag_entity==world) // dropped
1159 if(vlen(org-head.origin)<df_radius)
1160 navigation_routerating(head, ratingscale, 10000);
1163 navigation_routerating(head, ratingscale, 10000);
1166 head = head.ctf_worldflagnext;
1170 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1174 head = findchainfloat(bot_pickup, TRUE);
1177 // gather health and armor only
1179 if (head.health || head.armorvalue)
1180 if (vlen(head.origin - org) < sradius)
1182 // get the value of the item
1183 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1185 navigation_routerating(head, t * ratingscale, 500);
1191 void havocbot_ctf_reset_role(entity bot)
1193 float cdefense, cmiddle, coffense;
1194 entity mf, ef, head;
1197 if(bot.deadflag != DEAD_NO)
1200 if(vlen(havocbot_ctf_middlepoint)==0)
1201 havocbot_calculate_middlepoint();
1204 if (bot.flagcarried)
1206 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1210 mf = havocbot_ctf_find_flag(bot);
1211 ef = havocbot_ctf_find_enemy_flag(bot);
1213 // Retrieve stolen flag
1214 if(mf.ctf_status!=FLAG_BASE)
1216 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1220 // If enemy flag is taken go to the middle to intercept pursuers
1221 if(ef.ctf_status!=FLAG_BASE)
1223 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1227 // if there is only me on the team switch to offense
1229 FOR_EACH_PLAYER(head)
1230 if(head.team==bot.team)
1235 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1239 // Evaluate best position to take
1240 // Count mates on middle position
1241 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1243 // Count mates on defense position
1244 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1246 // Count mates on offense position
1247 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1249 if(cdefense<=coffense)
1250 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1251 else if(coffense<=cmiddle)
1252 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1254 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1257 void havocbot_role_ctf_carrier()
1259 if(self.deadflag != DEAD_NO)
1261 havocbot_ctf_reset_role(self);
1265 if (self.flagcarried == world)
1267 havocbot_ctf_reset_role(self);
1271 if (self.bot_strategytime < time)
1273 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1275 navigation_goalrating_start();
1276 havocbot_goalrating_ctf_ourbase(50000);
1279 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1281 navigation_goalrating_end();
1283 if (self.navigation_hasgoals)
1284 self.havocbot_cantfindflag = time + 10;
1285 else if (time > self.havocbot_cantfindflag)
1287 // Can't navigate to my own base, suicide!
1288 // TODO: drop it and wander around
1289 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1295 void havocbot_role_ctf_escort()
1299 if(self.deadflag != DEAD_NO)
1301 havocbot_ctf_reset_role(self);
1305 if (self.flagcarried)
1307 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1311 // If enemy flag is back on the base switch to previous role
1312 ef = havocbot_ctf_find_enemy_flag(self);
1313 if(ef.ctf_status==FLAG_BASE)
1315 self.havocbot_role = self.havocbot_previous_role;
1316 self.havocbot_role_timeout = 0;
1320 // If the flag carrier reached the base switch to defense
1321 mf = havocbot_ctf_find_flag(self);
1322 if(mf.ctf_status!=FLAG_BASE)
1323 if(vlen(ef.origin - mf.dropped_origin) < 300)
1325 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1329 // Set the role timeout if necessary
1330 if (!self.havocbot_role_timeout)
1332 self.havocbot_role_timeout = time + random() * 30 + 60;
1335 // If nothing happened just switch to previous role
1336 if (time > self.havocbot_role_timeout)
1338 self.havocbot_role = self.havocbot_previous_role;
1339 self.havocbot_role_timeout = 0;
1343 // Chase the flag carrier
1344 if (self.bot_strategytime < time)
1346 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1347 navigation_goalrating_start();
1348 havocbot_goalrating_ctf_enemyflag(30000);
1349 havocbot_goalrating_ctf_ourstolenflag(40000);
1350 havocbot_goalrating_items(10000, self.origin, 10000);
1351 navigation_goalrating_end();
1355 void havocbot_role_ctf_offense()
1360 if(self.deadflag != DEAD_NO)
1362 havocbot_ctf_reset_role(self);
1366 if (self.flagcarried)
1368 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1373 mf = havocbot_ctf_find_flag(self);
1374 ef = havocbot_ctf_find_enemy_flag(self);
1377 if(mf.ctf_status!=FLAG_BASE)
1380 pos = mf.tag_entity.origin;
1384 // Try to get it if closer than the enemy base
1385 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1387 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1392 // Escort flag carrier
1393 if(ef.ctf_status!=FLAG_BASE)
1396 pos = ef.tag_entity.origin;
1400 if(vlen(pos-mf.dropped_origin)>700)
1402 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1407 // About to fail, switch to middlefield
1410 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1414 // Set the role timeout if necessary
1415 if (!self.havocbot_role_timeout)
1416 self.havocbot_role_timeout = time + 120;
1418 if (time > self.havocbot_role_timeout)
1420 havocbot_ctf_reset_role(self);
1424 if (self.bot_strategytime < time)
1426 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1427 navigation_goalrating_start();
1428 havocbot_goalrating_ctf_ourstolenflag(50000);
1429 havocbot_goalrating_ctf_enemybase(20000);
1430 havocbot_goalrating_items(5000, self.origin, 1000);
1431 havocbot_goalrating_items(1000, self.origin, 10000);
1432 navigation_goalrating_end();
1436 // Retriever (temporary role):
1437 void havocbot_role_ctf_retriever()
1441 if(self.deadflag != DEAD_NO)
1443 havocbot_ctf_reset_role(self);
1447 if (self.flagcarried)
1449 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1453 // If flag is back on the base switch to previous role
1454 mf = havocbot_ctf_find_flag(self);
1455 if(mf.ctf_status==FLAG_BASE)
1457 havocbot_ctf_reset_role(self);
1461 if (!self.havocbot_role_timeout)
1462 self.havocbot_role_timeout = time + 20;
1464 if (time > self.havocbot_role_timeout)
1466 havocbot_ctf_reset_role(self);
1470 if (self.bot_strategytime < time)
1475 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1476 navigation_goalrating_start();
1477 havocbot_goalrating_ctf_ourstolenflag(50000);
1478 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1479 havocbot_goalrating_ctf_enemybase(30000);
1480 havocbot_goalrating_items(500, self.origin, rt_radius);
1481 navigation_goalrating_end();
1485 void havocbot_role_ctf_middle()
1489 if(self.deadflag != DEAD_NO)
1491 havocbot_ctf_reset_role(self);
1495 if (self.flagcarried)
1497 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1501 mf = havocbot_ctf_find_flag(self);
1502 if(mf.ctf_status!=FLAG_BASE)
1504 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1508 if (!self.havocbot_role_timeout)
1509 self.havocbot_role_timeout = time + 10;
1511 if (time > self.havocbot_role_timeout)
1513 havocbot_ctf_reset_role(self);
1517 if (self.bot_strategytime < time)
1521 org = havocbot_ctf_middlepoint;
1522 org_z = self.origin_z;
1524 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1525 navigation_goalrating_start();
1526 havocbot_goalrating_ctf_ourstolenflag(50000);
1527 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1528 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1529 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1530 havocbot_goalrating_items(2500, self.origin, 10000);
1531 havocbot_goalrating_ctf_enemybase(2500);
1532 navigation_goalrating_end();
1536 void havocbot_role_ctf_defense()
1540 if(self.deadflag != DEAD_NO)
1542 havocbot_ctf_reset_role(self);
1546 if (self.flagcarried)
1548 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1552 // If own flag was captured
1553 mf = havocbot_ctf_find_flag(self);
1554 if(mf.ctf_status!=FLAG_BASE)
1556 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1560 if (!self.havocbot_role_timeout)
1561 self.havocbot_role_timeout = time + 30;
1563 if (time > self.havocbot_role_timeout)
1565 havocbot_ctf_reset_role(self);
1568 if (self.bot_strategytime < time)
1573 org = mf.dropped_origin;
1574 mp_radius = havocbot_ctf_middlepoint_radius;
1576 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1577 navigation_goalrating_start();
1579 // if enemies are closer to our base, go there
1580 entity head, closestplayer = world;
1581 float distance, bestdistance = 10000;
1582 FOR_EACH_PLAYER(head)
1584 if(head.deadflag!=DEAD_NO)
1587 distance = vlen(org - head.origin);
1588 if(distance<bestdistance)
1590 closestplayer = head;
1591 bestdistance = distance;
1596 if(closestplayer.team!=self.team)
1597 if(vlen(org - self.origin)>1000)
1598 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1599 havocbot_goalrating_ctf_ourbase(30000);
1601 havocbot_goalrating_ctf_ourstolenflag(20000);
1602 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1603 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1604 havocbot_goalrating_items(10000, org, mp_radius);
1605 havocbot_goalrating_items(5000, self.origin, 10000);
1606 navigation_goalrating_end();
1610 void havocbot_role_ctf_setrole(entity bot, float role)
1612 dprint(strcat(bot.netname," switched to "));
1615 case HAVOCBOT_CTF_ROLE_CARRIER:
1617 bot.havocbot_role = havocbot_role_ctf_carrier;
1618 bot.havocbot_role_timeout = 0;
1619 bot.havocbot_cantfindflag = time + 10;
1620 bot.bot_strategytime = 0;
1622 case HAVOCBOT_CTF_ROLE_DEFENSE:
1624 bot.havocbot_role = havocbot_role_ctf_defense;
1625 bot.havocbot_role_timeout = 0;
1627 case HAVOCBOT_CTF_ROLE_MIDDLE:
1629 bot.havocbot_role = havocbot_role_ctf_middle;
1630 bot.havocbot_role_timeout = 0;
1632 case HAVOCBOT_CTF_ROLE_OFFENSE:
1634 bot.havocbot_role = havocbot_role_ctf_offense;
1635 bot.havocbot_role_timeout = 0;
1637 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1638 dprint("retriever");
1639 bot.havocbot_previous_role = bot.havocbot_role;
1640 bot.havocbot_role = havocbot_role_ctf_retriever;
1641 bot.havocbot_role_timeout = time + 10;
1642 bot.bot_strategytime = 0;
1644 case HAVOCBOT_CTF_ROLE_ESCORT:
1646 bot.havocbot_previous_role = bot.havocbot_role;
1647 bot.havocbot_role = havocbot_role_ctf_escort;
1648 bot.havocbot_role_timeout = time + 30;
1649 bot.bot_strategytime = 0;
1660 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1664 // initially clear items so they can be set as necessary later.
1665 self.items &~= (IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
1666 | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
1668 // scan through all the flags and notify the client about them
1669 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1671 switch(flag.ctf_status)
1676 if((flag.owner == self) || (flag.pass_sender == self))
1677 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
1679 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
1684 self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
1690 // item for stopping players from capturing the flag too often
1691 if(self.ctf_captureshielded)
1692 self.items |= IT_CTF_SHIELDED;
1694 // update the health of the flag carrier waypointsprite
1695 if(self.wps_flagcarrier)
1696 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent));
1701 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1703 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1705 if(frag_target == frag_attacker) // damage done to yourself
1707 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1708 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1710 else // damage done to everyone else
1712 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1713 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1716 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
1718 if(autocvar_g_ctf_flagcarrier_auto_helpme_when_damaged > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
1719 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1724 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1726 if((frag_attacker != frag_target) && (frag_attacker.classname == "player") && (frag_target.flagcarried))
1728 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1729 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
1732 if(frag_target.flagcarried)
1733 { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
1738 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
1741 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
1744 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
1746 entity flag; // temporary entity for the search method
1748 if(self.flagcarried)
1749 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1751 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1753 if(flag.pass_sender == self) { flag.pass_sender = world; }
1754 if(flag.pass_target == self) { flag.pass_target = world; }
1755 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
1761 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
1763 if(self.flagcarried)
1764 if(!autocvar_g_ctf_portalteleport)
1765 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
1770 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
1772 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
1774 entity player = self;
1776 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
1778 // pass the flag to a team mate
1779 if(autocvar_g_ctf_pass)
1781 entity head, closest_target;
1782 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
1784 while(head) // find the closest acceptable target to pass to
1786 if(head.classname == "player" && head.deadflag == DEAD_NO)
1787 if(head != player && !IsDifferentTeam(head, player))
1788 if(!head.speedrunning && !head.vehicle)
1790 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
1791 vector head_center = WarpZone_UnTransformOrigin(head, PLAYER_CENTER(head));
1792 vector passer_center = PLAYER_CENTER(player);
1794 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
1796 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
1798 if(clienttype(head) == CLIENTTYPE_BOT)
1800 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1801 ctf_Handle_Throw(head, player, DROP_PASS);
1805 centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname));
1806 centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname));
1808 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
1811 else if(player.flagcarried)
1815 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, PLAYER_CENTER(closest_target));
1816 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
1817 { closest_target = head; }
1819 else { closest_target = head; }
1826 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
1829 // throw the flag in front of you
1830 if(autocvar_g_ctf_throw && player.flagcarried)
1831 { ctf_Handle_Throw(player, world, DROP_THROW); return TRUE; }
1837 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
1839 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
1841 WaypointSprite_HelpMePing(self.wps_flagcarrier);
1843 else // create a normal help me waypointsprite
1845 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');
1846 WaypointSprite_Ping(self.wps_helpme);
1852 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
1854 if(vh_player.flagcarried)
1856 if(!autocvar_g_ctf_allow_vehicle_carry)
1858 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
1862 setattachment(vh_player.flagcarried, vh_vehicle, "");
1863 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
1864 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
1865 //vh_player.flagcarried.angles = '0 0 0';
1873 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
1875 if(vh_player.flagcarried)
1877 setattachment(vh_player.flagcarried, vh_player, "");
1878 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
1879 vh_player.flagcarried.scale = FLAG_SCALE;
1880 vh_player.flagcarried.angles = '0 0 0';
1887 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
1889 if(self.flagcarried)
1891 bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
1892 ctf_RespawnFlag(self);
1899 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
1901 entity flag; // temporary entity for the search method
1903 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1905 switch(flag.ctf_status)
1910 // lock the flag, game is over
1911 flag.movetype = MOVETYPE_NONE;
1912 flag.takedamage = DAMAGE_NO;
1913 flag.solid = SOLID_NOT;
1914 flag.nextthink = FALSE; // stop thinking
1916 print("stopping the ", flag.netname, " from moving.\n");
1924 // do nothing for these flags
1933 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
1935 havocbot_ctf_reset_role(self);
1944 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
1945 CTF Starting point for a player in team one (Red).
1946 Keys: "angle" viewing angle when spawning. */
1947 void spawnfunc_info_player_team1()
1949 if(g_assault) { remove(self); return; }
1951 self.team = COLOR_TEAM1; // red
1952 spawnfunc_info_player_deathmatch();
1956 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
1957 CTF Starting point for a player in team two (Blue).
1958 Keys: "angle" viewing angle when spawning. */
1959 void spawnfunc_info_player_team2()
1961 if(g_assault) { remove(self); return; }
1963 self.team = COLOR_TEAM2; // blue
1964 spawnfunc_info_player_deathmatch();
1967 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
1968 CTF Starting point for a player in team three (Yellow).
1969 Keys: "angle" viewing angle when spawning. */
1970 void spawnfunc_info_player_team3()
1972 if(g_assault) { remove(self); return; }
1974 self.team = COLOR_TEAM3; // yellow
1975 spawnfunc_info_player_deathmatch();
1979 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
1980 CTF Starting point for a player in team four (Purple).
1981 Keys: "angle" viewing angle when spawning. */
1982 void spawnfunc_info_player_team4()
1984 if(g_assault) { remove(self); return; }
1986 self.team = COLOR_TEAM4; // purple
1987 spawnfunc_info_player_deathmatch();
1990 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
1991 CTF flag for team one (Red).
1993 "angle" Angle the flag will point (minus 90 degrees)...
1994 "model" model to use, note this needs red and blue as skins 0 and 1...
1995 "noise" sound played when flag is picked up...
1996 "noise1" sound played when flag is returned by a teammate...
1997 "noise2" sound played when flag is captured...
1998 "noise3" sound played when flag is lost in the field and respawns itself...
1999 "noise4" sound played when flag is dropped by a player...
2000 "noise5" sound played when flag touches the ground... */
2001 void spawnfunc_item_flag_team1()
2003 if(!g_ctf) { remove(self); return; }
2005 ctf_FlagSetup(1, self); // 1 = red
2008 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2009 CTF flag for team two (Blue).
2011 "angle" Angle the flag will point (minus 90 degrees)...
2012 "model" model to use, note this needs red and blue as skins 0 and 1...
2013 "noise" sound played when flag is picked up...
2014 "noise1" sound played when flag is returned by a teammate...
2015 "noise2" sound played when flag is captured...
2016 "noise3" sound played when flag is lost in the field and respawns itself...
2017 "noise4" sound played when flag is dropped by a player...
2018 "noise5" sound played when flag touches the ground... */
2019 void spawnfunc_item_flag_team2()
2021 if(!g_ctf) { remove(self); return; }
2023 ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
2026 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2027 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2028 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.
2030 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2031 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2032 void spawnfunc_ctf_team()
2034 if(!g_ctf) { remove(self); return; }
2036 self.classname = "ctf_team";
2037 self.team = self.cnt + 1;
2040 // compatibility for quake maps
2041 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2042 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2043 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2044 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2045 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2046 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2054 void ctf_ScoreRules()
2056 ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2057 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2058 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2059 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2060 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2061 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2062 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2063 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2064 ScoreRules_basics_end();
2067 // code from here on is just to support maps that don't have flag and team entities
2068 void ctf_SpawnTeam (string teamname, float teamcolor)
2073 self.classname = "ctf_team";
2074 self.netname = teamname;
2075 self.cnt = teamcolor;
2077 spawnfunc_ctf_team();
2082 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2084 // if no teams are found, spawn defaults
2085 if(find(world, classname, "ctf_team") == world)
2087 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2088 ctf_SpawnTeam("Red", COLOR_TEAM1 - 1);
2089 ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
2095 void ctf_Initialize()
2097 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2099 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2100 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2101 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2103 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2107 MUTATOR_DEFINITION(gamemode_ctf)
2109 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2110 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2111 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2112 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2113 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2114 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2115 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2116 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2117 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2118 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2119 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2120 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2121 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2122 MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
2126 if(time > 1) // game loads at time 1
2127 error("This is a game type and it cannot be added at runtime.");
2135 error("This is a game type and it cannot be removed at runtime.");