1 #include "../../common/effects.qh"
3 void ctf_FakeTimeLimit(entity e, float t)
6 WriteByte(MSG_ONE, 3); // svc_updatestat
7 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
9 WriteCoord(MSG_ONE, autocvar_timelimit);
11 WriteCoord(MSG_ONE, (t + 1) / 60);
14 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
16 if(autocvar_sv_eventlog)
17 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
18 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : ""), "));
21 void ctf_CaptureRecord(entity flag, entity player)
23 float cap_record = ctf_captimerecord;
24 float cap_time = (time - flag.ctf_pickuptime);
25 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
28 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
29 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)); }
30 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)); }
31 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)); }
33 // write that shit in the database
34 if(!ctf_oneflag) // but not in 1-flag mode
35 if((!ctf_captimerecord) || (cap_time < cap_record))
37 ctf_captimerecord = cap_time;
38 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
39 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
40 write_recordmarker(player, (time - cap_time), cap_time);
44 void ctf_FlagcarrierWaypoints(entity player)
46 WaypointSprite_Spawn("flagcarrier", 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG, WPCOLOR_FLAGCARRIER(player.team));
47 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON) * 2);
48 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
49 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
52 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
54 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
55 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
56 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
57 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
60 if(current_height) // make sure we can actually do this arcing path
62 targpos = (to + ('0 0 1' * current_height));
63 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
64 if(trace_fraction < 1)
66 //print("normal arc line failed, trying to find new pos...");
67 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
68 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
69 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
70 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
71 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
74 else { targpos = to; }
76 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
78 vector desired_direction = normalize(targpos - from);
79 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
80 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
83 float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
85 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
87 // directional tracing only
89 makevectors(passer_angle);
91 // find the closest point on the enemy to the center of the attack
92 float ang; // angle between shotdir and h
93 float h; // hypotenuse, which is the distance between attacker to head
94 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
96 h = vlen(head_center - passer_center);
97 ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
100 vector nearest_on_line = (passer_center + a * v_forward);
101 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
103 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
104 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
106 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
111 else { return true; }
115 // =======================
116 // CaptureShield Functions
117 // =======================
119 float ctf_CaptureShield_CheckStatus(entity p)
121 float s, s2, s3, s4, se, se2, se3, se4, sr, ser;
123 float players_worseeq, players_total;
125 if(ctf_captureshield_max_ratio <= 0)
128 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
129 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
130 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
131 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
133 sr = ((s - s2) + (s3 + s4));
135 if(sr >= -ctf_captureshield_min_negscore)
138 players_total = players_worseeq = 0;
143 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
144 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
145 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
146 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
148 ser = ((se - se2) + (se3 + se4));
155 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
156 // use this rule here
158 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
164 void ctf_CaptureShield_Update(entity player, float wanted_status)
166 float updated_status = ctf_CaptureShield_CheckStatus(player);
167 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
169 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
170 player.ctf_captureshielded = updated_status;
174 float ctf_CaptureShield_Customize()
176 if(self.enemy.active != ACTIVE_ACTIVE) { return true; }
177 if(!other.ctf_captureshielded) { return false; }
178 if(CTF_SAMETEAM(self, other)) { return false; }
183 void ctf_CaptureShield_Touch()
185 if(self.enemy.active != ACTIVE_ACTIVE)
187 vector mymid = (self.absmin + self.absmax) * 0.5;
188 vector othermid = (other.absmin + other.absmax) * 0.5;
190 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
191 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_INACTIVE); }
196 if(!other.ctf_captureshielded) { return; }
197 if(CTF_SAMETEAM(self, other)) { return; }
199 vector mymid = (self.absmin + self.absmax) * 0.5;
200 vector othermid = (other.absmin + other.absmax) * 0.5;
202 Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
203 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
206 void ctf_CaptureShield_Spawn(entity flag)
208 entity shield = spawn();
211 shield.team = self.team;
212 shield.touch = ctf_CaptureShield_Touch;
213 shield.customizeentityforclient = ctf_CaptureShield_Customize;
214 shield.classname = "ctf_captureshield";
215 shield.effects = EF_ADDITIVE;
216 shield.movetype = MOVETYPE_NOCLIP;
217 shield.solid = SOLID_TRIGGER;
218 shield.avelocity = '7 0 11';
221 setorigin(shield, self.origin);
222 setmodel(shield, "models/ctf/shield.md3");
223 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
227 // ====================
228 // Drop/Pass/Throw Code
229 // ====================
231 void ctf_Handle_Drop(entity flag, entity player, float droptype)
234 player = (player ? player : flag.pass_sender);
237 flag.movetype = MOVETYPE_TOSS;
238 flag.takedamage = DAMAGE_YES;
239 flag.angles = '0 0 0';
240 flag.health = flag.max_flag_health;
241 flag.ctf_droptime = time;
242 flag.ctf_dropper = player;
243 flag.ctf_status = FLAG_DROPPED;
245 // messages and sounds
246 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
247 sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
248 ctf_EventLog("dropped", player.team, player);
251 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
252 PlayerScore_Add(player, SP_CTF_DROPS, 1);
255 if(autocvar_g_ctf_flag_dropped_waypoint)
256 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));
258 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
260 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
261 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
264 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
266 if(droptype == DROP_PASS)
268 flag.pass_distance = 0;
269 flag.pass_sender = world;
270 flag.pass_target = world;
274 void ctf_Handle_Retrieve(entity flag, entity player)
276 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
277 entity sender = flag.pass_sender;
279 // transfer flag to player
281 flag.owner.flagcarried = flag;
286 setattachment(flag, player.vehicle, "");
287 setorigin(flag, VEHICLE_FLAG_OFFSET);
288 flag.scale = VEHICLE_FLAG_SCALE;
292 setattachment(flag, player, "");
293 setorigin(flag, FLAG_CARRY_OFFSET);
295 flag.movetype = MOVETYPE_NONE;
296 flag.takedamage = DAMAGE_NO;
297 flag.solid = SOLID_NOT;
298 flag.angles = '0 0 0';
299 flag.ctf_status = FLAG_CARRY;
301 // messages and sounds
302 sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
303 ctf_EventLog("receive", flag.team, player);
305 FOR_EACH_REALPLAYER(tmp_player)
307 if(tmp_player == sender)
308 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);
309 else if(tmp_player == player)
310 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);
311 else if(SAME_TEAM(tmp_player, sender))
312 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);
315 // create new waypoint
316 ctf_FlagcarrierWaypoints(player);
318 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
319 player.throw_antispam = sender.throw_antispam;
321 flag.pass_distance = 0;
322 flag.pass_sender = world;
323 flag.pass_target = world;
326 void ctf_Handle_Throw(entity player, entity receiver, float droptype)
328 entity flag = player.flagcarried;
329 vector targ_origin, flag_velocity;
331 if(!flag) { return; }
332 if((droptype == DROP_PASS) && !receiver) { return; }
334 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
337 setattachment(flag, world, "");
338 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
339 flag.owner.flagcarried = world;
341 flag.solid = SOLID_TRIGGER;
342 flag.ctf_dropper = player;
343 flag.ctf_droptime = time;
345 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
352 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
353 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
354 WarpZone_RefSys_Copy(flag, receiver);
355 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
356 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
358 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
359 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
362 flag.movetype = MOVETYPE_FLY;
363 flag.takedamage = DAMAGE_NO;
364 flag.pass_sender = player;
365 flag.pass_target = receiver;
366 flag.ctf_status = FLAG_PASSING;
369 sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
370 if(flag.passeffect == "") { Send_Effect(flag.passeffectnum, player.origin, targ_origin, 0); }
371 else { WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin); }
372 ctf_EventLog("pass", flag.team, player);
378 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'));
380 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward)));
381 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
382 ctf_Handle_Drop(flag, player, droptype);
388 flag.velocity = '0 0 0'; // do nothing
395 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);
396 ctf_Handle_Drop(flag, player, droptype);
401 // kill old waypointsprite
402 WaypointSprite_Ping(player.wps_flagcarrier);
403 WaypointSprite_Kill(player.wps_flagcarrier);
405 if(player.wps_enemyflagcarrier)
406 WaypointSprite_Kill(player.wps_enemyflagcarrier);
409 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
417 void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
419 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
420 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
421 entity player_team_flag = world, tmp_entity;
422 float old_time, new_time;
424 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
425 if(CTF_DIFFTEAM(player, flag)) { return; }
428 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
429 if(SAME_TEAM(tmp_entity, player))
431 player_team_flag = tmp_entity;
435 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
437 player.throw_prevtime = time;
438 player.throw_count = 0;
440 // messages and sounds
441 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
442 ctf_CaptureRecord(enemy_flag, player);
443 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);
447 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
448 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
453 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
454 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
456 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
457 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
458 if(!old_time || new_time < old_time)
459 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
462 if(flag.capeffect == "") { Send_Effect(flag.capeffectnum, flag.origin, '0 0 0', 1); }
463 else { pointparticles(particleeffectnum(flag.capeffect), flag.origin, '0 0 0', 1); }
464 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
467 if(capturetype == CAPTURE_NORMAL)
469 WaypointSprite_Kill(player.wps_flagcarrier);
470 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
472 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
473 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
477 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
478 ctf_RespawnFlag(enemy_flag);
481 void ctf_Handle_Return(entity flag, entity player)
483 // messages and sounds
484 if(IS_MONSTER(player))
486 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
490 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
491 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
493 sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
494 ctf_EventLog("return", flag.team, player);
497 if(IS_PLAYER(player))
499 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
500 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
502 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
505 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
509 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
510 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
511 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
515 if(player.flagcarried == flag)
516 WaypointSprite_Kill(player.wps_flagcarrier);
519 ctf_RespawnFlag(flag);
522 void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
525 float pickup_dropped_score; // used to calculate dropped pickup score
526 entity tmp_entity; // temporary entity
528 // attach the flag to the player
530 player.flagcarried = flag;
533 setattachment(flag, player.vehicle, "");
534 setorigin(flag, VEHICLE_FLAG_OFFSET);
535 flag.scale = VEHICLE_FLAG_SCALE;
539 setattachment(flag, player, "");
540 setorigin(flag, FLAG_CARRY_OFFSET);
544 flag.movetype = MOVETYPE_NONE;
545 flag.takedamage = DAMAGE_NO;
546 flag.solid = SOLID_NOT;
547 flag.angles = '0 0 0';
548 flag.ctf_status = FLAG_CARRY;
552 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
553 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
557 // messages and sounds
558 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
559 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
560 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
561 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
562 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)); }
564 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);
567 FOR_EACH_PLAYER(tmp_entity)
568 if(tmp_entity != player)
569 if(DIFF_TEAM(player, tmp_entity))
570 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
573 FOR_EACH_PLAYER(tmp_entity)
574 if(tmp_entity != player)
575 if(CTF_SAMETEAM(flag, tmp_entity))
576 if(SAME_TEAM(player, tmp_entity))
577 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
579 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);
581 sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
584 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
585 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
590 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
591 ctf_EventLog("steal", flag.team, player);
597 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);
598 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);
599 dprint("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
600 PlayerTeamScore_AddScore(player, pickup_dropped_score);
601 ctf_EventLog("pickup", flag.team, player);
609 if(pickuptype == PICKUP_BASE)
611 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
612 if((player.speedrunning) && (ctf_captimerecord))
613 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
617 if(flag.toucheffect == "") { Send_Effect(flag.toucheffectnum, player.origin, '0 0 0', 1); }
618 else { pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1); }
621 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
622 ctf_FlagcarrierWaypoints(player);
623 WaypointSprite_Ping(player.wps_flagcarrier);
627 // ===================
628 // Main Flag Functions
629 // ===================
631 void ctf_CheckFlagReturn(entity flag, float returntype)
633 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
635 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
637 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
641 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;
642 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;
643 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;
644 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;
648 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
650 sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
651 ctf_EventLog("returned", flag.team, world);
652 ctf_RespawnFlag(flag);
657 float ctf_Stalemate_Customize()
659 // make spectators see what the player would see
661 e = WaypointSprite_getviewentity(other);
662 wp_owner = self.owner;
665 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
666 if(SAME_TEAM(wp_owner, e)) { return false; }
667 if(!IS_PLAYER(e)) { return false; }
672 void ctf_CheckStalemate(void)
675 float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
678 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
680 // build list of stale flags
681 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
683 if(autocvar_g_ctf_stalemate)
684 if(tmp_entity.ctf_status != FLAG_BASE)
685 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
687 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
688 ctf_staleflaglist = tmp_entity;
690 switch(tmp_entity.team)
692 case NUM_TEAM_1: ++stale_red_flags; break;
693 case NUM_TEAM_2: ++stale_blue_flags; break;
694 case NUM_TEAM_3: ++stale_yellow_flags; break;
695 case NUM_TEAM_4: ++stale_pink_flags; break;
696 default: ++stale_neutral_flags; break;
702 stale_flags = (stale_neutral_flags >= 1);
704 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
706 if(ctf_oneflag && stale_flags == 1)
707 ctf_stalemate = true;
708 else if(stale_flags == ctf_teams)
709 ctf_stalemate = true;
710 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
711 { ctf_stalemate = false; wpforenemy_announced = false; }
712 else if(stale_flags < ctf_teams && autocvar_g_ctf_stalemate_endcondition == 1)
713 { ctf_stalemate = false; wpforenemy_announced = false; }
715 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
718 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
720 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
722 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));
723 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
727 if (!wpforenemy_announced)
729 FOR_EACH_REALPLAYER(tmp_entity)
730 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
732 wpforenemy_announced = true;
737 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
739 if(ITEM_DAMAGE_NEEDKILL(deathtype))
741 if(autocvar_g_ctf_flag_return_damage_delay)
743 self.ctf_flagdamaged = true;
748 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
752 if(autocvar_g_ctf_flag_return_damage)
754 // reduce health and check if it should be returned
755 self.health = self.health - damage;
756 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
766 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
769 if(self == ctf_worldflaglist) // only for the first flag
770 FOR_EACH_CLIENT(tmp_entity)
771 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
774 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
775 dprint("wtf the flag got squashed?\n");
776 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
777 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
778 setsize(self, FLAG_MIN, FLAG_MAX); }
780 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
784 self.angles = '0 0 0';
792 switch(self.ctf_status)
796 if(autocvar_g_ctf_dropped_capture_radius)
798 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
799 if(tmp_entity.ctf_status == FLAG_DROPPED)
800 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
801 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
802 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
809 if(autocvar_g_ctf_flag_dropped_floatinwater)
811 vector midpoint = ((self.absmin + self.absmax) * 0.5);
812 if(pointcontents(midpoint) == CONTENT_WATER)
814 self.velocity = self.velocity * 0.5;
816 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
817 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
819 { self.movetype = MOVETYPE_FLY; }
821 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
823 if(autocvar_g_ctf_flag_return_dropped)
825 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
828 ctf_CheckFlagReturn(self, RETURN_DROPPED);
832 if(self.ctf_flagdamaged)
834 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
835 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
838 else if(autocvar_g_ctf_flag_return_time)
840 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
841 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
849 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
852 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
856 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
860 if(autocvar_g_ctf_stalemate)
862 if(time >= wpforenemy_nextthink)
864 ctf_CheckStalemate();
865 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
868 if(CTF_SAMETEAM(self, self.owner) && self.team)
870 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
871 ctf_Handle_Throw(self.owner, world, DROP_THROW);
872 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried)
873 ctf_Handle_Return(self, self.owner);
880 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
881 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
882 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
884 if((self.pass_target == world)
885 || (self.pass_target.deadflag != DEAD_NO)
886 || (self.pass_target.flagcarried)
887 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
888 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
889 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
891 // give up, pass failed
892 ctf_Handle_Drop(self, world, DROP_PASS);
896 // still a viable target, go for it
897 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
902 default: // this should never happen
904 dprint("ctf_FlagThink(): Flag exists with no status?\n");
912 if(gameover) { return; }
913 if(self.active != ACTIVE_ACTIVE) { return; }
914 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
916 entity toucher = other, tmp_entity;
917 float is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
919 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
920 if(ITEM_TOUCH_NEEDKILL())
922 if(!autocvar_g_ctf_flag_return_damage_delay)
925 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
927 if(!self.ctf_flagdamaged) { return; }
930 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
932 // special touch behaviors
933 if(toucher.frozen) { return; }
934 else if(IS_VEHICLE(toucher))
936 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
937 toucher = toucher.owner; // the player is actually the vehicle owner, not other
939 return; // do nothing
941 else if(IS_MONSTER(toucher))
943 if(!autocvar_g_ctf_allow_monster_touch)
944 return; // do nothing
946 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
948 if(time > self.wait) // if we haven't in a while, play a sound/effect
950 if(self.toucheffect == "") { Send_Effect(self.toucheffectnum, self.origin, '0 0 0', 1); }
951 else { pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1); }
952 sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
953 self.wait = time + FLAG_TOUCHRATE;
957 else if(toucher.deadflag != DEAD_NO) { return; }
959 switch(self.ctf_status)
965 if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
966 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
967 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
968 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
970 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
971 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
972 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
973 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
979 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
980 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
981 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
982 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
988 dprint("Someone touched a flag even though it was being carried?\n");
994 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
996 if(DIFF_TEAM(toucher, self.pass_sender))
997 ctf_Handle_Return(self, toucher);
999 ctf_Handle_Retrieve(self, toucher);
1006 .float last_respawn;
1007 void ctf_RespawnFlag(entity flag)
1009 // check for flag respawn being called twice in a row
1010 if(flag.last_respawn > time - 0.5)
1011 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1013 flag.last_respawn = time;
1015 // reset the player (if there is one)
1016 if((flag.owner) && (flag.owner.flagcarried == flag))
1018 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1019 WaypointSprite_Kill(flag.wps_flagcarrier);
1021 flag.owner.flagcarried = world;
1023 if(flag.speedrunning)
1024 ctf_FakeTimeLimit(flag.owner, -1);
1027 if((flag.owner) && (flag.owner.vehicle))
1028 flag.scale = FLAG_SCALE;
1030 if(flag.ctf_status == FLAG_DROPPED)
1031 { WaypointSprite_Kill(flag.wps_flagdropped); }
1034 setattachment(flag, world, "");
1035 setorigin(flag, flag.ctf_spawnorigin);
1037 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1038 flag.takedamage = DAMAGE_NO;
1039 flag.health = flag.max_flag_health;
1040 flag.solid = SOLID_TRIGGER;
1041 flag.velocity = '0 0 0';
1042 flag.angles = flag.mangle;
1043 flag.flags = FL_ITEM | FL_NOTARGET;
1045 flag.ctf_status = FLAG_BASE;
1047 flag.pass_distance = 0;
1048 flag.pass_sender = world;
1049 flag.pass_target = world;
1050 flag.ctf_dropper = world;
1051 flag.ctf_pickuptime = 0;
1052 flag.ctf_droptime = 0;
1053 flag.ctf_flagdamaged = 0;
1055 ctf_CheckStalemate();
1061 if(IS_PLAYER(self.owner))
1062 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1064 ctf_RespawnFlag(self);
1069 if(self.ctf_status != FLAG_BASE) { return; }
1071 self.active = ((self.active) ? ACTIVE_NOT : ACTIVE_ACTIVE);
1073 if(self.active == ACTIVE_ACTIVE)
1074 WaypointSprite_Ping(self.wps_flagbase);
1077 float ctf_FlagWaypoint_Customize()
1079 if(self.owner.active != ACTIVE_ACTIVE) { return false; }
1083 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1086 waypoint_spawnforitem_force(self, self.origin);
1087 self.nearestwaypointtimeout = 0; // activate waypointing again
1088 self.bot_basewaypoint = self.nearestwaypoint;
1091 string basename = "base";
1095 case NUM_TEAM_1: basename = "redbase"; break;
1096 case NUM_TEAM_2: basename = "bluebase"; break;
1097 case NUM_TEAM_3: basename = "yellowbase"; break;
1098 case NUM_TEAM_4: basename = "pinkbase"; break;
1099 default: basename = "neutralbase"; break;
1102 WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'));
1103 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1104 self.wps_flagbase.customizeentityforclient = ctf_FlagWaypoint_Customize;
1106 // captureshield setup
1107 ctf_CaptureShield_Spawn(self);
1110 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1113 self = flag; // for later usage with droptofloor()
1116 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1117 ctf_worldflaglist = flag;
1119 setattachment(flag, world, "");
1121 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1122 flag.team = teamnumber;
1123 flag.classname = "item_flag_team";
1124 flag.target = "###item###"; // wut?
1125 flag.flags = FL_ITEM | FL_NOTARGET;
1126 flag.solid = SOLID_TRIGGER;
1127 flag.takedamage = DAMAGE_NO;
1128 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1129 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1130 flag.health = flag.max_flag_health;
1131 flag.event_damage = ctf_FlagDamage;
1132 flag.pushable = true;
1133 flag.teleportable = TELEPORT_NORMAL;
1134 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1135 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1136 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1137 flag.velocity = '0 0 0';
1138 flag.mangle = flag.angles;
1139 flag.reset = ctf_Reset;
1141 flag.touch = ctf_FlagTouch;
1142 flag.think = ctf_FlagThink;
1143 flag.nextthink = time + FLAG_THINKRATE;
1144 flag.ctf_status = FLAG_BASE;
1147 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)))); }
1148 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1149 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)))); }
1150 if(flag.toucheffect == "") { flag.toucheffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_FLAG_RED_TOUCH : ((teamnumber == NUM_TEAM_2) ? EFFECT_FLAG_BLUE_TOUCH : ((teamnumber == NUM_TEAM_3) ? EFFECT_FLAG_YELLOW_TOUCH : ((teamnumber == NUM_TEAM_4) ? EFFECT_FLAG_PINK_TOUCH : EFFECT_FLAG_NEUTRAL_TOUCH)))); }
1151 if(flag.passeffect == "") { flag.passeffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_RED_PASS : ((teamnumber == NUM_TEAM_2) ? EFFECT_BLUE_PASS : ((teamnumber == NUM_TEAM_3) ? EFFECT_YELLOW_PASS : ((teamnumber == NUM_TEAM_4) ? EFFECT_PINK_PASS : EFFECT_NEUTRAL_PASS)))); }
1152 if(flag.capeffect == "") { flag.capeffectnum = ((teamnumber == NUM_TEAM_1) ? EFFECT_RED_CAP : ((teamnumber == NUM_TEAM_2) ? EFFECT_BLUE_CAP : ((teamnumber == NUM_TEAM_3) ? EFFECT_YELLOW_CAP : ((teamnumber == NUM_TEAM_4) ? EFFECT_PINK_CAP : 0)))); } // neutral flag cant be capped itself
1153 if(!flag.active) { flag.active = ACTIVE_ACTIVE; }
1156 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")))); }
1157 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
1158 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
1159 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.
1160 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")))); }
1161 if(flag.snd_flag_touch == "") { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
1162 if(flag.snd_flag_pass == "") { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
1165 precache_sound(flag.snd_flag_taken);
1166 precache_sound(flag.snd_flag_returned);
1167 precache_sound(flag.snd_flag_capture);
1168 precache_sound(flag.snd_flag_respawn);
1169 precache_sound(flag.snd_flag_dropped);
1170 precache_sound(flag.snd_flag_touch);
1171 precache_sound(flag.snd_flag_pass);
1172 precache_model(flag.model);
1173 precache_model("models/ctf/shield.md3");
1174 precache_model("models/ctf/shockwavetransring.md3");
1177 setmodel(flag, flag.model); // precision set below
1178 setsize(flag, FLAG_MIN, FLAG_MAX);
1179 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1181 if(autocvar_g_ctf_flag_glowtrails)
1183 flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : ((teamnumber == NUM_TEAM_4) ? 145 : 254))));
1184 flag.glow_size = 25;
1185 flag.glow_trail = 1;
1188 flag.effects |= EF_LOWPRECISION;
1189 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1190 if(autocvar_g_ctf_dynamiclights)
1194 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1195 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1196 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1197 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1198 default: flag.effects |= EF_DIMLIGHT; break;
1203 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1205 flag.dropped_origin = flag.origin;
1206 flag.noalign = true;
1207 flag.movetype = MOVETYPE_NONE;
1209 else // drop to floor, automatically find a platform and set that as spawn origin
1211 flag.noalign = false;
1214 flag.movetype = MOVETYPE_TOSS;
1217 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1225 // NOTE: LEGACY CODE, needs to be re-written!
1227 void havocbot_calculate_middlepoint()
1231 vector fo = '0 0 0';
1234 f = ctf_worldflaglist;
1239 f = f.ctf_worldflagnext;
1243 havocbot_ctf_middlepoint = s * (1.0 / n);
1244 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1248 entity havocbot_ctf_find_flag(entity bot)
1251 f = ctf_worldflaglist;
1254 if (CTF_SAMETEAM(bot, f))
1256 f = f.ctf_worldflagnext;
1261 entity havocbot_ctf_find_enemy_flag(entity bot)
1264 f = ctf_worldflaglist;
1269 if(CTF_DIFFTEAM(bot, f))
1276 else if(!bot.flagcarried)
1280 else if (CTF_DIFFTEAM(bot, f))
1282 f = f.ctf_worldflagnext;
1287 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1295 FOR_EACH_PLAYER(head)
1297 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1300 if(vlen(head.origin - org) < tc_radius)
1307 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1310 head = ctf_worldflaglist;
1313 if (CTF_SAMETEAM(self, head))
1315 head = head.ctf_worldflagnext;
1318 navigation_routerating(head, ratingscale, 10000);
1321 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1324 head = ctf_worldflaglist;
1327 if (CTF_SAMETEAM(self, head))
1329 head = head.ctf_worldflagnext;
1334 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1337 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1340 head = ctf_worldflaglist;
1345 if(CTF_DIFFTEAM(self, head))
1349 if(self.flagcarried)
1352 else if(!self.flagcarried)
1356 else if(CTF_DIFFTEAM(self, head))
1358 head = head.ctf_worldflagnext;
1361 navigation_routerating(head, ratingscale, 10000);
1364 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1366 if (!bot_waypoints_for_items)
1368 havocbot_goalrating_ctf_enemyflag(ratingscale);
1374 head = havocbot_ctf_find_enemy_flag(self);
1379 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1382 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1386 mf = havocbot_ctf_find_flag(self);
1388 if(mf.ctf_status == FLAG_BASE)
1392 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1395 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1398 head = ctf_worldflaglist;
1401 // flag is out in the field
1402 if(head.ctf_status != FLAG_BASE)
1403 if(head.tag_entity==world) // dropped
1407 if(vlen(org-head.origin)<df_radius)
1408 navigation_routerating(head, ratingscale, 10000);
1411 navigation_routerating(head, ratingscale, 10000);
1414 head = head.ctf_worldflagnext;
1418 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1422 head = findchainfloat(bot_pickup, true);
1425 // gather health and armor only
1427 if (head.health || head.armorvalue)
1428 if (vlen(head.origin - org) < sradius)
1430 // get the value of the item
1431 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1433 navigation_routerating(head, t * ratingscale, 500);
1439 void havocbot_ctf_reset_role(entity bot)
1441 float cdefense, cmiddle, coffense;
1442 entity mf, ef, head;
1445 if(bot.deadflag != DEAD_NO)
1448 if(vlen(havocbot_ctf_middlepoint)==0)
1449 havocbot_calculate_middlepoint();
1452 if (bot.flagcarried)
1454 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1458 mf = havocbot_ctf_find_flag(bot);
1459 ef = havocbot_ctf_find_enemy_flag(bot);
1461 // Retrieve stolen flag
1462 if(mf.ctf_status!=FLAG_BASE)
1464 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1468 // If enemy flag is taken go to the middle to intercept pursuers
1469 if(ef.ctf_status!=FLAG_BASE)
1471 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1475 // if there is only me on the team switch to offense
1477 FOR_EACH_PLAYER(head)
1478 if(SAME_TEAM(head, bot))
1483 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1487 // Evaluate best position to take
1488 // Count mates on middle position
1489 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1491 // Count mates on defense position
1492 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1494 // Count mates on offense position
1495 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1497 if(cdefense<=coffense)
1498 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1499 else if(coffense<=cmiddle)
1500 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1502 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1505 void havocbot_role_ctf_carrier()
1507 if(self.deadflag != DEAD_NO)
1509 havocbot_ctf_reset_role(self);
1513 if (self.flagcarried == world)
1515 havocbot_ctf_reset_role(self);
1519 if (self.bot_strategytime < time)
1521 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1523 navigation_goalrating_start();
1525 havocbot_goalrating_ctf_enemybase(50000);
1527 havocbot_goalrating_ctf_ourbase(50000);
1530 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1532 navigation_goalrating_end();
1534 if (self.navigation_hasgoals)
1535 self.havocbot_cantfindflag = time + 10;
1536 else if (time > self.havocbot_cantfindflag)
1538 // Can't navigate to my own base, suicide!
1539 // TODO: drop it and wander around
1540 Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');
1546 void havocbot_role_ctf_escort()
1550 if(self.deadflag != DEAD_NO)
1552 havocbot_ctf_reset_role(self);
1556 if (self.flagcarried)
1558 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1562 // If enemy flag is back on the base switch to previous role
1563 ef = havocbot_ctf_find_enemy_flag(self);
1564 if(ef.ctf_status==FLAG_BASE)
1566 self.havocbot_role = self.havocbot_previous_role;
1567 self.havocbot_role_timeout = 0;
1571 // If the flag carrier reached the base switch to defense
1572 mf = havocbot_ctf_find_flag(self);
1573 if(mf.ctf_status!=FLAG_BASE)
1574 if(vlen(ef.origin - mf.dropped_origin) < 300)
1576 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1580 // Set the role timeout if necessary
1581 if (!self.havocbot_role_timeout)
1583 self.havocbot_role_timeout = time + random() * 30 + 60;
1586 // If nothing happened just switch to previous role
1587 if (time > self.havocbot_role_timeout)
1589 self.havocbot_role = self.havocbot_previous_role;
1590 self.havocbot_role_timeout = 0;
1594 // Chase the flag carrier
1595 if (self.bot_strategytime < time)
1597 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1598 navigation_goalrating_start();
1599 havocbot_goalrating_ctf_enemyflag(30000);
1600 havocbot_goalrating_ctf_ourstolenflag(40000);
1601 havocbot_goalrating_items(10000, self.origin, 10000);
1602 navigation_goalrating_end();
1606 void havocbot_role_ctf_offense()
1611 if(self.deadflag != DEAD_NO)
1613 havocbot_ctf_reset_role(self);
1617 if (self.flagcarried)
1619 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1624 mf = havocbot_ctf_find_flag(self);
1625 ef = havocbot_ctf_find_enemy_flag(self);
1628 if(mf.ctf_status!=FLAG_BASE)
1631 pos = mf.tag_entity.origin;
1635 // Try to get it if closer than the enemy base
1636 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1638 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1643 // Escort flag carrier
1644 if(ef.ctf_status!=FLAG_BASE)
1647 pos = ef.tag_entity.origin;
1651 if(vlen(pos-mf.dropped_origin)>700)
1653 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1658 // About to fail, switch to middlefield
1661 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1665 // Set the role timeout if necessary
1666 if (!self.havocbot_role_timeout)
1667 self.havocbot_role_timeout = time + 120;
1669 if (time > self.havocbot_role_timeout)
1671 havocbot_ctf_reset_role(self);
1675 if (self.bot_strategytime < time)
1677 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1678 navigation_goalrating_start();
1679 havocbot_goalrating_ctf_ourstolenflag(50000);
1680 havocbot_goalrating_ctf_enemybase(20000);
1681 havocbot_goalrating_items(5000, self.origin, 1000);
1682 havocbot_goalrating_items(1000, self.origin, 10000);
1683 navigation_goalrating_end();
1687 // Retriever (temporary role):
1688 void havocbot_role_ctf_retriever()
1692 if(self.deadflag != DEAD_NO)
1694 havocbot_ctf_reset_role(self);
1698 if (self.flagcarried)
1700 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1704 // If flag is back on the base switch to previous role
1705 mf = havocbot_ctf_find_flag(self);
1706 if(mf.ctf_status==FLAG_BASE)
1708 havocbot_ctf_reset_role(self);
1712 if (!self.havocbot_role_timeout)
1713 self.havocbot_role_timeout = time + 20;
1715 if (time > self.havocbot_role_timeout)
1717 havocbot_ctf_reset_role(self);
1721 if (self.bot_strategytime < time)
1726 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1727 navigation_goalrating_start();
1728 havocbot_goalrating_ctf_ourstolenflag(50000);
1729 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1730 havocbot_goalrating_ctf_enemybase(30000);
1731 havocbot_goalrating_items(500, self.origin, rt_radius);
1732 navigation_goalrating_end();
1736 void havocbot_role_ctf_middle()
1740 if(self.deadflag != DEAD_NO)
1742 havocbot_ctf_reset_role(self);
1746 if (self.flagcarried)
1748 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1752 mf = havocbot_ctf_find_flag(self);
1753 if(mf.ctf_status!=FLAG_BASE)
1755 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1759 if (!self.havocbot_role_timeout)
1760 self.havocbot_role_timeout = time + 10;
1762 if (time > self.havocbot_role_timeout)
1764 havocbot_ctf_reset_role(self);
1768 if (self.bot_strategytime < time)
1772 org = havocbot_ctf_middlepoint;
1773 org.z = self.origin.z;
1775 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1776 navigation_goalrating_start();
1777 havocbot_goalrating_ctf_ourstolenflag(50000);
1778 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1779 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1780 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1781 havocbot_goalrating_items(2500, self.origin, 10000);
1782 havocbot_goalrating_ctf_enemybase(2500);
1783 navigation_goalrating_end();
1787 void havocbot_role_ctf_defense()
1791 if(self.deadflag != DEAD_NO)
1793 havocbot_ctf_reset_role(self);
1797 if (self.flagcarried)
1799 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1803 // If own flag was captured
1804 mf = havocbot_ctf_find_flag(self);
1805 if(mf.ctf_status!=FLAG_BASE)
1807 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1811 if (!self.havocbot_role_timeout)
1812 self.havocbot_role_timeout = time + 30;
1814 if (time > self.havocbot_role_timeout)
1816 havocbot_ctf_reset_role(self);
1819 if (self.bot_strategytime < time)
1824 org = mf.dropped_origin;
1825 mp_radius = havocbot_ctf_middlepoint_radius;
1827 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1828 navigation_goalrating_start();
1830 // if enemies are closer to our base, go there
1831 entity head, closestplayer = world;
1832 float distance, bestdistance = 10000;
1833 FOR_EACH_PLAYER(head)
1835 if(head.deadflag!=DEAD_NO)
1838 distance = vlen(org - head.origin);
1839 if(distance<bestdistance)
1841 closestplayer = head;
1842 bestdistance = distance;
1847 if(DIFF_TEAM(closestplayer, self))
1848 if(vlen(org - self.origin)>1000)
1849 if(checkpvs(self.origin,closestplayer)||random()<0.5)
1850 havocbot_goalrating_ctf_ourbase(30000);
1852 havocbot_goalrating_ctf_ourstolenflag(20000);
1853 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
1854 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
1855 havocbot_goalrating_items(10000, org, mp_radius);
1856 havocbot_goalrating_items(5000, self.origin, 10000);
1857 navigation_goalrating_end();
1861 void havocbot_role_ctf_setrole(entity bot, float role)
1863 dprint(strcat(bot.netname," switched to "));
1866 case HAVOCBOT_CTF_ROLE_CARRIER:
1868 bot.havocbot_role = havocbot_role_ctf_carrier;
1869 bot.havocbot_role_timeout = 0;
1870 bot.havocbot_cantfindflag = time + 10;
1871 bot.bot_strategytime = 0;
1873 case HAVOCBOT_CTF_ROLE_DEFENSE:
1875 bot.havocbot_role = havocbot_role_ctf_defense;
1876 bot.havocbot_role_timeout = 0;
1878 case HAVOCBOT_CTF_ROLE_MIDDLE:
1880 bot.havocbot_role = havocbot_role_ctf_middle;
1881 bot.havocbot_role_timeout = 0;
1883 case HAVOCBOT_CTF_ROLE_OFFENSE:
1885 bot.havocbot_role = havocbot_role_ctf_offense;
1886 bot.havocbot_role_timeout = 0;
1888 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1889 dprint("retriever");
1890 bot.havocbot_previous_role = bot.havocbot_role;
1891 bot.havocbot_role = havocbot_role_ctf_retriever;
1892 bot.havocbot_role_timeout = time + 10;
1893 bot.bot_strategytime = 0;
1895 case HAVOCBOT_CTF_ROLE_ESCORT:
1897 bot.havocbot_previous_role = bot.havocbot_role;
1898 bot.havocbot_role = havocbot_role_ctf_escort;
1899 bot.havocbot_role_timeout = time + 30;
1900 bot.bot_strategytime = 0;
1911 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
1915 float t = 0, t2 = 0, t3 = 0;
1917 // initially clear items so they can be set as necessary later.
1918 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
1919 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
1920 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
1921 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
1922 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
1923 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1925 // scan through all the flags and notify the client about them
1926 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1928 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
1929 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
1930 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
1931 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
1932 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; }
1934 switch(flag.ctf_status)
1939 if((flag.owner == self) || (flag.pass_sender == self))
1940 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
1942 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
1947 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
1953 // item for stopping players from capturing the flag too often
1954 if(self.ctf_captureshielded)
1955 self.ctf_flagstatus |= CTF_SHIELDED;
1957 // update the health of the flag carrier waypointsprite
1958 if(self.wps_flagcarrier)
1959 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
1964 MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
1966 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
1968 if(frag_target == frag_attacker) // damage done to yourself
1970 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
1971 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
1973 else // damage done to everyone else
1975 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
1976 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
1979 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
1981 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)))
1982 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
1984 frag_target.wps_helpme_time = time;
1985 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
1987 // todo: add notification for when flag carrier needs help?
1992 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
1994 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
1996 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
1997 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2000 if(frag_target.flagcarried)
2002 entity tmp_entity = frag_target.flagcarried;
2003 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2004 tmp_entity.ctf_dropper = world;
2010 MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
2013 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2016 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
2018 entity flag; // temporary entity for the search method
2020 if(self.flagcarried)
2021 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2023 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2025 if(flag.pass_sender == self) { flag.pass_sender = world; }
2026 if(flag.pass_target == self) { flag.pass_target = world; }
2027 if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
2033 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
2035 if(self.flagcarried)
2036 if(!autocvar_g_ctf_portalteleport)
2037 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2042 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
2044 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2046 entity player = self;
2048 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2050 // pass the flag to a team mate
2051 if(autocvar_g_ctf_pass)
2053 entity head, closest_target = world;
2054 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2056 while(head) // find the closest acceptable target to pass to
2058 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2059 if(head != player && SAME_TEAM(head, player))
2060 if(!head.speedrunning && !head.vehicle)
2062 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2063 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2064 vector passer_center = CENTER_OR_VIEWOFS(player);
2066 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2068 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2070 if(IS_BOT_CLIENT(head))
2072 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2073 ctf_Handle_Throw(head, player, DROP_PASS);
2077 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2078 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2080 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2083 else if(player.flagcarried)
2087 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2088 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2089 { closest_target = head; }
2091 else { closest_target = head; }
2098 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2101 // throw the flag in front of you
2102 if(autocvar_g_ctf_throw && player.flagcarried)
2104 if(player.throw_count == -1)
2106 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2108 player.throw_prevtime = time;
2109 player.throw_count = 1;
2110 ctf_Handle_Throw(player, world, DROP_THROW);
2115 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2121 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2122 else { player.throw_count += 1; }
2123 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2125 player.throw_prevtime = time;
2126 ctf_Handle_Throw(player, world, DROP_THROW);
2135 MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
2137 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2139 self.wps_helpme_time = time;
2140 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2142 else // create a normal help me waypointsprite
2144 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');
2145 WaypointSprite_Ping(self.wps_helpme);
2151 MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
2153 if(vh_player.flagcarried)
2155 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2157 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2161 setattachment(vh_player.flagcarried, vh_vehicle, "");
2162 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2163 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2164 //vh_player.flagcarried.angles = '0 0 0';
2172 MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
2174 if(vh_player.flagcarried)
2176 setattachment(vh_player.flagcarried, vh_player, "");
2177 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2178 vh_player.flagcarried.scale = FLAG_SCALE;
2179 vh_player.flagcarried.angles = '0 0 0';
2186 MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
2188 if(self.flagcarried)
2190 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));
2191 ctf_RespawnFlag(self.flagcarried);
2198 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
2200 entity flag; // temporary entity for the search method
2202 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2204 switch(flag.ctf_status)
2209 // lock the flag, game is over
2210 flag.movetype = MOVETYPE_NONE;
2211 flag.takedamage = DAMAGE_NO;
2212 flag.solid = SOLID_NOT;
2213 flag.nextthink = false; // stop thinking
2215 //dprint("stopping the ", flag.netname, " from moving.\n");
2223 // do nothing for these flags
2232 MUTATOR_HOOKFUNCTION(ctf_BotRoles)
2234 havocbot_ctf_reset_role(self);
2238 MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
2240 //ret_float = ctf_teams;
2241 ret_string = "ctf_team";
2245 MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
2247 self.ctf_flagstatus = other.ctf_flagstatus;
2251 MUTATOR_HOOKFUNCTION(ctf_FormatMessage)
2253 entity bluefc = world, redfc = world, yellowfc = world, pinkfc = world, tmp_entity; // NOTE: blue = red player
2254 entity tfc = world, sfc = world;
2256 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2258 if(tmp_entity.owner)
2260 switch(tmp_entity.team)
2262 case NUM_TEAM_1: redfc = tmp_entity.owner; break;
2263 case NUM_TEAM_2: bluefc = tmp_entity.owner; break;
2264 case NUM_TEAM_3: yellowfc = tmp_entity.owner; break;
2265 case NUM_TEAM_4: pinkfc = tmp_entity.owner; break;
2268 if(SAME_TEAM(tmp_entity.owner, self)) { tfc = tmp_entity.owner; }
2269 if(SAME_TEAM(tmp_entity, self)) { sfc = tmp_entity.owner; }
2273 switch(format_escape)
2275 case "r": format_replacement = ((tfc) ? tfc.netname : "(nobody)"); break;
2276 case "R": format_replacement = ((sfc) ? sfc.netname : "(nobody)"); break;
2277 case "t": format_replacement = ((redfc) ? redfc.netname : "(nobody)"); break;
2278 case "T": format_replacement = ((bluefc) ? bluefc.netname : "(nobody)"); break;
2279 case "p": format_replacement = ((yellowfc) ? yellowfc.netname : "(nobody)"); break;
2280 case "P": format_replacement = ((pinkfc) ? pinkfc.netname : "(nobody)"); break;
2290 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2291 CTF flag for team one (Red).
2293 "angle" Angle the flag will point (minus 90 degrees)...
2294 "model" model to use, note this needs red and blue as skins 0 and 1...
2295 "noise" sound played when flag is picked up...
2296 "noise1" sound played when flag is returned by a teammate...
2297 "noise2" sound played when flag is captured...
2298 "noise3" sound played when flag is lost in the field and respawns itself...
2299 "noise4" sound played when flag is dropped by a player...
2300 "noise5" sound played when flag touches the ground... */
2301 void spawnfunc_item_flag_team1()
2303 if(!g_ctf) { remove(self); return; }
2305 ctf_FlagSetup(NUM_TEAM_1, self);
2308 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2309 CTF flag for team two (Blue).
2311 "angle" Angle the flag will point (minus 90 degrees)...
2312 "model" model to use, note this needs red and blue as skins 0 and 1...
2313 "noise" sound played when flag is picked up...
2314 "noise1" sound played when flag is returned by a teammate...
2315 "noise2" sound played when flag is captured...
2316 "noise3" sound played when flag is lost in the field and respawns itself...
2317 "noise4" sound played when flag is dropped by a player...
2318 "noise5" sound played when flag touches the ground... */
2319 void spawnfunc_item_flag_team2()
2321 if(!g_ctf) { remove(self); return; }
2323 ctf_FlagSetup(NUM_TEAM_2, self);
2326 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2327 CTF flag for team three (Yellow).
2329 "angle" Angle the flag will point (minus 90 degrees)...
2330 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2331 "noise" sound played when flag is picked up...
2332 "noise1" sound played when flag is returned by a teammate...
2333 "noise2" sound played when flag is captured...
2334 "noise3" sound played when flag is lost in the field and respawns itself...
2335 "noise4" sound played when flag is dropped by a player...
2336 "noise5" sound played when flag touches the ground... */
2337 void spawnfunc_item_flag_team3()
2339 if(!g_ctf) { remove(self); return; }
2341 ctf_FlagSetup(NUM_TEAM_3, self);
2344 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2345 CTF flag for team four (Pink).
2347 "angle" Angle the flag will point (minus 90 degrees)...
2348 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2349 "noise" sound played when flag is picked up...
2350 "noise1" sound played when flag is returned by a teammate...
2351 "noise2" sound played when flag is captured...
2352 "noise3" sound played when flag is lost in the field and respawns itself...
2353 "noise4" sound played when flag is dropped by a player...
2354 "noise5" sound played when flag touches the ground... */
2355 void spawnfunc_item_flag_team4()
2357 if(!g_ctf) { remove(self); return; }
2359 ctf_FlagSetup(NUM_TEAM_4, self);
2362 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2365 "angle" Angle the flag will point (minus 90 degrees)...
2366 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2367 "noise" sound played when flag is picked up...
2368 "noise1" sound played when flag is returned by a teammate...
2369 "noise2" sound played when flag is captured...
2370 "noise3" sound played when flag is lost in the field and respawns itself...
2371 "noise4" sound played when flag is dropped by a player...
2372 "noise5" sound played when flag touches the ground... */
2373 void spawnfunc_item_flag_neutral()
2375 if(!g_ctf) { remove(self); return; }
2376 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2378 ctf_FlagSetup(0, self);
2381 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2382 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2383 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.
2385 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2386 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2387 void spawnfunc_ctf_team()
2389 if(!g_ctf) { remove(self); return; }
2391 self.classname = "ctf_team";
2392 self.team = self.cnt + 1;
2395 // compatibility for quake maps
2396 void spawnfunc_team_CTF_redflag() { spawnfunc_item_flag_team1(); }
2397 void spawnfunc_team_CTF_blueflag() { spawnfunc_item_flag_team2(); }
2398 void spawnfunc_team_CTF_redplayer() { spawnfunc_info_player_team1(); }
2399 void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2(); }
2400 void spawnfunc_team_CTF_redspawn() { spawnfunc_info_player_team1(); }
2401 void spawnfunc_team_CTF_bluespawn() { spawnfunc_info_player_team2(); }
2409 void ctf_ScoreRules(float teams)
2411 CheckAllowedTeams(world);
2412 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2413 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2414 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2415 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2416 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2417 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2418 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2419 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2420 ScoreRules_basics_end();
2423 // code from here on is just to support maps that don't have flag and team entities
2424 void ctf_SpawnTeam (string teamname, float teamcolor)
2429 self.classname = "ctf_team";
2430 self.netname = teamname;
2431 self.cnt = teamcolor;
2433 spawnfunc_ctf_team();
2438 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2443 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2445 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2446 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2447 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2450 ctf_teams = bound(2, ctf_teams, 4);
2452 // if no teams are found, spawn defaults
2453 if(find(world, classname, "ctf_team") == world)
2455 print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2457 for(i = 1; i <= ctf_teams; ++i)
2458 ctf_SpawnTeam(Team_ColorName(Team_NumberToTeam(i)), Team_NumberToTeam(i) - 1);
2461 ctf_ScoreRules(ctf_teams);
2464 void ctf_Initialize()
2466 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2468 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2469 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2470 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2472 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2474 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2478 MUTATOR_DEFINITION(gamemode_ctf)
2480 MUTATOR_HOOK(MakePlayerObserver, ctf_RemovePlayer, CBC_ORDER_ANY);
2481 MUTATOR_HOOK(ClientDisconnect, ctf_RemovePlayer, CBC_ORDER_ANY);
2482 MUTATOR_HOOK(PlayerDies, ctf_PlayerDies, CBC_ORDER_ANY);
2483 MUTATOR_HOOK(MatchEnd, ctf_MatchEnd, CBC_ORDER_ANY);
2484 MUTATOR_HOOK(PortalTeleport, ctf_PortalTeleport, CBC_ORDER_ANY);
2485 MUTATOR_HOOK(GiveFragsForKill, ctf_GiveFragsForKill, CBC_ORDER_ANY);
2486 MUTATOR_HOOK(PlayerPreThink, ctf_PlayerPreThink, CBC_ORDER_ANY);
2487 MUTATOR_HOOK(PlayerDamage_Calculate, ctf_PlayerDamage, CBC_ORDER_ANY);
2488 MUTATOR_HOOK(PlayerUseKey, ctf_PlayerUseKey, CBC_ORDER_ANY);
2489 MUTATOR_HOOK(HelpMePing, ctf_HelpMePing, CBC_ORDER_ANY);
2490 MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
2491 MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
2492 MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
2493 MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
2494 MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
2495 MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
2496 MUTATOR_HOOK(FormatMessage, ctf_FormatMessage, CBC_ORDER_ANY);
2500 if(time > 1) // game loads at time 1
2501 error("This is a game type and it cannot be added at runtime.");
2505 MUTATOR_ONROLLBACK_OR_REMOVE
2507 // we actually cannot roll back ctf_Initialize here
2508 // BUT: we don't need to! If this gets called, adding always
2514 print("This is a game type and it cannot be removed at runtime.");