6 void ctf_RespawnFlag(entity flag);
8 // score rule declarations
9 const int ST_CTF_CAPS = 1;
10 const int SP_CTF_CAPS = 4;
11 const int SP_CTF_CAPTIME = 5;
12 const int SP_CTF_PICKUPS = 6;
13 const int SP_CTF_DROPS = 7;
14 const int SP_CTF_FCKILLS = 8;
15 const int SP_CTF_RETURNS = 9;
17 // flag constants // for most of these, there is just one question to be asked: WHYYYYY?
18 #define FLAG_MIN (PL_MIN_CONST + '0 0 -13')
19 #define FLAG_MAX (PL_MAX_CONST + '0 0 -13')
21 const float FLAG_SCALE = 0.6;
23 const float FLAG_THINKRATE = 0.2;
24 const float FLAG_TOUCHRATE = 0.5;
25 const float WPFE_THINKRATE = 0.5;
27 const vector FLAG_DROP_OFFSET = ('0 0 32');
28 const vector FLAG_CARRY_OFFSET = ('-16 0 8');
29 #define FLAG_SPAWN_OFFSET ('0 0 1' * (PL_MAX_CONST.z - 13))
30 const vector FLAG_WAYPOINT_OFFSET = ('0 0 64');
31 const vector FLAG_FLOAT_OFFSET = ('0 0 32');
32 const vector FLAG_PASS_ARC_OFFSET = ('0 0 -10');
34 const vector VEHICLE_FLAG_OFFSET = ('0 0 96');
35 const float VEHICLE_FLAG_SCALE = 1.0;
38 #define WPCOLOR_ENEMYFC(t) ((t) ? colormapPaletteColor(t - 1, false) * 0.75 : '1 1 1')
39 #define WPCOLOR_FLAGCARRIER(t) (WP_FlagCarrier.m_color)
40 #define WPCOLOR_DROPPEDFLAG(t) ((t) ? ('0.25 0.25 0.25' + colormapPaletteColor(t - 1, false)) * 0.5 : '1 1 1')
43 #define snd_flag_taken noise
44 #define snd_flag_returned noise1
45 #define snd_flag_capture noise2
46 #define snd_flag_respawn noise3
47 .string snd_flag_dropped;
48 .string snd_flag_touch;
49 .string snd_flag_pass;
56 // list of flags on the map
57 entity ctf_worldflaglist;
58 .entity ctf_worldflagnext;
59 .entity ctf_staleflagnext;
62 .entity bot_basewaypoint; // flag waypointsprite
65 .entity wps_flagcarrier;
66 .entity wps_flagdropped;
67 .entity wps_enemyflagcarrier;
68 .float wps_helpme_time;
69 bool wpforenemy_announced;
70 float wpforenemy_nextthink;
73 const int FLAG_BASE = 1;
74 const int FLAG_DROPPED = 2;
75 const int FLAG_CARRY = 3;
76 const int FLAG_PASSING = 4;
78 const int DROP_NORMAL = 1;
79 const int DROP_THROW = 2;
80 const int DROP_PASS = 3;
81 const int DROP_RESET = 4;
83 const int PICKUP_BASE = 1;
84 const int PICKUP_DROPPED = 2;
86 const int CAPTURE_NORMAL = 1;
87 const int CAPTURE_DROPPED = 2;
89 const int RETURN_TIMEOUT = 1;
90 const int RETURN_DROPPED = 2;
91 const int RETURN_DAMAGE = 3;
92 const int RETURN_SPEEDRUN = 4;
93 const int RETURN_NEEDKILL = 5;
96 #define ctf_spawnorigin dropped_origin
97 bool ctf_stalemate; // indicates that a stalemate is active
98 float ctf_captimerecord; // record time for capturing the flag
99 .float ctf_pickuptime;
101 .int ctf_status; // status of the flag (FLAG_BASE, FLAG_DROPPED, FLAG_CARRY declared globally)
102 .entity ctf_dropper; // don't allow spam of dropping the flag
103 .int max_flag_health;
104 .float next_take_time;
105 .bool ctf_flagdamaged;
108 // passing/throwing properties
109 .float pass_distance;
112 .float throw_antispam;
113 .float throw_prevtime;
116 // CaptureShield: If the player is too bad to be allowed to capture, shield them from taking the flag.
117 .bool ctf_captureshielded; // set to 1 if the player is too bad to be allowed to capture
118 float ctf_captureshield_min_negscore; // punish at -20 points
119 float ctf_captureshield_max_ratio; // punish at most 30% of each team
120 float ctf_captureshield_force; // push force of the shield
123 bool ctf_oneflag; // indicates whether or not a neutral flag has been found
126 const int HAVOCBOT_CTF_ROLE_NONE = 0;
127 const int HAVOCBOT_CTF_ROLE_DEFENSE = 2;
128 const int HAVOCBOT_CTF_ROLE_MIDDLE = 4;
129 const int HAVOCBOT_CTF_ROLE_OFFENSE = 8;
130 const int HAVOCBOT_CTF_ROLE_CARRIER = 16;
131 const int HAVOCBOT_CTF_ROLE_RETRIEVER = 32;
132 const int HAVOCBOT_CTF_ROLE_ESCORT = 64;
134 .bool havocbot_cantfindflag;
136 vector havocbot_ctf_middlepoint;
137 float havocbot_ctf_middlepoint_radius;
139 void havocbot_role_ctf_setrole(entity bot, int role);
142 #define CTF_SAMETEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? DIFF_TEAM(a,b) : SAME_TEAM(a,b))
143 #define CTF_DIFFTEAM(a,b) ((autocvar_g_ctf_reverse || (ctf_oneflag && autocvar_g_ctf_oneflag_reverse)) ? SAME_TEAM(a,b) : DIFF_TEAM(a,b))
145 // networked flag statuses
149 const int CTF_RED_FLAG_TAKEN = 1;
150 const int CTF_RED_FLAG_LOST = 2;
151 const int CTF_RED_FLAG_CARRYING = 3;
152 const int CTF_BLUE_FLAG_TAKEN = 4;
153 const int CTF_BLUE_FLAG_LOST = 8;
154 const int CTF_BLUE_FLAG_CARRYING = 12;
155 const int CTF_YELLOW_FLAG_TAKEN = 16;
156 const int CTF_YELLOW_FLAG_LOST = 32;
157 const int CTF_YELLOW_FLAG_CARRYING = 48;
158 const int CTF_PINK_FLAG_TAKEN = 64;
159 const int CTF_PINK_FLAG_LOST = 128;
160 const int CTF_PINK_FLAG_CARRYING = 192;
161 const int CTF_NEUTRAL_FLAG_TAKEN = 256;
162 const int CTF_NEUTRAL_FLAG_LOST = 512;
163 const int CTF_NEUTRAL_FLAG_CARRYING = 768;
164 const int CTF_FLAG_NEUTRAL = 2048;
165 const int CTF_SHIELDED = 4096;
168 #ifdef IMPLEMENTATION
171 #include "../../../common/vehicles/all.qh"
172 #include "../../teamplay.qh"
175 #include "../../../lib/warpzone/common.qh"
177 bool autocvar_g_ctf_allow_vehicle_carry;
178 bool autocvar_g_ctf_allow_vehicle_touch;
179 bool autocvar_g_ctf_allow_monster_touch;
180 bool autocvar_g_ctf_throw;
181 float autocvar_g_ctf_throw_angle_max;
182 float autocvar_g_ctf_throw_angle_min;
183 int autocvar_g_ctf_throw_punish_count;
184 float autocvar_g_ctf_throw_punish_delay;
185 float autocvar_g_ctf_throw_punish_time;
186 float autocvar_g_ctf_throw_strengthmultiplier;
187 float autocvar_g_ctf_throw_velocity_forward;
188 float autocvar_g_ctf_throw_velocity_up;
189 float autocvar_g_ctf_drop_velocity_up;
190 float autocvar_g_ctf_drop_velocity_side;
191 bool autocvar_g_ctf_oneflag_reverse;
192 bool autocvar_g_ctf_portalteleport;
193 bool autocvar_g_ctf_pass;
194 float autocvar_g_ctf_pass_arc;
195 float autocvar_g_ctf_pass_arc_max;
196 float autocvar_g_ctf_pass_directional_max;
197 float autocvar_g_ctf_pass_directional_min;
198 float autocvar_g_ctf_pass_radius;
199 float autocvar_g_ctf_pass_wait;
200 bool autocvar_g_ctf_pass_request;
201 float autocvar_g_ctf_pass_turnrate;
202 float autocvar_g_ctf_pass_timelimit;
203 float autocvar_g_ctf_pass_velocity;
204 bool autocvar_g_ctf_dynamiclights;
205 float autocvar_g_ctf_flag_collect_delay;
206 float autocvar_g_ctf_flag_damageforcescale;
207 bool autocvar_g_ctf_flag_dropped_waypoint;
208 bool autocvar_g_ctf_flag_dropped_floatinwater;
209 bool autocvar_g_ctf_flag_glowtrails;
210 int autocvar_g_ctf_flag_health;
211 bool autocvar_g_ctf_flag_return;
212 float autocvar_g_ctf_flag_return_carried_radius;
213 float autocvar_g_ctf_flag_return_time;
214 bool autocvar_g_ctf_flag_return_when_unreachable;
215 float autocvar_g_ctf_flag_return_damage;
216 float autocvar_g_ctf_flag_return_damage_delay;
217 float autocvar_g_ctf_flag_return_dropped;
218 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
219 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
220 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
221 float autocvar_g_ctf_flagcarrier_selfforcefactor;
222 float autocvar_g_ctf_flagcarrier_damagefactor;
223 float autocvar_g_ctf_flagcarrier_forcefactor;
224 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
225 bool autocvar_g_ctf_fullbrightflags;
226 bool autocvar_g_ctf_ignore_frags;
227 int autocvar_g_ctf_score_capture;
228 int autocvar_g_ctf_score_capture_assist;
229 int autocvar_g_ctf_score_kill;
230 int autocvar_g_ctf_score_penalty_drop;
231 int autocvar_g_ctf_score_penalty_returned;
232 int autocvar_g_ctf_score_pickup_base;
233 int autocvar_g_ctf_score_pickup_dropped_early;
234 int autocvar_g_ctf_score_pickup_dropped_late;
235 int autocvar_g_ctf_score_return;
236 float autocvar_g_ctf_shield_force;
237 float autocvar_g_ctf_shield_max_ratio;
238 int autocvar_g_ctf_shield_min_negscore;
239 bool autocvar_g_ctf_stalemate;
240 int autocvar_g_ctf_stalemate_endcondition;
241 float autocvar_g_ctf_stalemate_time;
242 bool autocvar_g_ctf_reverse;
243 float autocvar_g_ctf_dropped_capture_delay;
244 float autocvar_g_ctf_dropped_capture_radius;
246 void ctf_FakeTimeLimit(entity e, float t)
249 WriteByte(MSG_ONE, 3); // svc_updatestat
250 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
252 WriteCoord(MSG_ONE, autocvar_timelimit);
254 WriteCoord(MSG_ONE, (t + 1) / 60);
257 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
259 if(autocvar_sv_eventlog)
260 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
261 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
264 void ctf_CaptureRecord(entity flag, entity player)
266 float cap_record = ctf_captimerecord;
267 float cap_time = (time - flag.ctf_pickuptime);
268 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
271 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
272 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)); }
273 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)); }
274 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)); }
276 // write that shit in the database
277 if(!ctf_oneflag) // but not in 1-flag mode
278 if((!ctf_captimerecord) || (cap_time < cap_record))
280 ctf_captimerecord = cap_time;
281 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
282 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
283 write_recordmarker(player, (time - cap_time), cap_time);
287 void ctf_FlagcarrierWaypoints(entity player)
289 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, world, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
290 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
291 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
292 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
295 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
297 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
298 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
299 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
300 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
303 if(current_height) // make sure we can actually do this arcing path
305 targpos = (to + ('0 0 1' * current_height));
306 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
307 if(trace_fraction < 1)
309 //print("normal arc line failed, trying to find new pos...");
310 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
311 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
312 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
313 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
314 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
317 else { targpos = to; }
319 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
321 vector desired_direction = normalize(targpos - from);
322 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
323 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
326 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
328 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
330 // directional tracing only
332 makevectors(passer_angle);
334 // find the closest point on the enemy to the center of the attack
335 float h; // hypotenuse, which is the distance between attacker to head
336 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
338 h = vlen(head_center - passer_center);
339 a = h * (normalize(head_center - passer_center) * v_forward);
341 vector nearest_on_line = (passer_center + a * v_forward);
342 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
344 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
345 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
347 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
352 else { return true; }
356 // =======================
357 // CaptureShield Functions
358 // =======================
360 bool ctf_CaptureShield_CheckStatus(entity p)
362 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
364 int players_worseeq, players_total;
366 if(ctf_captureshield_max_ratio <= 0)
369 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
370 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
371 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
372 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
374 sr = ((s - s2) + (s3 + s4));
376 if(sr >= -ctf_captureshield_min_negscore)
379 players_total = players_worseeq = 0;
384 se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
385 se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
386 se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
387 se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
389 ser = ((se - se2) + (se3 + se4));
396 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
397 // use this rule here
399 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
405 void ctf_CaptureShield_Update(entity player, bool wanted_status)
407 bool updated_status = ctf_CaptureShield_CheckStatus(player);
408 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
410 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
411 player.ctf_captureshielded = updated_status;
415 bool ctf_CaptureShield_Customize()
417 if(!other.ctf_captureshielded) { return false; }
418 if(CTF_SAMETEAM(self, other)) { return false; }
423 void ctf_CaptureShield_Touch()
425 if(!other.ctf_captureshielded) { return; }
426 if(CTF_SAMETEAM(self, other)) { return; }
428 vector mymid = (self.absmin + self.absmax) * 0.5;
429 vector othermid = (other.absmin + other.absmax) * 0.5;
431 Damage(other, self, self, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
432 if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
435 void ctf_CaptureShield_Spawn(entity flag)
437 entity shield = spawn();
440 shield.team = self.team;
441 shield.touch = ctf_CaptureShield_Touch;
442 shield.customizeentityforclient = ctf_CaptureShield_Customize;
443 shield.classname = "ctf_captureshield";
444 shield.effects = EF_ADDITIVE;
445 shield.movetype = MOVETYPE_NOCLIP;
446 shield.solid = SOLID_TRIGGER;
447 shield.avelocity = '7 0 11';
450 setorigin(shield, self.origin);
451 setmodel(shield, MDL_CTF_SHIELD);
452 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
456 // ====================
457 // Drop/Pass/Throw Code
458 // ====================
460 void ctf_Handle_Drop(entity flag, entity player, int droptype)
463 player = (player ? player : flag.pass_sender);
466 flag.movetype = MOVETYPE_TOSS;
467 flag.takedamage = DAMAGE_YES;
468 flag.angles = '0 0 0';
469 flag.health = flag.max_flag_health;
470 flag.ctf_droptime = time;
471 flag.ctf_dropper = player;
472 flag.ctf_status = FLAG_DROPPED;
474 // messages and sounds
475 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
476 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
477 ctf_EventLog("dropped", player.team, player);
480 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
481 PlayerScore_Add(player, SP_CTF_DROPS, 1);
484 if(autocvar_g_ctf_flag_dropped_waypoint) {
485 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);
486 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
489 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
491 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
492 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
495 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
497 if(droptype == DROP_PASS)
499 flag.pass_distance = 0;
500 flag.pass_sender = world;
501 flag.pass_target = world;
505 void ctf_Handle_Retrieve(entity flag, entity player)
507 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
508 entity sender = flag.pass_sender;
510 // transfer flag to player
512 flag.owner.flagcarried = flag;
517 setattachment(flag, player.vehicle, "");
518 setorigin(flag, VEHICLE_FLAG_OFFSET);
519 flag.scale = VEHICLE_FLAG_SCALE;
523 setattachment(flag, player, "");
524 setorigin(flag, FLAG_CARRY_OFFSET);
526 flag.movetype = MOVETYPE_NONE;
527 flag.takedamage = DAMAGE_NO;
528 flag.solid = SOLID_NOT;
529 flag.angles = '0 0 0';
530 flag.ctf_status = FLAG_CARRY;
532 // messages and sounds
533 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
534 ctf_EventLog("receive", flag.team, player);
536 FOR_EACH_REALPLAYER(tmp_player)
538 if(tmp_player == sender)
539 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);
540 else if(tmp_player == player)
541 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);
542 else if(SAME_TEAM(tmp_player, sender))
543 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);
546 // create new waypoint
547 ctf_FlagcarrierWaypoints(player);
549 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
550 player.throw_antispam = sender.throw_antispam;
552 flag.pass_distance = 0;
553 flag.pass_sender = world;
554 flag.pass_target = world;
557 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
559 entity flag = player.flagcarried;
560 vector targ_origin, flag_velocity;
562 if(!flag) { return; }
563 if((droptype == DROP_PASS) && !receiver) { return; }
565 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
568 setattachment(flag, world, "");
569 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
570 flag.owner.flagcarried = world;
572 flag.solid = SOLID_TRIGGER;
573 flag.ctf_dropper = player;
574 flag.ctf_droptime = time;
576 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
583 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
584 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
585 WarpZone_RefSys_Copy(flag, receiver);
586 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
587 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
589 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
590 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
593 flag.movetype = MOVETYPE_FLY;
594 flag.takedamage = DAMAGE_NO;
595 flag.pass_sender = player;
596 flag.pass_target = receiver;
597 flag.ctf_status = FLAG_PASSING;
600 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
601 WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
602 ctf_EventLog("pass", flag.team, player);
608 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'));
610 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)));
611 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
612 ctf_Handle_Drop(flag, player, droptype);
618 flag.velocity = '0 0 0'; // do nothing
625 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);
626 ctf_Handle_Drop(flag, player, droptype);
631 // kill old waypointsprite
632 WaypointSprite_Ping(player.wps_flagcarrier);
633 WaypointSprite_Kill(player.wps_flagcarrier);
635 if(player.wps_enemyflagcarrier)
636 WaypointSprite_Kill(player.wps_enemyflagcarrier);
639 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
647 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
649 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
650 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
651 entity player_team_flag = world, tmp_entity;
652 float old_time, new_time;
654 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
655 if(CTF_DIFFTEAM(player, flag)) { return; }
658 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
659 if(SAME_TEAM(tmp_entity, player))
661 player_team_flag = tmp_entity;
665 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
667 player.throw_prevtime = time;
668 player.throw_count = 0;
670 // messages and sounds
671 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
672 ctf_CaptureRecord(enemy_flag, player);
673 _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);
677 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
678 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
683 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
684 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
686 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
687 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
688 if(!old_time || new_time < old_time)
689 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
692 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
693 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
696 if(capturetype == CAPTURE_NORMAL)
698 WaypointSprite_Kill(player.wps_flagcarrier);
699 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
701 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
702 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
706 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
707 ctf_RespawnFlag(enemy_flag);
710 void ctf_Handle_Return(entity flag, entity player)
712 // messages and sounds
713 if(IS_MONSTER(player))
715 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
719 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
720 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
722 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
723 ctf_EventLog("return", flag.team, player);
726 if(IS_PLAYER(player))
728 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
729 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
731 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
734 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
738 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
739 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
740 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
744 if(player.flagcarried == flag)
745 WaypointSprite_Kill(player.wps_flagcarrier);
748 ctf_RespawnFlag(flag);
751 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
754 float pickup_dropped_score; // used to calculate dropped pickup score
755 entity tmp_entity; // temporary entity
757 // attach the flag to the player
759 player.flagcarried = flag;
762 setattachment(flag, player.vehicle, "");
763 setorigin(flag, VEHICLE_FLAG_OFFSET);
764 flag.scale = VEHICLE_FLAG_SCALE;
768 setattachment(flag, player, "");
769 setorigin(flag, FLAG_CARRY_OFFSET);
773 flag.movetype = MOVETYPE_NONE;
774 flag.takedamage = DAMAGE_NO;
775 flag.solid = SOLID_NOT;
776 flag.angles = '0 0 0';
777 flag.ctf_status = FLAG_CARRY;
781 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
782 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
786 // messages and sounds
787 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
788 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
789 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
790 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
791 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)); }
793 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);
796 FOR_EACH_PLAYER(tmp_entity)
797 if(tmp_entity != player)
798 if(DIFF_TEAM(player, tmp_entity))
799 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
802 FOR_EACH_PLAYER(tmp_entity)
803 if(tmp_entity != player)
804 if(CTF_SAMETEAM(flag, tmp_entity))
805 if(SAME_TEAM(player, tmp_entity))
806 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
808 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);
810 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
813 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
814 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
819 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
820 ctf_EventLog("steal", flag.team, player);
826 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);
827 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);
828 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
829 PlayerTeamScore_AddScore(player, pickup_dropped_score);
830 ctf_EventLog("pickup", flag.team, player);
838 if(pickuptype == PICKUP_BASE)
840 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
841 if((player.speedrunning) && (ctf_captimerecord))
842 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
846 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
849 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
850 ctf_FlagcarrierWaypoints(player);
851 WaypointSprite_Ping(player.wps_flagcarrier);
855 // ===================
856 // Main Flag Functions
857 // ===================
859 void ctf_CheckFlagReturn(entity flag, int returntype)
861 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
863 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
865 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
869 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;
870 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;
871 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;
872 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;
876 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
878 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
879 ctf_EventLog("returned", flag.team, world);
880 ctf_RespawnFlag(flag);
885 bool ctf_Stalemate_Customize()
887 // make spectators see what the player would see
889 e = WaypointSprite_getviewentity(other);
890 wp_owner = self.owner;
893 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
894 if(SAME_TEAM(wp_owner, e)) { return false; }
895 if(!IS_PLAYER(e)) { return false; }
900 void ctf_CheckStalemate(void)
903 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
906 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
908 // build list of stale flags
909 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
911 if(autocvar_g_ctf_stalemate)
912 if(tmp_entity.ctf_status != FLAG_BASE)
913 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
915 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
916 ctf_staleflaglist = tmp_entity;
918 switch(tmp_entity.team)
920 case NUM_TEAM_1: ++stale_red_flags; break;
921 case NUM_TEAM_2: ++stale_blue_flags; break;
922 case NUM_TEAM_3: ++stale_yellow_flags; break;
923 case NUM_TEAM_4: ++stale_pink_flags; break;
924 default: ++stale_neutral_flags; break;
930 stale_flags = (stale_neutral_flags >= 1);
932 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
934 if(ctf_oneflag && stale_flags == 1)
935 ctf_stalemate = true;
936 else if(stale_flags >= 2)
937 ctf_stalemate = true;
938 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
939 { ctf_stalemate = false; wpforenemy_announced = false; }
940 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
941 { ctf_stalemate = false; wpforenemy_announced = false; }
943 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
946 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
948 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
950 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);
951 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
952 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
956 if (!wpforenemy_announced)
958 FOR_EACH_REALPLAYER(tmp_entity)
959 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
961 wpforenemy_announced = true;
966 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
968 if(ITEM_DAMAGE_NEEDKILL(deathtype))
970 if(autocvar_g_ctf_flag_return_damage_delay)
972 self.ctf_flagdamaged = true;
977 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
981 if(autocvar_g_ctf_flag_return_damage)
983 // reduce health and check if it should be returned
984 self.health = self.health - damage;
985 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
995 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
998 if(self == ctf_worldflaglist) // only for the first flag
999 FOR_EACH_CLIENT(tmp_entity)
1000 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
1003 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
1004 LOG_TRACE("wtf the flag got squashed?\n");
1005 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
1006 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
1007 setsize(self, FLAG_MIN, FLAG_MAX); }
1009 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
1013 self.angles = '0 0 0';
1020 // main think method
1021 switch(self.ctf_status)
1025 if(autocvar_g_ctf_dropped_capture_radius)
1027 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
1028 if(tmp_entity.ctf_status == FLAG_DROPPED)
1029 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
1030 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
1031 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
1038 if(autocvar_g_ctf_flag_dropped_floatinwater)
1040 vector midpoint = ((self.absmin + self.absmax) * 0.5);
1041 if(pointcontents(midpoint) == CONTENT_WATER)
1043 self.velocity = self.velocity * 0.5;
1045 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
1046 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
1048 { self.movetype = MOVETYPE_FLY; }
1050 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
1052 if(autocvar_g_ctf_flag_return_dropped)
1054 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
1057 ctf_CheckFlagReturn(self, RETURN_DROPPED);
1061 if(self.ctf_flagdamaged)
1063 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
1064 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1067 else if(autocvar_g_ctf_flag_return_time)
1069 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
1070 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
1078 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
1081 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
1083 setself(self.owner);
1084 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
1088 if(autocvar_g_ctf_stalemate)
1090 if(time >= wpforenemy_nextthink)
1092 ctf_CheckStalemate();
1093 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1096 if(CTF_SAMETEAM(self, self.owner) && self.team)
1098 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1099 ctf_Handle_Throw(self.owner, world, DROP_THROW);
1100 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
1101 ctf_Handle_Return(self, self.owner);
1108 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
1109 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
1110 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
1112 if((self.pass_target == world)
1113 || (self.pass_target.deadflag != DEAD_NO)
1114 || (self.pass_target.flagcarried)
1115 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
1116 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
1117 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1119 // give up, pass failed
1120 ctf_Handle_Drop(self, world, DROP_PASS);
1124 // still a viable target, go for it
1125 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
1130 default: // this should never happen
1132 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
1138 void ctf_FlagTouch()
1140 if(gameover) { return; }
1141 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1143 entity toucher = other, tmp_entity;
1144 bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
1146 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1147 if(ITEM_TOUCH_NEEDKILL())
1149 if(!autocvar_g_ctf_flag_return_damage_delay)
1152 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1154 if(!self.ctf_flagdamaged) { return; }
1157 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
1159 // special touch behaviors
1160 if(toucher.frozen) { return; }
1161 else if(IS_VEHICLE(toucher))
1163 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1164 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1166 return; // do nothing
1168 else if(IS_MONSTER(toucher))
1170 if(!autocvar_g_ctf_allow_monster_touch)
1171 return; // do nothing
1173 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1175 if(time > self.wait) // if we haven't in a while, play a sound/effect
1177 Send_Effect_(self.toucheffect, self.origin, '0 0 0', 1);
1178 _sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1179 self.wait = time + FLAG_TOUCHRATE;
1183 else if(toucher.deadflag != DEAD_NO) { return; }
1185 switch(self.ctf_status)
1191 if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1192 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1193 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1194 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1196 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
1197 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1198 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1199 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1205 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
1206 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
1207 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1208 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1214 LOG_TRACE("Someone touched a flag even though it was being carried?\n");
1220 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
1222 if(DIFF_TEAM(toucher, self.pass_sender))
1223 ctf_Handle_Return(self, toucher);
1225 ctf_Handle_Retrieve(self, toucher);
1232 .float last_respawn;
1233 void ctf_RespawnFlag(entity flag)
1235 // check for flag respawn being called twice in a row
1236 if(flag.last_respawn > time - 0.5)
1237 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1239 flag.last_respawn = time;
1241 // reset the player (if there is one)
1242 if((flag.owner) && (flag.owner.flagcarried == flag))
1244 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1245 WaypointSprite_Kill(flag.wps_flagcarrier);
1247 flag.owner.flagcarried = world;
1249 if(flag.speedrunning)
1250 ctf_FakeTimeLimit(flag.owner, -1);
1253 if((flag.owner) && (flag.owner.vehicle))
1254 flag.scale = FLAG_SCALE;
1256 if(flag.ctf_status == FLAG_DROPPED)
1257 { WaypointSprite_Kill(flag.wps_flagdropped); }
1260 setattachment(flag, world, "");
1261 setorigin(flag, flag.ctf_spawnorigin);
1263 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1264 flag.takedamage = DAMAGE_NO;
1265 flag.health = flag.max_flag_health;
1266 flag.solid = SOLID_TRIGGER;
1267 flag.velocity = '0 0 0';
1268 flag.angles = flag.mangle;
1269 flag.flags = FL_ITEM | FL_NOTARGET;
1271 flag.ctf_status = FLAG_BASE;
1273 flag.pass_distance = 0;
1274 flag.pass_sender = world;
1275 flag.pass_target = world;
1276 flag.ctf_dropper = world;
1277 flag.ctf_pickuptime = 0;
1278 flag.ctf_droptime = 0;
1279 flag.ctf_flagdamaged = 0;
1281 ctf_CheckStalemate();
1287 if(IS_PLAYER(self.owner))
1288 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1290 ctf_RespawnFlag(self);
1293 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1296 waypoint_spawnforitem_force(self, self.origin);
1297 self.nearestwaypointtimeout = 0; // activate waypointing again
1298 self.bot_basewaypoint = self.nearestwaypoint;
1304 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1305 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1306 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1307 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1308 default: basename = WP_FlagBaseNeutral; break;
1311 entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
1312 wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
1313 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1315 // captureshield setup
1316 ctf_CaptureShield_Spawn(self);
1319 void set_flag_string(entity flag, .string field, string value, string teamname)
1321 if(flag.(field) == "")
1322 flag.(field) = strzone(sprintf(value,teamname));
1325 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1328 setself(flag); // for later usage with droptofloor()
1331 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1332 ctf_worldflaglist = flag;
1334 setattachment(flag, world, "");
1336 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1337 flag.team = teamnumber;
1338 flag.classname = "item_flag_team";
1339 flag.target = "###item###"; // wut?
1340 flag.flags = FL_ITEM | FL_NOTARGET;
1341 flag.solid = SOLID_TRIGGER;
1342 flag.takedamage = DAMAGE_NO;
1343 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1344 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1345 flag.health = flag.max_flag_health;
1346 flag.event_damage = ctf_FlagDamage;
1347 flag.pushable = true;
1348 flag.teleportable = TELEPORT_NORMAL;
1349 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1350 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1351 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1352 flag.velocity = '0 0 0';
1353 flag.mangle = flag.angles;
1354 flag.reset = ctf_Reset;
1355 flag.touch = ctf_FlagTouch;
1356 flag.think = ctf_FlagThink;
1357 flag.nextthink = time + FLAG_THINKRATE;
1358 flag.ctf_status = FLAG_BASE;
1360 string teamname = Static_Team_ColorName_Lower(teamnumber);
1362 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1363 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1364 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1365 set_flag_string(flag, toucheffect, "%sflag_touch", teamname);
1366 set_flag_string(flag, passeffect, "%s_pass", teamname);
1367 set_flag_string(flag, capeffect, "%s_cap", teamname);
1370 flag.snd_flag_taken = SND(CTF_TAKEN(teamnumber));
1371 flag.snd_flag_returned = SND(CTF_RETURNED(teamnumber));
1372 flag.snd_flag_capture = SND(CTF_CAPTURE(teamnumber));
1373 flag.snd_flag_dropped = SND(CTF_DROPPED(teamnumber));
1374 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.
1375 precache_sound(flag.snd_flag_respawn);
1376 if (flag.snd_flag_touch == "") flag.snd_flag_touch = SND(CTF_TOUCH); // again has no team-based sound
1377 precache_sound(flag.snd_flag_touch);
1378 if (flag.snd_flag_pass == "") flag.snd_flag_pass = SND(CTF_PASS); // same story here
1379 precache_sound(flag.snd_flag_pass);
1382 precache_model(flag.model);
1385 _setmodel(flag, flag.model); // precision set below
1386 setsize(flag, FLAG_MIN, FLAG_MAX);
1387 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1389 if(autocvar_g_ctf_flag_glowtrails)
1393 case NUM_TEAM_1: flag.glow_color = 251; break;
1394 case NUM_TEAM_2: flag.glow_color = 210; break;
1395 case NUM_TEAM_3: flag.glow_color = 110; break;
1396 case NUM_TEAM_4: flag.glow_color = 145; break;
1397 default: flag.glow_color = 254; break;
1399 flag.glow_size = 25;
1400 flag.glow_trail = 1;
1403 flag.effects |= EF_LOWPRECISION;
1404 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1405 if(autocvar_g_ctf_dynamiclights)
1409 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1410 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1411 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1412 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1413 default: flag.effects |= EF_DIMLIGHT; break;
1418 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1420 flag.dropped_origin = flag.origin;
1421 flag.noalign = true;
1422 flag.movetype = MOVETYPE_NONE;
1424 else // drop to floor, automatically find a platform and set that as spawn origin
1426 flag.noalign = false;
1429 flag.movetype = MOVETYPE_TOSS;
1432 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1440 // NOTE: LEGACY CODE, needs to be re-written!
1442 void havocbot_calculate_middlepoint()
1446 vector fo = '0 0 0';
1449 f = ctf_worldflaglist;
1454 f = f.ctf_worldflagnext;
1458 havocbot_ctf_middlepoint = s * (1.0 / n);
1459 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1463 entity havocbot_ctf_find_flag(entity bot)
1466 f = ctf_worldflaglist;
1469 if (CTF_SAMETEAM(bot, f))
1471 f = f.ctf_worldflagnext;
1476 entity havocbot_ctf_find_enemy_flag(entity bot)
1479 f = ctf_worldflaglist;
1484 if(CTF_DIFFTEAM(bot, f))
1491 else if(!bot.flagcarried)
1495 else if (CTF_DIFFTEAM(bot, f))
1497 f = f.ctf_worldflagnext;
1502 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1510 FOR_EACH_PLAYER(head)
1512 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1515 if(vlen(head.origin - org) < tc_radius)
1522 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1525 head = ctf_worldflaglist;
1528 if (CTF_SAMETEAM(self, head))
1530 head = head.ctf_worldflagnext;
1533 navigation_routerating(head, ratingscale, 10000);
1536 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1539 head = ctf_worldflaglist;
1542 if (CTF_SAMETEAM(self, head))
1544 head = head.ctf_worldflagnext;
1549 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1552 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1555 head = ctf_worldflaglist;
1560 if(CTF_DIFFTEAM(self, head))
1564 if(self.flagcarried)
1567 else if(!self.flagcarried)
1571 else if(CTF_DIFFTEAM(self, head))
1573 head = head.ctf_worldflagnext;
1576 navigation_routerating(head, ratingscale, 10000);
1579 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1581 if (!bot_waypoints_for_items)
1583 havocbot_goalrating_ctf_enemyflag(ratingscale);
1589 head = havocbot_ctf_find_enemy_flag(self);
1594 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1597 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1601 mf = havocbot_ctf_find_flag(self);
1603 if(mf.ctf_status == FLAG_BASE)
1607 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1610 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1613 head = ctf_worldflaglist;
1616 // flag is out in the field
1617 if(head.ctf_status != FLAG_BASE)
1618 if(head.tag_entity==world) // dropped
1622 if(vlen(org-head.origin)<df_radius)
1623 navigation_routerating(head, ratingscale, 10000);
1626 navigation_routerating(head, ratingscale, 10000);
1629 head = head.ctf_worldflagnext;
1633 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1637 head = findchainfloat(bot_pickup, true);
1640 // gather health and armor only
1642 if (head.health || head.armorvalue)
1643 if (vlen(head.origin - org) < sradius)
1645 // get the value of the item
1646 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1648 navigation_routerating(head, t * ratingscale, 500);
1654 void havocbot_ctf_reset_role(entity bot)
1656 float cdefense, cmiddle, coffense;
1657 entity mf, ef, head;
1660 if(bot.deadflag != DEAD_NO)
1663 if(vlen(havocbot_ctf_middlepoint)==0)
1664 havocbot_calculate_middlepoint();
1667 if (bot.flagcarried)
1669 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1673 mf = havocbot_ctf_find_flag(bot);
1674 ef = havocbot_ctf_find_enemy_flag(bot);
1676 // Retrieve stolen flag
1677 if(mf.ctf_status!=FLAG_BASE)
1679 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1683 // If enemy flag is taken go to the middle to intercept pursuers
1684 if(ef.ctf_status!=FLAG_BASE)
1686 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1690 // if there is only me on the team switch to offense
1692 FOR_EACH_PLAYER(head)
1693 if(SAME_TEAM(head, bot))
1698 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1702 // Evaluate best position to take
1703 // Count mates on middle position
1704 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1706 // Count mates on defense position
1707 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1709 // Count mates on offense position
1710 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1712 if(cdefense<=coffense)
1713 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1714 else if(coffense<=cmiddle)
1715 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1717 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1720 void havocbot_role_ctf_carrier()
1722 if(self.deadflag != DEAD_NO)
1724 havocbot_ctf_reset_role(self);
1728 if (self.flagcarried == world)
1730 havocbot_ctf_reset_role(self);
1734 if (self.bot_strategytime < time)
1736 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1738 navigation_goalrating_start();
1740 havocbot_goalrating_ctf_enemybase(50000);
1742 havocbot_goalrating_ctf_ourbase(50000);
1745 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1747 navigation_goalrating_end();
1749 if (self.navigation_hasgoals)
1750 self.havocbot_cantfindflag = time + 10;
1751 else if (time > self.havocbot_cantfindflag)
1753 // Can't navigate to my own base, suicide!
1754 // TODO: drop it and wander around
1755 Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0');
1761 void havocbot_role_ctf_escort()
1765 if(self.deadflag != DEAD_NO)
1767 havocbot_ctf_reset_role(self);
1771 if (self.flagcarried)
1773 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1777 // If enemy flag is back on the base switch to previous role
1778 ef = havocbot_ctf_find_enemy_flag(self);
1779 if(ef.ctf_status==FLAG_BASE)
1781 self.havocbot_role = self.havocbot_previous_role;
1782 self.havocbot_role_timeout = 0;
1786 // If the flag carrier reached the base switch to defense
1787 mf = havocbot_ctf_find_flag(self);
1788 if(mf.ctf_status!=FLAG_BASE)
1789 if(vlen(ef.origin - mf.dropped_origin) < 300)
1791 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1795 // Set the role timeout if necessary
1796 if (!self.havocbot_role_timeout)
1798 self.havocbot_role_timeout = time + random() * 30 + 60;
1801 // If nothing happened just switch to previous role
1802 if (time > self.havocbot_role_timeout)
1804 self.havocbot_role = self.havocbot_previous_role;
1805 self.havocbot_role_timeout = 0;
1809 // Chase the flag carrier
1810 if (self.bot_strategytime < time)
1812 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1813 navigation_goalrating_start();
1814 havocbot_goalrating_ctf_enemyflag(30000);
1815 havocbot_goalrating_ctf_ourstolenflag(40000);
1816 havocbot_goalrating_items(10000, self.origin, 10000);
1817 navigation_goalrating_end();
1821 void havocbot_role_ctf_offense()
1826 if(self.deadflag != DEAD_NO)
1828 havocbot_ctf_reset_role(self);
1832 if (self.flagcarried)
1834 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1839 mf = havocbot_ctf_find_flag(self);
1840 ef = havocbot_ctf_find_enemy_flag(self);
1843 if(mf.ctf_status!=FLAG_BASE)
1846 pos = mf.tag_entity.origin;
1850 // Try to get it if closer than the enemy base
1851 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1853 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1858 // Escort flag carrier
1859 if(ef.ctf_status!=FLAG_BASE)
1862 pos = ef.tag_entity.origin;
1866 if(vlen(pos-mf.dropped_origin)>700)
1868 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1873 // About to fail, switch to middlefield
1876 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1880 // Set the role timeout if necessary
1881 if (!self.havocbot_role_timeout)
1882 self.havocbot_role_timeout = time + 120;
1884 if (time > self.havocbot_role_timeout)
1886 havocbot_ctf_reset_role(self);
1890 if (self.bot_strategytime < time)
1892 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1893 navigation_goalrating_start();
1894 havocbot_goalrating_ctf_ourstolenflag(50000);
1895 havocbot_goalrating_ctf_enemybase(20000);
1896 havocbot_goalrating_items(5000, self.origin, 1000);
1897 havocbot_goalrating_items(1000, self.origin, 10000);
1898 navigation_goalrating_end();
1902 // Retriever (temporary role):
1903 void havocbot_role_ctf_retriever()
1907 if(self.deadflag != DEAD_NO)
1909 havocbot_ctf_reset_role(self);
1913 if (self.flagcarried)
1915 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1919 // If flag is back on the base switch to previous role
1920 mf = havocbot_ctf_find_flag(self);
1921 if(mf.ctf_status==FLAG_BASE)
1923 havocbot_ctf_reset_role(self);
1927 if (!self.havocbot_role_timeout)
1928 self.havocbot_role_timeout = time + 20;
1930 if (time > self.havocbot_role_timeout)
1932 havocbot_ctf_reset_role(self);
1936 if (self.bot_strategytime < time)
1941 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1942 navigation_goalrating_start();
1943 havocbot_goalrating_ctf_ourstolenflag(50000);
1944 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1945 havocbot_goalrating_ctf_enemybase(30000);
1946 havocbot_goalrating_items(500, self.origin, rt_radius);
1947 navigation_goalrating_end();
1951 void havocbot_role_ctf_middle()
1955 if(self.deadflag != DEAD_NO)
1957 havocbot_ctf_reset_role(self);
1961 if (self.flagcarried)
1963 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1967 mf = havocbot_ctf_find_flag(self);
1968 if(mf.ctf_status!=FLAG_BASE)
1970 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1974 if (!self.havocbot_role_timeout)
1975 self.havocbot_role_timeout = time + 10;
1977 if (time > self.havocbot_role_timeout)
1979 havocbot_ctf_reset_role(self);
1983 if (self.bot_strategytime < time)
1987 org = havocbot_ctf_middlepoint;
1988 org.z = self.origin.z;
1990 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1991 navigation_goalrating_start();
1992 havocbot_goalrating_ctf_ourstolenflag(50000);
1993 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
1994 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1995 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1996 havocbot_goalrating_items(2500, self.origin, 10000);
1997 havocbot_goalrating_ctf_enemybase(2500);
1998 navigation_goalrating_end();
2002 void havocbot_role_ctf_defense()
2006 if(self.deadflag != DEAD_NO)
2008 havocbot_ctf_reset_role(self);
2012 if (self.flagcarried)
2014 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2018 // If own flag was captured
2019 mf = havocbot_ctf_find_flag(self);
2020 if(mf.ctf_status!=FLAG_BASE)
2022 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2026 if (!self.havocbot_role_timeout)
2027 self.havocbot_role_timeout = time + 30;
2029 if (time > self.havocbot_role_timeout)
2031 havocbot_ctf_reset_role(self);
2034 if (self.bot_strategytime < time)
2039 org = mf.dropped_origin;
2040 mp_radius = havocbot_ctf_middlepoint_radius;
2042 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2043 navigation_goalrating_start();
2045 // if enemies are closer to our base, go there
2046 entity head, closestplayer = world;
2047 float distance, bestdistance = 10000;
2048 FOR_EACH_PLAYER(head)
2050 if(head.deadflag!=DEAD_NO)
2053 distance = vlen(org - head.origin);
2054 if(distance<bestdistance)
2056 closestplayer = head;
2057 bestdistance = distance;
2062 if(DIFF_TEAM(closestplayer, self))
2063 if(vlen(org - self.origin)>1000)
2064 if(checkpvs(self.origin,closestplayer)||random()<0.5)
2065 havocbot_goalrating_ctf_ourbase(30000);
2067 havocbot_goalrating_ctf_ourstolenflag(20000);
2068 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
2069 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
2070 havocbot_goalrating_items(10000, org, mp_radius);
2071 havocbot_goalrating_items(5000, self.origin, 10000);
2072 navigation_goalrating_end();
2076 void havocbot_role_ctf_setrole(entity bot, int role)
2078 LOG_TRACE(strcat(bot.netname," switched to "));
2081 case HAVOCBOT_CTF_ROLE_CARRIER:
2082 LOG_TRACE("carrier");
2083 bot.havocbot_role = havocbot_role_ctf_carrier;
2084 bot.havocbot_role_timeout = 0;
2085 bot.havocbot_cantfindflag = time + 10;
2086 bot.bot_strategytime = 0;
2088 case HAVOCBOT_CTF_ROLE_DEFENSE:
2089 LOG_TRACE("defense");
2090 bot.havocbot_role = havocbot_role_ctf_defense;
2091 bot.havocbot_role_timeout = 0;
2093 case HAVOCBOT_CTF_ROLE_MIDDLE:
2094 LOG_TRACE("middle");
2095 bot.havocbot_role = havocbot_role_ctf_middle;
2096 bot.havocbot_role_timeout = 0;
2098 case HAVOCBOT_CTF_ROLE_OFFENSE:
2099 LOG_TRACE("offense");
2100 bot.havocbot_role = havocbot_role_ctf_offense;
2101 bot.havocbot_role_timeout = 0;
2103 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2104 LOG_TRACE("retriever");
2105 bot.havocbot_previous_role = bot.havocbot_role;
2106 bot.havocbot_role = havocbot_role_ctf_retriever;
2107 bot.havocbot_role_timeout = time + 10;
2108 bot.bot_strategytime = 0;
2110 case HAVOCBOT_CTF_ROLE_ESCORT:
2111 LOG_TRACE("escort");
2112 bot.havocbot_previous_role = bot.havocbot_role;
2113 bot.havocbot_role = havocbot_role_ctf_escort;
2114 bot.havocbot_role_timeout = time + 30;
2115 bot.bot_strategytime = 0;
2126 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2129 int t = 0, t2 = 0, t3 = 0;
2131 // initially clear items so they can be set as necessary later.
2132 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2133 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2134 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2135 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2136 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2137 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
2139 // scan through all the flags and notify the client about them
2140 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2142 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2143 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2144 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2145 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2146 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; }
2148 switch(flag.ctf_status)
2153 if((flag.owner == self) || (flag.pass_sender == self))
2154 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
2156 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2161 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2167 // item for stopping players from capturing the flag too often
2168 if(self.ctf_captureshielded)
2169 self.ctf_flagstatus |= CTF_SHIELDED;
2171 // update the health of the flag carrier waypointsprite
2172 if(self.wps_flagcarrier)
2173 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2178 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2180 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2182 if(frag_target == frag_attacker) // damage done to yourself
2184 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2185 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2187 else // damage done to everyone else
2189 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2190 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2193 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2195 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)))
2196 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2198 frag_target.wps_helpme_time = time;
2199 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2201 // todo: add notification for when flag carrier needs help?
2206 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2208 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2210 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2211 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2214 if(frag_target.flagcarried)
2216 entity tmp_entity = frag_target.flagcarried;
2217 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2218 tmp_entity.ctf_dropper = world;
2224 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2227 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2230 void ctf_RemovePlayer(entity player)
2232 if(player.flagcarried)
2233 { ctf_Handle_Throw(player, world, DROP_NORMAL); }
2235 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2237 if(flag.pass_sender == player) { flag.pass_sender = world; }
2238 if(flag.pass_target == player) { flag.pass_target = world; }
2239 if(flag.ctf_dropper == player) { flag.ctf_dropper = world; }
2243 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2245 ctf_RemovePlayer(self);
2249 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2251 ctf_RemovePlayer(self);
2255 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2257 if(self.flagcarried)
2258 if(!autocvar_g_ctf_portalteleport)
2259 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2264 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2266 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2268 entity player = self;
2270 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2272 // pass the flag to a team mate
2273 if(autocvar_g_ctf_pass)
2275 entity head, closest_target = world;
2276 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2278 while(head) // find the closest acceptable target to pass to
2280 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2281 if(head != player && SAME_TEAM(head, player))
2282 if(!head.speedrunning && !head.vehicle)
2284 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2285 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2286 vector passer_center = CENTER_OR_VIEWOFS(player);
2288 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2290 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2292 if(IS_BOT_CLIENT(head))
2294 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2295 ctf_Handle_Throw(head, player, DROP_PASS);
2299 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2300 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2302 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2305 else if(player.flagcarried)
2309 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2310 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2311 { closest_target = head; }
2313 else { closest_target = head; }
2320 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2323 // throw the flag in front of you
2324 if(autocvar_g_ctf_throw && player.flagcarried)
2326 if(player.throw_count == -1)
2328 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2330 player.throw_prevtime = time;
2331 player.throw_count = 1;
2332 ctf_Handle_Throw(player, world, DROP_THROW);
2337 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2343 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2344 else { player.throw_count += 1; }
2345 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2347 player.throw_prevtime = time;
2348 ctf_Handle_Throw(player, world, DROP_THROW);
2357 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2359 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2361 self.wps_helpme_time = time;
2362 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2364 else // create a normal help me waypointsprite
2366 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
2367 WaypointSprite_Ping(self.wps_helpme);
2373 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2375 if(vh_player.flagcarried)
2377 vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
2379 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2381 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2385 setattachment(vh_player.flagcarried, vh_vehicle, "");
2386 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2387 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2388 //vh_player.flagcarried.angles = '0 0 0';
2396 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2398 if(vh_player.flagcarried)
2400 setattachment(vh_player.flagcarried, vh_player, "");
2401 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2402 vh_player.flagcarried.scale = FLAG_SCALE;
2403 vh_player.flagcarried.angles = '0 0 0';
2404 vh_player.flagcarried.nodrawtoclient = world;
2411 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2413 if(self.flagcarried)
2415 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));
2416 ctf_RespawnFlag(self.flagcarried);
2423 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2425 entity flag; // temporary entity for the search method
2427 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2429 switch(flag.ctf_status)
2434 // lock the flag, game is over
2435 flag.movetype = MOVETYPE_NONE;
2436 flag.takedamage = DAMAGE_NO;
2437 flag.solid = SOLID_NOT;
2438 flag.nextthink = false; // stop thinking
2440 //dprint("stopping the ", flag.netname, " from moving.\n");
2448 // do nothing for these flags
2457 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2459 havocbot_ctf_reset_role(self);
2463 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2465 //ret_float = ctf_teams;
2466 ret_string = "ctf_team";
2470 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2472 self.ctf_flagstatus = other.ctf_flagstatus;
2476 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2478 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2480 if (MapInfo_Get_ByID(i))
2482 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2488 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2489 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2496 bool superspec_Spectate(entity _player); // TODO
2497 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2498 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2500 if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2502 if(cmd_name == "followfc")
2515 case "red": _team = NUM_TEAM_1; break;
2516 case "blue": _team = NUM_TEAM_2; break;
2517 case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
2518 case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
2522 FOR_EACH_PLAYER(_player)
2524 if(_player.flagcarried && (_player.team == _team || _team == 0))
2527 if(_team == 0 && IS_SPEC(self) && self.enemy == _player)
2528 continue; // already spectating a fc, try to find the other fc
2529 return superspec_Spectate(_player);
2534 superspec_msg("", "", self, "No active flag carrier\n", 1);
2541 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2543 if(frag_target.flagcarried)
2544 ctf_Handle_Throw(frag_target, world, DROP_THROW);
2554 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2555 CTF flag for team one (Red).
2557 "angle" Angle the flag will point (minus 90 degrees)...
2558 "model" model to use, note this needs red and blue as skins 0 and 1...
2559 "noise" sound played when flag is picked up...
2560 "noise1" sound played when flag is returned by a teammate...
2561 "noise2" sound played when flag is captured...
2562 "noise3" sound played when flag is lost in the field and respawns itself...
2563 "noise4" sound played when flag is dropped by a player...
2564 "noise5" sound played when flag touches the ground... */
2565 spawnfunc(item_flag_team1)
2567 if(!g_ctf) { remove(self); return; }
2569 ctf_FlagSetup(NUM_TEAM_1, self);
2572 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2573 CTF flag for team two (Blue).
2575 "angle" Angle the flag will point (minus 90 degrees)...
2576 "model" model to use, note this needs red and blue as skins 0 and 1...
2577 "noise" sound played when flag is picked up...
2578 "noise1" sound played when flag is returned by a teammate...
2579 "noise2" sound played when flag is captured...
2580 "noise3" sound played when flag is lost in the field and respawns itself...
2581 "noise4" sound played when flag is dropped by a player...
2582 "noise5" sound played when flag touches the ground... */
2583 spawnfunc(item_flag_team2)
2585 if(!g_ctf) { remove(self); return; }
2587 ctf_FlagSetup(NUM_TEAM_2, self);
2590 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2591 CTF flag for team three (Yellow).
2593 "angle" Angle the flag will point (minus 90 degrees)...
2594 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2595 "noise" sound played when flag is picked up...
2596 "noise1" sound played when flag is returned by a teammate...
2597 "noise2" sound played when flag is captured...
2598 "noise3" sound played when flag is lost in the field and respawns itself...
2599 "noise4" sound played when flag is dropped by a player...
2600 "noise5" sound played when flag touches the ground... */
2601 spawnfunc(item_flag_team3)
2603 if(!g_ctf) { remove(self); return; }
2605 ctf_FlagSetup(NUM_TEAM_3, self);
2608 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2609 CTF flag for team four (Pink).
2611 "angle" Angle the flag will point (minus 90 degrees)...
2612 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2613 "noise" sound played when flag is picked up...
2614 "noise1" sound played when flag is returned by a teammate...
2615 "noise2" sound played when flag is captured...
2616 "noise3" sound played when flag is lost in the field and respawns itself...
2617 "noise4" sound played when flag is dropped by a player...
2618 "noise5" sound played when flag touches the ground... */
2619 spawnfunc(item_flag_team4)
2621 if(!g_ctf) { remove(self); return; }
2623 ctf_FlagSetup(NUM_TEAM_4, self);
2626 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2629 "angle" Angle the flag will point (minus 90 degrees)...
2630 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2631 "noise" sound played when flag is picked up...
2632 "noise1" sound played when flag is returned by a teammate...
2633 "noise2" sound played when flag is captured...
2634 "noise3" sound played when flag is lost in the field and respawns itself...
2635 "noise4" sound played when flag is dropped by a player...
2636 "noise5" sound played when flag touches the ground... */
2637 spawnfunc(item_flag_neutral)
2639 if(!g_ctf) { remove(self); return; }
2640 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2642 ctf_FlagSetup(0, self);
2645 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2646 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2647 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.
2649 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2650 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2653 if(!g_ctf) { remove(self); return; }
2655 self.classname = "ctf_team";
2656 self.team = self.cnt + 1;
2659 // compatibility for quake maps
2660 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2661 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2662 spawnfunc(info_player_team1);
2663 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2664 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2665 spawnfunc(info_player_team2);
2666 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2667 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2669 void team_CTF_neutralflag() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2670 void team_neutralobelisk() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2678 void ctf_ScoreRules(int teams)
2680 CheckAllowedTeams(world);
2681 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2682 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2683 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2684 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2685 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2686 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2687 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2688 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2689 ScoreRules_basics_end();
2692 // code from here on is just to support maps that don't have flag and team entities
2693 void ctf_SpawnTeam (string teamname, int teamcolor)
2695 entity this = new(ctf_team);
2696 this.netname = teamname;
2697 this.cnt = teamcolor;
2698 this.spawnfunc_checked = true;
2699 WITH(entity, self, this, spawnfunc_ctf_team(this));
2702 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2707 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2709 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2710 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2711 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2714 ctf_teams = bound(2, ctf_teams, 4);
2716 // if no teams are found, spawn defaults
2717 if(find(world, classname, "ctf_team") == world)
2719 LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2720 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2721 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2723 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2725 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2728 ctf_ScoreRules(ctf_teams);
2731 void ctf_Initialize()
2733 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2735 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2736 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2737 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2739 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2741 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
2744 REGISTER_MUTATOR(ctf, g_ctf)
2747 SetLimits(autocvar_capturelimit_override, -1, autocvar_captureleadlimit_override, -1);
2748 have_team_spawns = -1; // request team spawns
2752 if(time > 1) // game loads at time 1
2753 error("This is a game type and it cannot be added at runtime.");
2757 MUTATOR_ONROLLBACK_OR_REMOVE
2759 // we actually cannot roll back ctf_Initialize here
2760 // BUT: we don't need to! If this gets called, adding always
2766 LOG_INFO("This is a game type and it cannot be removed at runtime.");