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
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 = spawn();
480 shield.team = self.team;
481 shield.touch = ctf_CaptureShield_Touch;
482 shield.customizeentityforclient = ctf_CaptureShield_Customize;
483 shield.classname = "ctf_captureshield";
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
687 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
689 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
690 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
691 entity player_team_flag = world, tmp_entity;
692 float old_time, new_time;
694 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
695 if(CTF_DIFFTEAM(player, flag)) { return; }
698 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
699 if(SAME_TEAM(tmp_entity, player))
701 player_team_flag = tmp_entity;
705 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
707 player.throw_prevtime = time;
708 player.throw_count = 0;
710 // messages and sounds
711 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
712 ctf_CaptureRecord(enemy_flag, player);
713 _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);
717 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
718 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
723 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
724 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
726 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
727 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
728 if(!old_time || new_time < old_time)
729 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
732 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
733 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
736 if(capturetype == CAPTURE_NORMAL)
738 WaypointSprite_Kill(player.wps_flagcarrier);
739 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
741 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
742 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
746 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
747 ctf_RespawnFlag(enemy_flag);
750 void ctf_Handle_Return(entity flag, entity player)
752 // messages and sounds
753 if(IS_MONSTER(player))
755 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
759 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
760 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
762 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
763 ctf_EventLog("return", flag.team, player);
766 if(IS_PLAYER(player))
768 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
769 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
771 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
774 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
778 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
779 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
780 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
784 if(player.flagcarried == flag)
785 WaypointSprite_Kill(player.wps_flagcarrier);
788 ctf_RespawnFlag(flag);
791 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
794 float pickup_dropped_score; // used to calculate dropped pickup score
795 entity tmp_entity; // temporary entity
797 // attach the flag to the player
799 player.flagcarried = flag;
802 setattachment(flag, player.vehicle, "");
803 setorigin(flag, VEHICLE_FLAG_OFFSET);
804 flag.scale = VEHICLE_FLAG_SCALE;
808 setattachment(flag, player, "");
809 setorigin(flag, FLAG_CARRY_OFFSET);
813 flag.movetype = MOVETYPE_NONE;
814 flag.takedamage = DAMAGE_NO;
815 flag.solid = SOLID_NOT;
816 flag.angles = '0 0 0';
817 flag.ctf_status = FLAG_CARRY;
821 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
822 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
826 // messages and sounds
827 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
828 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
829 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
830 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
831 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)); }
833 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);
836 FOR_EACH_PLAYER(tmp_entity)
837 if(tmp_entity != player)
838 if(DIFF_TEAM(player, tmp_entity))
839 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
842 FOR_EACH_PLAYER(tmp_entity)
843 if(tmp_entity != player)
844 if(CTF_SAMETEAM(flag, tmp_entity))
845 if(SAME_TEAM(player, tmp_entity))
846 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
848 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);
850 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
853 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
854 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
859 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
860 ctf_EventLog("steal", flag.team, player);
866 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);
867 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);
868 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
869 PlayerTeamScore_AddScore(player, pickup_dropped_score);
870 ctf_EventLog("pickup", flag.team, player);
878 if(pickuptype == PICKUP_BASE)
880 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
881 if((player.speedrunning) && (ctf_captimerecord))
882 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
886 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
889 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
890 ctf_FlagcarrierWaypoints(player);
891 WaypointSprite_Ping(player.wps_flagcarrier);
895 // ===================
896 // Main Flag Functions
897 // ===================
899 void ctf_CheckFlagReturn(entity flag, int returntype)
901 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
903 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
905 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
909 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;
910 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;
911 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;
912 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;
916 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
918 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
919 ctf_EventLog("returned", flag.team, world);
920 ctf_RespawnFlag(flag);
925 bool ctf_Stalemate_Customize()
927 // make spectators see what the player would see
929 e = WaypointSprite_getviewentity(other);
930 wp_owner = self.owner;
933 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
934 if(SAME_TEAM(wp_owner, e)) { return false; }
935 if(!IS_PLAYER(e)) { return false; }
940 void ctf_CheckStalemate(void)
943 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
946 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
948 // build list of stale flags
949 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
951 if(autocvar_g_ctf_stalemate)
952 if(tmp_entity.ctf_status != FLAG_BASE)
953 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
955 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
956 ctf_staleflaglist = tmp_entity;
958 switch(tmp_entity.team)
960 case NUM_TEAM_1: ++stale_red_flags; break;
961 case NUM_TEAM_2: ++stale_blue_flags; break;
962 case NUM_TEAM_3: ++stale_yellow_flags; break;
963 case NUM_TEAM_4: ++stale_pink_flags; break;
964 default: ++stale_neutral_flags; break;
970 stale_flags = (stale_neutral_flags >= 1);
972 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
974 if(ctf_oneflag && stale_flags == 1)
975 ctf_stalemate = true;
976 else if(stale_flags >= 2)
977 ctf_stalemate = true;
978 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
979 { ctf_stalemate = false; wpforenemy_announced = false; }
980 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
981 { ctf_stalemate = false; wpforenemy_announced = false; }
983 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
986 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
988 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
990 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);
991 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
992 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
996 if (!wpforenemy_announced)
998 FOR_EACH_REALPLAYER(tmp_entity)
999 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
1001 wpforenemy_announced = true;
1006 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1008 if(ITEM_DAMAGE_NEEDKILL(deathtype))
1010 if(autocvar_g_ctf_flag_return_damage_delay)
1012 self.ctf_flagdamaged = true;
1017 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1021 if(autocvar_g_ctf_flag_return_damage)
1023 // reduce health and check if it should be returned
1024 self.health = self.health - damage;
1025 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
1030 void ctf_FlagThink()
1035 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
1038 if(self == ctf_worldflaglist) // only for the first flag
1039 FOR_EACH_CLIENT(tmp_entity)
1040 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
1043 if(self.mins != CTF_FLAG.m_mins || self.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
1044 LOG_TRACE("wtf the flag got squashed?\n");
1045 tracebox(self.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, self.origin, MOVE_NOMONSTERS, self);
1046 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
1047 setsize(self, CTF_FLAG.m_mins, CTF_FLAG.m_maxs); }
1049 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
1053 self.angles = '0 0 0';
1060 // main think method
1061 switch(self.ctf_status)
1065 if(autocvar_g_ctf_dropped_capture_radius)
1067 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
1068 if(tmp_entity.ctf_status == FLAG_DROPPED)
1069 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
1070 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
1071 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
1078 if(autocvar_g_ctf_flag_dropped_floatinwater)
1080 vector midpoint = ((self.absmin + self.absmax) * 0.5);
1081 if(pointcontents(midpoint) == CONTENT_WATER)
1083 self.velocity = self.velocity * 0.5;
1085 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
1086 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
1088 { self.movetype = MOVETYPE_FLY; }
1090 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
1092 if(autocvar_g_ctf_flag_return_dropped)
1094 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
1097 ctf_CheckFlagReturn(self, RETURN_DROPPED);
1101 if(self.ctf_flagdamaged)
1103 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
1104 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1107 else if(autocvar_g_ctf_flag_return_time)
1109 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
1110 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
1118 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
1121 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
1123 setself(self.owner);
1124 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
1128 if(autocvar_g_ctf_stalemate)
1130 if(time >= wpforenemy_nextthink)
1132 ctf_CheckStalemate();
1133 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1136 if(CTF_SAMETEAM(self, self.owner) && self.team)
1138 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1139 ctf_Handle_Throw(self.owner, world, DROP_THROW);
1140 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
1141 ctf_Handle_Return(self, self.owner);
1148 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
1149 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
1150 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
1152 if((self.pass_target == world)
1153 || (self.pass_target.deadflag != DEAD_NO)
1154 || (self.pass_target.flagcarried)
1155 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
1156 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
1157 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1159 // give up, pass failed
1160 ctf_Handle_Drop(self, world, DROP_PASS);
1164 // still a viable target, go for it
1165 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
1170 default: // this should never happen
1172 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
1178 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1181 if(gameover) { return; }
1182 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1184 bool is_not_monster = (!IS_MONSTER(toucher));
1186 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1187 if(ITEM_TOUCH_NEEDKILL())
1189 if(!autocvar_g_ctf_flag_return_damage_delay)
1192 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1194 if(!flag.ctf_flagdamaged) { return; }
1197 int num_perteam = 0;
1198 entity tmp_entity; FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
1200 // special touch behaviors
1201 if(toucher.frozen) { return; }
1202 else if(IS_VEHICLE(toucher))
1204 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1205 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1207 return; // do nothing
1209 else if(IS_MONSTER(toucher))
1211 if(!autocvar_g_ctf_allow_monster_touch)
1212 return; // do nothing
1214 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1216 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1218 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1219 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1220 flag.wait = time + FLAG_TOUCHRATE;
1224 else if(toucher.deadflag != DEAD_NO) { return; }
1226 switch(flag.ctf_status)
1232 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1233 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1234 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1235 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1237 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1238 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1239 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1240 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1246 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
1247 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1248 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1249 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1255 LOG_TRACE("Someone touched a flag even though it was being carried?\n");
1261 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != flag.pass_sender))
1263 if(DIFF_TEAM(toucher, flag.pass_sender))
1264 ctf_Handle_Return(flag, toucher);
1266 ctf_Handle_Retrieve(flag, toucher);
1273 .float last_respawn;
1274 void ctf_RespawnFlag(entity flag)
1276 // check for flag respawn being called twice in a row
1277 if(flag.last_respawn > time - 0.5)
1278 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1280 flag.last_respawn = time;
1282 // reset the player (if there is one)
1283 if((flag.owner) && (flag.owner.flagcarried == flag))
1285 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1286 WaypointSprite_Kill(flag.wps_flagcarrier);
1288 flag.owner.flagcarried = world;
1290 if(flag.speedrunning)
1291 ctf_FakeTimeLimit(flag.owner, -1);
1294 if((flag.owner) && (flag.owner.vehicle))
1295 flag.scale = FLAG_SCALE;
1297 if(flag.ctf_status == FLAG_DROPPED)
1298 { WaypointSprite_Kill(flag.wps_flagdropped); }
1301 setattachment(flag, world, "");
1302 setorigin(flag, flag.ctf_spawnorigin);
1304 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1305 flag.takedamage = DAMAGE_NO;
1306 flag.health = flag.max_flag_health;
1307 flag.solid = SOLID_TRIGGER;
1308 flag.velocity = '0 0 0';
1309 flag.angles = flag.mangle;
1310 flag.flags = FL_ITEM | FL_NOTARGET;
1312 flag.ctf_status = FLAG_BASE;
1314 flag.pass_distance = 0;
1315 flag.pass_sender = world;
1316 flag.pass_target = world;
1317 flag.ctf_dropper = world;
1318 flag.ctf_pickuptime = 0;
1319 flag.ctf_droptime = 0;
1320 flag.ctf_flagdamaged = 0;
1322 ctf_CheckStalemate();
1328 if(IS_PLAYER(self.owner))
1329 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1331 ctf_RespawnFlag(self);
1334 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1337 waypoint_spawnforitem_force(self, self.origin);
1338 self.nearestwaypointtimeout = 0; // activate waypointing again
1339 self.bot_basewaypoint = self.nearestwaypoint;
1345 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1346 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1347 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1348 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1349 default: basename = WP_FlagBaseNeutral; break;
1352 entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
1353 wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
1354 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1356 // captureshield setup
1357 ctf_CaptureShield_Spawn(self);
1360 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1363 setself(flag); // for later usage with droptofloor()
1366 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1367 ctf_worldflaglist = flag;
1369 setattachment(flag, world, "");
1371 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1372 flag.team = teamnumber;
1373 flag.classname = "item_flag_team";
1374 flag.target = "###item###"; // wut?
1375 flag.flags = FL_ITEM | FL_NOTARGET;
1376 flag.solid = SOLID_TRIGGER;
1377 flag.takedamage = DAMAGE_NO;
1378 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1379 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1380 flag.health = flag.max_flag_health;
1381 flag.event_damage = ctf_FlagDamage;
1382 flag.pushable = true;
1383 flag.teleportable = TELEPORT_NORMAL;
1384 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1385 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1386 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1387 flag.velocity = '0 0 0';
1388 flag.mangle = flag.angles;
1389 flag.reset = ctf_Reset;
1390 flag.touch = ctf_FlagTouch;
1391 flag.think = ctf_FlagThink;
1392 flag.nextthink = time + FLAG_THINKRATE;
1393 flag.ctf_status = FLAG_BASE;
1395 string teamname = Static_Team_ColorName_Lower(teamnumber);
1397 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1398 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1399 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1400 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1401 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1402 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1405 flag.snd_flag_taken = strzone(SND(CTF_TAKEN(teamnumber)));
1406 flag.snd_flag_returned = strzone(SND(CTF_RETURNED(teamnumber)));
1407 flag.snd_flag_capture = strzone(SND(CTF_CAPTURE(teamnumber)));
1408 flag.snd_flag_dropped = strzone(SND(CTF_DROPPED(teamnumber)));
1409 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.
1410 precache_sound(flag.snd_flag_respawn);
1411 if (flag.snd_flag_touch == "") flag.snd_flag_touch = strzone(SND(CTF_TOUCH)); // again has no team-based sound
1412 precache_sound(flag.snd_flag_touch);
1413 if (flag.snd_flag_pass == "") flag.snd_flag_pass = strzone(SND(CTF_PASS)); // same story here
1414 precache_sound(flag.snd_flag_pass);
1417 precache_model(flag.model);
1420 _setmodel(flag, flag.model); // precision set below
1421 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1422 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1424 if(autocvar_g_ctf_flag_glowtrails)
1428 case NUM_TEAM_1: flag.glow_color = 251; break;
1429 case NUM_TEAM_2: flag.glow_color = 210; break;
1430 case NUM_TEAM_3: flag.glow_color = 110; break;
1431 case NUM_TEAM_4: flag.glow_color = 145; break;
1432 default: flag.glow_color = 254; break;
1434 flag.glow_size = 25;
1435 flag.glow_trail = 1;
1438 flag.effects |= EF_LOWPRECISION;
1439 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1440 if(autocvar_g_ctf_dynamiclights)
1444 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1445 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1446 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1447 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1448 default: flag.effects |= EF_DIMLIGHT; break;
1453 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1455 flag.dropped_origin = flag.origin;
1456 flag.noalign = true;
1457 flag.movetype = MOVETYPE_NONE;
1459 else // drop to floor, automatically find a platform and set that as spawn origin
1461 flag.noalign = false;
1464 flag.movetype = MOVETYPE_TOSS;
1467 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1475 // NOTE: LEGACY CODE, needs to be re-written!
1477 void havocbot_calculate_middlepoint()
1481 vector fo = '0 0 0';
1484 f = ctf_worldflaglist;
1489 f = f.ctf_worldflagnext;
1493 havocbot_ctf_middlepoint = s * (1.0 / n);
1494 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1498 entity havocbot_ctf_find_flag(entity bot)
1501 f = ctf_worldflaglist;
1504 if (CTF_SAMETEAM(bot, f))
1506 f = f.ctf_worldflagnext;
1511 entity havocbot_ctf_find_enemy_flag(entity bot)
1514 f = ctf_worldflaglist;
1519 if(CTF_DIFFTEAM(bot, f))
1526 else if(!bot.flagcarried)
1530 else if (CTF_DIFFTEAM(bot, f))
1532 f = f.ctf_worldflagnext;
1537 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1545 FOR_EACH_PLAYER(head)
1547 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1550 if(vlen(head.origin - org) < tc_radius)
1557 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1560 head = ctf_worldflaglist;
1563 if (CTF_SAMETEAM(self, head))
1565 head = head.ctf_worldflagnext;
1568 navigation_routerating(head, ratingscale, 10000);
1571 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1574 head = ctf_worldflaglist;
1577 if (CTF_SAMETEAM(self, head))
1579 head = head.ctf_worldflagnext;
1584 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1587 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1590 head = ctf_worldflaglist;
1595 if(CTF_DIFFTEAM(self, head))
1599 if(self.flagcarried)
1602 else if(!self.flagcarried)
1606 else if(CTF_DIFFTEAM(self, head))
1608 head = head.ctf_worldflagnext;
1611 navigation_routerating(head, ratingscale, 10000);
1614 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1616 if (!bot_waypoints_for_items)
1618 havocbot_goalrating_ctf_enemyflag(ratingscale);
1624 head = havocbot_ctf_find_enemy_flag(self);
1629 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1632 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1636 mf = havocbot_ctf_find_flag(self);
1638 if(mf.ctf_status == FLAG_BASE)
1642 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1645 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1648 head = ctf_worldflaglist;
1651 // flag is out in the field
1652 if(head.ctf_status != FLAG_BASE)
1653 if(head.tag_entity==world) // dropped
1657 if(vlen(org-head.origin)<df_radius)
1658 navigation_routerating(head, ratingscale, 10000);
1661 navigation_routerating(head, ratingscale, 10000);
1664 head = head.ctf_worldflagnext;
1668 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1672 head = findchainfloat(bot_pickup, true);
1675 // gather health and armor only
1677 if (head.health || head.armorvalue)
1678 if (vlen(head.origin - org) < sradius)
1680 // get the value of the item
1681 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1683 navigation_routerating(head, t * ratingscale, 500);
1689 void havocbot_ctf_reset_role(entity bot)
1691 float cdefense, cmiddle, coffense;
1692 entity mf, ef, head;
1695 if(bot.deadflag != DEAD_NO)
1698 if(vlen(havocbot_ctf_middlepoint)==0)
1699 havocbot_calculate_middlepoint();
1702 if (bot.flagcarried)
1704 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1708 mf = havocbot_ctf_find_flag(bot);
1709 ef = havocbot_ctf_find_enemy_flag(bot);
1711 // Retrieve stolen flag
1712 if(mf.ctf_status!=FLAG_BASE)
1714 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1718 // If enemy flag is taken go to the middle to intercept pursuers
1719 if(ef.ctf_status!=FLAG_BASE)
1721 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1725 // if there is only me on the team switch to offense
1727 FOR_EACH_PLAYER(head)
1728 if(SAME_TEAM(head, bot))
1733 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1737 // Evaluate best position to take
1738 // Count mates on middle position
1739 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1741 // Count mates on defense position
1742 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1744 // Count mates on offense position
1745 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1747 if(cdefense<=coffense)
1748 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1749 else if(coffense<=cmiddle)
1750 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1752 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1755 void havocbot_role_ctf_carrier()
1757 if(self.deadflag != DEAD_NO)
1759 havocbot_ctf_reset_role(self);
1763 if (self.flagcarried == world)
1765 havocbot_ctf_reset_role(self);
1769 if (self.bot_strategytime < time)
1771 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1773 navigation_goalrating_start();
1775 havocbot_goalrating_ctf_enemybase(50000);
1777 havocbot_goalrating_ctf_ourbase(50000);
1780 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1782 navigation_goalrating_end();
1784 if (self.navigation_hasgoals)
1785 self.havocbot_cantfindflag = time + 10;
1786 else if (time > self.havocbot_cantfindflag)
1788 // Can't navigate to my own base, suicide!
1789 // TODO: drop it and wander around
1790 Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0');
1796 void havocbot_role_ctf_escort()
1800 if(self.deadflag != DEAD_NO)
1802 havocbot_ctf_reset_role(self);
1806 if (self.flagcarried)
1808 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1812 // If enemy flag is back on the base switch to previous role
1813 ef = havocbot_ctf_find_enemy_flag(self);
1814 if(ef.ctf_status==FLAG_BASE)
1816 self.havocbot_role = self.havocbot_previous_role;
1817 self.havocbot_role_timeout = 0;
1821 // If the flag carrier reached the base switch to defense
1822 mf = havocbot_ctf_find_flag(self);
1823 if(mf.ctf_status!=FLAG_BASE)
1824 if(vlen(ef.origin - mf.dropped_origin) < 300)
1826 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1830 // Set the role timeout if necessary
1831 if (!self.havocbot_role_timeout)
1833 self.havocbot_role_timeout = time + random() * 30 + 60;
1836 // If nothing happened just switch to previous role
1837 if (time > self.havocbot_role_timeout)
1839 self.havocbot_role = self.havocbot_previous_role;
1840 self.havocbot_role_timeout = 0;
1844 // Chase the flag carrier
1845 if (self.bot_strategytime < time)
1847 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1848 navigation_goalrating_start();
1849 havocbot_goalrating_ctf_enemyflag(30000);
1850 havocbot_goalrating_ctf_ourstolenflag(40000);
1851 havocbot_goalrating_items(10000, self.origin, 10000);
1852 navigation_goalrating_end();
1856 void havocbot_role_ctf_offense()
1861 if(self.deadflag != DEAD_NO)
1863 havocbot_ctf_reset_role(self);
1867 if (self.flagcarried)
1869 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1874 mf = havocbot_ctf_find_flag(self);
1875 ef = havocbot_ctf_find_enemy_flag(self);
1878 if(mf.ctf_status!=FLAG_BASE)
1881 pos = mf.tag_entity.origin;
1885 // Try to get it if closer than the enemy base
1886 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1888 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1893 // Escort flag carrier
1894 if(ef.ctf_status!=FLAG_BASE)
1897 pos = ef.tag_entity.origin;
1901 if(vlen(pos-mf.dropped_origin)>700)
1903 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1908 // About to fail, switch to middlefield
1911 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1915 // Set the role timeout if necessary
1916 if (!self.havocbot_role_timeout)
1917 self.havocbot_role_timeout = time + 120;
1919 if (time > self.havocbot_role_timeout)
1921 havocbot_ctf_reset_role(self);
1925 if (self.bot_strategytime < time)
1927 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1928 navigation_goalrating_start();
1929 havocbot_goalrating_ctf_ourstolenflag(50000);
1930 havocbot_goalrating_ctf_enemybase(20000);
1931 havocbot_goalrating_items(5000, self.origin, 1000);
1932 havocbot_goalrating_items(1000, self.origin, 10000);
1933 navigation_goalrating_end();
1937 // Retriever (temporary role):
1938 void havocbot_role_ctf_retriever()
1942 if(self.deadflag != DEAD_NO)
1944 havocbot_ctf_reset_role(self);
1948 if (self.flagcarried)
1950 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1954 // If flag is back on the base switch to previous role
1955 mf = havocbot_ctf_find_flag(self);
1956 if(mf.ctf_status==FLAG_BASE)
1958 havocbot_ctf_reset_role(self);
1962 if (!self.havocbot_role_timeout)
1963 self.havocbot_role_timeout = time + 20;
1965 if (time > self.havocbot_role_timeout)
1967 havocbot_ctf_reset_role(self);
1971 if (self.bot_strategytime < time)
1976 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1977 navigation_goalrating_start();
1978 havocbot_goalrating_ctf_ourstolenflag(50000);
1979 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1980 havocbot_goalrating_ctf_enemybase(30000);
1981 havocbot_goalrating_items(500, self.origin, rt_radius);
1982 navigation_goalrating_end();
1986 void havocbot_role_ctf_middle()
1990 if(self.deadflag != DEAD_NO)
1992 havocbot_ctf_reset_role(self);
1996 if (self.flagcarried)
1998 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2002 mf = havocbot_ctf_find_flag(self);
2003 if(mf.ctf_status!=FLAG_BASE)
2005 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2009 if (!self.havocbot_role_timeout)
2010 self.havocbot_role_timeout = time + 10;
2012 if (time > self.havocbot_role_timeout)
2014 havocbot_ctf_reset_role(self);
2018 if (self.bot_strategytime < time)
2022 org = havocbot_ctf_middlepoint;
2023 org.z = self.origin.z;
2025 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2026 navigation_goalrating_start();
2027 havocbot_goalrating_ctf_ourstolenflag(50000);
2028 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
2029 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
2030 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
2031 havocbot_goalrating_items(2500, self.origin, 10000);
2032 havocbot_goalrating_ctf_enemybase(2500);
2033 navigation_goalrating_end();
2037 void havocbot_role_ctf_defense()
2041 if(self.deadflag != DEAD_NO)
2043 havocbot_ctf_reset_role(self);
2047 if (self.flagcarried)
2049 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2053 // If own flag was captured
2054 mf = havocbot_ctf_find_flag(self);
2055 if(mf.ctf_status!=FLAG_BASE)
2057 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2061 if (!self.havocbot_role_timeout)
2062 self.havocbot_role_timeout = time + 30;
2064 if (time > self.havocbot_role_timeout)
2066 havocbot_ctf_reset_role(self);
2069 if (self.bot_strategytime < time)
2074 org = mf.dropped_origin;
2075 mp_radius = havocbot_ctf_middlepoint_radius;
2077 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2078 navigation_goalrating_start();
2080 // if enemies are closer to our base, go there
2081 entity head, closestplayer = world;
2082 float distance, bestdistance = 10000;
2083 FOR_EACH_PLAYER(head)
2085 if(head.deadflag!=DEAD_NO)
2088 distance = vlen(org - head.origin);
2089 if(distance<bestdistance)
2091 closestplayer = head;
2092 bestdistance = distance;
2097 if(DIFF_TEAM(closestplayer, self))
2098 if(vlen(org - self.origin)>1000)
2099 if(checkpvs(self.origin,closestplayer)||random()<0.5)
2100 havocbot_goalrating_ctf_ourbase(30000);
2102 havocbot_goalrating_ctf_ourstolenflag(20000);
2103 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
2104 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
2105 havocbot_goalrating_items(10000, org, mp_radius);
2106 havocbot_goalrating_items(5000, self.origin, 10000);
2107 navigation_goalrating_end();
2111 void havocbot_role_ctf_setrole(entity bot, int role)
2113 LOG_TRACE(strcat(bot.netname," switched to "));
2116 case HAVOCBOT_CTF_ROLE_CARRIER:
2117 LOG_TRACE("carrier");
2118 bot.havocbot_role = havocbot_role_ctf_carrier;
2119 bot.havocbot_role_timeout = 0;
2120 bot.havocbot_cantfindflag = time + 10;
2121 bot.bot_strategytime = 0;
2123 case HAVOCBOT_CTF_ROLE_DEFENSE:
2124 LOG_TRACE("defense");
2125 bot.havocbot_role = havocbot_role_ctf_defense;
2126 bot.havocbot_role_timeout = 0;
2128 case HAVOCBOT_CTF_ROLE_MIDDLE:
2129 LOG_TRACE("middle");
2130 bot.havocbot_role = havocbot_role_ctf_middle;
2131 bot.havocbot_role_timeout = 0;
2133 case HAVOCBOT_CTF_ROLE_OFFENSE:
2134 LOG_TRACE("offense");
2135 bot.havocbot_role = havocbot_role_ctf_offense;
2136 bot.havocbot_role_timeout = 0;
2138 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2139 LOG_TRACE("retriever");
2140 bot.havocbot_previous_role = bot.havocbot_role;
2141 bot.havocbot_role = havocbot_role_ctf_retriever;
2142 bot.havocbot_role_timeout = time + 10;
2143 bot.bot_strategytime = 0;
2145 case HAVOCBOT_CTF_ROLE_ESCORT:
2146 LOG_TRACE("escort");
2147 bot.havocbot_previous_role = bot.havocbot_role;
2148 bot.havocbot_role = havocbot_role_ctf_escort;
2149 bot.havocbot_role_timeout = time + 30;
2150 bot.bot_strategytime = 0;
2161 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2164 int t = 0, t2 = 0, t3 = 0;
2166 // initially clear items so they can be set as necessary later.
2167 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2168 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2169 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2170 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2171 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2172 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
2174 // scan through all the flags and notify the client about them
2175 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2177 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2178 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2179 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2180 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2181 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; }
2183 switch(flag.ctf_status)
2188 if((flag.owner == self) || (flag.pass_sender == self))
2189 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
2191 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2196 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2202 // item for stopping players from capturing the flag too often
2203 if(self.ctf_captureshielded)
2204 self.ctf_flagstatus |= CTF_SHIELDED;
2206 // update the health of the flag carrier waypointsprite
2207 if(self.wps_flagcarrier)
2208 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2213 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2215 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2217 if(frag_target == frag_attacker) // damage done to yourself
2219 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2220 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2222 else // damage done to everyone else
2224 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2225 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2228 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2230 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)))
2231 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2233 frag_target.wps_helpme_time = time;
2234 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2236 // todo: add notification for when flag carrier needs help?
2241 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2243 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2245 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2246 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2249 if(frag_target.flagcarried)
2251 entity tmp_entity = frag_target.flagcarried;
2252 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2253 tmp_entity.ctf_dropper = world;
2259 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2262 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2265 void ctf_RemovePlayer(entity player)
2267 if(player.flagcarried)
2268 { ctf_Handle_Throw(player, world, DROP_NORMAL); }
2270 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2272 if(flag.pass_sender == player) { flag.pass_sender = world; }
2273 if(flag.pass_target == player) { flag.pass_target = world; }
2274 if(flag.ctf_dropper == player) { flag.ctf_dropper = world; }
2278 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2280 ctf_RemovePlayer(self);
2284 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2286 ctf_RemovePlayer(self);
2290 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2292 if(self.flagcarried)
2293 if(!autocvar_g_ctf_portalteleport)
2294 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2299 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2301 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2303 entity player = self;
2305 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2307 // pass the flag to a team mate
2308 if(autocvar_g_ctf_pass)
2310 entity head, closest_target = world;
2311 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2313 while(head) // find the closest acceptable target to pass to
2315 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2316 if(head != player && SAME_TEAM(head, player))
2317 if(!head.speedrunning && !head.vehicle)
2319 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2320 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2321 vector passer_center = CENTER_OR_VIEWOFS(player);
2323 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2325 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2327 if(IS_BOT_CLIENT(head))
2329 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2330 ctf_Handle_Throw(head, player, DROP_PASS);
2334 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2335 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2337 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2340 else if(player.flagcarried)
2344 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2345 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2346 { closest_target = head; }
2348 else { closest_target = head; }
2355 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2358 // throw the flag in front of you
2359 if(autocvar_g_ctf_throw && player.flagcarried)
2361 if(player.throw_count == -1)
2363 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2365 player.throw_prevtime = time;
2366 player.throw_count = 1;
2367 ctf_Handle_Throw(player, world, DROP_THROW);
2372 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2378 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2379 else { player.throw_count += 1; }
2380 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2382 player.throw_prevtime = time;
2383 ctf_Handle_Throw(player, world, DROP_THROW);
2392 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2394 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2396 self.wps_helpme_time = time;
2397 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2399 else // create a normal help me waypointsprite
2401 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
2402 WaypointSprite_Ping(self.wps_helpme);
2408 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2410 if(vh_player.flagcarried)
2412 vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
2414 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2416 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2420 setattachment(vh_player.flagcarried, vh_vehicle, "");
2421 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2422 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2423 //vh_player.flagcarried.angles = '0 0 0';
2431 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2433 if(vh_player.flagcarried)
2435 setattachment(vh_player.flagcarried, vh_player, "");
2436 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2437 vh_player.flagcarried.scale = FLAG_SCALE;
2438 vh_player.flagcarried.angles = '0 0 0';
2439 vh_player.flagcarried.nodrawtoclient = world;
2446 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2448 if(self.flagcarried)
2450 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));
2451 ctf_RespawnFlag(self.flagcarried);
2458 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2460 entity flag; // temporary entity for the search method
2462 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2464 switch(flag.ctf_status)
2469 // lock the flag, game is over
2470 flag.movetype = MOVETYPE_NONE;
2471 flag.takedamage = DAMAGE_NO;
2472 flag.solid = SOLID_NOT;
2473 flag.nextthink = false; // stop thinking
2475 //dprint("stopping the ", flag.netname, " from moving.\n");
2483 // do nothing for these flags
2492 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2494 havocbot_ctf_reset_role(self);
2498 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2500 //ret_float = ctf_teams;
2501 ret_string = "ctf_team";
2505 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2507 self.ctf_flagstatus = other.ctf_flagstatus;
2511 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2513 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2515 if (MapInfo_Get_ByID(i))
2517 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2523 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2524 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2531 bool superspec_Spectate(entity _player); // TODO
2532 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2533 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2535 if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2537 if(cmd_name == "followfc")
2550 case "red": _team = NUM_TEAM_1; break;
2551 case "blue": _team = NUM_TEAM_2; break;
2552 case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
2553 case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
2557 FOR_EACH_PLAYER(_player)
2559 if(_player.flagcarried && (_player.team == _team || _team == 0))
2562 if(_team == 0 && IS_SPEC(self) && self.enemy == _player)
2563 continue; // already spectating a fc, try to find the other fc
2564 return superspec_Spectate(_player);
2569 superspec_msg("", "", self, "No active flag carrier\n", 1);
2576 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2578 if(frag_target.flagcarried)
2579 ctf_Handle_Throw(frag_target, world, DROP_THROW);
2589 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2590 CTF flag for team one (Red).
2592 "angle" Angle the flag will point (minus 90 degrees)...
2593 "model" model to use, note this needs red and blue as skins 0 and 1...
2594 "noise" sound played when flag is picked up...
2595 "noise1" sound played when flag is returned by a teammate...
2596 "noise2" sound played when flag is captured...
2597 "noise3" sound played when flag is lost in the field and respawns itself...
2598 "noise4" sound played when flag is dropped by a player...
2599 "noise5" sound played when flag touches the ground... */
2600 spawnfunc(item_flag_team1)
2602 if(!g_ctf) { remove(self); return; }
2604 ctf_FlagSetup(NUM_TEAM_1, self);
2607 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2608 CTF flag for team two (Blue).
2610 "angle" Angle the flag will point (minus 90 degrees)...
2611 "model" model to use, note this needs red and blue as skins 0 and 1...
2612 "noise" sound played when flag is picked up...
2613 "noise1" sound played when flag is returned by a teammate...
2614 "noise2" sound played when flag is captured...
2615 "noise3" sound played when flag is lost in the field and respawns itself...
2616 "noise4" sound played when flag is dropped by a player...
2617 "noise5" sound played when flag touches the ground... */
2618 spawnfunc(item_flag_team2)
2620 if(!g_ctf) { remove(self); return; }
2622 ctf_FlagSetup(NUM_TEAM_2, self);
2625 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2626 CTF flag for team three (Yellow).
2628 "angle" Angle the flag will point (minus 90 degrees)...
2629 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2630 "noise" sound played when flag is picked up...
2631 "noise1" sound played when flag is returned by a teammate...
2632 "noise2" sound played when flag is captured...
2633 "noise3" sound played when flag is lost in the field and respawns itself...
2634 "noise4" sound played when flag is dropped by a player...
2635 "noise5" sound played when flag touches the ground... */
2636 spawnfunc(item_flag_team3)
2638 if(!g_ctf) { remove(self); return; }
2640 ctf_FlagSetup(NUM_TEAM_3, self);
2643 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2644 CTF flag for team four (Pink).
2646 "angle" Angle the flag will point (minus 90 degrees)...
2647 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2648 "noise" sound played when flag is picked up...
2649 "noise1" sound played when flag is returned by a teammate...
2650 "noise2" sound played when flag is captured...
2651 "noise3" sound played when flag is lost in the field and respawns itself...
2652 "noise4" sound played when flag is dropped by a player...
2653 "noise5" sound played when flag touches the ground... */
2654 spawnfunc(item_flag_team4)
2656 if(!g_ctf) { remove(self); return; }
2658 ctf_FlagSetup(NUM_TEAM_4, self);
2661 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2664 "angle" Angle the flag will point (minus 90 degrees)...
2665 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2666 "noise" sound played when flag is picked up...
2667 "noise1" sound played when flag is returned by a teammate...
2668 "noise2" sound played when flag is captured...
2669 "noise3" sound played when flag is lost in the field and respawns itself...
2670 "noise4" sound played when flag is dropped by a player...
2671 "noise5" sound played when flag touches the ground... */
2672 spawnfunc(item_flag_neutral)
2674 if(!g_ctf) { remove(self); return; }
2675 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2677 ctf_FlagSetup(0, self);
2680 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2681 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2682 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.
2684 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2685 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2688 if(!g_ctf) { remove(self); return; }
2690 self.classname = "ctf_team";
2691 self.team = self.cnt + 1;
2694 // compatibility for quake maps
2695 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2696 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2697 spawnfunc(info_player_team1);
2698 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2699 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2700 spawnfunc(info_player_team2);
2701 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2702 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2704 void team_CTF_neutralflag() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2705 void team_neutralobelisk() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2713 void ctf_ScoreRules(int teams)
2715 CheckAllowedTeams(world);
2716 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2717 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2718 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2719 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2720 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2721 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2722 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2723 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2724 ScoreRules_basics_end();
2727 // code from here on is just to support maps that don't have flag and team entities
2728 void ctf_SpawnTeam (string teamname, int teamcolor)
2730 entity this = new(ctf_team);
2731 this.netname = teamname;
2732 this.cnt = teamcolor;
2733 this.spawnfunc_checked = true;
2734 WITH(entity, self, this, spawnfunc_ctf_team(this));
2737 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2742 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2744 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2745 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2746 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2749 ctf_teams = bound(2, ctf_teams, 4);
2751 // if no teams are found, spawn defaults
2752 if(find(world, classname, "ctf_team") == world)
2754 LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2755 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2756 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2758 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2760 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2763 ctf_ScoreRules(ctf_teams);
2766 void ctf_Initialize()
2768 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2770 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2771 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2772 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2774 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2776 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);