7 REGISTER_MUTATOR(ctf, false)
11 if (time > 1) // game loads at time 1
12 error("This is a game type and it cannot be added at runtime.");
16 SetLimits(autocvar_capturelimit_override, autocvar_captureleadlimit_override, -1, -1);
17 have_team_spawns = -1; // request team spawns
20 MUTATOR_ONROLLBACK_OR_REMOVE
22 // we actually cannot roll back ctf_Initialize here
23 // BUT: we don't need to! If this gets called, adding always
29 LOG_INFO("This is a game type and it cannot be removed at runtime.");
39 void ctf_RespawnFlag(entity flag);
41 // score rule declarations
42 const int ST_CTF_CAPS = 1;
43 const int SP_CTF_CAPS = 4;
44 const int SP_CTF_CAPTIME = 5;
45 const int SP_CTF_PICKUPS = 6;
46 const int SP_CTF_DROPS = 7;
47 const int SP_CTF_FCKILLS = 8;
48 const int SP_CTF_RETURNS = 9;
51 ATTRIB(Flag, m_mins, vector, PL_MIN_CONST + '0 0 -13')
52 ATTRIB(Flag, m_maxs, vector, PL_MAX_CONST + '0 0 -13')
54 Flag CTF_FLAG; STATIC_INIT(Flag) { CTF_FLAG = NEW(Flag); }
55 void ctf_FlagTouch() { SELFPARAM(); ITEM_HANDLE(Pickup, CTF_FLAG, this, other); }
57 // flag constants // for most of these, there is just one question to be asked: WHYYYYY?
59 const float FLAG_SCALE = 0.6;
61 const float FLAG_THINKRATE = 0.2;
62 const float FLAG_TOUCHRATE = 0.5;
63 const float WPFE_THINKRATE = 0.5;
65 const vector FLAG_DROP_OFFSET = ('0 0 32');
66 const vector FLAG_CARRY_OFFSET = ('-16 0 8');
67 #define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
68 const vector FLAG_WAYPOINT_OFFSET = ('0 0 64');
69 const vector FLAG_FLOAT_OFFSET = ('0 0 32');
70 const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10');
72 const vector VEHICLE_FLAG_OFFSET = ('0 0 96');
73 const float VEHICLE_FLAG_SCALE = 1.0;
76 #define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
77 #define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color)
78 #define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1')
81 #define snd_flag_taken noise
82 #define snd_flag_returned noise1
83 #define snd_flag_capture noise2
84 #define snd_flag_respawn noise3
85 .string snd_flag_dropped;
86 .string snd_flag_touch;
87 .string snd_flag_pass;
94 // list of flags on the map
95 entity ctf_worldflaglist;
96 .entity ctf_worldflagnext;
97 .entity ctf_staleflagnext;
100 .entity bot_basewaypoint; // flag waypointsprite
102 .entity wps_flagbase;
103 .entity wps_flagcarrier;
104 .entity wps_flagdropped;
105 .entity wps_enemyflagcarrier;
106 .float wps_helpme_time;
107 bool wpforenemy_announced;
108 float wpforenemy_nextthink;
111 const int FLAG_BASE = 1;
112 const int FLAG_DROPPED = 2;
113 const int FLAG_CARRY = 3;
114 const int FLAG_PASSING = 4;
116 const int DROP_NORMAL = 1;
117 const int DROP_THROW = 2;
118 const int DROP_PASS = 3;
119 const int DROP_RESET = 4;
121 const int PICKUP_BASE = 1;
122 const int PICKUP_DROPPED = 2;
124 const int CAPTURE_NORMAL = 1;
125 const int CAPTURE_DROPPED = 2;
127 const int RETURN_TIMEOUT = 1;
128 const int RETURN_DROPPED = 2;
129 const int RETURN_DAMAGE = 3;
130 const int RETURN_SPEEDRUN = 4;
131 const int RETURN_NEEDKILL = 5;
133 void ctf_Handle_Throw(entity player, entity receiver, float droptype);
136 #define ctf_spawnorigin dropped_origin
137 bool ctf_stalemate; // indicates that a stalemate is active
138 float ctf_captimerecord; // record time for capturing the flag
139 .float ctf_pickuptime;
141 .int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
142 .entity ctf_dropper; // don't allow spam of dropping the flag
143 .int max_flag_health;
144 .float next_take_time;
145 .bool ctf_flagdamaged;
148 // passing/throwing properties
149 .float pass_distance;
152 .float throw_antispam;
153 .float throw_prevtime;
156 // CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
157 .bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
158 float ctf_captureshield_min_negscore; // punish at -20 points
159 float ctf_captureshield_max_ratio; // punish at most 30% of each team
160 float ctf_captureshield_force; // push force of the shield
163 bool ctf_oneflag; // indicates whether or not a neutral flag has been found
166 const int HAVOCBOT_CTF_ROLE_NONE = 0;
167 const int HAVOCBOT_CTF_ROLE_DEFENSE = 2;
168 const int HAVOCBOT_CTF_ROLE_MIDDLE = 4;
169 const int HAVOCBOT_CTF_ROLE_OFFENSE = 8;
170 const int HAVOCBOT_CTF_ROLE_CARRIER = 16;
171 const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32;
172 const int HAVOCBOT_CTF_ROLE_ESCORT = 64;
174 .bool havocbot_cantfindflag;
176 vector havocbot_ctf_middlepoint;
177 float havocbot_ctf_middlepoint_radius;
179 void havocbot_role_ctf_setrole(entity bot, int role);
182 #define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b))
183 #define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b))
185 // networked flag statuses
186 .int ctf_flagstatus = _STAT(CTF_FLAGSTATUS);
189 const int CTF_RED_FLAG_TAKEN = 1;
190 const int CTF_RED_FLAG_LOST = 2;
191 const int CTF_RED_FLAG_CARRYING = 3;
192 const int CTF_BLUE_FLAG_TAKEN = 4;
193 const int CTF_BLUE_FLAG_LOST = 8;
194 const int CTF_BLUE_FLAG_CARRYING = 12;
195 const int CTF_YELLOW_FLAG_TAKEN = 16;
196 const int CTF_YELLOW_FLAG_LOST = 32;
197 const int CTF_YELLOW_FLAG_CARRYING = 48;
198 const int CTF_PINK_FLAG_TAKEN = 64;
199 const int CTF_PINK_FLAG_LOST = 128;
200 const int CTF_PINK_FLAG_CARRYING = 192;
201 const int CTF_NEUTRAL_FLAG_TAKEN = 256;
202 const int CTF_NEUTRAL_FLAG_LOST = 512;
203 const int CTF_NEUTRAL_FLAG_CARRYING = 768;
204 const int CTF_FLAG_NEUTRAL = 2048;
205 const int CTF_SHIELDED = 4096;
208 #ifdef IMPLEMENTATION
211 #include "../../../common/vehicles/all.qh"
212 #include "../../teamplay.qh"
215 #include "../../../lib/warpzone/common.qh"
217 bool autocvar_g_ctf_allow_vehicle_carry;
218 bool autocvar_g_ctf_allow_vehicle_touch;
219 bool autocvar_g_ctf_allow_monster_touch;
220 bool autocvar_g_ctf_throw;
221 float autocvar_g_ctf_throw_angle_max;
222 float autocvar_g_ctf_throw_angle_min;
223 int autocvar_g_ctf_throw_punish_count;
224 float autocvar_g_ctf_throw_punish_delay;
225 float autocvar_g_ctf_throw_punish_time;
226 float autocvar_g_ctf_throw_strengthmultiplier;
227 float autocvar_g_ctf_throw_velocity_forward;
228 float autocvar_g_ctf_throw_velocity_up;
229 float autocvar_g_ctf_drop_velocity_up;
230 float autocvar_g_ctf_drop_velocity_side;
231 bool autocvar_g_ctf_oneflag_reverse;
232 bool autocvar_g_ctf_portalteleport;
233 bool autocvar_g_ctf_pass;
234 float autocvar_g_ctf_pass_arc;
235 float autocvar_g_ctf_pass_arc_max;
236 float autocvar_g_ctf_pass_directional_max;
237 float autocvar_g_ctf_pass_directional_min;
238 float autocvar_g_ctf_pass_radius;
239 float autocvar_g_ctf_pass_wait;
240 bool autocvar_g_ctf_pass_request;
241 float autocvar_g_ctf_pass_turnrate;
242 float autocvar_g_ctf_pass_timelimit;
243 float autocvar_g_ctf_pass_velocity;
244 bool autocvar_g_ctf_dynamiclights;
245 float autocvar_g_ctf_flag_collect_delay;
246 float autocvar_g_ctf_flag_damageforcescale;
247 bool autocvar_g_ctf_flag_dropped_waypoint;
248 bool autocvar_g_ctf_flag_dropped_floatinwater;
249 bool autocvar_g_ctf_flag_glowtrails;
250 int autocvar_g_ctf_flag_health;
251 bool autocvar_g_ctf_flag_return;
252 bool autocvar_g_ctf_flag_return_carrying;
253 float autocvar_g_ctf_flag_return_carried_radius;
254 float autocvar_g_ctf_flag_return_time;
255 bool autocvar_g_ctf_flag_return_when_unreachable;
256 float autocvar_g_ctf_flag_return_damage;
257 float autocvar_g_ctf_flag_return_damage_delay;
258 float autocvar_g_ctf_flag_return_dropped;
259 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
260 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
261 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
262 float autocvar_g_ctf_flagcarrier_selfforcefactor;
263 float autocvar_g_ctf_flagcarrier_damagefactor;
264 float autocvar_g_ctf_flagcarrier_forcefactor;
265 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
266 bool autocvar_g_ctf_fullbrightflags;
267 bool autocvar_g_ctf_ignore_frags;
268 int autocvar_g_ctf_score_capture;
269 int autocvar_g_ctf_score_capture_assist;
270 int autocvar_g_ctf_score_kill;
271 int autocvar_g_ctf_score_penalty_drop;
272 int autocvar_g_ctf_score_penalty_returned;
273 int autocvar_g_ctf_score_pickup_base;
274 int autocvar_g_ctf_score_pickup_dropped_early;
275 int autocvar_g_ctf_score_pickup_dropped_late;
276 int autocvar_g_ctf_score_return;
277 float autocvar_g_ctf_shield_force;
278 float autocvar_g_ctf_shield_max_ratio;
279 int autocvar_g_ctf_shield_min_negscore;
280 bool autocvar_g_ctf_stalemate;
281 int autocvar_g_ctf_stalemate_endcondition;
282 float autocvar_g_ctf_stalemate_time;
283 bool autocvar_g_ctf_reverse;
284 float autocvar_g_ctf_dropped_capture_delay;
285 float autocvar_g_ctf_dropped_capture_radius;
287 void ctf_FakeTimeLimit(entity e, float t)
290 WriteByte(MSG_ONE, 3); // svc_updatestat
291 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
293 WriteCoord(MSG_ONE, autocvar_timelimit);
295 WriteCoord(MSG_ONE, (t + 1) / 60);
298 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
300 if(autocvar_sv_eventlog)
301 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
302 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
305 void ctf_CaptureRecord(entity flag, entity player)
307 float cap_record = ctf_captimerecord;
308 float cap_time = (time - flag.ctf_pickuptime);
309 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
312 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
313 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)); }
314 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)); }
315 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)); }
317 // write that shit in the database
318 if(!ctf_oneflag) // but not in 1-flag mode
319 if((!ctf_captimerecord) || (cap_time < cap_record))
321 ctf_captimerecord = cap_time;
322 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
323 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
324 write_recordmarker(player, (time - cap_time), cap_time);
328 void ctf_FlagcarrierWaypoints(entity player)
330 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
331 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
332 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
333 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
336 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
338 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
339 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
340 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
341 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
344 if(current_height) // make sure we can actually do this arcing path
346 targpos = (to + ('0 0 1' * current_height));
347 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
348 if(trace_fraction < 1)
350 //print("normal arc line failed, trying to find new pos...");
351 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
352 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
353 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
354 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
355 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
358 else { targpos = to; }
360 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
362 vector desired_direction = normalize(targpos - from);
363 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
364 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
367 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
369 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
371 // directional tracing only
373 makevectors(passer_angle);
375 // find the closest point on the enemy to the center of the attack
376 float h; // hypotenuse, which is the distance between attacker to head
377 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
379 h = vlen(head_center - passer_center);
380 a = h * (normalize(head_center - passer_center) * v_forward);
382 vector nearest_on_line = (passer_center + a * v_forward);
383 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
385 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
386 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
388 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
393 else { return true; }
397 // =======================
398 // CaptureShield Functions
399 // =======================
401 bool ctf_CaptureShield_CheckStatus(entity p)
403 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
405 int players_worseeq, players_total;
407 if(ctf_captureshield_max_ratio <= 0)
410 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
411 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
412 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
413 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
415 sr = ((s - s2) + (s3 + s4));
417 if(sr >= -ctf_captureshield_min_negscore)
420 players_total = players_worseeq = 0;
425 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
426 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
427 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
428 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
430 ser = ((se - se2) + (se3 + se4));
437 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
438 // use this rule here
440 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
446 void ctf_CaptureShield_Update(entity player, bool wanted_status)
448 bool updated_status = ctf_CaptureShield_CheckStatus(player);
449 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
451 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
452 player.ctf_captureshielded = updated_status;
456 bool ctf_CaptureShield_Customize()
458 if(!other.ctf_captureshielded) { return false; }
459 if(CTF_SAMETEAM(self, other)) { return false; }
464 void ctf_CaptureShield_Touch()
466 if(!other.ctf_captureshielded) { return; }
467 if(CTF_SAMETEAM(self, other)) { return; }
469 vector mymid = (self.absmin + self.absmax) * 0.5;
470 vector othermid = (other.absmin + other.absmax) * 0.5;
472 Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
473 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
476 void ctf_CaptureShield_Spawn(entity flag)
478 entity shield = new(ctf_captureshield);
481 shield.team = self.team;
482 shield.touch = ctf_CaptureShield_Touch;
483 shield.customizeentityforclient = ctf_CaptureShield_Customize;
484 shield.effects = EF_ADDITIVE;
485 shield.movetype = MOVETYPE_NOCLIP;
486 shield.solid = SOLID_TRIGGER;
487 shield.avelocity = '7 0 11';
490 setorigin(shield, self.origin);
491 setmodel(shield, MDL_CTF_SHIELD);
492 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
496 // ====================
497 // Drop/Pass/Throw Code
498 // ====================
500 void ctf_Handle_Drop(entity flag, entity player, int droptype)
503 player = (player ? player : flag.pass_sender);
506 flag.movetype = MOVETYPE_TOSS;
507 flag.takedamage = DAMAGE_YES;
508 flag.angles = '0 0 0';
509 flag.health = flag.max_flag_health;
510 flag.ctf_droptime = time;
511 flag.ctf_dropper = player;
512 flag.ctf_status = FLAG_DROPPED;
514 // messages and sounds
515 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
516 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
517 ctf_EventLog("dropped", player.team, player);
520 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
521 PlayerScore_Add(player, SP_CTF_DROPS, 1);
524 if(autocvar_g_ctf_flag_dropped_waypoint) {
525 entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
526 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
529 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
531 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
532 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
535 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
537 if(droptype == DROP_PASS)
539 flag.pass_distance = 0;
540 flag.pass_sender = world;
541 flag.pass_target = world;
545 void ctf_Handle_Retrieve(entity flag, entity player)
547 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
548 entity sender = flag.pass_sender;
550 // transfer flag to player
552 flag.owner.flagcarried = flag;
557 setattachment(flag, player.vehicle, "");
558 setorigin(flag, VEHICLE_FLAG_OFFSET);
559 flag.scale = VEHICLE_FLAG_SCALE;
563 setattachment(flag, player, "");
564 setorigin(flag, FLAG_CARRY_OFFSET);
566 flag.movetype = MOVETYPE_NONE;
567 flag.takedamage = DAMAGE_NO;
568 flag.solid = SOLID_NOT;
569 flag.angles = '0 0 0';
570 flag.ctf_status = FLAG_CARRY;
572 // messages and sounds
573 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
574 ctf_EventLog("receive", flag.team, player);
576 FOR_EACH_REALPLAYER(tmp_player)
578 if(tmp_player == sender)
579 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);
580 else if(tmp_player == player)
581 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);
582 else if(SAME_TEAM(tmp_player, sender))
583 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);
586 // create new waypoint
587 ctf_FlagcarrierWaypoints(player);
589 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
590 player.throw_antispam = sender.throw_antispam;
592 flag.pass_distance = 0;
593 flag.pass_sender = world;
594 flag.pass_target = world;
597 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
599 entity flag = player.flagcarried;
600 vector targ_origin, flag_velocity;
602 if(!flag) { return; }
603 if((droptype == DROP_PASS) && !receiver) { return; }
605 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
608 setattachment(flag, world, "");
609 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
610 flag.owner.flagcarried = world;
612 flag.solid = SOLID_TRIGGER;
613 flag.ctf_dropper = player;
614 flag.ctf_droptime = time;
616 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
623 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
624 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
625 WarpZone_RefSys_Copy(flag, receiver);
626 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
627 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
629 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
630 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
633 flag.movetype = MOVETYPE_FLY;
634 flag.takedamage = DAMAGE_NO;
635 flag.pass_sender = player;
636 flag.pass_target = receiver;
637 flag.ctf_status = FLAG_PASSING;
640 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
641 WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
642 ctf_EventLog("pass", flag.team, player);
648 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'));
650 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
651 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
652 ctf_Handle_Drop(flag, player, droptype);
658 flag.velocity = '0 0 0'; // do nothing
665 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);
666 ctf_Handle_Drop(flag, player, droptype);
671 // kill old waypointsprite
672 WaypointSprite_Ping(player.wps_flagcarrier);
673 WaypointSprite_Kill(player.wps_flagcarrier);
675 if(player.wps_enemyflagcarrier)
676 WaypointSprite_Kill(player.wps_enemyflagcarrier);
679 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
682 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
684 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
691 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
693 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
694 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
695 entity player_team_flag = world, tmp_entity;
696 float old_time, new_time;
698 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
699 if(CTF_DIFFTEAM(player, flag)) { return; }
702 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
703 if(SAME_TEAM(tmp_entity, player))
705 player_team_flag = tmp_entity;
709 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
711 player.throw_prevtime = time;
712 player.throw_count = 0;
714 // messages and sounds
715 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
716 ctf_CaptureRecord(enemy_flag, player);
717 _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);
721 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
722 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
727 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
728 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
730 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
731 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
732 if(!old_time || new_time < old_time)
733 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
736 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
737 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
740 if(capturetype == CAPTURE_NORMAL)
742 WaypointSprite_Kill(player.wps_flagcarrier);
743 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
745 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
746 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
750 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
751 ctf_RespawnFlag(enemy_flag);
754 void ctf_Handle_Return(entity flag, entity player)
756 // messages and sounds
757 if(IS_MONSTER(player))
759 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
763 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
764 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
766 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
767 ctf_EventLog("return", flag.team, player);
770 if(IS_PLAYER(player))
772 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
773 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
775 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
778 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
782 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
783 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
784 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
788 if(player.flagcarried == flag)
789 WaypointSprite_Kill(player.wps_flagcarrier);
792 ctf_RespawnFlag(flag);
795 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
798 float pickup_dropped_score; // used to calculate dropped pickup score
799 entity tmp_entity; // temporary entity
801 // attach the flag to the player
803 player.flagcarried = flag;
806 setattachment(flag, player.vehicle, "");
807 setorigin(flag, VEHICLE_FLAG_OFFSET);
808 flag.scale = VEHICLE_FLAG_SCALE;
812 setattachment(flag, player, "");
813 setorigin(flag, FLAG_CARRY_OFFSET);
817 flag.movetype = MOVETYPE_NONE;
818 flag.takedamage = DAMAGE_NO;
819 flag.solid = SOLID_NOT;
820 flag.angles = '0 0 0';
821 flag.ctf_status = FLAG_CARRY;
825 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
826 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
830 // messages and sounds
831 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
832 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
833 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
834 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
835 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)); }
837 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);
840 FOR_EACH_PLAYER(tmp_entity)
841 if(tmp_entity != player)
842 if(DIFF_TEAM(player, tmp_entity))
843 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
846 FOR_EACH_PLAYER(tmp_entity)
847 if(tmp_entity != player)
848 if(CTF_SAMETEAM(flag, tmp_entity))
849 if(SAME_TEAM(player, tmp_entity))
850 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
852 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);
854 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
857 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
858 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
863 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
864 ctf_EventLog("steal", flag.team, player);
870 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);
871 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);
872 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
873 PlayerTeamScore_AddScore(player, pickup_dropped_score);
874 ctf_EventLog("pickup", flag.team, player);
882 if(pickuptype == PICKUP_BASE)
884 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
885 if((player.speedrunning) && (ctf_captimerecord))
886 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
890 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
893 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
894 ctf_FlagcarrierWaypoints(player);
895 WaypointSprite_Ping(player.wps_flagcarrier);
899 // ===================
900 // Main Flag Functions
901 // ===================
903 void ctf_CheckFlagReturn(entity flag, int returntype)
905 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
907 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
909 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
913 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;
914 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;
915 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;
916 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;
920 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
922 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
923 ctf_EventLog("returned", flag.team, world);
924 ctf_RespawnFlag(flag);
929 bool ctf_Stalemate_Customize()
931 // make spectators see what the player would see
933 e = WaypointSprite_getviewentity(other);
934 wp_owner = self.owner;
937 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
938 if(SAME_TEAM(wp_owner, e)) { return false; }
939 if(!IS_PLAYER(e)) { return false; }
944 void ctf_CheckStalemate()
947 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
950 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
952 // build list of stale flags
953 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
955 if(autocvar_g_ctf_stalemate)
956 if(tmp_entity.ctf_status != FLAG_BASE)
957 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
959 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
960 ctf_staleflaglist = tmp_entity;
962 switch(tmp_entity.team)
964 case NUM_TEAM_1: ++stale_red_flags; break;
965 case NUM_TEAM_2: ++stale_blue_flags; break;
966 case NUM_TEAM_3: ++stale_yellow_flags; break;
967 case NUM_TEAM_4: ++stale_pink_flags; break;
968 default: ++stale_neutral_flags; break;
974 stale_flags = (stale_neutral_flags >= 1);
976 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
978 if(ctf_oneflag && stale_flags == 1)
979 ctf_stalemate = true;
980 else if(stale_flags >= 2)
981 ctf_stalemate = true;
982 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
983 { ctf_stalemate = false; wpforenemy_announced = false; }
984 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
985 { ctf_stalemate = false; wpforenemy_announced = false; }
987 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
990 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
992 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
994 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
995 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
996 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
1000 if (!wpforenemy_announced)
1002 FOR_EACH_REALPLAYER(tmp_entity)
1003 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
1005 wpforenemy_announced = true;
1010 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1012 if(ITEM_DAMAGE_NEEDKILL(deathtype))
1014 if(autocvar_g_ctf_flag_return_damage_delay)
1016 self.ctf_flagdamaged = true;
1021 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1025 if(autocvar_g_ctf_flag_return_damage)
1027 // reduce health and check if it should be returned
1028 self.health = self.health - damage;
1029 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
1034 void ctf_FlagThink()
1039 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
1042 if(self == ctf_worldflaglist) // only for the first flag
1043 FOR_EACH_CLIENT(tmp_entity)
1044 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
1047 if(self.mins != CTF_FLAG.m_mins || self.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
1048 LOG_TRACE("wtf the flag got squashed?\n");
1049 tracebox(self.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, self.origin, MOVE_NOMONSTERS, self);
1050 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
1051 setsize(self, CTF_FLAG.m_mins, CTF_FLAG.m_maxs); }
1053 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
1057 self.angles = '0 0 0';
1064 // main think method
1065 switch(self.ctf_status)
1069 if(autocvar_g_ctf_dropped_capture_radius)
1071 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
1072 if(tmp_entity.ctf_status == FLAG_DROPPED)
1073 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
1074 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
1075 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
1082 if(autocvar_g_ctf_flag_dropped_floatinwater)
1084 vector midpoint = ((self.absmin + self.absmax) * 0.5);
1085 if(pointcontents(midpoint) == CONTENT_WATER)
1087 self.velocity = self.velocity * 0.5;
1089 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
1090 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
1092 { self.movetype = MOVETYPE_FLY; }
1094 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
1096 if(autocvar_g_ctf_flag_return_dropped)
1098 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
1101 ctf_CheckFlagReturn(self, RETURN_DROPPED);
1105 if(self.ctf_flagdamaged)
1107 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
1108 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1111 else if(autocvar_g_ctf_flag_return_time)
1113 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
1114 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
1122 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
1125 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
1127 setself(self.owner);
1128 self.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
1129 ImpulseCommands(self);
1132 if(autocvar_g_ctf_stalemate)
1134 if(time >= wpforenemy_nextthink)
1136 ctf_CheckStalemate();
1137 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1140 if(CTF_SAMETEAM(self, self.owner) && self.team)
1142 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1143 ctf_Handle_Throw(self.owner, world, DROP_THROW);
1144 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
1145 ctf_Handle_Return(self, self.owner);
1152 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
1153 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
1154 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
1156 if((self.pass_target == world)
1157 || (self.pass_target.deadflag != DEAD_NO)
1158 || (self.pass_target.flagcarried)
1159 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
1160 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
1161 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1163 // give up, pass failed
1164 ctf_Handle_Drop(self, world, DROP_PASS);
1168 // still a viable target, go for it
1169 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
1174 default: // this should never happen
1176 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
1182 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1185 if(gameover) { return; }
1186 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1188 bool is_not_monster = (!IS_MONSTER(toucher));
1190 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1191 if(ITEM_TOUCH_NEEDKILL())
1193 if(!autocvar_g_ctf_flag_return_damage_delay)
1196 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1198 if(!flag.ctf_flagdamaged) { return; }
1201 int num_perteam = 0;
1202 entity tmp_entity; FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
1204 // special touch behaviors
1205 if(toucher.frozen) { return; }
1206 else if(IS_VEHICLE(toucher))
1208 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1209 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1211 return; // do nothing
1213 else if(IS_MONSTER(toucher))
1215 if(!autocvar_g_ctf_allow_monster_touch)
1216 return; // do nothing
1218 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1220 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1222 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1223 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1224 flag.wait = time + FLAG_TOUCHRATE;
1228 else if(toucher.deadflag != DEAD_NO) { return; }
1230 switch(flag.ctf_status)
1236 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1237 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1238 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1239 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1241 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1242 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1243 else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
1245 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1246 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1248 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1249 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1255 if(CTF_SAMETEAM(toucher, flag) && (autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried)) && flag.team) // automatically return if there's only 1 player on the team
1256 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1257 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1258 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1264 LOG_TRACE("Someone touched a flag even though it was being carried?\n");
1270 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != flag.pass_sender))
1272 if(DIFF_TEAM(toucher, flag.pass_sender))
1273 ctf_Handle_Return(flag, toucher);
1275 ctf_Handle_Retrieve(flag, toucher);
1282 .float last_respawn;
1283 void ctf_RespawnFlag(entity flag)
1285 // check for flag respawn being called twice in a row
1286 if(flag.last_respawn > time - 0.5)
1287 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1289 flag.last_respawn = time;
1291 // reset the player (if there is one)
1292 if((flag.owner) && (flag.owner.flagcarried == flag))
1294 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1295 WaypointSprite_Kill(flag.wps_flagcarrier);
1297 flag.owner.flagcarried = world;
1299 if(flag.speedrunning)
1300 ctf_FakeTimeLimit(flag.owner, -1);
1303 if((flag.owner) && (flag.owner.vehicle))
1304 flag.scale = FLAG_SCALE;
1306 if(flag.ctf_status == FLAG_DROPPED)
1307 { WaypointSprite_Kill(flag.wps_flagdropped); }
1310 setattachment(flag, world, "");
1311 setorigin(flag, flag.ctf_spawnorigin);
1313 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1314 flag.takedamage = DAMAGE_NO;
1315 flag.health = flag.max_flag_health;
1316 flag.solid = SOLID_TRIGGER;
1317 flag.velocity = '0 0 0';
1318 flag.angles = flag.mangle;
1319 flag.flags = FL_ITEM | FL_NOTARGET;
1321 flag.ctf_status = FLAG_BASE;
1323 flag.pass_distance = 0;
1324 flag.pass_sender = world;
1325 flag.pass_target = world;
1326 flag.ctf_dropper = world;
1327 flag.ctf_pickuptime = 0;
1328 flag.ctf_droptime = 0;
1329 flag.ctf_flagdamaged = 0;
1331 ctf_CheckStalemate();
1334 void ctf_Reset(entity this)
1336 if(this.owner && IS_PLAYER(this.owner))
1337 ctf_Handle_Throw(this.owner, world, DROP_RESET);
1339 ctf_RespawnFlag(this);
1342 void ctf_DelayedFlagSetup() // called after a flag is placed on a map by ctf_FlagSetup()
1345 waypoint_spawnforitem_force(self, self.origin);
1346 self.nearestwaypointtimeout = 0; // activate waypointing again
1347 self.bot_basewaypoint = self.nearestwaypoint;
1353 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1354 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1355 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1356 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1357 default: basename = WP_FlagBaseNeutral; break;
1360 entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
1361 wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
1362 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1364 // captureshield setup
1365 ctf_CaptureShield_Spawn(self);
1368 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1371 setself(flag); // for later usage with droptofloor()
1374 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1375 ctf_worldflaglist = flag;
1377 setattachment(flag, world, "");
1379 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1380 flag.team = teamnumber;
1381 flag.classname = "item_flag_team";
1382 flag.target = "###item###"; // wut?
1383 flag.flags = FL_ITEM | FL_NOTARGET;
1384 flag.solid = SOLID_TRIGGER;
1385 flag.takedamage = DAMAGE_NO;
1386 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1387 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1388 flag.health = flag.max_flag_health;
1389 flag.event_damage = ctf_FlagDamage;
1390 flag.pushable = true;
1391 flag.teleportable = TELEPORT_NORMAL;
1392 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1393 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1394 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1395 flag.velocity = '0 0 0';
1396 flag.mangle = flag.angles;
1397 flag.reset = ctf_Reset;
1398 flag.touch = ctf_FlagTouch;
1399 flag.think = ctf_FlagThink;
1400 flag.nextthink = time + FLAG_THINKRATE;
1401 flag.ctf_status = FLAG_BASE;
1403 string teamname = Static_Team_ColorName_Lower(teamnumber);
1405 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1406 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1407 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1408 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1409 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1410 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1413 flag.snd_flag_taken = strzone(SND(CTF_TAKEN(teamnumber)));
1414 flag.snd_flag_returned = strzone(SND(CTF_RETURNED(teamnumber)));
1415 flag.snd_flag_capture = strzone(SND(CTF_CAPTURE(teamnumber)));
1416 flag.snd_flag_dropped = strzone(SND(CTF_DROPPED(teamnumber)));
1417 if (flag.snd_flag_respawn == "") flag.snd_flag_respawn = strzone(SND(CTF_RESPAWN)); // if there is ever a team-based sound for this, update the code to match.
1418 precache_sound(flag.snd_flag_respawn);
1419 if (flag.snd_flag_touch == "") flag.snd_flag_touch = strzone(SND(CTF_TOUCH)); // again has no team-based sound
1420 precache_sound(flag.snd_flag_touch);
1421 if (flag.snd_flag_pass == "") flag.snd_flag_pass = strzone(SND(CTF_PASS)); // same story here
1422 precache_sound(flag.snd_flag_pass);
1425 precache_model(flag.model);
1428 _setmodel(flag, flag.model); // precision set below
1429 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1430 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1432 if(autocvar_g_ctf_flag_glowtrails)
1436 case NUM_TEAM_1: flag.glow_color = 251; break;
1437 case NUM_TEAM_2: flag.glow_color = 210; break;
1438 case NUM_TEAM_3: flag.glow_color = 110; break;
1439 case NUM_TEAM_4: flag.glow_color = 145; break;
1440 default: flag.glow_color = 254; break;
1442 flag.glow_size = 25;
1443 flag.glow_trail = 1;
1446 flag.effects |= EF_LOWPRECISION;
1447 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1448 if(autocvar_g_ctf_dynamiclights)
1452 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1453 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1454 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1455 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1456 default: flag.effects |= EF_DIMLIGHT; break;
1461 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1463 flag.dropped_origin = flag.origin;
1464 flag.noalign = true;
1465 flag.movetype = MOVETYPE_NONE;
1467 else // drop to floor, automatically find a platform and set that as spawn origin
1469 flag.noalign = false;
1472 flag.movetype = MOVETYPE_TOSS;
1475 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1483 // NOTE: LEGACY CODE, needs to be re-written!
1485 void havocbot_calculate_middlepoint()
1489 vector fo = '0 0 0';
1492 f = ctf_worldflaglist;
1497 f = f.ctf_worldflagnext;
1501 havocbot_ctf_middlepoint = s * (1.0 / n);
1502 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1506 entity havocbot_ctf_find_flag(entity bot)
1509 f = ctf_worldflaglist;
1512 if (CTF_SAMETEAM(bot, f))
1514 f = f.ctf_worldflagnext;
1519 entity havocbot_ctf_find_enemy_flag(entity bot)
1522 f = ctf_worldflaglist;
1527 if(CTF_DIFFTEAM(bot, f))
1534 else if(!bot.flagcarried)
1538 else if (CTF_DIFFTEAM(bot, f))
1540 f = f.ctf_worldflagnext;
1545 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1553 FOR_EACH_PLAYER(head)
1555 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1558 if(vlen(head.origin - org) < tc_radius)
1565 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1568 head = ctf_worldflaglist;
1571 if (CTF_SAMETEAM(self, head))
1573 head = head.ctf_worldflagnext;
1576 navigation_routerating(head, ratingscale, 10000);
1579 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1582 head = ctf_worldflaglist;
1585 if (CTF_SAMETEAM(self, head))
1587 head = head.ctf_worldflagnext;
1592 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1595 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1598 head = ctf_worldflaglist;
1603 if(CTF_DIFFTEAM(self, head))
1607 if(self.flagcarried)
1610 else if(!self.flagcarried)
1614 else if(CTF_DIFFTEAM(self, head))
1616 head = head.ctf_worldflagnext;
1619 navigation_routerating(head, ratingscale, 10000);
1622 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1624 if (!bot_waypoints_for_items)
1626 havocbot_goalrating_ctf_enemyflag(ratingscale);
1632 head = havocbot_ctf_find_enemy_flag(self);
1637 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1640 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1644 mf = havocbot_ctf_find_flag(self);
1646 if(mf.ctf_status == FLAG_BASE)
1650 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1653 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1656 head = ctf_worldflaglist;
1659 // flag is out in the field
1660 if(head.ctf_status != FLAG_BASE)
1661 if(head.tag_entity==world) // dropped
1665 if(vlen(org-head.origin)<df_radius)
1666 navigation_routerating(head, ratingscale, 10000);
1669 navigation_routerating(head, ratingscale, 10000);
1672 head = head.ctf_worldflagnext;
1676 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1680 head = findchainfloat(bot_pickup, true);
1683 // gather health and armor only
1685 if (head.health || head.armorvalue)
1686 if (vlen(head.origin - org) < sradius)
1688 // get the value of the item
1689 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1691 navigation_routerating(head, t * ratingscale, 500);
1697 void havocbot_ctf_reset_role(entity bot)
1699 float cdefense, cmiddle, coffense;
1700 entity mf, ef, head;
1703 if(bot.deadflag != DEAD_NO)
1706 if(vlen(havocbot_ctf_middlepoint)==0)
1707 havocbot_calculate_middlepoint();
1710 if (bot.flagcarried)
1712 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1716 mf = havocbot_ctf_find_flag(bot);
1717 ef = havocbot_ctf_find_enemy_flag(bot);
1719 // Retrieve stolen flag
1720 if(mf.ctf_status!=FLAG_BASE)
1722 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1726 // If enemy flag is taken go to the middle to intercept pursuers
1727 if(ef.ctf_status!=FLAG_BASE)
1729 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1733 // if there is only me on the team switch to offense
1735 FOR_EACH_PLAYER(head)
1736 if(SAME_TEAM(head, bot))
1741 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1745 // Evaluate best position to take
1746 // Count mates on middle position
1747 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1749 // Count mates on defense position
1750 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1752 // Count mates on offense position
1753 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1755 if(cdefense<=coffense)
1756 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1757 else if(coffense<=cmiddle)
1758 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1760 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1763 void havocbot_role_ctf_carrier()
1765 if(self.deadflag != DEAD_NO)
1767 havocbot_ctf_reset_role(self);
1771 if (self.flagcarried == world)
1773 havocbot_ctf_reset_role(self);
1777 if (self.bot_strategytime < time)
1779 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1781 navigation_goalrating_start();
1783 havocbot_goalrating_ctf_enemybase(50000);
1785 havocbot_goalrating_ctf_ourbase(50000);
1788 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1790 navigation_goalrating_end();
1792 if (self.navigation_hasgoals)
1793 self.havocbot_cantfindflag = time + 10;
1794 else if (time > self.havocbot_cantfindflag)
1796 // Can't navigate to my own base, suicide!
1797 // TODO: drop it and wander around
1798 Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0');
1804 void havocbot_role_ctf_escort()
1808 if(self.deadflag != DEAD_NO)
1810 havocbot_ctf_reset_role(self);
1814 if (self.flagcarried)
1816 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1820 // If enemy flag is back on the base switch to previous role
1821 ef = havocbot_ctf_find_enemy_flag(self);
1822 if(ef.ctf_status==FLAG_BASE)
1824 self.havocbot_role = self.havocbot_previous_role;
1825 self.havocbot_role_timeout = 0;
1829 // If the flag carrier reached the base switch to defense
1830 mf = havocbot_ctf_find_flag(self);
1831 if(mf.ctf_status!=FLAG_BASE)
1832 if(vlen(ef.origin - mf.dropped_origin) < 300)
1834 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1838 // Set the role timeout if necessary
1839 if (!self.havocbot_role_timeout)
1841 self.havocbot_role_timeout = time + random() * 30 + 60;
1844 // If nothing happened just switch to previous role
1845 if (time > self.havocbot_role_timeout)
1847 self.havocbot_role = self.havocbot_previous_role;
1848 self.havocbot_role_timeout = 0;
1852 // Chase the flag carrier
1853 if (self.bot_strategytime < time)
1855 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1856 navigation_goalrating_start();
1857 havocbot_goalrating_ctf_enemyflag(30000);
1858 havocbot_goalrating_ctf_ourstolenflag(40000);
1859 havocbot_goalrating_items(10000, self.origin, 10000);
1860 navigation_goalrating_end();
1864 void havocbot_role_ctf_offense()
1869 if(self.deadflag != DEAD_NO)
1871 havocbot_ctf_reset_role(self);
1875 if (self.flagcarried)
1877 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1882 mf = havocbot_ctf_find_flag(self);
1883 ef = havocbot_ctf_find_enemy_flag(self);
1886 if(mf.ctf_status!=FLAG_BASE)
1889 pos = mf.tag_entity.origin;
1893 // Try to get it if closer than the enemy base
1894 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1896 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1901 // Escort flag carrier
1902 if(ef.ctf_status!=FLAG_BASE)
1905 pos = ef.tag_entity.origin;
1909 if(vlen(pos-mf.dropped_origin)>700)
1911 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1916 // About to fail, switch to middlefield
1919 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1923 // Set the role timeout if necessary
1924 if (!self.havocbot_role_timeout)
1925 self.havocbot_role_timeout = time + 120;
1927 if (time > self.havocbot_role_timeout)
1929 havocbot_ctf_reset_role(self);
1933 if (self.bot_strategytime < time)
1935 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1936 navigation_goalrating_start();
1937 havocbot_goalrating_ctf_ourstolenflag(50000);
1938 havocbot_goalrating_ctf_enemybase(20000);
1939 havocbot_goalrating_items(5000, self.origin, 1000);
1940 havocbot_goalrating_items(1000, self.origin, 10000);
1941 navigation_goalrating_end();
1945 // Retriever (temporary role):
1946 void havocbot_role_ctf_retriever()
1950 if(self.deadflag != DEAD_NO)
1952 havocbot_ctf_reset_role(self);
1956 if (self.flagcarried)
1958 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1962 // If flag is back on the base switch to previous role
1963 mf = havocbot_ctf_find_flag(self);
1964 if(mf.ctf_status==FLAG_BASE)
1966 havocbot_ctf_reset_role(self);
1970 if (!self.havocbot_role_timeout)
1971 self.havocbot_role_timeout = time + 20;
1973 if (time > self.havocbot_role_timeout)
1975 havocbot_ctf_reset_role(self);
1979 if (self.bot_strategytime < time)
1984 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1985 navigation_goalrating_start();
1986 havocbot_goalrating_ctf_ourstolenflag(50000);
1987 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1988 havocbot_goalrating_ctf_enemybase(30000);
1989 havocbot_goalrating_items(500, self.origin, rt_radius);
1990 navigation_goalrating_end();
1994 void havocbot_role_ctf_middle()
1998 if(self.deadflag != DEAD_NO)
2000 havocbot_ctf_reset_role(self);
2004 if (self.flagcarried)
2006 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2010 mf = havocbot_ctf_find_flag(self);
2011 if(mf.ctf_status!=FLAG_BASE)
2013 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2017 if (!self.havocbot_role_timeout)
2018 self.havocbot_role_timeout = time + 10;
2020 if (time > self.havocbot_role_timeout)
2022 havocbot_ctf_reset_role(self);
2026 if (self.bot_strategytime < time)
2030 org = havocbot_ctf_middlepoint;
2031 org.z = self.origin.z;
2033 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2034 navigation_goalrating_start();
2035 havocbot_goalrating_ctf_ourstolenflag(50000);
2036 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
2037 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
2038 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
2039 havocbot_goalrating_items(2500, self.origin, 10000);
2040 havocbot_goalrating_ctf_enemybase(2500);
2041 navigation_goalrating_end();
2045 void havocbot_role_ctf_defense()
2049 if(self.deadflag != DEAD_NO)
2051 havocbot_ctf_reset_role(self);
2055 if (self.flagcarried)
2057 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2061 // If own flag was captured
2062 mf = havocbot_ctf_find_flag(self);
2063 if(mf.ctf_status!=FLAG_BASE)
2065 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2069 if (!self.havocbot_role_timeout)
2070 self.havocbot_role_timeout = time + 30;
2072 if (time > self.havocbot_role_timeout)
2074 havocbot_ctf_reset_role(self);
2077 if (self.bot_strategytime < time)
2082 org = mf.dropped_origin;
2083 mp_radius = havocbot_ctf_middlepoint_radius;
2085 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2086 navigation_goalrating_start();
2088 // if enemies are closer to our base, go there
2089 entity head, closestplayer = world;
2090 float distance, bestdistance = 10000;
2091 FOR_EACH_PLAYER(head)
2093 if(head.deadflag!=DEAD_NO)
2096 distance = vlen(org - head.origin);
2097 if(distance<bestdistance)
2099 closestplayer = head;
2100 bestdistance = distance;
2105 if(DIFF_TEAM(closestplayer, self))
2106 if(vlen(org - self.origin)>1000)
2107 if(checkpvs(self.origin,closestplayer)||random()<0.5)
2108 havocbot_goalrating_ctf_ourbase(30000);
2110 havocbot_goalrating_ctf_ourstolenflag(20000);
2111 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
2112 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
2113 havocbot_goalrating_items(10000, org, mp_radius);
2114 havocbot_goalrating_items(5000, self.origin, 10000);
2115 navigation_goalrating_end();
2119 void havocbot_role_ctf_setrole(entity bot, int role)
2121 LOG_TRACE(strcat(bot.netname," switched to "));
2124 case HAVOCBOT_CTF_ROLE_CARRIER:
2125 LOG_TRACE("carrier");
2126 bot.havocbot_role = havocbot_role_ctf_carrier;
2127 bot.havocbot_role_timeout = 0;
2128 bot.havocbot_cantfindflag = time + 10;
2129 bot.bot_strategytime = 0;
2131 case HAVOCBOT_CTF_ROLE_DEFENSE:
2132 LOG_TRACE("defense");
2133 bot.havocbot_role = havocbot_role_ctf_defense;
2134 bot.havocbot_role_timeout = 0;
2136 case HAVOCBOT_CTF_ROLE_MIDDLE:
2137 LOG_TRACE("middle");
2138 bot.havocbot_role = havocbot_role_ctf_middle;
2139 bot.havocbot_role_timeout = 0;
2141 case HAVOCBOT_CTF_ROLE_OFFENSE:
2142 LOG_TRACE("offense");
2143 bot.havocbot_role = havocbot_role_ctf_offense;
2144 bot.havocbot_role_timeout = 0;
2146 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2147 LOG_TRACE("retriever");
2148 bot.havocbot_previous_role = bot.havocbot_role;
2149 bot.havocbot_role = havocbot_role_ctf_retriever;
2150 bot.havocbot_role_timeout = time + 10;
2151 bot.bot_strategytime = 0;
2153 case HAVOCBOT_CTF_ROLE_ESCORT:
2154 LOG_TRACE("escort");
2155 bot.havocbot_previous_role = bot.havocbot_role;
2156 bot.havocbot_role = havocbot_role_ctf_escort;
2157 bot.havocbot_role_timeout = time + 30;
2158 bot.bot_strategytime = 0;
2169 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2172 int t = 0, t2 = 0, t3 = 0;
2174 // initially clear items so they can be set as necessary later.
2175 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2176 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2177 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2178 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2179 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2180 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
2182 // scan through all the flags and notify the client about them
2183 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2185 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2186 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2187 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2188 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2189 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; }
2191 switch(flag.ctf_status)
2196 if((flag.owner == self) || (flag.pass_sender == self))
2197 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
2199 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2204 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2210 // item for stopping players from capturing the flag too often
2211 if(self.ctf_captureshielded)
2212 self.ctf_flagstatus |= CTF_SHIELDED;
2214 // update the health of the flag carrier waypointsprite
2215 if(self.wps_flagcarrier)
2216 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2221 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2223 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2225 if(frag_target == frag_attacker) // damage done to yourself
2227 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2228 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2230 else // damage done to everyone else
2232 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2233 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2236 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2238 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.m_id)))
2239 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2241 frag_target.wps_helpme_time = time;
2242 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2244 // todo: add notification for when flag carrier needs help?
2249 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2251 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2253 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2254 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2257 if(frag_target.flagcarried)
2259 entity tmp_entity = frag_target.flagcarried;
2260 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2261 tmp_entity.ctf_dropper = world;
2267 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2270 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2273 void ctf_RemovePlayer(entity player)
2275 if(player.flagcarried)
2276 { ctf_Handle_Throw(player, world, DROP_NORMAL); }
2278 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2280 if(flag.pass_sender == player) { flag.pass_sender = world; }
2281 if(flag.pass_target == player) { flag.pass_target = world; }
2282 if(flag.ctf_dropper == player) { flag.ctf_dropper = world; }
2286 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2288 ctf_RemovePlayer(self);
2292 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2294 ctf_RemovePlayer(self);
2298 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2300 if(self.flagcarried)
2301 if(!autocvar_g_ctf_portalteleport)
2302 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2307 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2309 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2311 entity player = self;
2313 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2315 // pass the flag to a team mate
2316 if(autocvar_g_ctf_pass)
2318 entity head, closest_target = world;
2319 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2321 while(head) // find the closest acceptable target to pass to
2323 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2324 if(head != player && SAME_TEAM(head, player))
2325 if(!head.speedrunning && !head.vehicle)
2327 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2328 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2329 vector passer_center = CENTER_OR_VIEWOFS(player);
2331 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2333 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2335 if(IS_BOT_CLIENT(head))
2337 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2338 ctf_Handle_Throw(head, player, DROP_PASS);
2342 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2343 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2345 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2348 else if(player.flagcarried)
2352 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2353 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2354 { closest_target = head; }
2356 else { closest_target = head; }
2363 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2366 // throw the flag in front of you
2367 if(autocvar_g_ctf_throw && player.flagcarried)
2369 if(player.throw_count == -1)
2371 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2373 player.throw_prevtime = time;
2374 player.throw_count = 1;
2375 ctf_Handle_Throw(player, world, DROP_THROW);
2380 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2386 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2387 else { player.throw_count += 1; }
2388 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2390 player.throw_prevtime = time;
2391 ctf_Handle_Throw(player, world, DROP_THROW);
2400 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2402 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2404 self.wps_helpme_time = time;
2405 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2407 else // create a normal help me waypointsprite
2409 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
2410 WaypointSprite_Ping(self.wps_helpme);
2416 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2418 if(vh_player.flagcarried)
2420 vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
2422 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2424 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2428 setattachment(vh_player.flagcarried, vh_vehicle, "");
2429 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2430 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2431 //vh_player.flagcarried.angles = '0 0 0';
2439 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2441 if(vh_player.flagcarried)
2443 setattachment(vh_player.flagcarried, vh_player, "");
2444 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2445 vh_player.flagcarried.scale = FLAG_SCALE;
2446 vh_player.flagcarried.angles = '0 0 0';
2447 vh_player.flagcarried.nodrawtoclient = world;
2454 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2456 if(self.flagcarried)
2458 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));
2459 ctf_RespawnFlag(self.flagcarried);
2466 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2468 entity flag; // temporary entity for the search method
2470 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2472 switch(flag.ctf_status)
2477 // lock the flag, game is over
2478 flag.movetype = MOVETYPE_NONE;
2479 flag.takedamage = DAMAGE_NO;
2480 flag.solid = SOLID_NOT;
2481 flag.nextthink = false; // stop thinking
2483 //dprint("stopping the ", flag.netname, " from moving.\n");
2491 // do nothing for these flags
2500 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2502 havocbot_ctf_reset_role(self);
2506 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2508 //ret_float = ctf_teams;
2509 ret_string = "ctf_team";
2513 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2515 self.ctf_flagstatus = other.ctf_flagstatus;
2519 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2521 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2523 if (MapInfo_Get_ByID(i))
2525 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2531 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2532 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2539 bool superspec_Spectate(entity _player); // TODO
2540 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2541 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2543 if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2545 if(cmd_name == "followfc")
2558 case "red": _team = NUM_TEAM_1; break;
2559 case "blue": _team = NUM_TEAM_2; break;
2560 case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
2561 case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
2565 FOR_EACH_PLAYER(_player)
2567 if(_player.flagcarried && (_player.team == _team || _team == 0))
2570 if(_team == 0 && IS_SPEC(self) && self.enemy == _player)
2571 continue; // already spectating a fc, try to find the other fc
2572 return superspec_Spectate(_player);
2577 superspec_msg("", "", self, "No active flag carrier\n", 1);
2584 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2586 if(frag_target.flagcarried)
2587 ctf_Handle_Throw(frag_target, world, DROP_THROW);
2597 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2598 CTF flag for team one (Red).
2600 "angle" Angle the flag will point (minus 90 degrees)...
2601 "model" model to use, note this needs red and blue as skins 0 and 1...
2602 "noise" sound played when flag is picked up...
2603 "noise1" sound played when flag is returned by a teammate...
2604 "noise2" sound played when flag is captured...
2605 "noise3" sound played when flag is lost in the field and respawns itself...
2606 "noise4" sound played when flag is dropped by a player...
2607 "noise5" sound played when flag touches the ground... */
2608 spawnfunc(item_flag_team1)
2610 if(!g_ctf) { remove(self); return; }
2612 ctf_FlagSetup(NUM_TEAM_1, self);
2615 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2616 CTF flag for team two (Blue).
2618 "angle" Angle the flag will point (minus 90 degrees)...
2619 "model" model to use, note this needs red and blue as skins 0 and 1...
2620 "noise" sound played when flag is picked up...
2621 "noise1" sound played when flag is returned by a teammate...
2622 "noise2" sound played when flag is captured...
2623 "noise3" sound played when flag is lost in the field and respawns itself...
2624 "noise4" sound played when flag is dropped by a player...
2625 "noise5" sound played when flag touches the ground... */
2626 spawnfunc(item_flag_team2)
2628 if(!g_ctf) { remove(self); return; }
2630 ctf_FlagSetup(NUM_TEAM_2, self);
2633 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2634 CTF flag for team three (Yellow).
2636 "angle" Angle the flag will point (minus 90 degrees)...
2637 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2638 "noise" sound played when flag is picked up...
2639 "noise1" sound played when flag is returned by a teammate...
2640 "noise2" sound played when flag is captured...
2641 "noise3" sound played when flag is lost in the field and respawns itself...
2642 "noise4" sound played when flag is dropped by a player...
2643 "noise5" sound played when flag touches the ground... */
2644 spawnfunc(item_flag_team3)
2646 if(!g_ctf) { remove(self); return; }
2648 ctf_FlagSetup(NUM_TEAM_3, self);
2651 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2652 CTF flag for team four (Pink).
2654 "angle" Angle the flag will point (minus 90 degrees)...
2655 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2656 "noise" sound played when flag is picked up...
2657 "noise1" sound played when flag is returned by a teammate...
2658 "noise2" sound played when flag is captured...
2659 "noise3" sound played when flag is lost in the field and respawns itself...
2660 "noise4" sound played when flag is dropped by a player...
2661 "noise5" sound played when flag touches the ground... */
2662 spawnfunc(item_flag_team4)
2664 if(!g_ctf) { remove(self); return; }
2666 ctf_FlagSetup(NUM_TEAM_4, self);
2669 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2672 "angle" Angle the flag will point (minus 90 degrees)...
2673 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2674 "noise" sound played when flag is picked up...
2675 "noise1" sound played when flag is returned by a teammate...
2676 "noise2" sound played when flag is captured...
2677 "noise3" sound played when flag is lost in the field and respawns itself...
2678 "noise4" sound played when flag is dropped by a player...
2679 "noise5" sound played when flag touches the ground... */
2680 spawnfunc(item_flag_neutral)
2682 if(!g_ctf) { remove(self); return; }
2683 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2685 ctf_FlagSetup(0, self);
2688 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2689 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2690 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.
2692 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2693 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2696 if(!g_ctf) { remove(self); return; }
2698 self.classname = "ctf_team";
2699 self.team = self.cnt + 1;
2702 // compatibility for quake maps
2703 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2704 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2705 spawnfunc(info_player_team1);
2706 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2707 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2708 spawnfunc(info_player_team2);
2709 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2710 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2712 void team_CTF_neutralflag() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2713 void team_neutralobelisk() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2721 void ctf_ScoreRules(int teams)
2723 CheckAllowedTeams(world);
2724 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2725 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2726 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2727 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2728 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2729 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2730 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2731 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2732 ScoreRules_basics_end();
2735 // code from here on is just to support maps that don't have flag and team entities
2736 void ctf_SpawnTeam (string teamname, int teamcolor)
2738 entity this = new(ctf_team);
2739 this.netname = teamname;
2740 this.cnt = teamcolor;
2741 this.spawnfunc_checked = true;
2742 WITH(entity, self, this, spawnfunc_ctf_team(this));
2745 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2750 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2752 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2753 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2754 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2757 ctf_teams = bound(2, ctf_teams, 4);
2759 // if no teams are found, spawn defaults
2760 if(find(world, classname, "ctf_team") == world)
2762 LOG_INFO("No \"ctf_team\" entities found on this map, creating them anyway.\n");
2763 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2764 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2766 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2768 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2771 ctf_ScoreRules(ctf_teams);
2774 void ctf_Initialize()
2776 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2778 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2779 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2780 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2782 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);