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, -1, autocvar_captureleadlimit_override, -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
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 float autocvar_g_ctf_flag_return_carried_radius;
253 float autocvar_g_ctf_flag_return_time;
254 bool autocvar_g_ctf_flag_return_when_unreachable;
255 float autocvar_g_ctf_flag_return_damage;
256 float autocvar_g_ctf_flag_return_damage_delay;
257 float autocvar_g_ctf_flag_return_dropped;
258 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
259 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
260 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
261 float autocvar_g_ctf_flagcarrier_selfforcefactor;
262 float autocvar_g_ctf_flagcarrier_damagefactor;
263 float autocvar_g_ctf_flagcarrier_forcefactor;
264 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
265 bool autocvar_g_ctf_fullbrightflags;
266 bool autocvar_g_ctf_ignore_frags;
267 int autocvar_g_ctf_score_capture;
268 int autocvar_g_ctf_score_capture_assist;
269 int autocvar_g_ctf_score_kill;
270 int autocvar_g_ctf_score_penalty_drop;
271 int autocvar_g_ctf_score_penalty_returned;
272 int autocvar_g_ctf_score_pickup_base;
273 int autocvar_g_ctf_score_pickup_dropped_early;
274 int autocvar_g_ctf_score_pickup_dropped_late;
275 int autocvar_g_ctf_score_return;
276 float autocvar_g_ctf_shield_force;
277 float autocvar_g_ctf_shield_max_ratio;
278 int autocvar_g_ctf_shield_min_negscore;
279 bool autocvar_g_ctf_stalemate;
280 int autocvar_g_ctf_stalemate_endcondition;
281 float autocvar_g_ctf_stalemate_time;
282 bool autocvar_g_ctf_reverse;
283 float autocvar_g_ctf_dropped_capture_delay;
284 float autocvar_g_ctf_dropped_capture_radius;
286 void ctf_FakeTimeLimit(entity e, float t)
289 WriteByte(MSG_ONE, 3); // svc_updatestat
290 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
292 WriteCoord(MSG_ONE, autocvar_timelimit);
294 WriteCoord(MSG_ONE, (t + 1) / 60);
297 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
299 if(autocvar_sv_eventlog)
300 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
301 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
304 void ctf_CaptureRecord(entity flag, entity player)
306 float cap_record = ctf_captimerecord;
307 float cap_time = (time - flag.ctf_pickuptime);
308 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
311 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
312 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)); }
313 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)); }
314 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)); }
316 // write that shit in the database
317 if(!ctf_oneflag) // but not in 1-flag mode
318 if((!ctf_captimerecord) || (cap_time < cap_record))
320 ctf_captimerecord = cap_time;
321 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
322 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
323 write_recordmarker(player, (time - cap_time), cap_time);
327 void ctf_FlagcarrierWaypoints(entity player)
329 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
330 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
331 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
332 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
335 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
337 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
338 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
339 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
340 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
343 if(current_height) // make sure we can actually do this arcing path
345 targpos = (to + ('0 0 1' * current_height));
346 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
347 if(trace_fraction < 1)
349 //print("normal arc line failed, trying to find new pos...");
350 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
351 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
352 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
353 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
354 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
357 else { targpos = to; }
359 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
361 vector desired_direction = normalize(targpos - from);
362 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
363 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
366 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
368 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
370 // directional tracing only
372 makevectors(passer_angle);
374 // find the closest point on the enemy to the center of the attack
375 float h; // hypotenuse, which is the distance between attacker to head
376 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
378 h = vlen(head_center - passer_center);
379 a = h * (normalize(head_center - passer_center) * v_forward);
381 vector nearest_on_line = (passer_center + a * v_forward);
382 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
384 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
385 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
387 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
392 else { return true; }
396 // =======================
397 // CaptureShield Functions
398 // =======================
400 bool ctf_CaptureShield_CheckStatus(entity p)
402 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
404 int players_worseeq, players_total;
406 if(ctf_captureshield_max_ratio <= 0)
409 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
410 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
411 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
412 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
414 sr = ((s - s2) + (s3 + s4));
416 if(sr >= -ctf_captureshield_min_negscore)
419 players_total = players_worseeq = 0;
424 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
425 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
426 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
427 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
429 ser = ((se - se2) + (se3 + se4));
436 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
437 // use this rule here
439 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
445 void ctf_CaptureShield_Update(entity player, bool wanted_status)
447 bool updated_status = ctf_CaptureShield_CheckStatus(player);
448 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
450 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
451 player.ctf_captureshielded = updated_status;
455 bool ctf_CaptureShield_Customize()
457 if(!other.ctf_captureshielded) { return false; }
458 if(CTF_SAMETEAM(self, other)) { return false; }
463 void ctf_CaptureShield_Touch()
465 if(!other.ctf_captureshielded) { return; }
466 if(CTF_SAMETEAM(self, other)) { return; }
468 vector mymid = (self.absmin + self.absmax) * 0.5;
469 vector othermid = (other.absmin + other.absmax) * 0.5;
471 Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
472 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
475 void ctf_CaptureShield_Spawn(entity flag)
477 entity shield = new(ctf_captureshield);
480 shield.team = self.team;
481 shield.touch = ctf_CaptureShield_Touch;
482 shield.customizeentityforclient = ctf_CaptureShield_Customize;
483 shield.effects = EF_ADDITIVE;
484 shield.movetype = MOVETYPE_NOCLIP;
485 shield.solid = SOLID_TRIGGER;
486 shield.avelocity = '7 0 11';
489 setorigin(shield, self.origin);
490 setmodel(shield, MDL_CTF_SHIELD);
491 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
495 // ====================
496 // Drop/Pass/Throw Code
497 // ====================
499 void ctf_Handle_Drop(entity flag, entity player, int droptype)
502 player = (player ? player : flag.pass_sender);
505 flag.movetype = MOVETYPE_TOSS;
506 flag.takedamage = DAMAGE_YES;
507 flag.angles = '0 0 0';
508 flag.health = flag.max_flag_health;
509 flag.ctf_droptime = time;
510 flag.ctf_dropper = player;
511 flag.ctf_status = FLAG_DROPPED;
513 // messages and sounds
514 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
515 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
516 ctf_EventLog("dropped", player.team, player);
519 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
520 PlayerScore_Add(player, SP_CTF_DROPS, 1);
523 if(autocvar_g_ctf_flag_dropped_waypoint) {
524 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);
525 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
528 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
530 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
531 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
534 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
536 if(droptype == DROP_PASS)
538 flag.pass_distance = 0;
539 flag.pass_sender = world;
540 flag.pass_target = world;
544 void ctf_Handle_Retrieve(entity flag, entity player)
546 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
547 entity sender = flag.pass_sender;
549 // transfer flag to player
551 flag.owner.flagcarried = flag;
556 setattachment(flag, player.vehicle, "");
557 setorigin(flag, VEHICLE_FLAG_OFFSET);
558 flag.scale = VEHICLE_FLAG_SCALE;
562 setattachment(flag, player, "");
563 setorigin(flag, FLAG_CARRY_OFFSET);
565 flag.movetype = MOVETYPE_NONE;
566 flag.takedamage = DAMAGE_NO;
567 flag.solid = SOLID_NOT;
568 flag.angles = '0 0 0';
569 flag.ctf_status = FLAG_CARRY;
571 // messages and sounds
572 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
573 ctf_EventLog("receive", flag.team, player);
575 FOR_EACH_REALPLAYER(tmp_player)
577 if(tmp_player == sender)
578 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);
579 else if(tmp_player == player)
580 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);
581 else if(SAME_TEAM(tmp_player, sender))
582 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);
585 // create new waypoint
586 ctf_FlagcarrierWaypoints(player);
588 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
589 player.throw_antispam = sender.throw_antispam;
591 flag.pass_distance = 0;
592 flag.pass_sender = world;
593 flag.pass_target = world;
596 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
598 entity flag = player.flagcarried;
599 vector targ_origin, flag_velocity;
601 if(!flag) { return; }
602 if((droptype == DROP_PASS) && !receiver) { return; }
604 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
607 setattachment(flag, world, "");
608 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
609 flag.owner.flagcarried = world;
611 flag.solid = SOLID_TRIGGER;
612 flag.ctf_dropper = player;
613 flag.ctf_droptime = time;
615 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
622 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
623 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
624 WarpZone_RefSys_Copy(flag, receiver);
625 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
626 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
628 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
629 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
632 flag.movetype = MOVETYPE_FLY;
633 flag.takedamage = DAMAGE_NO;
634 flag.pass_sender = player;
635 flag.pass_target = receiver;
636 flag.ctf_status = FLAG_PASSING;
639 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
640 WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
641 ctf_EventLog("pass", flag.team, player);
647 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'));
649 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)));
650 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
651 ctf_Handle_Drop(flag, player, droptype);
657 flag.velocity = '0 0 0'; // do nothing
664 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);
665 ctf_Handle_Drop(flag, player, droptype);
670 // kill old waypointsprite
671 WaypointSprite_Ping(player.wps_flagcarrier);
672 WaypointSprite_Kill(player.wps_flagcarrier);
674 if(player.wps_enemyflagcarrier)
675 WaypointSprite_Kill(player.wps_enemyflagcarrier);
678 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
686 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
688 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
689 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
690 entity player_team_flag = world, tmp_entity;
691 float old_time, new_time;
693 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
694 if(CTF_DIFFTEAM(player, flag)) { return; }
697 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
698 if(SAME_TEAM(tmp_entity, player))
700 player_team_flag = tmp_entity;
704 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
706 player.throw_prevtime = time;
707 player.throw_count = 0;
709 // messages and sounds
710 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
711 ctf_CaptureRecord(enemy_flag, player);
712 _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);
716 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
717 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
722 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
723 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
725 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
726 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
727 if(!old_time || new_time < old_time)
728 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
731 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
732 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
735 if(capturetype == CAPTURE_NORMAL)
737 WaypointSprite_Kill(player.wps_flagcarrier);
738 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
740 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
741 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
745 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
746 ctf_RespawnFlag(enemy_flag);
749 void ctf_Handle_Return(entity flag, entity player)
751 // messages and sounds
752 if(IS_MONSTER(player))
754 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
758 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
759 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
761 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
762 ctf_EventLog("return", flag.team, player);
765 if(IS_PLAYER(player))
767 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
768 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
770 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
773 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
777 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
778 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
779 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
783 if(player.flagcarried == flag)
784 WaypointSprite_Kill(player.wps_flagcarrier);
787 ctf_RespawnFlag(flag);
790 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
793 float pickup_dropped_score; // used to calculate dropped pickup score
794 entity tmp_entity; // temporary entity
796 // attach the flag to the player
798 player.flagcarried = flag;
801 setattachment(flag, player.vehicle, "");
802 setorigin(flag, VEHICLE_FLAG_OFFSET);
803 flag.scale = VEHICLE_FLAG_SCALE;
807 setattachment(flag, player, "");
808 setorigin(flag, FLAG_CARRY_OFFSET);
812 flag.movetype = MOVETYPE_NONE;
813 flag.takedamage = DAMAGE_NO;
814 flag.solid = SOLID_NOT;
815 flag.angles = '0 0 0';
816 flag.ctf_status = FLAG_CARRY;
820 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
821 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
825 // messages and sounds
826 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
827 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
828 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
829 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
830 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)); }
832 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);
835 FOR_EACH_PLAYER(tmp_entity)
836 if(tmp_entity != player)
837 if(DIFF_TEAM(player, tmp_entity))
838 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
841 FOR_EACH_PLAYER(tmp_entity)
842 if(tmp_entity != player)
843 if(CTF_SAMETEAM(flag, tmp_entity))
844 if(SAME_TEAM(player, tmp_entity))
845 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
847 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);
849 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
852 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
853 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
858 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
859 ctf_EventLog("steal", flag.team, player);
865 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);
866 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);
867 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
868 PlayerTeamScore_AddScore(player, pickup_dropped_score);
869 ctf_EventLog("pickup", flag.team, player);
877 if(pickuptype == PICKUP_BASE)
879 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
880 if((player.speedrunning) && (ctf_captimerecord))
881 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
885 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
888 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
889 ctf_FlagcarrierWaypoints(player);
890 WaypointSprite_Ping(player.wps_flagcarrier);
894 // ===================
895 // Main Flag Functions
896 // ===================
898 void ctf_CheckFlagReturn(entity flag, int returntype)
900 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
902 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
904 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
908 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;
909 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;
910 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;
911 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;
915 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
917 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
918 ctf_EventLog("returned", flag.team, world);
919 ctf_RespawnFlag(flag);
924 bool ctf_Stalemate_Customize()
926 // make spectators see what the player would see
928 e = WaypointSprite_getviewentity(other);
929 wp_owner = self.owner;
932 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
933 if(SAME_TEAM(wp_owner, e)) { return false; }
934 if(!IS_PLAYER(e)) { return false; }
939 void ctf_CheckStalemate(void)
942 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
945 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
947 // build list of stale flags
948 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
950 if(autocvar_g_ctf_stalemate)
951 if(tmp_entity.ctf_status != FLAG_BASE)
952 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
954 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
955 ctf_staleflaglist = tmp_entity;
957 switch(tmp_entity.team)
959 case NUM_TEAM_1: ++stale_red_flags; break;
960 case NUM_TEAM_2: ++stale_blue_flags; break;
961 case NUM_TEAM_3: ++stale_yellow_flags; break;
962 case NUM_TEAM_4: ++stale_pink_flags; break;
963 default: ++stale_neutral_flags; break;
969 stale_flags = (stale_neutral_flags >= 1);
971 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
973 if(ctf_oneflag && stale_flags == 1)
974 ctf_stalemate = true;
975 else if(stale_flags >= 2)
976 ctf_stalemate = true;
977 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
978 { ctf_stalemate = false; wpforenemy_announced = false; }
979 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
980 { ctf_stalemate = false; wpforenemy_announced = false; }
982 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
985 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
987 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
989 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);
990 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
991 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
995 if (!wpforenemy_announced)
997 FOR_EACH_REALPLAYER(tmp_entity)
998 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
1000 wpforenemy_announced = true;
1005 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1007 if(ITEM_DAMAGE_NEEDKILL(deathtype))
1009 if(autocvar_g_ctf_flag_return_damage_delay)
1011 self.ctf_flagdamaged = true;
1016 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1020 if(autocvar_g_ctf_flag_return_damage)
1022 // reduce health and check if it should be returned
1023 self.health = self.health - damage;
1024 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
1029 void ctf_FlagThink()
1034 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
1037 if(self == ctf_worldflaglist) // only for the first flag
1038 FOR_EACH_CLIENT(tmp_entity)
1039 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
1042 if(self.mins != CTF_FLAG.m_mins || self.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
1043 LOG_TRACE("wtf the flag got squashed?\n");
1044 tracebox(self.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, self.origin, MOVE_NOMONSTERS, self);
1045 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
1046 setsize(self, CTF_FLAG.m_mins, CTF_FLAG.m_maxs); }
1048 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
1052 self.angles = '0 0 0';
1059 // main think method
1060 switch(self.ctf_status)
1064 if(autocvar_g_ctf_dropped_capture_radius)
1066 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
1067 if(tmp_entity.ctf_status == FLAG_DROPPED)
1068 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
1069 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
1070 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
1077 if(autocvar_g_ctf_flag_dropped_floatinwater)
1079 vector midpoint = ((self.absmin + self.absmax) * 0.5);
1080 if(pointcontents(midpoint) == CONTENT_WATER)
1082 self.velocity = self.velocity * 0.5;
1084 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
1085 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
1087 { self.movetype = MOVETYPE_FLY; }
1089 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
1091 if(autocvar_g_ctf_flag_return_dropped)
1093 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
1096 ctf_CheckFlagReturn(self, RETURN_DROPPED);
1100 if(self.ctf_flagdamaged)
1102 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
1103 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1106 else if(autocvar_g_ctf_flag_return_time)
1108 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
1109 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
1117 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
1120 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
1122 setself(self.owner);
1123 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
1127 if(autocvar_g_ctf_stalemate)
1129 if(time >= wpforenemy_nextthink)
1131 ctf_CheckStalemate();
1132 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1135 if(CTF_SAMETEAM(self, self.owner) && self.team)
1137 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1138 ctf_Handle_Throw(self.owner, world, DROP_THROW);
1139 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
1140 ctf_Handle_Return(self, self.owner);
1147 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
1148 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
1149 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
1151 if((self.pass_target == world)
1152 || (self.pass_target.deadflag != DEAD_NO)
1153 || (self.pass_target.flagcarried)
1154 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
1155 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
1156 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1158 // give up, pass failed
1159 ctf_Handle_Drop(self, world, DROP_PASS);
1163 // still a viable target, go for it
1164 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
1169 default: // this should never happen
1171 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
1177 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1180 if(gameover) { return; }
1181 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1183 bool is_not_monster = (!IS_MONSTER(toucher));
1185 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1186 if(ITEM_TOUCH_NEEDKILL())
1188 if(!autocvar_g_ctf_flag_return_damage_delay)
1191 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1193 if(!flag.ctf_flagdamaged) { return; }
1196 int num_perteam = 0;
1197 entity tmp_entity; FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
1199 // special touch behaviors
1200 if(toucher.frozen) { return; }
1201 else if(IS_VEHICLE(toucher))
1203 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1204 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1206 return; // do nothing
1208 else if(IS_MONSTER(toucher))
1210 if(!autocvar_g_ctf_allow_monster_touch)
1211 return; // do nothing
1213 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1215 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1217 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1218 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1219 flag.wait = time + FLAG_TOUCHRATE;
1223 else if(toucher.deadflag != DEAD_NO) { return; }
1225 switch(flag.ctf_status)
1231 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1232 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1233 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1234 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1236 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1237 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1238 else if(CTF_DIFFTEAM(toucher, flag) && (!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 enemies flag
1245 if(CTF_SAMETEAM(toucher, flag) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && flag.team) // automatically return if there's only 1 player on the team
1246 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1247 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1248 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1254 LOG_TRACE("Someone touched a flag even though it was being carried?\n");
1260 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != flag.pass_sender))
1262 if(DIFF_TEAM(toucher, flag.pass_sender))
1263 ctf_Handle_Return(flag, toucher);
1265 ctf_Handle_Retrieve(flag, toucher);
1272 .float last_respawn;
1273 void ctf_RespawnFlag(entity flag)
1275 // check for flag respawn being called twice in a row
1276 if(flag.last_respawn > time - 0.5)
1277 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1279 flag.last_respawn = time;
1281 // reset the player (if there is one)
1282 if((flag.owner) && (flag.owner.flagcarried == flag))
1284 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1285 WaypointSprite_Kill(flag.wps_flagcarrier);
1287 flag.owner.flagcarried = world;
1289 if(flag.speedrunning)
1290 ctf_FakeTimeLimit(flag.owner, -1);
1293 if((flag.owner) && (flag.owner.vehicle))
1294 flag.scale = FLAG_SCALE;
1296 if(flag.ctf_status == FLAG_DROPPED)
1297 { WaypointSprite_Kill(flag.wps_flagdropped); }
1300 setattachment(flag, world, "");
1301 setorigin(flag, flag.ctf_spawnorigin);
1303 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1304 flag.takedamage = DAMAGE_NO;
1305 flag.health = flag.max_flag_health;
1306 flag.solid = SOLID_TRIGGER;
1307 flag.velocity = '0 0 0';
1308 flag.angles = flag.mangle;
1309 flag.flags = FL_ITEM | FL_NOTARGET;
1311 flag.ctf_status = FLAG_BASE;
1313 flag.pass_distance = 0;
1314 flag.pass_sender = world;
1315 flag.pass_target = world;
1316 flag.ctf_dropper = world;
1317 flag.ctf_pickuptime = 0;
1318 flag.ctf_droptime = 0;
1319 flag.ctf_flagdamaged = 0;
1321 ctf_CheckStalemate();
1327 if(IS_PLAYER(self.owner))
1328 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1330 ctf_RespawnFlag(self);
1333 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1336 waypoint_spawnforitem_force(self, self.origin);
1337 self.nearestwaypointtimeout = 0; // activate waypointing again
1338 self.bot_basewaypoint = self.nearestwaypoint;
1344 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1345 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1346 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1347 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1348 default: basename = WP_FlagBaseNeutral; break;
1351 entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
1352 wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
1353 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1355 // captureshield setup
1356 ctf_CaptureShield_Spawn(self);
1359 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1362 setself(flag); // for later usage with droptofloor()
1365 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1366 ctf_worldflaglist = flag;
1368 setattachment(flag, world, "");
1370 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1371 flag.team = teamnumber;
1372 flag.classname = "item_flag_team";
1373 flag.target = "###item###"; // wut?
1374 flag.flags = FL_ITEM | FL_NOTARGET;
1375 flag.solid = SOLID_TRIGGER;
1376 flag.takedamage = DAMAGE_NO;
1377 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1378 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1379 flag.health = flag.max_flag_health;
1380 flag.event_damage = ctf_FlagDamage;
1381 flag.pushable = true;
1382 flag.teleportable = TELEPORT_NORMAL;
1383 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1384 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1385 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1386 flag.velocity = '0 0 0';
1387 flag.mangle = flag.angles;
1388 flag.reset = ctf_Reset;
1389 flag.touch = ctf_FlagTouch;
1390 flag.think = ctf_FlagThink;
1391 flag.nextthink = time + FLAG_THINKRATE;
1392 flag.ctf_status = FLAG_BASE;
1394 string teamname = Static_Team_ColorName_Lower(teamnumber);
1396 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1397 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1398 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1399 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1400 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1401 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1404 flag.snd_flag_taken = strzone(SND(CTF_TAKEN(teamnumber)));
1405 flag.snd_flag_returned = strzone(SND(CTF_RETURNED(teamnumber)));
1406 flag.snd_flag_capture = strzone(SND(CTF_CAPTURE(teamnumber)));
1407 flag.snd_flag_dropped = strzone(SND(CTF_DROPPED(teamnumber)));
1408 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.
1409 precache_sound(flag.snd_flag_respawn);
1410 if (flag.snd_flag_touch == "") flag.snd_flag_touch = strzone(SND(CTF_TOUCH)); // again has no team-based sound
1411 precache_sound(flag.snd_flag_touch);
1412 if (flag.snd_flag_pass == "") flag.snd_flag_pass = strzone(SND(CTF_PASS)); // same story here
1413 precache_sound(flag.snd_flag_pass);
1416 precache_model(flag.model);
1419 _setmodel(flag, flag.model); // precision set below
1420 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1421 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1423 if(autocvar_g_ctf_flag_glowtrails)
1427 case NUM_TEAM_1: flag.glow_color = 251; break;
1428 case NUM_TEAM_2: flag.glow_color = 210; break;
1429 case NUM_TEAM_3: flag.glow_color = 110; break;
1430 case NUM_TEAM_4: flag.glow_color = 145; break;
1431 default: flag.glow_color = 254; break;
1433 flag.glow_size = 25;
1434 flag.glow_trail = 1;
1437 flag.effects |= EF_LOWPRECISION;
1438 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1439 if(autocvar_g_ctf_dynamiclights)
1443 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1444 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1445 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1446 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1447 default: flag.effects |= EF_DIMLIGHT; break;
1452 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1454 flag.dropped_origin = flag.origin;
1455 flag.noalign = true;
1456 flag.movetype = MOVETYPE_NONE;
1458 else // drop to floor, automatically find a platform and set that as spawn origin
1460 flag.noalign = false;
1463 flag.movetype = MOVETYPE_TOSS;
1466 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1474 // NOTE: LEGACY CODE, needs to be re-written!
1476 void havocbot_calculate_middlepoint()
1480 vector fo = '0 0 0';
1483 f = ctf_worldflaglist;
1488 f = f.ctf_worldflagnext;
1492 havocbot_ctf_middlepoint = s * (1.0 / n);
1493 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1497 entity havocbot_ctf_find_flag(entity bot)
1500 f = ctf_worldflaglist;
1503 if (CTF_SAMETEAM(bot, f))
1505 f = f.ctf_worldflagnext;
1510 entity havocbot_ctf_find_enemy_flag(entity bot)
1513 f = ctf_worldflaglist;
1518 if(CTF_DIFFTEAM(bot, f))
1525 else if(!bot.flagcarried)
1529 else if (CTF_DIFFTEAM(bot, f))
1531 f = f.ctf_worldflagnext;
1536 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1544 FOR_EACH_PLAYER(head)
1546 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1549 if(vlen(head.origin - org) < tc_radius)
1556 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1559 head = ctf_worldflaglist;
1562 if (CTF_SAMETEAM(self, head))
1564 head = head.ctf_worldflagnext;
1567 navigation_routerating(head, ratingscale, 10000);
1570 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1573 head = ctf_worldflaglist;
1576 if (CTF_SAMETEAM(self, head))
1578 head = head.ctf_worldflagnext;
1583 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1586 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1589 head = ctf_worldflaglist;
1594 if(CTF_DIFFTEAM(self, head))
1598 if(self.flagcarried)
1601 else if(!self.flagcarried)
1605 else if(CTF_DIFFTEAM(self, head))
1607 head = head.ctf_worldflagnext;
1610 navigation_routerating(head, ratingscale, 10000);
1613 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1615 if (!bot_waypoints_for_items)
1617 havocbot_goalrating_ctf_enemyflag(ratingscale);
1623 head = havocbot_ctf_find_enemy_flag(self);
1628 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1631 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1635 mf = havocbot_ctf_find_flag(self);
1637 if(mf.ctf_status == FLAG_BASE)
1641 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1644 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1647 head = ctf_worldflaglist;
1650 // flag is out in the field
1651 if(head.ctf_status != FLAG_BASE)
1652 if(head.tag_entity==world) // dropped
1656 if(vlen(org-head.origin)<df_radius)
1657 navigation_routerating(head, ratingscale, 10000);
1660 navigation_routerating(head, ratingscale, 10000);
1663 head = head.ctf_worldflagnext;
1667 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1671 head = findchainfloat(bot_pickup, true);
1674 // gather health and armor only
1676 if (head.health || head.armorvalue)
1677 if (vlen(head.origin - org) < sradius)
1679 // get the value of the item
1680 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1682 navigation_routerating(head, t * ratingscale, 500);
1688 void havocbot_ctf_reset_role(entity bot)
1690 float cdefense, cmiddle, coffense;
1691 entity mf, ef, head;
1694 if(bot.deadflag != DEAD_NO)
1697 if(vlen(havocbot_ctf_middlepoint)==0)
1698 havocbot_calculate_middlepoint();
1701 if (bot.flagcarried)
1703 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1707 mf = havocbot_ctf_find_flag(bot);
1708 ef = havocbot_ctf_find_enemy_flag(bot);
1710 // Retrieve stolen flag
1711 if(mf.ctf_status!=FLAG_BASE)
1713 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1717 // If enemy flag is taken go to the middle to intercept pursuers
1718 if(ef.ctf_status!=FLAG_BASE)
1720 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1724 // if there is only me on the team switch to offense
1726 FOR_EACH_PLAYER(head)
1727 if(SAME_TEAM(head, bot))
1732 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1736 // Evaluate best position to take
1737 // Count mates on middle position
1738 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1740 // Count mates on defense position
1741 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1743 // Count mates on offense position
1744 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1746 if(cdefense<=coffense)
1747 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1748 else if(coffense<=cmiddle)
1749 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1751 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1754 void havocbot_role_ctf_carrier()
1756 if(self.deadflag != DEAD_NO)
1758 havocbot_ctf_reset_role(self);
1762 if (self.flagcarried == world)
1764 havocbot_ctf_reset_role(self);
1768 if (self.bot_strategytime < time)
1770 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1772 navigation_goalrating_start();
1774 havocbot_goalrating_ctf_enemybase(50000);
1776 havocbot_goalrating_ctf_ourbase(50000);
1779 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1781 navigation_goalrating_end();
1783 if (self.navigation_hasgoals)
1784 self.havocbot_cantfindflag = time + 10;
1785 else if (time > self.havocbot_cantfindflag)
1787 // Can't navigate to my own base, suicide!
1788 // TODO: drop it and wander around
1789 Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0');
1795 void havocbot_role_ctf_escort()
1799 if(self.deadflag != DEAD_NO)
1801 havocbot_ctf_reset_role(self);
1805 if (self.flagcarried)
1807 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1811 // If enemy flag is back on the base switch to previous role
1812 ef = havocbot_ctf_find_enemy_flag(self);
1813 if(ef.ctf_status==FLAG_BASE)
1815 self.havocbot_role = self.havocbot_previous_role;
1816 self.havocbot_role_timeout = 0;
1820 // If the flag carrier reached the base switch to defense
1821 mf = havocbot_ctf_find_flag(self);
1822 if(mf.ctf_status!=FLAG_BASE)
1823 if(vlen(ef.origin - mf.dropped_origin) < 300)
1825 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1829 // Set the role timeout if necessary
1830 if (!self.havocbot_role_timeout)
1832 self.havocbot_role_timeout = time + random() * 30 + 60;
1835 // If nothing happened just switch to previous role
1836 if (time > self.havocbot_role_timeout)
1838 self.havocbot_role = self.havocbot_previous_role;
1839 self.havocbot_role_timeout = 0;
1843 // Chase the flag carrier
1844 if (self.bot_strategytime < time)
1846 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1847 navigation_goalrating_start();
1848 havocbot_goalrating_ctf_enemyflag(30000);
1849 havocbot_goalrating_ctf_ourstolenflag(40000);
1850 havocbot_goalrating_items(10000, self.origin, 10000);
1851 navigation_goalrating_end();
1855 void havocbot_role_ctf_offense()
1860 if(self.deadflag != DEAD_NO)
1862 havocbot_ctf_reset_role(self);
1866 if (self.flagcarried)
1868 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1873 mf = havocbot_ctf_find_flag(self);
1874 ef = havocbot_ctf_find_enemy_flag(self);
1877 if(mf.ctf_status!=FLAG_BASE)
1880 pos = mf.tag_entity.origin;
1884 // Try to get it if closer than the enemy base
1885 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1887 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1892 // Escort flag carrier
1893 if(ef.ctf_status!=FLAG_BASE)
1896 pos = ef.tag_entity.origin;
1900 if(vlen(pos-mf.dropped_origin)>700)
1902 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1907 // About to fail, switch to middlefield
1910 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1914 // Set the role timeout if necessary
1915 if (!self.havocbot_role_timeout)
1916 self.havocbot_role_timeout = time + 120;
1918 if (time > self.havocbot_role_timeout)
1920 havocbot_ctf_reset_role(self);
1924 if (self.bot_strategytime < time)
1926 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1927 navigation_goalrating_start();
1928 havocbot_goalrating_ctf_ourstolenflag(50000);
1929 havocbot_goalrating_ctf_enemybase(20000);
1930 havocbot_goalrating_items(5000, self.origin, 1000);
1931 havocbot_goalrating_items(1000, self.origin, 10000);
1932 navigation_goalrating_end();
1936 // Retriever (temporary role):
1937 void havocbot_role_ctf_retriever()
1941 if(self.deadflag != DEAD_NO)
1943 havocbot_ctf_reset_role(self);
1947 if (self.flagcarried)
1949 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1953 // If flag is back on the base switch to previous role
1954 mf = havocbot_ctf_find_flag(self);
1955 if(mf.ctf_status==FLAG_BASE)
1957 havocbot_ctf_reset_role(self);
1961 if (!self.havocbot_role_timeout)
1962 self.havocbot_role_timeout = time + 20;
1964 if (time > self.havocbot_role_timeout)
1966 havocbot_ctf_reset_role(self);
1970 if (self.bot_strategytime < time)
1975 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1976 navigation_goalrating_start();
1977 havocbot_goalrating_ctf_ourstolenflag(50000);
1978 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1979 havocbot_goalrating_ctf_enemybase(30000);
1980 havocbot_goalrating_items(500, self.origin, rt_radius);
1981 navigation_goalrating_end();
1985 void havocbot_role_ctf_middle()
1989 if(self.deadflag != DEAD_NO)
1991 havocbot_ctf_reset_role(self);
1995 if (self.flagcarried)
1997 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2001 mf = havocbot_ctf_find_flag(self);
2002 if(mf.ctf_status!=FLAG_BASE)
2004 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2008 if (!self.havocbot_role_timeout)
2009 self.havocbot_role_timeout = time + 10;
2011 if (time > self.havocbot_role_timeout)
2013 havocbot_ctf_reset_role(self);
2017 if (self.bot_strategytime < time)
2021 org = havocbot_ctf_middlepoint;
2022 org.z = self.origin.z;
2024 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2025 navigation_goalrating_start();
2026 havocbot_goalrating_ctf_ourstolenflag(50000);
2027 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
2028 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
2029 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
2030 havocbot_goalrating_items(2500, self.origin, 10000);
2031 havocbot_goalrating_ctf_enemybase(2500);
2032 navigation_goalrating_end();
2036 void havocbot_role_ctf_defense()
2040 if(self.deadflag != DEAD_NO)
2042 havocbot_ctf_reset_role(self);
2046 if (self.flagcarried)
2048 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2052 // If own flag was captured
2053 mf = havocbot_ctf_find_flag(self);
2054 if(mf.ctf_status!=FLAG_BASE)
2056 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2060 if (!self.havocbot_role_timeout)
2061 self.havocbot_role_timeout = time + 30;
2063 if (time > self.havocbot_role_timeout)
2065 havocbot_ctf_reset_role(self);
2068 if (self.bot_strategytime < time)
2073 org = mf.dropped_origin;
2074 mp_radius = havocbot_ctf_middlepoint_radius;
2076 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2077 navigation_goalrating_start();
2079 // if enemies are closer to our base, go there
2080 entity head, closestplayer = world;
2081 float distance, bestdistance = 10000;
2082 FOR_EACH_PLAYER(head)
2084 if(head.deadflag!=DEAD_NO)
2087 distance = vlen(org - head.origin);
2088 if(distance<bestdistance)
2090 closestplayer = head;
2091 bestdistance = distance;
2096 if(DIFF_TEAM(closestplayer, self))
2097 if(vlen(org - self.origin)>1000)
2098 if(checkpvs(self.origin,closestplayer)||random()<0.5)
2099 havocbot_goalrating_ctf_ourbase(30000);
2101 havocbot_goalrating_ctf_ourstolenflag(20000);
2102 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
2103 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
2104 havocbot_goalrating_items(10000, org, mp_radius);
2105 havocbot_goalrating_items(5000, self.origin, 10000);
2106 navigation_goalrating_end();
2110 void havocbot_role_ctf_setrole(entity bot, int role)
2112 LOG_TRACE(strcat(bot.netname," switched to "));
2115 case HAVOCBOT_CTF_ROLE_CARRIER:
2116 LOG_TRACE("carrier");
2117 bot.havocbot_role = havocbot_role_ctf_carrier;
2118 bot.havocbot_role_timeout = 0;
2119 bot.havocbot_cantfindflag = time + 10;
2120 bot.bot_strategytime = 0;
2122 case HAVOCBOT_CTF_ROLE_DEFENSE:
2123 LOG_TRACE("defense");
2124 bot.havocbot_role = havocbot_role_ctf_defense;
2125 bot.havocbot_role_timeout = 0;
2127 case HAVOCBOT_CTF_ROLE_MIDDLE:
2128 LOG_TRACE("middle");
2129 bot.havocbot_role = havocbot_role_ctf_middle;
2130 bot.havocbot_role_timeout = 0;
2132 case HAVOCBOT_CTF_ROLE_OFFENSE:
2133 LOG_TRACE("offense");
2134 bot.havocbot_role = havocbot_role_ctf_offense;
2135 bot.havocbot_role_timeout = 0;
2137 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2138 LOG_TRACE("retriever");
2139 bot.havocbot_previous_role = bot.havocbot_role;
2140 bot.havocbot_role = havocbot_role_ctf_retriever;
2141 bot.havocbot_role_timeout = time + 10;
2142 bot.bot_strategytime = 0;
2144 case HAVOCBOT_CTF_ROLE_ESCORT:
2145 LOG_TRACE("escort");
2146 bot.havocbot_previous_role = bot.havocbot_role;
2147 bot.havocbot_role = havocbot_role_ctf_escort;
2148 bot.havocbot_role_timeout = time + 30;
2149 bot.bot_strategytime = 0;
2160 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2163 int t = 0, t2 = 0, t3 = 0;
2165 // initially clear items so they can be set as necessary later.
2166 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2167 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2168 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2169 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2170 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2171 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
2173 // scan through all the flags and notify the client about them
2174 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2176 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2177 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2178 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2179 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2180 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; }
2182 switch(flag.ctf_status)
2187 if((flag.owner == self) || (flag.pass_sender == self))
2188 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
2190 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2195 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2201 // item for stopping players from capturing the flag too often
2202 if(self.ctf_captureshielded)
2203 self.ctf_flagstatus |= CTF_SHIELDED;
2205 // update the health of the flag carrier waypointsprite
2206 if(self.wps_flagcarrier)
2207 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2212 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2214 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2216 if(frag_target == frag_attacker) // damage done to yourself
2218 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2219 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2221 else // damage done to everyone else
2223 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2224 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2227 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2229 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)))
2230 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2232 frag_target.wps_helpme_time = time;
2233 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2235 // todo: add notification for when flag carrier needs help?
2240 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2242 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2244 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2245 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2248 if(frag_target.flagcarried)
2250 entity tmp_entity = frag_target.flagcarried;
2251 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2252 tmp_entity.ctf_dropper = world;
2258 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2261 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2264 void ctf_RemovePlayer(entity player)
2266 if(player.flagcarried)
2267 { ctf_Handle_Throw(player, world, DROP_NORMAL); }
2269 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2271 if(flag.pass_sender == player) { flag.pass_sender = world; }
2272 if(flag.pass_target == player) { flag.pass_target = world; }
2273 if(flag.ctf_dropper == player) { flag.ctf_dropper = world; }
2277 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2279 ctf_RemovePlayer(self);
2283 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2285 ctf_RemovePlayer(self);
2289 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2291 if(self.flagcarried)
2292 if(!autocvar_g_ctf_portalteleport)
2293 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2298 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2300 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2302 entity player = self;
2304 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2306 // pass the flag to a team mate
2307 if(autocvar_g_ctf_pass)
2309 entity head, closest_target = world;
2310 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2312 while(head) // find the closest acceptable target to pass to
2314 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2315 if(head != player && SAME_TEAM(head, player))
2316 if(!head.speedrunning && !head.vehicle)
2318 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2319 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2320 vector passer_center = CENTER_OR_VIEWOFS(player);
2322 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2324 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2326 if(IS_BOT_CLIENT(head))
2328 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2329 ctf_Handle_Throw(head, player, DROP_PASS);
2333 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2334 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2336 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2339 else if(player.flagcarried)
2343 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2344 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2345 { closest_target = head; }
2347 else { closest_target = head; }
2354 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2357 // throw the flag in front of you
2358 if(autocvar_g_ctf_throw && player.flagcarried)
2360 if(player.throw_count == -1)
2362 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2364 player.throw_prevtime = time;
2365 player.throw_count = 1;
2366 ctf_Handle_Throw(player, world, DROP_THROW);
2371 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2377 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2378 else { player.throw_count += 1; }
2379 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2381 player.throw_prevtime = time;
2382 ctf_Handle_Throw(player, world, DROP_THROW);
2391 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2393 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2395 self.wps_helpme_time = time;
2396 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2398 else // create a normal help me waypointsprite
2400 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
2401 WaypointSprite_Ping(self.wps_helpme);
2407 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2409 if(vh_player.flagcarried)
2411 vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
2413 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2415 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2419 setattachment(vh_player.flagcarried, vh_vehicle, "");
2420 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2421 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2422 //vh_player.flagcarried.angles = '0 0 0';
2430 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2432 if(vh_player.flagcarried)
2434 setattachment(vh_player.flagcarried, vh_player, "");
2435 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2436 vh_player.flagcarried.scale = FLAG_SCALE;
2437 vh_player.flagcarried.angles = '0 0 0';
2438 vh_player.flagcarried.nodrawtoclient = world;
2445 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2447 if(self.flagcarried)
2449 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));
2450 ctf_RespawnFlag(self.flagcarried);
2457 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2459 entity flag; // temporary entity for the search method
2461 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2463 switch(flag.ctf_status)
2468 // lock the flag, game is over
2469 flag.movetype = MOVETYPE_NONE;
2470 flag.takedamage = DAMAGE_NO;
2471 flag.solid = SOLID_NOT;
2472 flag.nextthink = false; // stop thinking
2474 //dprint("stopping the ", flag.netname, " from moving.\n");
2482 // do nothing for these flags
2491 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2493 havocbot_ctf_reset_role(self);
2497 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2499 //ret_float = ctf_teams;
2500 ret_string = "ctf_team";
2504 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2506 self.ctf_flagstatus = other.ctf_flagstatus;
2510 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2512 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2514 if (MapInfo_Get_ByID(i))
2516 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2522 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2523 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2530 bool superspec_Spectate(entity _player); // TODO
2531 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2532 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2534 if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2536 if(cmd_name == "followfc")
2549 case "red": _team = NUM_TEAM_1; break;
2550 case "blue": _team = NUM_TEAM_2; break;
2551 case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
2552 case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
2556 FOR_EACH_PLAYER(_player)
2558 if(_player.flagcarried && (_player.team == _team || _team == 0))
2561 if(_team == 0 && IS_SPEC(self) && self.enemy == _player)
2562 continue; // already spectating a fc, try to find the other fc
2563 return superspec_Spectate(_player);
2568 superspec_msg("", "", self, "No active flag carrier\n", 1);
2575 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2577 if(frag_target.flagcarried)
2578 ctf_Handle_Throw(frag_target, world, DROP_THROW);
2588 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2589 CTF flag for team one (Red).
2591 "angle" Angle the flag will point (minus 90 degrees)...
2592 "model" model to use, note this needs red and blue as skins 0 and 1...
2593 "noise" sound played when flag is picked up...
2594 "noise1" sound played when flag is returned by a teammate...
2595 "noise2" sound played when flag is captured...
2596 "noise3" sound played when flag is lost in the field and respawns itself...
2597 "noise4" sound played when flag is dropped by a player...
2598 "noise5" sound played when flag touches the ground... */
2599 spawnfunc(item_flag_team1)
2601 if(!g_ctf) { remove(self); return; }
2603 ctf_FlagSetup(NUM_TEAM_1, self);
2606 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2607 CTF flag for team two (Blue).
2609 "angle" Angle the flag will point (minus 90 degrees)...
2610 "model" model to use, note this needs red and blue as skins 0 and 1...
2611 "noise" sound played when flag is picked up...
2612 "noise1" sound played when flag is returned by a teammate...
2613 "noise2" sound played when flag is captured...
2614 "noise3" sound played when flag is lost in the field and respawns itself...
2615 "noise4" sound played when flag is dropped by a player...
2616 "noise5" sound played when flag touches the ground... */
2617 spawnfunc(item_flag_team2)
2619 if(!g_ctf) { remove(self); return; }
2621 ctf_FlagSetup(NUM_TEAM_2, self);
2624 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2625 CTF flag for team three (Yellow).
2627 "angle" Angle the flag will point (minus 90 degrees)...
2628 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2629 "noise" sound played when flag is picked up...
2630 "noise1" sound played when flag is returned by a teammate...
2631 "noise2" sound played when flag is captured...
2632 "noise3" sound played when flag is lost in the field and respawns itself...
2633 "noise4" sound played when flag is dropped by a player...
2634 "noise5" sound played when flag touches the ground... */
2635 spawnfunc(item_flag_team3)
2637 if(!g_ctf) { remove(self); return; }
2639 ctf_FlagSetup(NUM_TEAM_3, self);
2642 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2643 CTF flag for team four (Pink).
2645 "angle" Angle the flag will point (minus 90 degrees)...
2646 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2647 "noise" sound played when flag is picked up...
2648 "noise1" sound played when flag is returned by a teammate...
2649 "noise2" sound played when flag is captured...
2650 "noise3" sound played when flag is lost in the field and respawns itself...
2651 "noise4" sound played when flag is dropped by a player...
2652 "noise5" sound played when flag touches the ground... */
2653 spawnfunc(item_flag_team4)
2655 if(!g_ctf) { remove(self); return; }
2657 ctf_FlagSetup(NUM_TEAM_4, self);
2660 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2663 "angle" Angle the flag will point (minus 90 degrees)...
2664 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2665 "noise" sound played when flag is picked up...
2666 "noise1" sound played when flag is returned by a teammate...
2667 "noise2" sound played when flag is captured...
2668 "noise3" sound played when flag is lost in the field and respawns itself...
2669 "noise4" sound played when flag is dropped by a player...
2670 "noise5" sound played when flag touches the ground... */
2671 spawnfunc(item_flag_neutral)
2673 if(!g_ctf) { remove(self); return; }
2674 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2676 ctf_FlagSetup(0, self);
2679 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2680 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2681 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.
2683 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2684 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2687 if(!g_ctf) { remove(self); return; }
2689 self.classname = "ctf_team";
2690 self.team = self.cnt + 1;
2693 // compatibility for quake maps
2694 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2695 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2696 spawnfunc(info_player_team1);
2697 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2698 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2699 spawnfunc(info_player_team2);
2700 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2701 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2703 void team_CTF_neutralflag() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2704 void team_neutralobelisk() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2712 void ctf_ScoreRules(int teams)
2714 CheckAllowedTeams(world);
2715 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2716 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2717 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2718 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2719 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2720 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2721 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2722 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2723 ScoreRules_basics_end();
2726 // code from here on is just to support maps that don't have flag and team entities
2727 void ctf_SpawnTeam (string teamname, int teamcolor)
2729 entity this = new(ctf_team);
2730 this.netname = teamname;
2731 this.cnt = teamcolor;
2732 this.spawnfunc_checked = true;
2733 WITH(entity, self, this, spawnfunc_ctf_team(this));
2736 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2741 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2743 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2744 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2745 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2748 ctf_teams = bound(2, ctf_teams, 4);
2750 // if no teams are found, spawn defaults
2751 if(find(world, classname, "ctf_team") == world)
2753 LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2754 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2755 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2757 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2759 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2762 ctf_ScoreRules(ctf_teams);
2765 void ctf_Initialize()
2767 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2769 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2770 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2771 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2773 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2775 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);