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(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
21 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
24 void ctf_CaptureRecord(entity flag, entity player)
26 float cap_record = ctf_captimerecord;
27 float cap_time = (time - flag.ctf_pickuptime);
28 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
31 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
32 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)); }
33 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)); }
34 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)); }
36 // write that shit in the database
37 if(!ctf_oneflag) // but not in 1-flag mode
38 if((!ctf_captimerecord) || (cap_time < cap_record))
40 ctf_captimerecord = cap_time;
41 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
42 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
43 write_recordmarker(player, (time - cap_time), cap_time);
47 void ctf_FlagcarrierWaypoints(entity player)
49 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
50 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
51 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
52 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
55 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
57 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
58 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
59 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
60 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
63 if(current_height) // make sure we can actually do this arcing path
65 targpos = (to + ('0 0 1' * current_height));
66 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
67 if(trace_fraction < 1)
69 //print("normal arc line failed, trying to find new pos...");
70 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
71 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
72 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
73 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
74 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
77 else { targpos = to; }
79 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
81 vector desired_direction = normalize(targpos - from);
82 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
83 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
86 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
88 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
90 // directional tracing only
92 makevectors(passer_angle);
94 // find the closest point on the enemy to the center of the attack
95 float ang; // angle between shotdir and h
96 float h; // hypotenuse, which is the distance between attacker to head
97 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
99 h = vlen(head_center - passer_center);
100 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
103 vector nearest_on_line = (passer_center + a * v_forward);
104 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
106 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
107 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
109 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
114 else { return TRUE; }
118 // =======================
119 // CaptureShield Functions
120 // =======================
122 float ctf_CaptureShield_CheckStatus(entity p)
124 float s, s2, s3, s4, se, se2, se3, se4, sr, ser;
126 float players_worseeq, players_total;
128 if(ctf_captureshield_max_ratio <= 0)
131 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
132 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
133 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
134 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
136 sr = ((s - s2) + (s3 + s4));
138 if(sr >= -ctf_captureshield_min_negscore)
141 players_total = players_worseeq = 0;
146 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
147 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
148 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
149 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
151 ser = ((se - se2) + (se3 + se4));
158 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
159 // use this rule here
161 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
167 void ctf_CaptureShield_Update(entity player, float wanted_status)
169 float updated_status = ctf_CaptureShield_CheckStatus(player);
170 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
172 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
173 player.ctf_captureshielded = updated_status;
177 float ctf_CaptureShield_Customize()
179 if(self.enemy.active != ACTIVE_ACTIVE) { return TRUE; }
180 if(!other.ctf_captureshielded) { return FALSE; }
181 if(CTF_SAMETEAM(self, other)) { return FALSE; }
186 void ctf_CaptureShield_Touch()
188 if(self.enemy.active != ACTIVE_ACTIVE)
190 vector mymid = (self.absmin + self.absmax) * 0.5;
191 vector othermid = (other.absmin + other.absmax) * 0.5;
193 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
194 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_INACTIVE); }
199 if(!other.ctf_captureshielded) { return; }
200 if(CTF_SAMETEAM(self, other)) { return; }
202 vector mymid = (self.absmin + self.absmax) * 0.5;
203 vector othermid = (other.absmin + other.absmax) * 0.5;
205 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
206 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
209 void ctf_CaptureShield_Spawn(entity flag)
211 entity shield = spawn();
214 shield.team = self.team;
215 shield.touch = ctf_CaptureShield_Touch;
216 shield.customizeentityforclient = ctf_CaptureShield_Customize;
217 shield.classname = "ctf_captureshield";
218 shield.effects = EF_ADDITIVE;
219 shield.movetype = MOVETYPE_NOCLIP;
220 shield.solid = SOLID_TRIGGER;
221 shield.avelocity = '7 0 11';
224 setorigin(shield, self.origin);
225 setmodel(shield, "models/ctf/shield.md3");
226 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
230 // ====================
231 // Drop/Pass/Throw Code
232 // ====================
234 void ctf_Handle_Drop(entity flag, entity player, float droptype)
237 player = (player ? player : flag.pass_sender);
240 flag.movetype = MOVETYPE_TOSS;
241 flag.takedamage = DAMAGE_YES;
242 flag.angles = '0 0 0';
243 flag.health = flag.max_flag_health;
244 flag.ctf_droptime = time;
245 flag.ctf_dropper = player;
246 flag.ctf_status = FLAG_DROPPED;
248 // messages and sounds
249 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
250 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
251 ctf_EventLog("dropped", player.team, player);
254 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
255 PlayerScore_Add(player, SP_CTF_DROPS, 1);
258 if(autocvar_g_ctf_flag_dropped_waypoint)
259 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));
261 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
263 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
264 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
267 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
269 if(droptype == DROP_PASS)
271 flag.pass_distance = 0;
272 flag.pass_sender = world;
273 flag.pass_target = world;
277 void ctf_Handle_Retrieve(entity flag, entity player)
279 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
280 entity sender = flag.pass_sender;
282 // transfer flag to player
284 flag.owner.flagcarried = flag;
289 setattachment(flag, player.vehicle, "");
290 setorigin(flag, VEHICLE_FLAG_OFFSET);
291 flag.scale = VEHICLE_FLAG_SCALE;
295 setattachment(flag, player, "");
296 setorigin(flag, FLAG_CARRY_OFFSET);
298 flag.movetype = MOVETYPE_NONE;
299 flag.takedamage = DAMAGE_NO;
300 flag.solid = SOLID_NOT;
301 flag.angles = '0 0 0';
302 flag.ctf_status = FLAG_CARRY;
304 // messages and sounds
305 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
306 ctf_EventLog("receive", flag.team, player);
308 FOR_EACH_REALPLAYER(tmp_player)
310 if(tmp_player == sender)
311 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);
312 else if(tmp_player == player)
313 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);
314 else if(SAME_TEAM(tmp_player, sender))
315 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);
318 // create new waypoint
319 ctf_FlagcarrierWaypoints(player);
321 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
322 player.throw_antispam = sender.throw_antispam;
324 flag.pass_distance = 0;
325 flag.pass_sender = world;
326 flag.pass_target = world;
329 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
331 entity flag = player.flagcarried;
332 vector targ_origin, flag_velocity;
334 if(!flag) { return; }
335 if((droptype == DROP_PASS) && !receiver) { return; }
337 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
340 setattachment(flag, world, "");
341 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
342 flag.owner.flagcarried = world;
344 flag.solid = SOLID_TRIGGER;
345 flag.ctf_dropper = player;
346 flag.ctf_droptime = time;
348 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
355 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
356 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
357 WarpZone_RefSys_Copy(flag, receiver);
358 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
359 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
361 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
362 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, FALSE);
365 flag.movetype = MOVETYPE_FLY;
366 flag.takedamage = DAMAGE_NO;
367 flag.pass_sender = player;
368 flag.pass_target = receiver;
369 flag.ctf_status = FLAG_PASSING;
372 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
373 WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
374 ctf_EventLog("pass", flag.team, player);
380 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'));
382 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)));
383 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
384 ctf_Handle_Drop(flag, player, droptype);
390 flag.velocity = '0 0 0'; // do nothing
397 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);
398 ctf_Handle_Drop(flag, player, droptype);
403 // kill old waypointsprite
404 WaypointSprite_Ping(player.wps_flagcarrier);
405 WaypointSprite_Kill(player.wps_flagcarrier);
407 if(player.wps_enemyflagcarrier)
408 WaypointSprite_Kill(player.wps_enemyflagcarrier);
411 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
419 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
421 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
422 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
423 entity player_team_flag = world, tmp_entity;
424 float old_time, new_time;
426 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
429 if(CTF_SAMETEAM(player, flag)) { return; }
431 else if(CTF_DIFFTEAM(player, flag)) { return; }
434 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
435 if(SAME_TEAM(tmp_entity, player))
437 player_team_flag = tmp_entity;
441 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
443 player.throw_prevtime = time;
444 player.throw_count = 0;
446 // messages and sounds
447 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
448 ctf_CaptureRecord(enemy_flag, player);
449 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);
453 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
454 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
459 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
460 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
462 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
463 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
464 if(!old_time || new_time < old_time)
465 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
468 pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1);
469 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
472 if(capturetype == CAPTURE_NORMAL)
474 WaypointSprite_Kill(player.wps_flagcarrier);
475 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
477 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
478 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
482 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
483 ctf_RespawnFlag(enemy_flag);
486 void ctf_Handle_Return(entity flag, entity player)
488 // messages and sounds
489 if(player.flags & FL_MONSTER)
491 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
495 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
496 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
498 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
499 ctf_EventLog("return", flag.team, player);
502 if(IS_PLAYER(player))
504 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
505 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
507 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
510 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
514 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
515 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
516 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
520 if(player.flagcarried == flag)
521 WaypointSprite_Kill(player.wps_flagcarrier);
524 ctf_RespawnFlag(flag);
527 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
530 float pickup_dropped_score; // used to calculate dropped pickup score
531 entity tmp_entity; // temporary entity
533 // attach the flag to the player
535 player.flagcarried = flag;
538 setattachment(flag, player.vehicle, "");
539 setorigin(flag, VEHICLE_FLAG_OFFSET);
540 flag.scale = VEHICLE_FLAG_SCALE;
544 setattachment(flag, player, "");
545 setorigin(flag, FLAG_CARRY_OFFSET);
549 flag.movetype = MOVETYPE_NONE;
550 flag.takedamage = DAMAGE_NO;
551 flag.solid = SOLID_NOT;
552 flag.angles = '0 0 0';
553 flag.ctf_status = FLAG_CARRY;
557 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
558 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
562 // messages and sounds
563 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
564 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
565 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
566 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
567 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)); }
569 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);
572 FOR_EACH_PLAYER(tmp_entity)
573 if(tmp_entity != player)
574 if(DIFF_TEAM(player, tmp_entity))
575 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
578 FOR_EACH_PLAYER(tmp_entity)
579 if(tmp_entity != player)
580 if(CTF_SAMETEAM(flag, tmp_entity))
581 if(SAME_TEAM(player, tmp_entity))
582 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
584 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);
586 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
589 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
590 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
595 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
596 ctf_EventLog("steal", flag.team, player);
602 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);
603 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);
604 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
605 PlayerTeamScore_AddScore(player, pickup_dropped_score);
606 ctf_EventLog("pickup", flag.team, player);
614 if(pickuptype == PICKUP_BASE)
616 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
617 if((player.speedrunning) && (ctf_captimerecord))
618 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
622 pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
625 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
626 ctf_FlagcarrierWaypoints(player);
627 WaypointSprite_Ping(player.wps_flagcarrier);
631 // ===================
632 // Main Flag Functions
633 // ===================
635 void ctf_CheckFlagReturn(entity flag, float returntype)
637 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
639 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
641 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
645 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;
646 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;
647 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;
648 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;
652 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
654 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
655 ctf_EventLog("returned", flag.team, world);
656 ctf_RespawnFlag(flag);
661 float ctf_Stalemate_Customize()
663 // make spectators see what the player would see
665 e = WaypointSprite_getviewentity(other);
666 wp_owner = self.owner;
669 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return FALSE; }
670 if(SAME_TEAM(wp_owner, e)) { return FALSE; }
671 if(!IS_PLAYER(e)) { return FALSE; }
676 void ctf_CheckStalemate(void)
679 float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
682 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
684 // build list of stale flags
685 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
687 if(autocvar_g_ctf_stalemate)
688 if(tmp_entity.ctf_status != FLAG_BASE)
689 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
691 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
692 ctf_staleflaglist = tmp_entity;
694 switch(tmp_entity.team)
696 case NUM_TEAM_1: ++stale_red_flags; break;
697 case NUM_TEAM_2: ++stale_blue_flags; break;
698 case NUM_TEAM_3: ++stale_yellow_flags; break;
699 case NUM_TEAM_4: ++stale_pink_flags; break;
700 default: ++stale_neutral_flags; break;
706 stale_flags = (stale_neutral_flags >= 1);
708 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
710 if(ctf_oneflag && stale_flags == 1)
711 ctf_stalemate = TRUE;
712 else if(stale_flags == ctf_teams)
713 ctf_stalemate = TRUE;
714 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
715 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
716 else if(stale_flags < ctf_teams && autocvar_g_ctf_stalemate_endcondition == 1)
717 { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
719 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
722 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
724 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
726 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));
727 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
731 if (!wpforenemy_announced)
733 FOR_EACH_REALPLAYER(tmp_entity)
734 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
736 wpforenemy_announced = TRUE;
741 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
743 if(ITEM_DAMAGE_NEEDKILL(deathtype))
745 if(autocvar_g_ctf_flag_return_damage_delay)
747 self.ctf_flagdamaged = TRUE;
752 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
756 if(autocvar_g_ctf_flag_return_damage)
758 // reduce health and check if it should be returned
759 self.health = self.health - damage;
760 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
770 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
773 if(self == ctf_worldflaglist) // only for the first flag
774 FOR_EACH_CLIENT(tmp_entity)
775 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
778 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
779 dprint("wtf the flag got squashed?\n");
780 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
781 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
782 setsize(self, FLAG_MIN, FLAG_MAX); }
784 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
788 self.angles = '0 0 0';
796 switch(self.ctf_status)
800 if(autocvar_g_ctf_dropped_capture_radius)
802 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
803 if(tmp_entity.ctf_status == FLAG_DROPPED)
804 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
805 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
806 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
813 if(autocvar_g_ctf_flag_dropped_floatinwater)
815 vector midpoint = ((self.absmin + self.absmax) * 0.5);
816 if(pointcontents(midpoint) == CONTENT_WATER)
818 self.velocity = self.velocity * 0.5;
820 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
821 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
823 { self.movetype = MOVETYPE_FLY; }
825 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
827 if(autocvar_g_ctf_flag_return_dropped)
829 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
832 ctf_CheckFlagReturn(self, RETURN_DROPPED);
836 if(self.ctf_flagdamaged)
838 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
839 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
842 else if(autocvar_g_ctf_flag_return_time)
844 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
845 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
853 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
856 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
860 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
864 if(autocvar_g_ctf_stalemate)
866 if(time >= wpforenemy_nextthink)
868 ctf_CheckStalemate();
869 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
872 if(CTF_SAMETEAM(self, self.owner) && self.team)
874 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
875 ctf_Handle_Throw(self.owner, world, DROP_THROW);
876 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
877 ctf_Handle_Return(self, self.owner);
884 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
885 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
886 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
888 if((self.pass_target == world)
889 || (self.pass_target.deadflag != DEAD_NO)
890 || (self.pass_target.flagcarried)
891 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
892 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
893 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
895 // give up, pass failed
896 ctf_Handle_Drop(self, world, DROP_PASS);
900 // still a viable target, go for it
901 ctf_CalculatePassVelocity(self, targ_origin, self.origin, TRUE);
906 default: // this should never happen
908 dprint("ctf_FlagThink(): Flag exists with no status?\n");
916 if(gameover) { return; }
917 if(self.active != ACTIVE_ACTIVE) { return; }
918 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
920 entity toucher = other, tmp_entity;
921 float is_not_monster = (!(toucher.flags & FL_MONSTER)), num_perteam = 0;
923 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
924 if(ITEM_TOUCH_NEEDKILL())
926 if(!autocvar_g_ctf_flag_return_damage_delay)
929 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
934 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
936 // special touch behaviors
937 if(toucher.frozen) { return; }
938 else if(toucher.vehicle_flags & VHF_ISVEHICLE)
940 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
941 toucher = toucher.owner; // the player is actually the vehicle owner, not other
943 return; // do nothing
945 else if(toucher.flags & FL_MONSTER)
947 if(!autocvar_g_ctf_allow_monster_touch)
948 return; // do nothing
950 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
952 if(time > self.wait) // if we haven't in a while, play a sound/effect
954 pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
955 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
956 self.wait = time + FLAG_TOUCHRATE;
960 else if(toucher.deadflag != DEAD_NO) { return; }
962 switch(self.ctf_status)
968 if(CTF_DIFFTEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
969 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
970 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
971 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
973 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
974 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
975 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
976 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
982 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
983 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
984 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
985 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
991 dprint("Someone touched a flag even though it was being carried?\n");
997 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
999 if(DIFF_TEAM(toucher, self.pass_sender))
1000 ctf_Handle_Return(self, toucher);
1002 ctf_Handle_Retrieve(self, toucher);
1009 .float last_respawn;
1010 void ctf_RespawnFlag(entity flag)
1012 // check for flag respawn being called twice in a row
1013 if(flag.last_respawn > time - 0.5)
1014 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1016 flag.last_respawn = time;
1018 // reset the player (if there is one)
1019 if((flag.owner) && (flag.owner.flagcarried == flag))
1021 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1022 WaypointSprite_Kill(flag.wps_flagcarrier);
1024 flag.owner.flagcarried = world;
1026 if(flag.speedrunning)
1027 ctf_FakeTimeLimit(flag.owner, -1);
1030 if((flag.owner) && (flag.owner.vehicle))
1031 flag.scale = FLAG_SCALE;
1033 if(flag.ctf_status == FLAG_DROPPED)
1034 { WaypointSprite_Kill(flag.wps_flagdropped); }
1037 setattachment(flag, world, "");
1038 setorigin(flag, flag.ctf_spawnorigin);
1040 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1041 flag.takedamage = DAMAGE_NO;
1042 flag.health = flag.max_flag_health;
1043 flag.solid = SOLID_TRIGGER;
1044 flag.velocity = '0 0 0';
1045 flag.angles = flag.mangle;
1046 flag.flags = FL_ITEM | FL_NOTARGET;
1048 flag.ctf_status = FLAG_BASE;
1050 flag.pass_distance = 0;
1051 flag.pass_sender = world;
1052 flag.pass_target = world;
1053 flag.ctf_dropper = world;
1054 flag.ctf_pickuptime = 0;
1055 flag.ctf_droptime = 0;
1056 flag.ctf_flagdamaged = 0;
1058 ctf_CheckStalemate();
1064 if(IS_PLAYER(self.owner))
1065 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1067 ctf_RespawnFlag(self);
1072 if(self.ctf_status != FLAG_BASE) { return; }
1074 self.active = ((self.active) ? ACTIVE_NOT : ACTIVE_ACTIVE);
1076 if(self.active == ACTIVE_ACTIVE)
1077 WaypointSprite_Ping(self.wps_flagbase);
1080 float ctf_FlagWaypoint_Customize()
1082 if(self.owner.active != ACTIVE_ACTIVE) { return FALSE; }
1086 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1089 waypoint_spawnforitem_force(self, self.origin);
1090 self.nearestwaypointtimeout = 0; // activate waypointing again
1091 self.bot_basewaypoint = self.nearestwaypoint;
1094 string basename = "base";
1098 case NUM_TEAM_1: basename = "redbase"; break;
1099 case NUM_TEAM_2: basename = "bluebase"; break;
1100 case NUM_TEAM_3: basename = "yellowbase"; break;
1101 case NUM_TEAM_4: basename = "pinkbase"; break;
1102 default: basename = "neutralbase"; break;
1105 WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'));
1106 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, FALSE) : '1 1 1'));
1107 self.wps_flagbase.customizeentityforclient = ctf_FlagWaypoint_Customize;
1109 // captureshield setup
1110 ctf_CaptureShield_Spawn(self);
1113 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1116 self = flag; // for later usage with droptofloor()
1119 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1120 ctf_worldflaglist = flag;
1122 setattachment(flag, world, "");
1124 flag.netname = sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber));
1125 flag.team = teamnumber;
1126 flag.classname = "item_flag_team";
1127 flag.target = "###item###"; // wut?
1128 flag.flags = FL_ITEM | FL_NOTARGET;
1129 flag.solid = SOLID_TRIGGER;
1130 flag.takedamage = DAMAGE_NO;
1131 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1132 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1133 flag.health = flag.max_flag_health;
1134 flag.event_damage = ctf_FlagDamage;
1135 flag.pushable = TRUE;
1136 flag.teleportable = TELEPORT_NORMAL;
1137 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1138 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1139 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1140 flag.velocity = '0 0 0';
1141 flag.mangle = flag.angles;
1142 flag.reset = ctf_Reset;
1144 flag.touch = ctf_FlagTouch;
1145 flag.think = ctf_FlagThink;
1146 flag.nextthink = time + FLAG_THINKRATE;
1147 flag.ctf_status = FLAG_BASE;
1150 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)))); }
1151 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1152 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)))); }
1153 if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber == NUM_TEAM_1) ? "redflag_touch" : ((teamnumber == NUM_TEAM_2) ? "blueflag_touch" : ((teamnumber == NUM_TEAM_3) ? "yellowflag_touch" : ((teamnumber == NUM_TEAM_4) ? "pinkflag_touch" : "neutralflag_touch")))); }
1154 if(flag.passeffect == "") { flag.passeffect = ((teamnumber == NUM_TEAM_1) ? "red_pass" : ((teamnumber == NUM_TEAM_2) ? "blue_pass" : ((teamnumber == NUM_TEAM_3) ? "yellow_pass" : ((teamnumber == NUM_TEAM_4) ? "pink_pass" : "neutral_pass")))); }
1155 if(flag.capeffect == "") { flag.capeffect = ((teamnumber == NUM_TEAM_1) ? "red_cap" : ((teamnumber == NUM_TEAM_2) ? "blue_cap" : ((teamnumber == NUM_TEAM_3) ? "yellow_cap" : ((teamnumber == NUM_TEAM_4) ? "pink_cap" : "")))); } // neutral flag cant be capped itself
1156 if(!flag.active) { flag.active = ACTIVE_ACTIVE; }
1159 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")))); }
1160 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
1161 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
1162 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.
1163 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")))); }
1164 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1165 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1168 precache_sound(flag.snd_flag_taken);
1169 precache_sound(flag.snd_flag_returned);
1170 precache_sound(flag.snd_flag_capture);
1171 precache_sound(flag.snd_flag_respawn);
1172 precache_sound(flag.snd_flag_dropped);
1173 precache_sound(flag.snd_flag_touch);
1174 precache_sound(flag.snd_flag_pass);
1175 precache_model(flag.model);
1176 precache_model("models/ctf/shield.md3");
1177 precache_model("models/ctf/shockwavetransring.md3");
1180 setmodel(flag, flag.model); // precision set below
1181 setsize(flag, FLAG_MIN, FLAG_MAX);
1182 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1184 if(autocvar_g_ctf_flag_glowtrails)
1186 flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : ((teamnumber == NUM_TEAM_4) ? 145 : 254))));
1187 flag.glow_size = 25;
1188 flag.glow_trail = 1;
1191 flag.effects |= EF_LOWPRECISION;
1192 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1193 if(autocvar_g_ctf_dynamiclights)
1197 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1198 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1199 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1200 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1201 default: flag.effects |= EF_DIMLIGHT; break;
1206 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1208 flag.dropped_origin = flag.origin;
1209 flag.noalign = TRUE;
1210 flag.movetype = MOVETYPE_NONE;
1212 else // drop to floor, automatically find a platform and set that as spawn origin
1214 flag.noalign = FALSE;
1217 flag.movetype = MOVETYPE_TOSS;
1220 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1228 // NOTE: LEGACY CODE, needs to be re-written!
1230 void havocbot_calculate_middlepoint()
1234 vector fo = '0 0 0';
1237 f = ctf_worldflaglist;
1242 f = f.ctf_worldflagnext;
1246 havocbot_ctf_middlepoint = s * (1.0 / n);
1247 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1251 entity havocbot_ctf_find_flag(entity bot)
1254 f = ctf_worldflaglist;
1257 if (CTF_SAMETEAM(bot, f))
1259 f = f.ctf_worldflagnext;
1264 entity havocbot_ctf_find_enemy_flag(entity bot)
1267 f = ctf_worldflaglist;
1272 if(CTF_DIFFTEAM(bot, f))
1279 else if(!bot.flagcarried)
1283 else if (CTF_DIFFTEAM(bot, f))
1285 f = f.ctf_worldflagnext;
1290 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1298 FOR_EACH_PLAYER(head)
1300 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1303 if(vlen(head.origin - org) < tc_radius)
1310 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1313 head = ctf_worldflaglist;
1316 if (CTF_SAMETEAM(self, head))
1318 head = head.ctf_worldflagnext;
1321 navigation_routerating(head, ratingscale, 10000);
1324 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1327 head = ctf_worldflaglist;
1330 if (CTF_SAMETEAM(self, head))
1332 head = head.ctf_worldflagnext;
1337 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1340 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1343 head = ctf_worldflaglist;
1348 if(CTF_DIFFTEAM(self, head))
1352 if(self.flagcarried)
1355 else if(!self.flagcarried)
1359 else if(CTF_DIFFTEAM(self, head))
1361 head = head.ctf_worldflagnext;
1364 navigation_routerating(head, ratingscale, 10000);
1367 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1369 if (!bot_waypoints_for_items)
1371 havocbot_goalrating_ctf_enemyflag(ratingscale);
1377 head = havocbot_ctf_find_enemy_flag(self);
1382 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1385 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1389 mf = havocbot_ctf_find_flag(self);
1391 if(mf.ctf_status == FLAG_BASE)
1395 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1398 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1401 head = ctf_worldflaglist;
1404 // flag is out in the field
1405 if(head.ctf_status != FLAG_BASE)
1406 if(head.tag_entity==world) // dropped
1410 if(vlen(org-head.origin)<df_radius)
1411 navigation_routerating(head, ratingscale, 10000);
1414 navigation_routerating(head, ratingscale, 10000);
1417 head = head.ctf_worldflagnext;
1421 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1425 head = findchainfloat(bot_pickup, TRUE);
1428 // gather health and armor only
1430 if (head.health || head.armorvalue)
1431 if (vlen(head.origin - org) < sradius)
1433 // get the value of the item
1434 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1436 navigation_routerating(head, t * ratingscale, 500);
1442 void havocbot_ctf_reset_role(entity bot)
1444 float cdefense, cmiddle, coffense;
1445 entity mf, ef, head;
1448 if(bot.deadflag != DEAD_NO)
1451 if(vlen(havocbot_ctf_middlepoint)==0)
1452 havocbot_calculate_middlepoint();
1455 if (bot.flagcarried)
1457 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1461 mf = havocbot_ctf_find_flag(bot);
1462 ef = havocbot_ctf_find_enemy_flag(bot);
1464 // Retrieve stolen flag
1465 if(mf.ctf_status!=FLAG_BASE)
1467 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1471 // If enemy flag is taken go to the middle to intercept pursuers
1472 if(ef.ctf_status!=FLAG_BASE)
1474 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1478 // if there is only me on the team switch to offense
1480 FOR_EACH_PLAYER(head)
1481 if(SAME_TEAM(head, bot))
1486 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1490 // Evaluate best position to take
1491 // Count mates on middle position
1492 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1494 // Count mates on defense position
1495 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1497 // Count mates on offense position
1498 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1500 if(cdefense<=coffense)
1501 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1502 else if(coffense<=cmiddle)
1503 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1505 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1508 void havocbot_role_ctf_carrier()
1510 if(self.deadflag != DEAD_NO)
1512 havocbot_ctf_reset_role(self);
1516 if (self.flagcarried == world)
1518 havocbot_ctf_reset_role(self);
1522 if (self.bot_strategytime < time)
1524 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1526 navigation_goalrating_start();
1528 havocbot_goalrating_ctf_enemybase(50000);
1530 havocbot_goalrating_ctf_ourbase(50000);
1533 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1535 navigation_goalrating_end();
1537 if (self.navigation_hasgoals)
1538 self.havocbot_cantfindflag = time + 10;
1539 else if (time > self.havocbot_cantfindflag)
1541 // Can't navigate to my own base, suicide!
1542 // TODO: drop it and wander around
1543 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1549 void havocbot_role_ctf_escort()
1553 if(self.deadflag != DEAD_NO)
1555 havocbot_ctf_reset_role(self);
1559 if (self.flagcarried)
1561 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1565 // If enemy flag is back on the base switch to previous role
1566 ef = havocbot_ctf_find_enemy_flag(self);
1567 if(ef.ctf_status==FLAG_BASE)
1569 self.havocbot_role = self.havocbot_previous_role;
1570 self.havocbot_role_timeout = 0;
1574 // If the flag carrier reached the base switch to defense
1575 mf = havocbot_ctf_find_flag(self);
1576 if(mf.ctf_status!=FLAG_BASE)
1577 if(vlen(ef.origin - mf.dropped_origin) < 300)
1579 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1583 // Set the role timeout if necessary
1584 if (!self.havocbot_role_timeout)
1586 self.havocbot_role_timeout = time + random() * 30 + 60;
1589 // If nothing happened just switch to previous role
1590 if (time > self.havocbot_role_timeout)
1592 self.havocbot_role = self.havocbot_previous_role;
1593 self.havocbot_role_timeout = 0;
1597 // Chase the flag carrier
1598 if (self.bot_strategytime < time)
1600 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1601 navigation_goalrating_start();
1602 havocbot_goalrating_ctf_enemyflag(30000);
1603 havocbot_goalrating_ctf_ourstolenflag(40000);
1604 havocbot_goalrating_items(10000, self.origin, 10000);
1605 navigation_goalrating_end();
1609 void havocbot_role_ctf_offense()
1614 if(self.deadflag != DEAD_NO)
1616 havocbot_ctf_reset_role(self);
1620 if (self.flagcarried)
1622 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1627 mf = havocbot_ctf_find_flag(self);
1628 ef = havocbot_ctf_find_enemy_flag(self);
1631 if(mf.ctf_status!=FLAG_BASE)
1634 pos = mf.tag_entity.origin;
1638 // Try to get it if closer than the enemy base
1639 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1641 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1646 // Escort flag carrier
1647 if(ef.ctf_status!=FLAG_BASE)
1650 pos = ef.tag_entity.origin;
1654 if(vlen(pos-mf.dropped_origin)>700)
1656 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1661 // About to fail, switch to middlefield
1664 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1668 // Set the role timeout if necessary
1669 if (!self.havocbot_role_timeout)
1670 self.havocbot_role_timeout = time + 120;
1672 if (time > self.havocbot_role_timeout)
1674 havocbot_ctf_reset_role(self);
1678 if (self.bot_strategytime < time)
1680 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1681 navigation_goalrating_start();
1682 havocbot_goalrating_ctf_ourstolenflag(50000);
1683 havocbot_goalrating_ctf_enemybase(20000);
1684 havocbot_goalrating_items(5000, self.origin, 1000);
1685 havocbot_goalrating_items(1000, self.origin, 10000);
1686 navigation_goalrating_end();
1690 // Retriever (temporary role):
1691 void havocbot_role_ctf_retriever()
1695 if(self.deadflag != DEAD_NO)
1697 havocbot_ctf_reset_role(self);
1701 if (self.flagcarried)
1703 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1707 // If flag is back on the base switch to previous role
1708 mf = havocbot_ctf_find_flag(self);
1709 if(mf.ctf_status==FLAG_BASE)
1711 havocbot_ctf_reset_role(self);
1715 if (!self.havocbot_role_timeout)
1716 self.havocbot_role_timeout = time + 20;
1718 if (time > self.havocbot_role_timeout)
1720 havocbot_ctf_reset_role(self);
1724 if (self.bot_strategytime < time)
1729 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1730 navigation_goalrating_start();
1731 havocbot_goalrating_ctf_ourstolenflag(50000);
1732 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1733 havocbot_goalrating_ctf_enemybase(30000);
1734 havocbot_goalrating_items(500, self.origin, rt_radius);
1735 navigation_goalrating_end();
1739 void havocbot_role_ctf_middle()
1743 if(self.deadflag != DEAD_NO)
1745 havocbot_ctf_reset_role(self);
1749 if (self.flagcarried)
1751 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1755 mf = havocbot_ctf_find_flag(self);
1756 if(mf.ctf_status!=FLAG_BASE)
1758 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1762 if (!self.havocbot_role_timeout)
1763 self.havocbot_role_timeout = time + 10;
1765 if (time > self.havocbot_role_timeout)
1767 havocbot_ctf_reset_role(self);
1771 if (self.bot_strategytime < time)
1775 org = havocbot_ctf_middlepoint;
1776 org_z = self.origin_z;
1778 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1779 navigation_goalrating_start();
1780 havocbot_goalrating_ctf_ourstolenflag(50000);
1781 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1782 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1783 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1784 havocbot_goalrating_items(2500, self.origin, 10000);
1785 havocbot_goalrating_ctf_enemybase(2500);
1786 navigation_goalrating_end();
1790 void havocbot_role_ctf_defense()
1794 if(self.deadflag != DEAD_NO)
1796 havocbot_ctf_reset_role(self);
1800 if (self.flagcarried)
1802 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1806 // If own flag was captured
1807 mf = havocbot_ctf_find_flag(self);
1808 if(mf.ctf_status!=FLAG_BASE)
1810 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1814 if (!self.havocbot_role_timeout)
1815 self.havocbot_role_timeout = time + 30;
1817 if (time > self.havocbot_role_timeout)
1819 havocbot_ctf_reset_role(self);
1822 if (self.bot_strategytime < time)
1827 org = mf.dropped_origin;
1828 mp_radius = havocbot_ctf_middlepoint_radius;
1830 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1831 navigation_goalrating_start();
1833 // if enemies are closer to our base, go there
1834 entity head, closestplayer = world;
1835 float distance, bestdistance = 10000;
1836 FOR_EACH_PLAYER(head)
1838 if(head.deadflag!=DEAD_NO)
1841 distance = vlen(org - head.origin);
1842 if(distance<bestdistance)
1844 closestplayer = head;
1845 bestdistance = distance;
1850 if(DIFF_TEAM(closestplayer, self))
1851 if(vlen(org - self.origin)>1000)
1852 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1853 havocbot_goalrating_ctf_ourbase(30000);
1855 havocbot_goalrating_ctf_ourstolenflag(20000);
1856 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1857 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1858 havocbot_goalrating_items(10000, org, mp_radius);
1859 havocbot_goalrating_items(5000, self.origin, 10000);
1860 navigation_goalrating_end();
1864 void havocbot_role_ctf_setrole(entity bot, float role)
1866 dprint(strcat(bot.netname," switched to "));
1869 case HAVOCBOT_CTF_ROLE_CARRIER:
1871 bot.havocbot_role = havocbot_role_ctf_carrier;
1872 bot.havocbot_role_timeout = 0;
1873 bot.havocbot_cantfindflag = time + 10;
1874 bot.bot_strategytime = 0;
1876 case HAVOCBOT_CTF_ROLE_DEFENSE:
1878 bot.havocbot_role = havocbot_role_ctf_defense;
1879 bot.havocbot_role_timeout = 0;
1881 case HAVOCBOT_CTF_ROLE_MIDDLE:
1883 bot.havocbot_role = havocbot_role_ctf_middle;
1884 bot.havocbot_role_timeout = 0;
1886 case HAVOCBOT_CTF_ROLE_OFFENSE:
1888 bot.havocbot_role = havocbot_role_ctf_offense;
1889 bot.havocbot_role_timeout = 0;
1891 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1892 dprint("retriever");
1893 bot.havocbot_previous_role = bot.havocbot_role;
1894 bot.havocbot_role = havocbot_role_ctf_retriever;
1895 bot.havocbot_role_timeout = time + 10;
1896 bot.bot_strategytime = 0;
1898 case HAVOCBOT_CTF_ROLE_ESCORT:
1900 bot.havocbot_previous_role = bot.havocbot_role;
1901 bot.havocbot_role = havocbot_role_ctf_escort;
1902 bot.havocbot_role_timeout = time + 30;
1903 bot.bot_strategytime = 0;
1914 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1918 float t = 0, t2 = 0, t3 = 0;
1920 // initially clear items so they can be set as necessary later.
1921 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
1922 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
1923 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
1924 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
1925 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
1926 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1928 // scan through all the flags and notify the client about them
1929 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1931 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
1932 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
1933 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
1934 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
1935 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; }
1937 switch(flag.ctf_status)
1942 if((flag.owner == self) || (flag.pass_sender == self))
1943 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1945 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1950 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1956 // item for stopping players from capturing the flag too often
1957 if(self.ctf_captureshielded)
1958 self.ctf_flagstatus |= CTF_SHIELDED;
1960 // update the health of the flag carrier waypointsprite
1961 if(self.wps_flagcarrier)
1962 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1967 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1969 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1971 if(frag_target == frag_attacker) // damage done to yourself
1973 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1974 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1976 else // damage done to everyone else
1978 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1979 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1982 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1984 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)))
1985 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1987 frag_target.wps_helpme_time = time;
1988 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1990 // todo: add notification for when flag carrier needs help?
1995 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1997 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1999 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2000 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2003 if(frag_target.flagcarried)
2005 entity tmp_entity = frag_target.flagcarried;
2006 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2007 tmp_entity.ctf_dropper = world;
2013 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
2016 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2019 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
2021 entity flag; // temporary entity for the search method
2023 if(self.flagcarried)
2024 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2026 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2028 if(flag.pass_sender == self) { flag.pass_sender = world; }
2029 if(flag.pass_target == self) { flag.pass_target = world; }
2030 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
2036 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2038 if(self.flagcarried)
2039 if(!autocvar_g_ctf_portalteleport)
2040 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2045 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2047 if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
2049 entity player = self;
2051 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2053 // pass the flag to a team mate
2054 if(autocvar_g_ctf_pass)
2056 entity head, closest_target = world;
2057 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
2059 while(head) // find the closest acceptable target to pass to
2061 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2062 if(head != player && SAME_TEAM(head, player))
2063 if(!head.speedrunning && !head.vehicle)
2065 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2066 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2067 vector passer_center = CENTER_OR_VIEWOFS(player);
2069 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2071 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2073 if(IS_BOT_CLIENT(head))
2075 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2076 ctf_Handle_Throw(head, player, DROP_PASS);
2080 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2081 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2083 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2086 else if(player.flagcarried)
2090 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2091 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2092 { closest_target = head; }
2094 else { closest_target = head; }
2101 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
2104 // throw the flag in front of you
2105 if(autocvar_g_ctf_throw && player.flagcarried)
2107 if(player.throw_count == -1)
2109 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2111 player.throw_prevtime = time;
2112 player.throw_count = 1;
2113 ctf_Handle_Throw(player, world, DROP_THROW);
2118 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2124 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2125 else { player.throw_count += 1; }
2126 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2128 player.throw_prevtime = time;
2129 ctf_Handle_Throw(player, world, DROP_THROW);
2138 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2140 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2142 self.wps_helpme_time = time;
2143 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2145 else // create a normal help me waypointsprite
2147 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');
2148 WaypointSprite_Ping(self.wps_helpme);
2154 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2156 if(vh_player.flagcarried)
2158 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2160 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2164 setattachment(vh_player.flagcarried, vh_vehicle, "");
2165 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2166 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2167 //vh_player.flagcarried.angles = '0 0 0';
2175 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2177 if(vh_player.flagcarried)
2179 setattachment(vh_player.flagcarried, vh_player, "");
2180 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2181 vh_player.flagcarried.scale = FLAG_SCALE;
2182 vh_player.flagcarried.angles = '0 0 0';
2189 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2191 if(self.flagcarried)
2193 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));
2194 ctf_RespawnFlag(self.flagcarried);
2201 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2203 entity flag; // temporary entity for the search method
2205 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2207 switch(flag.ctf_status)
2212 // lock the flag, game is over
2213 flag.movetype = MOVETYPE_NONE;
2214 flag.takedamage = DAMAGE_NO;
2215 flag.solid = SOLID_NOT;
2216 flag.nextthink = FALSE; // stop thinking
2218 //dprint("stopping the ", flag.netname, " from moving.\n");
2226 // do nothing for these flags
2235 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2237 havocbot_ctf_reset_role(self);
2241 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2243 //ret_float = ctf_teams;
2244 ret_string = "ctf_team";
2248 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2250 self.ctf_flagstatus = other.ctf_flagstatus;
2259 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
2260 CTF Starting point for a player in team one (Red).
2261 Keys: "angle" viewing angle when spawning. */
2262 void spawnfunc_info_player_team1()
2264 if(g_assault) { remove(self); return; }
2266 self.team = NUM_TEAM_1; // red
2267 spawnfunc_info_player_deathmatch();
2271 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
2272 CTF Starting point for a player in team two (Blue).
2273 Keys: "angle" viewing angle when spawning. */
2274 void spawnfunc_info_player_team2()
2276 if(g_assault) { remove(self); return; }
2278 self.team = NUM_TEAM_2; // blue
2279 spawnfunc_info_player_deathmatch();
2282 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
2283 CTF Starting point for a player in team three (Yellow).
2284 Keys: "angle" viewing angle when spawning. */
2285 void spawnfunc_info_player_team3()
2287 if(g_assault) { remove(self); return; }
2289 self.team = NUM_TEAM_3; // yellow
2290 spawnfunc_info_player_deathmatch();
2294 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
2295 CTF Starting point for a player in team four (Purple).
2296 Keys: "angle" viewing angle when spawning. */
2297 void spawnfunc_info_player_team4()
2299 if(g_assault) { remove(self); return; }
2301 self.team = NUM_TEAM_4; // purple
2302 spawnfunc_info_player_deathmatch();
2305 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2306 CTF flag for team one (Red).
2308 "angle" Angle the flag will point (minus 90 degrees)...
2309 "model" model to use, note this needs red and blue as skins 0 and 1...
2310 "noise" sound played when flag is picked up...
2311 "noise1" sound played when flag is returned by a teammate...
2312 "noise2" sound played when flag is captured...
2313 "noise3" sound played when flag is lost in the field and respawns itself...
2314 "noise4" sound played when flag is dropped by a player...
2315 "noise5" sound played when flag touches the ground... */
2316 void spawnfunc_item_flag_team1()
2318 if(!g_ctf) { remove(self); return; }
2320 ctf_FlagSetup(NUM_TEAM_1, self);
2323 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2324 CTF flag for team two (Blue).
2326 "angle" Angle the flag will point (minus 90 degrees)...
2327 "model" model to use, note this needs red and blue as skins 0 and 1...
2328 "noise" sound played when flag is picked up...
2329 "noise1" sound played when flag is returned by a teammate...
2330 "noise2" sound played when flag is captured...
2331 "noise3" sound played when flag is lost in the field and respawns itself...
2332 "noise4" sound played when flag is dropped by a player...
2333 "noise5" sound played when flag touches the ground... */
2334 void spawnfunc_item_flag_team2()
2336 if(!g_ctf) { remove(self); return; }
2338 ctf_FlagSetup(NUM_TEAM_2, self);
2341 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2342 CTF flag for team three (Yellow).
2344 "angle" Angle the flag will point (minus 90 degrees)...
2345 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2346 "noise" sound played when flag is picked up...
2347 "noise1" sound played when flag is returned by a teammate...
2348 "noise2" sound played when flag is captured...
2349 "noise3" sound played when flag is lost in the field and respawns itself...
2350 "noise4" sound played when flag is dropped by a player...
2351 "noise5" sound played when flag touches the ground... */
2352 void spawnfunc_item_flag_team3()
2354 if(!g_ctf) { remove(self); return; }
2356 ctf_FlagSetup(NUM_TEAM_3, self);
2359 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2360 CTF flag for team four (Pink).
2362 "angle" Angle the flag will point (minus 90 degrees)...
2363 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2364 "noise" sound played when flag is picked up...
2365 "noise1" sound played when flag is returned by a teammate...
2366 "noise2" sound played when flag is captured...
2367 "noise3" sound played when flag is lost in the field and respawns itself...
2368 "noise4" sound played when flag is dropped by a player...
2369 "noise5" sound played when flag touches the ground... */
2370 void spawnfunc_item_flag_team4()
2372 if(!g_ctf) { remove(self); return; }
2374 ctf_FlagSetup(NUM_TEAM_4, self);
2377 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2380 "angle" Angle the flag will point (minus 90 degrees)...
2381 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2382 "noise" sound played when flag is picked up...
2383 "noise1" sound played when flag is returned by a teammate...
2384 "noise2" sound played when flag is captured...
2385 "noise3" sound played when flag is lost in the field and respawns itself...
2386 "noise4" sound played when flag is dropped by a player...
2387 "noise5" sound played when flag touches the ground... */
2388 void spawnfunc_item_flag_neutral()
2390 if(!g_ctf) { remove(self); return; }
2391 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2393 ctf_FlagSetup(0, self);
2396 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2397 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2398 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.
2400 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2401 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2402 void spawnfunc_ctf_team()
2404 if(!g_ctf) { remove(self); return; }
2406 self.classname = "ctf_team";
2407 self.team = self.cnt + 1;
2410 // compatibility for quake maps
2411 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2412 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2413 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2414 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2415 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2416 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2424 void ctf_ScoreRules(float teams)
2426 CheckAllowedTeams(world);
2427 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
2428 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2429 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2430 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2431 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2432 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2433 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2434 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2435 ScoreRules_basics_end();
2438 // code from here on is just to support maps that don't have flag and team entities
2439 void ctf_SpawnTeam (string teamname, float teamcolor)
2444 self.classname = "ctf_team";
2445 self.netname = teamname;
2446 self.cnt = teamcolor;
2448 spawnfunc_ctf_team();
2453 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2458 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2460 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2461 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2462 if(tmp_entity.team == 0) { ctf_oneflag = TRUE; }
2465 ctf_teams = bound(2, ctf_teams, 4);
2467 // if no teams are found, spawn defaults
2468 if(find(world, classname, "ctf_team") == world)
2470 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2471 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2472 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2474 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2476 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2479 ctf_ScoreRules(ctf_teams);
2482 void ctf_Initialize()
2484 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2486 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2487 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2488 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2490 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2492 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2496 MUTATOR_DEFINITION(gamemode_ctf)
2498 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2499 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2500 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2501 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2502 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2503 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2504 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2505 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2506 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2507 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2508 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2509 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2510 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2511 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2512 MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2513 MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
2517 if(time > 1) // game loads at time 1
2518 error("This is a game type and it cannot be added at runtime.");
2522 MUTATOR_ONROLLBACK_OR_REMOVE
2524 // we actually cannot roll back ctf_Initialize here
2525 // BUT: we don't need to! If this gets called, adding always
2531 print("This is a game type and it cannot be removed at runtime.");