7 REGISTER_MUTATOR(ctf, false)
10 SetLimits(autocvar_capturelimit_override, -1, autocvar_captureleadlimit_override, -1);
11 have_team_spawns = -1; // request team spawns
15 if (time > 1) // game loads at time 1
16 error("This is a game type and it cannot be added at runtime.");
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;
50 // flag constants // for most of these, there is just one question to be asked: WHYYYYY?
51 #define FLAG_MIN (PL_MIN_CONST + '0 0 -13')
52 #define FLAG_MAX (PL_MAX_CONST + '0 0 -13')
54 const float FLAG_SCALE = 0.6;
56 const float FLAG_THINKRATE = 0.2;
57 const float FLAG_TOUCHRATE = 0.5;
58 const float WPFE_THINKRATE = 0.5;
60 const vector FLAG_DROP_OFFSET = ('0 0 32');
61 const vector FLAG_CARRY_OFFSET = ('-16 0 8');
62 #define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
63 const vector FLAG_WAYPOINT_OFFSET = ('0 0 64');
64 const vector FLAG_FLOAT_OFFSET = ('0 0 32');
65 const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10');
67 const vector VEHICLE_FLAG_OFFSET = ('0 0 96');
68 const float VEHICLE_FLAG_SCALE = 1.0;
71 #define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
72 #define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color)
73 #define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1')
76 #define snd_flag_taken noise
77 #define snd_flag_returned noise1
78 #define snd_flag_capture noise2
79 #define snd_flag_respawn noise3
80 .string snd_flag_dropped;
81 .string snd_flag_touch;
82 .string snd_flag_pass;
89 // list of flags on the map
90 entity ctf_worldflaglist;
91 .entity ctf_worldflagnext;
92 .entity ctf_staleflagnext;
95 .entity bot_basewaypoint; // flag waypointsprite
98 .entity wps_flagcarrier;
99 .entity wps_flagdropped;
100 .entity wps_enemyflagcarrier;
101 .float wps_helpme_time;
102 bool wpforenemy_announced;
103 float wpforenemy_nextthink;
106 const int FLAG_BASE = 1;
107 const int FLAG_DROPPED = 2;
108 const int FLAG_CARRY = 3;
109 const int FLAG_PASSING = 4;
111 const int DROP_NORMAL = 1;
112 const int DROP_THROW = 2;
113 const int DROP_PASS = 3;
114 const int DROP_RESET = 4;
116 const int PICKUP_BASE = 1;
117 const int PICKUP_DROPPED = 2;
119 const int CAPTURE_NORMAL = 1;
120 const int CAPTURE_DROPPED = 2;
122 const int RETURN_TIMEOUT = 1;
123 const int RETURN_DROPPED = 2;
124 const int RETURN_DAMAGE = 3;
125 const int RETURN_SPEEDRUN = 4;
126 const int RETURN_NEEDKILL = 5;
129 #define ctf_spawnorigin dropped_origin
130 bool ctf_stalemate; // indicates that a stalemate is active
131 float ctf_captimerecord; // record time for capturing the flag
132 .float ctf_pickuptime;
134 .int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
135 .entity ctf_dropper; // don't allow spam of dropping the flag
136 .int max_flag_health;
137 .float next_take_time;
138 .bool ctf_flagdamaged;
141 // passing/throwing properties
142 .float pass_distance;
145 .float throw_antispam;
146 .float throw_prevtime;
149 // CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
150 .bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
151 float ctf_captureshield_min_negscore; // punish at -20 points
152 float ctf_captureshield_max_ratio; // punish at most 30% of each team
153 float ctf_captureshield_force; // push force of the shield
156 bool ctf_oneflag; // indicates whether or not a neutral flag has been found
159 const int HAVOCBOT_CTF_ROLE_NONE = 0;
160 const int HAVOCBOT_CTF_ROLE_DEFENSE = 2;
161 const int HAVOCBOT_CTF_ROLE_MIDDLE = 4;
162 const int HAVOCBOT_CTF_ROLE_OFFENSE = 8;
163 const int HAVOCBOT_CTF_ROLE_CARRIER = 16;
164 const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32;
165 const int HAVOCBOT_CTF_ROLE_ESCORT = 64;
167 .bool havocbot_cantfindflag;
169 vector havocbot_ctf_middlepoint;
170 float havocbot_ctf_middlepoint_radius;
172 void havocbot_role_ctf_setrole(entity bot, int role);
175 #define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b))
176 #define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b))
178 // networked flag statuses
182 const int CTF_RED_FLAG_TAKEN = 1;
183 const int CTF_RED_FLAG_LOST = 2;
184 const int CTF_RED_FLAG_CARRYING = 3;
185 const int CTF_BLUE_FLAG_TAKEN = 4;
186 const int CTF_BLUE_FLAG_LOST = 8;
187 const int CTF_BLUE_FLAG_CARRYING = 12;
188 const int CTF_YELLOW_FLAG_TAKEN = 16;
189 const int CTF_YELLOW_FLAG_LOST = 32;
190 const int CTF_YELLOW_FLAG_CARRYING = 48;
191 const int CTF_PINK_FLAG_TAKEN = 64;
192 const int CTF_PINK_FLAG_LOST = 128;
193 const int CTF_PINK_FLAG_CARRYING = 192;
194 const int CTF_NEUTRAL_FLAG_TAKEN = 256;
195 const int CTF_NEUTRAL_FLAG_LOST = 512;
196 const int CTF_NEUTRAL_FLAG_CARRYING = 768;
197 const int CTF_FLAG_NEUTRAL = 2048;
198 const int CTF_SHIELDED = 4096;
201 #ifdef IMPLEMENTATION
204 #include "../../../common/vehicles/all.qh"
205 #include "../../teamplay.qh"
208 #include "../../../lib/warpzone/common.qh"
210 bool autocvar_g_ctf_allow_vehicle_carry;
211 bool autocvar_g_ctf_allow_vehicle_touch;
212 bool autocvar_g_ctf_allow_monster_touch;
213 bool autocvar_g_ctf_throw;
214 float autocvar_g_ctf_throw_angle_max;
215 float autocvar_g_ctf_throw_angle_min;
216 int autocvar_g_ctf_throw_punish_count;
217 float autocvar_g_ctf_throw_punish_delay;
218 float autocvar_g_ctf_throw_punish_time;
219 float autocvar_g_ctf_throw_strengthmultiplier;
220 float autocvar_g_ctf_throw_velocity_forward;
221 float autocvar_g_ctf_throw_velocity_up;
222 float autocvar_g_ctf_drop_velocity_up;
223 float autocvar_g_ctf_drop_velocity_side;
224 bool autocvar_g_ctf_oneflag_reverse;
225 bool autocvar_g_ctf_portalteleport;
226 bool autocvar_g_ctf_pass;
227 float autocvar_g_ctf_pass_arc;
228 float autocvar_g_ctf_pass_arc_max;
229 float autocvar_g_ctf_pass_directional_max;
230 float autocvar_g_ctf_pass_directional_min;
231 float autocvar_g_ctf_pass_radius;
232 float autocvar_g_ctf_pass_wait;
233 bool autocvar_g_ctf_pass_request;
234 float autocvar_g_ctf_pass_turnrate;
235 float autocvar_g_ctf_pass_timelimit;
236 float autocvar_g_ctf_pass_velocity;
237 bool autocvar_g_ctf_dynamiclights;
238 float autocvar_g_ctf_flag_collect_delay;
239 float autocvar_g_ctf_flag_damageforcescale;
240 bool autocvar_g_ctf_flag_dropped_waypoint;
241 bool autocvar_g_ctf_flag_dropped_floatinwater;
242 bool autocvar_g_ctf_flag_glowtrails;
243 int autocvar_g_ctf_flag_health;
244 bool autocvar_g_ctf_flag_return;
245 float autocvar_g_ctf_flag_return_carried_radius;
246 float autocvar_g_ctf_flag_return_time;
247 bool autocvar_g_ctf_flag_return_when_unreachable;
248 float autocvar_g_ctf_flag_return_damage;
249 float autocvar_g_ctf_flag_return_damage_delay;
250 float autocvar_g_ctf_flag_return_dropped;
251 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
252 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
253 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
254 float autocvar_g_ctf_flagcarrier_selfforcefactor;
255 float autocvar_g_ctf_flagcarrier_damagefactor;
256 float autocvar_g_ctf_flagcarrier_forcefactor;
257 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
258 bool autocvar_g_ctf_fullbrightflags;
259 bool autocvar_g_ctf_ignore_frags;
260 int autocvar_g_ctf_score_capture;
261 int autocvar_g_ctf_score_capture_assist;
262 int autocvar_g_ctf_score_kill;
263 int autocvar_g_ctf_score_penalty_drop;
264 int autocvar_g_ctf_score_penalty_returned;
265 int autocvar_g_ctf_score_pickup_base;
266 int autocvar_g_ctf_score_pickup_dropped_early;
267 int autocvar_g_ctf_score_pickup_dropped_late;
268 int autocvar_g_ctf_score_return;
269 float autocvar_g_ctf_shield_force;
270 float autocvar_g_ctf_shield_max_ratio;
271 int autocvar_g_ctf_shield_min_negscore;
272 bool autocvar_g_ctf_stalemate;
273 int autocvar_g_ctf_stalemate_endcondition;
274 float autocvar_g_ctf_stalemate_time;
275 bool autocvar_g_ctf_reverse;
276 float autocvar_g_ctf_dropped_capture_delay;
277 float autocvar_g_ctf_dropped_capture_radius;
279 void ctf_FakeTimeLimit(entity e, float t)
282 WriteByte(MSG_ONE, 3); // svc_updatestat
283 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
285 WriteCoord(MSG_ONE, autocvar_timelimit);
287 WriteCoord(MSG_ONE, (t + 1) / 60);
290 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
292 if(autocvar_sv_eventlog)
293 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
294 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
297 void ctf_CaptureRecord(entity flag, entity player)
299 float cap_record = ctf_captimerecord;
300 float cap_time = (time - flag.ctf_pickuptime);
301 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
304 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
305 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)); }
306 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)); }
307 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)); }
309 // write that shit in the database
310 if(!ctf_oneflag) // but not in 1-flag mode
311 if((!ctf_captimerecord) || (cap_time < cap_record))
313 ctf_captimerecord = cap_time;
314 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
315 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
316 write_recordmarker(player, (time - cap_time), cap_time);
320 void ctf_FlagcarrierWaypoints(entity player)
322 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
323 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
324 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
325 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
328 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
330 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
331 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
332 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
333 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
336 if(current_height) // make sure we can actually do this arcing path
338 targpos = (to + ('0 0 1' * current_height));
339 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
340 if(trace_fraction < 1)
342 //print("normal arc line failed, trying to find new pos...");
343 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
344 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
345 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
346 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
347 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
350 else { targpos = to; }
352 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
354 vector desired_direction = normalize(targpos - from);
355 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
356 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
359 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
361 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
363 // directional tracing only
365 makevectors(passer_angle);
367 // find the closest point on the enemy to the center of the attack
368 float h; // hypotenuse, which is the distance between attacker to head
369 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
371 h = vlen(head_center - passer_center);
372 a = h * (normalize(head_center - passer_center) * v_forward);
374 vector nearest_on_line = (passer_center + a * v_forward);
375 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
377 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
378 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
380 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
385 else { return true; }
389 // =======================
390 // CaptureShield Functions
391 // =======================
393 bool ctf_CaptureShield_CheckStatus(entity p)
395 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
397 int players_worseeq, players_total;
399 if(ctf_captureshield_max_ratio <= 0)
402 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
403 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
404 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
405 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
407 sr = ((s - s2) + (s3 + s4));
409 if(sr >= -ctf_captureshield_min_negscore)
412 players_total = players_worseeq = 0;
417 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
418 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
419 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
420 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
422 ser = ((se - se2) + (se3 + se4));
429 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
430 // use this rule here
432 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
438 void ctf_CaptureShield_Update(entity player, bool wanted_status)
440 bool updated_status = ctf_CaptureShield_CheckStatus(player);
441 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
443 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
444 player.ctf_captureshielded = updated_status;
448 bool ctf_CaptureShield_Customize()
450 if(!other.ctf_captureshielded) { return false; }
451 if(CTF_SAMETEAM(self, other)) { return false; }
456 void ctf_CaptureShield_Touch()
458 if(!other.ctf_captureshielded) { return; }
459 if(CTF_SAMETEAM(self, other)) { return; }
461 vector mymid = (self.absmin + self.absmax) * 0.5;
462 vector othermid = (other.absmin + other.absmax) * 0.5;
464 Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
465 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
468 void ctf_CaptureShield_Spawn(entity flag)
470 entity shield = new(ctf_captureshield);
473 shield.team = self.team;
474 shield.touch = ctf_CaptureShield_Touch;
475 shield.customizeentityforclient = ctf_CaptureShield_Customize;
476 shield.effects = EF_ADDITIVE;
477 shield.movetype = MOVETYPE_NOCLIP;
478 shield.solid = SOLID_TRIGGER;
479 shield.avelocity = '7 0 11';
482 setorigin(shield, self.origin);
483 setmodel(shield, MDL_CTF_SHIELD);
484 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
488 // ====================
489 // Drop/Pass/Throw Code
490 // ====================
492 void ctf_Handle_Drop(entity flag, entity player, int droptype)
495 player = (player ? player : flag.pass_sender);
498 flag.movetype = MOVETYPE_TOSS;
499 flag.takedamage = DAMAGE_YES;
500 flag.angles = '0 0 0';
501 flag.health = flag.max_flag_health;
502 flag.ctf_droptime = time;
503 flag.ctf_dropper = player;
504 flag.ctf_status = FLAG_DROPPED;
506 // messages and sounds
507 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
508 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
509 ctf_EventLog("dropped", player.team, player);
512 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
513 PlayerScore_Add(player, SP_CTF_DROPS, 1);
516 if(autocvar_g_ctf_flag_dropped_waypoint) {
517 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);
518 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
521 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
523 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
524 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
527 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
529 if(droptype == DROP_PASS)
531 flag.pass_distance = 0;
532 flag.pass_sender = world;
533 flag.pass_target = world;
537 void ctf_Handle_Retrieve(entity flag, entity player)
539 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
540 entity sender = flag.pass_sender;
542 // transfer flag to player
544 flag.owner.flagcarried = flag;
549 setattachment(flag, player.vehicle, "");
550 setorigin(flag, VEHICLE_FLAG_OFFSET);
551 flag.scale = VEHICLE_FLAG_SCALE;
555 setattachment(flag, player, "");
556 setorigin(flag, FLAG_CARRY_OFFSET);
558 flag.movetype = MOVETYPE_NONE;
559 flag.takedamage = DAMAGE_NO;
560 flag.solid = SOLID_NOT;
561 flag.angles = '0 0 0';
562 flag.ctf_status = FLAG_CARRY;
564 // messages and sounds
565 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
566 ctf_EventLog("receive", flag.team, player);
568 FOR_EACH_REALPLAYER(tmp_player)
570 if(tmp_player == sender)
571 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);
572 else if(tmp_player == player)
573 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);
574 else if(SAME_TEAM(tmp_player, sender))
575 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);
578 // create new waypoint
579 ctf_FlagcarrierWaypoints(player);
581 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
582 player.throw_antispam = sender.throw_antispam;
584 flag.pass_distance = 0;
585 flag.pass_sender = world;
586 flag.pass_target = world;
589 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
591 entity flag = player.flagcarried;
592 vector targ_origin, flag_velocity;
594 if(!flag) { return; }
595 if((droptype == DROP_PASS) && !receiver) { return; }
597 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
600 setattachment(flag, world, "");
601 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
602 flag.owner.flagcarried = world;
604 flag.solid = SOLID_TRIGGER;
605 flag.ctf_dropper = player;
606 flag.ctf_droptime = time;
608 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
615 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
616 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
617 WarpZone_RefSys_Copy(flag, receiver);
618 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
619 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
621 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
622 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
625 flag.movetype = MOVETYPE_FLY;
626 flag.takedamage = DAMAGE_NO;
627 flag.pass_sender = player;
628 flag.pass_target = receiver;
629 flag.ctf_status = FLAG_PASSING;
632 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
633 WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
634 ctf_EventLog("pass", flag.team, player);
640 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'));
642 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)));
643 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
644 ctf_Handle_Drop(flag, player, droptype);
650 flag.velocity = '0 0 0'; // do nothing
657 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);
658 ctf_Handle_Drop(flag, player, droptype);
663 // kill old waypointsprite
664 WaypointSprite_Ping(player.wps_flagcarrier);
665 WaypointSprite_Kill(player.wps_flagcarrier);
667 if(player.wps_enemyflagcarrier)
668 WaypointSprite_Kill(player.wps_enemyflagcarrier);
671 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
679 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
681 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
682 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
683 entity player_team_flag = world, tmp_entity;
684 float old_time, new_time;
686 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
687 if(CTF_DIFFTEAM(player, flag)) { return; }
690 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
691 if(SAME_TEAM(tmp_entity, player))
693 player_team_flag = tmp_entity;
697 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
699 player.throw_prevtime = time;
700 player.throw_count = 0;
702 // messages and sounds
703 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
704 ctf_CaptureRecord(enemy_flag, player);
705 _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);
709 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
710 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
715 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
716 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
718 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
719 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
720 if(!old_time || new_time < old_time)
721 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
724 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
725 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
728 if(capturetype == CAPTURE_NORMAL)
730 WaypointSprite_Kill(player.wps_flagcarrier);
731 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
733 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
734 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
738 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
739 ctf_RespawnFlag(enemy_flag);
742 void ctf_Handle_Return(entity flag, entity player)
744 // messages and sounds
745 if(IS_MONSTER(player))
747 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
751 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
752 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
754 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
755 ctf_EventLog("return", flag.team, player);
758 if(IS_PLAYER(player))
760 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
761 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
763 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
766 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
770 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
771 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
772 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
776 if(player.flagcarried == flag)
777 WaypointSprite_Kill(player.wps_flagcarrier);
780 ctf_RespawnFlag(flag);
783 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
786 float pickup_dropped_score; // used to calculate dropped pickup score
787 entity tmp_entity; // temporary entity
789 // attach the flag to the player
791 player.flagcarried = flag;
794 setattachment(flag, player.vehicle, "");
795 setorigin(flag, VEHICLE_FLAG_OFFSET);
796 flag.scale = VEHICLE_FLAG_SCALE;
800 setattachment(flag, player, "");
801 setorigin(flag, FLAG_CARRY_OFFSET);
805 flag.movetype = MOVETYPE_NONE;
806 flag.takedamage = DAMAGE_NO;
807 flag.solid = SOLID_NOT;
808 flag.angles = '0 0 0';
809 flag.ctf_status = FLAG_CARRY;
813 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
814 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
818 // messages and sounds
819 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
820 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
821 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
822 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
823 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)); }
825 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);
828 FOR_EACH_PLAYER(tmp_entity)
829 if(tmp_entity != player)
830 if(DIFF_TEAM(player, tmp_entity))
831 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
834 FOR_EACH_PLAYER(tmp_entity)
835 if(tmp_entity != player)
836 if(CTF_SAMETEAM(flag, tmp_entity))
837 if(SAME_TEAM(player, tmp_entity))
838 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
840 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);
842 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
845 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
846 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
851 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
852 ctf_EventLog("steal", flag.team, player);
858 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);
859 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);
860 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
861 PlayerTeamScore_AddScore(player, pickup_dropped_score);
862 ctf_EventLog("pickup", flag.team, player);
870 if(pickuptype == PICKUP_BASE)
872 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
873 if((player.speedrunning) && (ctf_captimerecord))
874 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
878 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
881 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
882 ctf_FlagcarrierWaypoints(player);
883 WaypointSprite_Ping(player.wps_flagcarrier);
887 // ===================
888 // Main Flag Functions
889 // ===================
891 void ctf_CheckFlagReturn(entity flag, int returntype)
893 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
895 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
897 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
901 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;
902 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;
903 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;
904 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;
908 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
910 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
911 ctf_EventLog("returned", flag.team, world);
912 ctf_RespawnFlag(flag);
917 bool ctf_Stalemate_Customize()
919 // make spectators see what the player would see
921 e = WaypointSprite_getviewentity(other);
922 wp_owner = self.owner;
925 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
926 if(SAME_TEAM(wp_owner, e)) { return false; }
927 if(!IS_PLAYER(e)) { return false; }
932 void ctf_CheckStalemate(void)
935 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
938 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
940 // build list of stale flags
941 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
943 if(autocvar_g_ctf_stalemate)
944 if(tmp_entity.ctf_status != FLAG_BASE)
945 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
947 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
948 ctf_staleflaglist = tmp_entity;
950 switch(tmp_entity.team)
952 case NUM_TEAM_1: ++stale_red_flags; break;
953 case NUM_TEAM_2: ++stale_blue_flags; break;
954 case NUM_TEAM_3: ++stale_yellow_flags; break;
955 case NUM_TEAM_4: ++stale_pink_flags; break;
956 default: ++stale_neutral_flags; break;
962 stale_flags = (stale_neutral_flags >= 1);
964 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
966 if(ctf_oneflag && stale_flags == 1)
967 ctf_stalemate = true;
968 else if(stale_flags >= 2)
969 ctf_stalemate = true;
970 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
971 { ctf_stalemate = false; wpforenemy_announced = false; }
972 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
973 { ctf_stalemate = false; wpforenemy_announced = false; }
975 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
978 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
980 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
982 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);
983 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
984 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
988 if (!wpforenemy_announced)
990 FOR_EACH_REALPLAYER(tmp_entity)
991 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
993 wpforenemy_announced = true;
998 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1000 if(ITEM_DAMAGE_NEEDKILL(deathtype))
1002 if(autocvar_g_ctf_flag_return_damage_delay)
1004 self.ctf_flagdamaged = true;
1009 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1013 if(autocvar_g_ctf_flag_return_damage)
1015 // reduce health and check if it should be returned
1016 self.health = self.health - damage;
1017 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
1022 void ctf_FlagThink()
1027 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
1030 if(self == ctf_worldflaglist) // only for the first flag
1031 FOR_EACH_CLIENT(tmp_entity)
1032 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
1035 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
1036 LOG_TRACE("wtf the flag got squashed?\n");
1037 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
1038 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
1039 setsize(self, FLAG_MIN, FLAG_MAX); }
1041 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
1045 self.angles = '0 0 0';
1052 // main think method
1053 switch(self.ctf_status)
1057 if(autocvar_g_ctf_dropped_capture_radius)
1059 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
1060 if(tmp_entity.ctf_status == FLAG_DROPPED)
1061 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
1062 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
1063 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
1070 if(autocvar_g_ctf_flag_dropped_floatinwater)
1072 vector midpoint = ((self.absmin + self.absmax) * 0.5);
1073 if(pointcontents(midpoint) == CONTENT_WATER)
1075 self.velocity = self.velocity * 0.5;
1077 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
1078 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
1080 { self.movetype = MOVETYPE_FLY; }
1082 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
1084 if(autocvar_g_ctf_flag_return_dropped)
1086 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
1089 ctf_CheckFlagReturn(self, RETURN_DROPPED);
1093 if(self.ctf_flagdamaged)
1095 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
1096 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1099 else if(autocvar_g_ctf_flag_return_time)
1101 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
1102 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
1110 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
1113 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
1115 setself(self.owner);
1116 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
1120 if(autocvar_g_ctf_stalemate)
1122 if(time >= wpforenemy_nextthink)
1124 ctf_CheckStalemate();
1125 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1128 if(CTF_SAMETEAM(self, self.owner) && self.team)
1130 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1131 ctf_Handle_Throw(self.owner, world, DROP_THROW);
1132 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
1133 ctf_Handle_Return(self, self.owner);
1140 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
1141 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
1142 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
1144 if((self.pass_target == world)
1145 || (self.pass_target.deadflag != DEAD_NO)
1146 || (self.pass_target.flagcarried)
1147 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
1148 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
1149 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1151 // give up, pass failed
1152 ctf_Handle_Drop(self, world, DROP_PASS);
1156 // still a viable target, go for it
1157 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
1162 default: // this should never happen
1164 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
1170 void ctf_FlagTouch()
1172 if(gameover) { return; }
1173 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1175 entity toucher = other, tmp_entity;
1176 bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
1178 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1179 if(ITEM_TOUCH_NEEDKILL())
1181 if(!autocvar_g_ctf_flag_return_damage_delay)
1184 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1186 if(!self.ctf_flagdamaged) { return; }
1189 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
1191 // special touch behaviors
1192 if(toucher.frozen) { return; }
1193 else if(IS_VEHICLE(toucher))
1195 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1196 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1198 return; // do nothing
1200 else if(IS_MONSTER(toucher))
1202 if(!autocvar_g_ctf_allow_monster_touch)
1203 return; // do nothing
1205 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1207 if(time > self.wait) // if we haven't in a while, play a sound/effect
1209 Send_Effect_(self.toucheffect, self.origin, '0 0 0', 1);
1210 _sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1211 self.wait = time + FLAG_TOUCHRATE;
1215 else if(toucher.deadflag != DEAD_NO) { return; }
1217 switch(self.ctf_status)
1223 if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1224 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1225 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1226 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1228 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
1229 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1230 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1231 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1237 if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team
1238 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
1239 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1240 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1246 LOG_TRACE("Someone touched a flag even though it was being carried?\n");
1252 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
1254 if(DIFF_TEAM(toucher, self.pass_sender))
1255 ctf_Handle_Return(self, toucher);
1257 ctf_Handle_Retrieve(self, toucher);
1264 .float last_respawn;
1265 void ctf_RespawnFlag(entity flag)
1267 // check for flag respawn being called twice in a row
1268 if(flag.last_respawn > time - 0.5)
1269 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1271 flag.last_respawn = time;
1273 // reset the player (if there is one)
1274 if((flag.owner) && (flag.owner.flagcarried == flag))
1276 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1277 WaypointSprite_Kill(flag.wps_flagcarrier);
1279 flag.owner.flagcarried = world;
1281 if(flag.speedrunning)
1282 ctf_FakeTimeLimit(flag.owner, -1);
1285 if((flag.owner) && (flag.owner.vehicle))
1286 flag.scale = FLAG_SCALE;
1288 if(flag.ctf_status == FLAG_DROPPED)
1289 { WaypointSprite_Kill(flag.wps_flagdropped); }
1292 setattachment(flag, world, "");
1293 setorigin(flag, flag.ctf_spawnorigin);
1295 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1296 flag.takedamage = DAMAGE_NO;
1297 flag.health = flag.max_flag_health;
1298 flag.solid = SOLID_TRIGGER;
1299 flag.velocity = '0 0 0';
1300 flag.angles = flag.mangle;
1301 flag.flags = FL_ITEM | FL_NOTARGET;
1303 flag.ctf_status = FLAG_BASE;
1305 flag.pass_distance = 0;
1306 flag.pass_sender = world;
1307 flag.pass_target = world;
1308 flag.ctf_dropper = world;
1309 flag.ctf_pickuptime = 0;
1310 flag.ctf_droptime = 0;
1311 flag.ctf_flagdamaged = 0;
1313 ctf_CheckStalemate();
1319 if(IS_PLAYER(self.owner))
1320 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1322 ctf_RespawnFlag(self);
1325 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1328 waypoint_spawnforitem_force(self, self.origin);
1329 self.nearestwaypointtimeout = 0; // activate waypointing again
1330 self.bot_basewaypoint = self.nearestwaypoint;
1336 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1337 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1338 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1339 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1340 default: basename = WP_FlagBaseNeutral; break;
1343 entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
1344 wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
1345 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1347 // captureshield setup
1348 ctf_CaptureShield_Spawn(self);
1351 void set_flag_string(entity flag, .string field, string value, string teamname)
1353 if(flag.(field) == "")
1354 flag.(field) = strzone(sprintf(value,teamname));
1357 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1360 setself(flag); // for later usage with droptofloor()
1363 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1364 ctf_worldflaglist = flag;
1366 setattachment(flag, world, "");
1368 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1369 flag.team = teamnumber;
1370 flag.classname = "item_flag_team";
1371 flag.target = "###item###"; // wut?
1372 flag.flags = FL_ITEM | FL_NOTARGET;
1373 flag.solid = SOLID_TRIGGER;
1374 flag.takedamage = DAMAGE_NO;
1375 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1376 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1377 flag.health = flag.max_flag_health;
1378 flag.event_damage = ctf_FlagDamage;
1379 flag.pushable = true;
1380 flag.teleportable = TELEPORT_NORMAL;
1381 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1382 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1383 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1384 flag.velocity = '0 0 0';
1385 flag.mangle = flag.angles;
1386 flag.reset = ctf_Reset;
1387 flag.touch = ctf_FlagTouch;
1388 flag.think = ctf_FlagThink;
1389 flag.nextthink = time + FLAG_THINKRATE;
1390 flag.ctf_status = FLAG_BASE;
1392 string teamname = Static_Team_ColorName_Lower(teamnumber);
1394 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1395 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1396 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1397 set_flag_string(flag, toucheffect, "%sflag_touch", teamname);
1398 set_flag_string(flag, passeffect, "%s_pass", teamname);
1399 set_flag_string(flag, capeffect, "%s_cap", teamname);
1402 flag.snd_flag_taken = SND(CTF_TAKEN(teamnumber));
1403 flag.snd_flag_returned = SND(CTF_RETURNED(teamnumber));
1404 flag.snd_flag_capture = SND(CTF_CAPTURE(teamnumber));
1405 flag.snd_flag_dropped = SND(CTF_DROPPED(teamnumber));
1406 if (flag.snd_flag_respawn == "") flag.snd_flag_respawn = SND(CTF_RESPAWN); // if there is ever a team-based sound for this, update the code to match.
1407 precache_sound(flag.snd_flag_respawn);
1408 if (flag.snd_flag_touch == "") flag.snd_flag_touch = SND(CTF_TOUCH); // again has no team-based sound
1409 precache_sound(flag.snd_flag_touch);
1410 if (flag.snd_flag_pass == "") flag.snd_flag_pass = SND(CTF_PASS); // same story here
1411 precache_sound(flag.snd_flag_pass);
1414 precache_model(flag.model);
1417 _setmodel(flag, flag.model); // precision set below
1418 setsize(flag, FLAG_MIN, FLAG_MAX);
1419 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1421 if(autocvar_g_ctf_flag_glowtrails)
1425 case NUM_TEAM_1: flag.glow_color = 251; break;
1426 case NUM_TEAM_2: flag.glow_color = 210; break;
1427 case NUM_TEAM_3: flag.glow_color = 110; break;
1428 case NUM_TEAM_4: flag.glow_color = 145; break;
1429 default: flag.glow_color = 254; break;
1431 flag.glow_size = 25;
1432 flag.glow_trail = 1;
1435 flag.effects |= EF_LOWPRECISION;
1436 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1437 if(autocvar_g_ctf_dynamiclights)
1441 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1442 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1443 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1444 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1445 default: flag.effects |= EF_DIMLIGHT; break;
1450 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1452 flag.dropped_origin = flag.origin;
1453 flag.noalign = true;
1454 flag.movetype = MOVETYPE_NONE;
1456 else // drop to floor, automatically find a platform and set that as spawn origin
1458 flag.noalign = false;
1461 flag.movetype = MOVETYPE_TOSS;
1464 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1472 // NOTE: LEGACY CODE, needs to be re-written!
1474 void havocbot_calculate_middlepoint()
1478 vector fo = '0 0 0';
1481 f = ctf_worldflaglist;
1486 f = f.ctf_worldflagnext;
1490 havocbot_ctf_middlepoint = s * (1.0 / n);
1491 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1495 entity havocbot_ctf_find_flag(entity bot)
1498 f = ctf_worldflaglist;
1501 if (CTF_SAMETEAM(bot, f))
1503 f = f.ctf_worldflagnext;
1508 entity havocbot_ctf_find_enemy_flag(entity bot)
1511 f = ctf_worldflaglist;
1516 if(CTF_DIFFTEAM(bot, f))
1523 else if(!bot.flagcarried)
1527 else if (CTF_DIFFTEAM(bot, f))
1529 f = f.ctf_worldflagnext;
1534 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1542 FOR_EACH_PLAYER(head)
1544 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1547 if(vlen(head.origin - org) < tc_radius)
1554 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1557 head = ctf_worldflaglist;
1560 if (CTF_SAMETEAM(self, head))
1562 head = head.ctf_worldflagnext;
1565 navigation_routerating(head, ratingscale, 10000);
1568 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1571 head = ctf_worldflaglist;
1574 if (CTF_SAMETEAM(self, head))
1576 head = head.ctf_worldflagnext;
1581 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1584 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1587 head = ctf_worldflaglist;
1592 if(CTF_DIFFTEAM(self, head))
1596 if(self.flagcarried)
1599 else if(!self.flagcarried)
1603 else if(CTF_DIFFTEAM(self, head))
1605 head = head.ctf_worldflagnext;
1608 navigation_routerating(head, ratingscale, 10000);
1611 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1613 if (!bot_waypoints_for_items)
1615 havocbot_goalrating_ctf_enemyflag(ratingscale);
1621 head = havocbot_ctf_find_enemy_flag(self);
1626 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1629 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1633 mf = havocbot_ctf_find_flag(self);
1635 if(mf.ctf_status == FLAG_BASE)
1639 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1642 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1645 head = ctf_worldflaglist;
1648 // flag is out in the field
1649 if(head.ctf_status != FLAG_BASE)
1650 if(head.tag_entity==world) // dropped
1654 if(vlen(org-head.origin)<df_radius)
1655 navigation_routerating(head, ratingscale, 10000);
1658 navigation_routerating(head, ratingscale, 10000);
1661 head = head.ctf_worldflagnext;
1665 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1669 head = findchainfloat(bot_pickup, true);
1672 // gather health and armor only
1674 if (head.health || head.armorvalue)
1675 if (vlen(head.origin - org) < sradius)
1677 // get the value of the item
1678 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1680 navigation_routerating(head, t * ratingscale, 500);
1686 void havocbot_ctf_reset_role(entity bot)
1688 float cdefense, cmiddle, coffense;
1689 entity mf, ef, head;
1692 if(bot.deadflag != DEAD_NO)
1695 if(vlen(havocbot_ctf_middlepoint)==0)
1696 havocbot_calculate_middlepoint();
1699 if (bot.flagcarried)
1701 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1705 mf = havocbot_ctf_find_flag(bot);
1706 ef = havocbot_ctf_find_enemy_flag(bot);
1708 // Retrieve stolen flag
1709 if(mf.ctf_status!=FLAG_BASE)
1711 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1715 // If enemy flag is taken go to the middle to intercept pursuers
1716 if(ef.ctf_status!=FLAG_BASE)
1718 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1722 // if there is only me on the team switch to offense
1724 FOR_EACH_PLAYER(head)
1725 if(SAME_TEAM(head, bot))
1730 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1734 // Evaluate best position to take
1735 // Count mates on middle position
1736 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1738 // Count mates on defense position
1739 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1741 // Count mates on offense position
1742 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1744 if(cdefense<=coffense)
1745 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1746 else if(coffense<=cmiddle)
1747 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1749 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1752 void havocbot_role_ctf_carrier()
1754 if(self.deadflag != DEAD_NO)
1756 havocbot_ctf_reset_role(self);
1760 if (self.flagcarried == world)
1762 havocbot_ctf_reset_role(self);
1766 if (self.bot_strategytime < time)
1768 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1770 navigation_goalrating_start();
1772 havocbot_goalrating_ctf_enemybase(50000);
1774 havocbot_goalrating_ctf_ourbase(50000);
1777 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1779 navigation_goalrating_end();
1781 if (self.navigation_hasgoals)
1782 self.havocbot_cantfindflag = time + 10;
1783 else if (time > self.havocbot_cantfindflag)
1785 // Can't navigate to my own base, suicide!
1786 // TODO: drop it and wander around
1787 Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0');
1793 void havocbot_role_ctf_escort()
1797 if(self.deadflag != DEAD_NO)
1799 havocbot_ctf_reset_role(self);
1803 if (self.flagcarried)
1805 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1809 // If enemy flag is back on the base switch to previous role
1810 ef = havocbot_ctf_find_enemy_flag(self);
1811 if(ef.ctf_status==FLAG_BASE)
1813 self.havocbot_role = self.havocbot_previous_role;
1814 self.havocbot_role_timeout = 0;
1818 // If the flag carrier reached the base switch to defense
1819 mf = havocbot_ctf_find_flag(self);
1820 if(mf.ctf_status!=FLAG_BASE)
1821 if(vlen(ef.origin - mf.dropped_origin) < 300)
1823 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1827 // Set the role timeout if necessary
1828 if (!self.havocbot_role_timeout)
1830 self.havocbot_role_timeout = time + random() * 30 + 60;
1833 // If nothing happened just switch to previous role
1834 if (time > self.havocbot_role_timeout)
1836 self.havocbot_role = self.havocbot_previous_role;
1837 self.havocbot_role_timeout = 0;
1841 // Chase the flag carrier
1842 if (self.bot_strategytime < time)
1844 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1845 navigation_goalrating_start();
1846 havocbot_goalrating_ctf_enemyflag(30000);
1847 havocbot_goalrating_ctf_ourstolenflag(40000);
1848 havocbot_goalrating_items(10000, self.origin, 10000);
1849 navigation_goalrating_end();
1853 void havocbot_role_ctf_offense()
1858 if(self.deadflag != DEAD_NO)
1860 havocbot_ctf_reset_role(self);
1864 if (self.flagcarried)
1866 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1871 mf = havocbot_ctf_find_flag(self);
1872 ef = havocbot_ctf_find_enemy_flag(self);
1875 if(mf.ctf_status!=FLAG_BASE)
1878 pos = mf.tag_entity.origin;
1882 // Try to get it if closer than the enemy base
1883 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1885 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1890 // Escort flag carrier
1891 if(ef.ctf_status!=FLAG_BASE)
1894 pos = ef.tag_entity.origin;
1898 if(vlen(pos-mf.dropped_origin)>700)
1900 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1905 // About to fail, switch to middlefield
1908 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1912 // Set the role timeout if necessary
1913 if (!self.havocbot_role_timeout)
1914 self.havocbot_role_timeout = time + 120;
1916 if (time > self.havocbot_role_timeout)
1918 havocbot_ctf_reset_role(self);
1922 if (self.bot_strategytime < time)
1924 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1925 navigation_goalrating_start();
1926 havocbot_goalrating_ctf_ourstolenflag(50000);
1927 havocbot_goalrating_ctf_enemybase(20000);
1928 havocbot_goalrating_items(5000, self.origin, 1000);
1929 havocbot_goalrating_items(1000, self.origin, 10000);
1930 navigation_goalrating_end();
1934 // Retriever (temporary role):
1935 void havocbot_role_ctf_retriever()
1939 if(self.deadflag != DEAD_NO)
1941 havocbot_ctf_reset_role(self);
1945 if (self.flagcarried)
1947 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1951 // If flag is back on the base switch to previous role
1952 mf = havocbot_ctf_find_flag(self);
1953 if(mf.ctf_status==FLAG_BASE)
1955 havocbot_ctf_reset_role(self);
1959 if (!self.havocbot_role_timeout)
1960 self.havocbot_role_timeout = time + 20;
1962 if (time > self.havocbot_role_timeout)
1964 havocbot_ctf_reset_role(self);
1968 if (self.bot_strategytime < time)
1973 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1974 navigation_goalrating_start();
1975 havocbot_goalrating_ctf_ourstolenflag(50000);
1976 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1977 havocbot_goalrating_ctf_enemybase(30000);
1978 havocbot_goalrating_items(500, self.origin, rt_radius);
1979 navigation_goalrating_end();
1983 void havocbot_role_ctf_middle()
1987 if(self.deadflag != DEAD_NO)
1989 havocbot_ctf_reset_role(self);
1993 if (self.flagcarried)
1995 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1999 mf = havocbot_ctf_find_flag(self);
2000 if(mf.ctf_status!=FLAG_BASE)
2002 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2006 if (!self.havocbot_role_timeout)
2007 self.havocbot_role_timeout = time + 10;
2009 if (time > self.havocbot_role_timeout)
2011 havocbot_ctf_reset_role(self);
2015 if (self.bot_strategytime < time)
2019 org = havocbot_ctf_middlepoint;
2020 org.z = self.origin.z;
2022 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2023 navigation_goalrating_start();
2024 havocbot_goalrating_ctf_ourstolenflag(50000);
2025 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
2026 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
2027 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
2028 havocbot_goalrating_items(2500, self.origin, 10000);
2029 havocbot_goalrating_ctf_enemybase(2500);
2030 navigation_goalrating_end();
2034 void havocbot_role_ctf_defense()
2038 if(self.deadflag != DEAD_NO)
2040 havocbot_ctf_reset_role(self);
2044 if (self.flagcarried)
2046 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2050 // If own flag was captured
2051 mf = havocbot_ctf_find_flag(self);
2052 if(mf.ctf_status!=FLAG_BASE)
2054 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2058 if (!self.havocbot_role_timeout)
2059 self.havocbot_role_timeout = time + 30;
2061 if (time > self.havocbot_role_timeout)
2063 havocbot_ctf_reset_role(self);
2066 if (self.bot_strategytime < time)
2071 org = mf.dropped_origin;
2072 mp_radius = havocbot_ctf_middlepoint_radius;
2074 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2075 navigation_goalrating_start();
2077 // if enemies are closer to our base, go there
2078 entity head, closestplayer = world;
2079 float distance, bestdistance = 10000;
2080 FOR_EACH_PLAYER(head)
2082 if(head.deadflag!=DEAD_NO)
2085 distance = vlen(org - head.origin);
2086 if(distance<bestdistance)
2088 closestplayer = head;
2089 bestdistance = distance;
2094 if(DIFF_TEAM(closestplayer, self))
2095 if(vlen(org - self.origin)>1000)
2096 if(checkpvs(self.origin,closestplayer)||random()<0.5)
2097 havocbot_goalrating_ctf_ourbase(30000);
2099 havocbot_goalrating_ctf_ourstolenflag(20000);
2100 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
2101 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
2102 havocbot_goalrating_items(10000, org, mp_radius);
2103 havocbot_goalrating_items(5000, self.origin, 10000);
2104 navigation_goalrating_end();
2108 void havocbot_role_ctf_setrole(entity bot, int role)
2110 LOG_TRACE(strcat(bot.netname," switched to "));
2113 case HAVOCBOT_CTF_ROLE_CARRIER:
2114 LOG_TRACE("carrier");
2115 bot.havocbot_role = havocbot_role_ctf_carrier;
2116 bot.havocbot_role_timeout = 0;
2117 bot.havocbot_cantfindflag = time + 10;
2118 bot.bot_strategytime = 0;
2120 case HAVOCBOT_CTF_ROLE_DEFENSE:
2121 LOG_TRACE("defense");
2122 bot.havocbot_role = havocbot_role_ctf_defense;
2123 bot.havocbot_role_timeout = 0;
2125 case HAVOCBOT_CTF_ROLE_MIDDLE:
2126 LOG_TRACE("middle");
2127 bot.havocbot_role = havocbot_role_ctf_middle;
2128 bot.havocbot_role_timeout = 0;
2130 case HAVOCBOT_CTF_ROLE_OFFENSE:
2131 LOG_TRACE("offense");
2132 bot.havocbot_role = havocbot_role_ctf_offense;
2133 bot.havocbot_role_timeout = 0;
2135 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2136 LOG_TRACE("retriever");
2137 bot.havocbot_previous_role = bot.havocbot_role;
2138 bot.havocbot_role = havocbot_role_ctf_retriever;
2139 bot.havocbot_role_timeout = time + 10;
2140 bot.bot_strategytime = 0;
2142 case HAVOCBOT_CTF_ROLE_ESCORT:
2143 LOG_TRACE("escort");
2144 bot.havocbot_previous_role = bot.havocbot_role;
2145 bot.havocbot_role = havocbot_role_ctf_escort;
2146 bot.havocbot_role_timeout = time + 30;
2147 bot.bot_strategytime = 0;
2158 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2161 int t = 0, t2 = 0, t3 = 0;
2163 // initially clear items so they can be set as necessary later.
2164 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2165 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2166 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2167 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2168 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2169 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
2171 // scan through all the flags and notify the client about them
2172 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2174 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2175 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2176 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2177 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2178 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; }
2180 switch(flag.ctf_status)
2185 if((flag.owner == self) || (flag.pass_sender == self))
2186 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
2188 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2193 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2199 // item for stopping players from capturing the flag too often
2200 if(self.ctf_captureshielded)
2201 self.ctf_flagstatus |= CTF_SHIELDED;
2203 // update the health of the flag carrier waypointsprite
2204 if(self.wps_flagcarrier)
2205 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2210 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2212 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2214 if(frag_target == frag_attacker) // damage done to yourself
2216 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2217 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2219 else // damage done to everyone else
2221 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2222 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2225 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2227 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)))
2228 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2230 frag_target.wps_helpme_time = time;
2231 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2233 // todo: add notification for when flag carrier needs help?
2238 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2240 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2242 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2243 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2246 if(frag_target.flagcarried)
2248 entity tmp_entity = frag_target.flagcarried;
2249 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2250 tmp_entity.ctf_dropper = world;
2256 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2259 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2262 void ctf_RemovePlayer(entity player)
2264 if(player.flagcarried)
2265 { ctf_Handle_Throw(player, world, DROP_NORMAL); }
2267 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2269 if(flag.pass_sender == player) { flag.pass_sender = world; }
2270 if(flag.pass_target == player) { flag.pass_target = world; }
2271 if(flag.ctf_dropper == player) { flag.ctf_dropper = world; }
2275 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2277 ctf_RemovePlayer(self);
2281 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2283 ctf_RemovePlayer(self);
2287 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2289 if(self.flagcarried)
2290 if(!autocvar_g_ctf_portalteleport)
2291 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2296 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2298 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2300 entity player = self;
2302 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2304 // pass the flag to a team mate
2305 if(autocvar_g_ctf_pass)
2307 entity head, closest_target = world;
2308 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2310 while(head) // find the closest acceptable target to pass to
2312 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2313 if(head != player && SAME_TEAM(head, player))
2314 if(!head.speedrunning && !head.vehicle)
2316 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2317 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2318 vector passer_center = CENTER_OR_VIEWOFS(player);
2320 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2322 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2324 if(IS_BOT_CLIENT(head))
2326 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2327 ctf_Handle_Throw(head, player, DROP_PASS);
2331 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2332 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2334 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2337 else if(player.flagcarried)
2341 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2342 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2343 { closest_target = head; }
2345 else { closest_target = head; }
2352 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2355 // throw the flag in front of you
2356 if(autocvar_g_ctf_throw && player.flagcarried)
2358 if(player.throw_count == -1)
2360 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2362 player.throw_prevtime = time;
2363 player.throw_count = 1;
2364 ctf_Handle_Throw(player, world, DROP_THROW);
2369 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2375 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2376 else { player.throw_count += 1; }
2377 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2379 player.throw_prevtime = time;
2380 ctf_Handle_Throw(player, world, DROP_THROW);
2389 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2391 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2393 self.wps_helpme_time = time;
2394 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2396 else // create a normal help me waypointsprite
2398 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
2399 WaypointSprite_Ping(self.wps_helpme);
2405 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2407 if(vh_player.flagcarried)
2409 vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
2411 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2413 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2417 setattachment(vh_player.flagcarried, vh_vehicle, "");
2418 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2419 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2420 //vh_player.flagcarried.angles = '0 0 0';
2428 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2430 if(vh_player.flagcarried)
2432 setattachment(vh_player.flagcarried, vh_player, "");
2433 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2434 vh_player.flagcarried.scale = FLAG_SCALE;
2435 vh_player.flagcarried.angles = '0 0 0';
2436 vh_player.flagcarried.nodrawtoclient = world;
2443 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2445 if(self.flagcarried)
2447 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));
2448 ctf_RespawnFlag(self.flagcarried);
2455 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2457 entity flag; // temporary entity for the search method
2459 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2461 switch(flag.ctf_status)
2466 // lock the flag, game is over
2467 flag.movetype = MOVETYPE_NONE;
2468 flag.takedamage = DAMAGE_NO;
2469 flag.solid = SOLID_NOT;
2470 flag.nextthink = false; // stop thinking
2472 //dprint("stopping the ", flag.netname, " from moving.\n");
2480 // do nothing for these flags
2489 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2491 havocbot_ctf_reset_role(self);
2495 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2497 //ret_float = ctf_teams;
2498 ret_string = "ctf_team";
2502 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2504 self.ctf_flagstatus = other.ctf_flagstatus;
2508 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2510 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2512 if (MapInfo_Get_ByID(i))
2514 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2520 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2521 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2528 bool superspec_Spectate(entity _player); // TODO
2529 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2530 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2532 if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2534 if(cmd_name == "followfc")
2547 case "red": _team = NUM_TEAM_1; break;
2548 case "blue": _team = NUM_TEAM_2; break;
2549 case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
2550 case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
2554 FOR_EACH_PLAYER(_player)
2556 if(_player.flagcarried && (_player.team == _team || _team == 0))
2559 if(_team == 0 && IS_SPEC(self) && self.enemy == _player)
2560 continue; // already spectating a fc, try to find the other fc
2561 return superspec_Spectate(_player);
2566 superspec_msg("", "", self, "No active flag carrier\n", 1);
2573 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2575 if(frag_target.flagcarried)
2576 ctf_Handle_Throw(frag_target, world, DROP_THROW);
2586 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2587 CTF flag for team one (Red).
2589 "angle" Angle the flag will point (minus 90 degrees)...
2590 "model" model to use, note this needs red and blue as skins 0 and 1...
2591 "noise" sound played when flag is picked up...
2592 "noise1" sound played when flag is returned by a teammate...
2593 "noise2" sound played when flag is captured...
2594 "noise3" sound played when flag is lost in the field and respawns itself...
2595 "noise4" sound played when flag is dropped by a player...
2596 "noise5" sound played when flag touches the ground... */
2597 spawnfunc(item_flag_team1)
2599 if(!g_ctf) { remove(self); return; }
2601 ctf_FlagSetup(NUM_TEAM_1, self);
2604 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2605 CTF flag for team two (Blue).
2607 "angle" Angle the flag will point (minus 90 degrees)...
2608 "model" model to use, note this needs red and blue as skins 0 and 1...
2609 "noise" sound played when flag is picked up...
2610 "noise1" sound played when flag is returned by a teammate...
2611 "noise2" sound played when flag is captured...
2612 "noise3" sound played when flag is lost in the field and respawns itself...
2613 "noise4" sound played when flag is dropped by a player...
2614 "noise5" sound played when flag touches the ground... */
2615 spawnfunc(item_flag_team2)
2617 if(!g_ctf) { remove(self); return; }
2619 ctf_FlagSetup(NUM_TEAM_2, self);
2622 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2623 CTF flag for team three (Yellow).
2625 "angle" Angle the flag will point (minus 90 degrees)...
2626 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2627 "noise" sound played when flag is picked up...
2628 "noise1" sound played when flag is returned by a teammate...
2629 "noise2" sound played when flag is captured...
2630 "noise3" sound played when flag is lost in the field and respawns itself...
2631 "noise4" sound played when flag is dropped by a player...
2632 "noise5" sound played when flag touches the ground... */
2633 spawnfunc(item_flag_team3)
2635 if(!g_ctf) { remove(self); return; }
2637 ctf_FlagSetup(NUM_TEAM_3, self);
2640 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2641 CTF flag for team four (Pink).
2643 "angle" Angle the flag will point (minus 90 degrees)...
2644 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2645 "noise" sound played when flag is picked up...
2646 "noise1" sound played when flag is returned by a teammate...
2647 "noise2" sound played when flag is captured...
2648 "noise3" sound played when flag is lost in the field and respawns itself...
2649 "noise4" sound played when flag is dropped by a player...
2650 "noise5" sound played when flag touches the ground... */
2651 spawnfunc(item_flag_team4)
2653 if(!g_ctf) { remove(self); return; }
2655 ctf_FlagSetup(NUM_TEAM_4, self);
2658 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2661 "angle" Angle the flag will point (minus 90 degrees)...
2662 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2663 "noise" sound played when flag is picked up...
2664 "noise1" sound played when flag is returned by a teammate...
2665 "noise2" sound played when flag is captured...
2666 "noise3" sound played when flag is lost in the field and respawns itself...
2667 "noise4" sound played when flag is dropped by a player...
2668 "noise5" sound played when flag touches the ground... */
2669 spawnfunc(item_flag_neutral)
2671 if(!g_ctf) { remove(self); return; }
2672 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2674 ctf_FlagSetup(0, self);
2677 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2678 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2679 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.
2681 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2682 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2685 if(!g_ctf) { remove(self); return; }
2687 self.classname = "ctf_team";
2688 self.team = self.cnt + 1;
2691 // compatibility for quake maps
2692 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2693 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2694 spawnfunc(info_player_team1);
2695 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2696 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2697 spawnfunc(info_player_team2);
2698 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2699 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2701 void team_CTF_neutralflag() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2702 void team_neutralobelisk() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2710 void ctf_ScoreRules(int teams)
2712 CheckAllowedTeams(world);
2713 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2714 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2715 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2716 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2717 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2718 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2719 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2720 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2721 ScoreRules_basics_end();
2724 // code from here on is just to support maps that don't have flag and team entities
2725 void ctf_SpawnTeam (string teamname, int teamcolor)
2727 entity this = new(ctf_team);
2728 this.netname = teamname;
2729 this.cnt = teamcolor;
2730 this.spawnfunc_checked = true;
2731 WITH(entity, self, this, spawnfunc_ctf_team(this));
2734 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2739 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2741 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2742 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2743 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2746 ctf_teams = bound(2, ctf_teams, 4);
2748 // if no teams are found, spawn defaults
2749 if(find(world, classname, "ctf_team") == world)
2751 LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2752 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2753 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2755 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2757 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2760 ctf_ScoreRules(ctf_teams);
2763 void ctf_Initialize()
2765 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2767 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2768 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2769 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2771 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2773 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);