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 = spawn();
473 shield.team = self.team;
474 shield.touch = ctf_CaptureShield_Touch;
475 shield.customizeentityforclient = ctf_CaptureShield_Customize;
476 shield.classname = "ctf_captureshield";
477 shield.effects = EF_ADDITIVE;
478 shield.movetype = MOVETYPE_NOCLIP;
479 shield.solid = SOLID_TRIGGER;
480 shield.avelocity = '7 0 11';
483 setorigin(shield, self.origin);
484 setmodel(shield, MDL_CTF_SHIELD);
485 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
489 // ====================
490 // Drop/Pass/Throw Code
491 // ====================
493 void ctf_Handle_Drop(entity flag, entity player, int droptype)
496 player = (player ? player : flag.pass_sender);
499 flag.movetype = MOVETYPE_TOSS;
500 flag.takedamage = DAMAGE_YES;
501 flag.angles = '0 0 0';
502 flag.health = flag.max_flag_health;
503 flag.ctf_droptime = time;
504 flag.ctf_dropper = player;
505 flag.ctf_status = FLAG_DROPPED;
507 // messages and sounds
508 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
509 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
510 ctf_EventLog("dropped", player.team, player);
513 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
514 PlayerScore_Add(player, SP_CTF_DROPS, 1);
517 if(autocvar_g_ctf_flag_dropped_waypoint) {
518 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);
519 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
522 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
524 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
525 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
528 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
530 if(droptype == DROP_PASS)
532 flag.pass_distance = 0;
533 flag.pass_sender = world;
534 flag.pass_target = world;
538 void ctf_Handle_Retrieve(entity flag, entity player)
540 entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
541 entity sender = flag.pass_sender;
543 // transfer flag to player
545 flag.owner.flagcarried = flag;
550 setattachment(flag, player.vehicle, "");
551 setorigin(flag, VEHICLE_FLAG_OFFSET);
552 flag.scale = VEHICLE_FLAG_SCALE;
556 setattachment(flag, player, "");
557 setorigin(flag, FLAG_CARRY_OFFSET);
559 flag.movetype = MOVETYPE_NONE;
560 flag.takedamage = DAMAGE_NO;
561 flag.solid = SOLID_NOT;
562 flag.angles = '0 0 0';
563 flag.ctf_status = FLAG_CARRY;
565 // messages and sounds
566 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
567 ctf_EventLog("receive", flag.team, player);
569 FOR_EACH_REALPLAYER(tmp_player)
571 if(tmp_player == sender)
572 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);
573 else if(tmp_player == player)
574 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);
575 else if(SAME_TEAM(tmp_player, sender))
576 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);
579 // create new waypoint
580 ctf_FlagcarrierWaypoints(player);
582 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
583 player.throw_antispam = sender.throw_antispam;
585 flag.pass_distance = 0;
586 flag.pass_sender = world;
587 flag.pass_target = world;
590 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
592 entity flag = player.flagcarried;
593 vector targ_origin, flag_velocity;
595 if(!flag) { return; }
596 if((droptype == DROP_PASS) && !receiver) { return; }
598 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
601 setattachment(flag, world, "");
602 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
603 flag.owner.flagcarried = world;
605 flag.solid = SOLID_TRIGGER;
606 flag.ctf_dropper = player;
607 flag.ctf_droptime = time;
609 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
616 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
617 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
618 WarpZone_RefSys_Copy(flag, receiver);
619 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
620 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
622 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
623 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
626 flag.movetype = MOVETYPE_FLY;
627 flag.takedamage = DAMAGE_NO;
628 flag.pass_sender = player;
629 flag.pass_target = receiver;
630 flag.ctf_status = FLAG_PASSING;
633 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
634 WarpZone_TrailParticles(world, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
635 ctf_EventLog("pass", flag.team, player);
641 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'));
643 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)));
644 flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
645 ctf_Handle_Drop(flag, player, droptype);
651 flag.velocity = '0 0 0'; // do nothing
658 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);
659 ctf_Handle_Drop(flag, player, droptype);
664 // kill old waypointsprite
665 WaypointSprite_Ping(player.wps_flagcarrier);
666 WaypointSprite_Kill(player.wps_flagcarrier);
668 if(player.wps_enemyflagcarrier)
669 WaypointSprite_Kill(player.wps_enemyflagcarrier);
672 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
680 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
682 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
683 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
684 entity player_team_flag = world, tmp_entity;
685 float old_time, new_time;
687 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
688 if(CTF_DIFFTEAM(player, flag)) { return; }
691 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
692 if(SAME_TEAM(tmp_entity, player))
694 player_team_flag = tmp_entity;
698 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
700 player.throw_prevtime = time;
701 player.throw_count = 0;
703 // messages and sounds
704 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
705 ctf_CaptureRecord(enemy_flag, player);
706 _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);
710 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
711 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
716 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
717 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
719 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
720 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
721 if(!old_time || new_time < old_time)
722 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
725 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
726 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
729 if(capturetype == CAPTURE_NORMAL)
731 WaypointSprite_Kill(player.wps_flagcarrier);
732 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
734 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
735 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
739 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
740 ctf_RespawnFlag(enemy_flag);
743 void ctf_Handle_Return(entity flag, entity player)
745 // messages and sounds
746 if(IS_MONSTER(player))
748 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
752 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
753 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
755 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
756 ctf_EventLog("return", flag.team, player);
759 if(IS_PLAYER(player))
761 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
762 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
764 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
767 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
771 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
772 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
773 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
777 if(player.flagcarried == flag)
778 WaypointSprite_Kill(player.wps_flagcarrier);
781 ctf_RespawnFlag(flag);
784 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
787 float pickup_dropped_score; // used to calculate dropped pickup score
788 entity tmp_entity; // temporary entity
790 // attach the flag to the player
792 player.flagcarried = flag;
795 setattachment(flag, player.vehicle, "");
796 setorigin(flag, VEHICLE_FLAG_OFFSET);
797 flag.scale = VEHICLE_FLAG_SCALE;
801 setattachment(flag, player, "");
802 setorigin(flag, FLAG_CARRY_OFFSET);
806 flag.movetype = MOVETYPE_NONE;
807 flag.takedamage = DAMAGE_NO;
808 flag.solid = SOLID_NOT;
809 flag.angles = '0 0 0';
810 flag.ctf_status = FLAG_CARRY;
814 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
815 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
819 // messages and sounds
820 Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
821 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
822 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
823 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
824 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)); }
826 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);
829 FOR_EACH_PLAYER(tmp_entity)
830 if(tmp_entity != player)
831 if(DIFF_TEAM(player, tmp_entity))
832 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
835 FOR_EACH_PLAYER(tmp_entity)
836 if(tmp_entity != player)
837 if(CTF_SAMETEAM(flag, tmp_entity))
838 if(SAME_TEAM(player, tmp_entity))
839 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
841 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);
843 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
846 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
847 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
852 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
853 ctf_EventLog("steal", flag.team, player);
859 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);
860 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);
861 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
862 PlayerTeamScore_AddScore(player, pickup_dropped_score);
863 ctf_EventLog("pickup", flag.team, player);
871 if(pickuptype == PICKUP_BASE)
873 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
874 if((player.speedrunning) && (ctf_captimerecord))
875 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
879 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
882 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
883 ctf_FlagcarrierWaypoints(player);
884 WaypointSprite_Ping(player.wps_flagcarrier);
888 // ===================
889 // Main Flag Functions
890 // ===================
892 void ctf_CheckFlagReturn(entity flag, int returntype)
894 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
896 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
898 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
902 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;
903 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;
904 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;
905 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;
909 { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
911 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
912 ctf_EventLog("returned", flag.team, world);
913 ctf_RespawnFlag(flag);
918 bool ctf_Stalemate_Customize()
920 // make spectators see what the player would see
922 e = WaypointSprite_getviewentity(other);
923 wp_owner = self.owner;
926 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
927 if(SAME_TEAM(wp_owner, e)) { return false; }
928 if(!IS_PLAYER(e)) { return false; }
933 void ctf_CheckStalemate(void)
936 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
939 entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
941 // build list of stale flags
942 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
944 if(autocvar_g_ctf_stalemate)
945 if(tmp_entity.ctf_status != FLAG_BASE)
946 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
948 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
949 ctf_staleflaglist = tmp_entity;
951 switch(tmp_entity.team)
953 case NUM_TEAM_1: ++stale_red_flags; break;
954 case NUM_TEAM_2: ++stale_blue_flags; break;
955 case NUM_TEAM_3: ++stale_yellow_flags; break;
956 case NUM_TEAM_4: ++stale_pink_flags; break;
957 default: ++stale_neutral_flags; break;
963 stale_flags = (stale_neutral_flags >= 1);
965 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
967 if(ctf_oneflag && stale_flags == 1)
968 ctf_stalemate = true;
969 else if(stale_flags >= 2)
970 ctf_stalemate = true;
971 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
972 { ctf_stalemate = false; wpforenemy_announced = false; }
973 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
974 { ctf_stalemate = false; wpforenemy_announced = false; }
976 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
979 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
981 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
983 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);
984 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
985 tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
989 if (!wpforenemy_announced)
991 FOR_EACH_REALPLAYER(tmp_entity)
992 Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
994 wpforenemy_announced = true;
999 void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
1001 if(ITEM_DAMAGE_NEEDKILL(deathtype))
1003 if(autocvar_g_ctf_flag_return_damage_delay)
1005 self.ctf_flagdamaged = true;
1010 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1014 if(autocvar_g_ctf_flag_return_damage)
1016 // reduce health and check if it should be returned
1017 self.health = self.health - damage;
1018 ctf_CheckFlagReturn(self, RETURN_DAMAGE);
1023 void ctf_FlagThink()
1028 self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
1031 if(self == ctf_worldflaglist) // only for the first flag
1032 FOR_EACH_CLIENT(tmp_entity)
1033 ctf_CaptureShield_Update(tmp_entity, 1); // release shield only
1036 if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
1037 LOG_TRACE("wtf the flag got squashed?\n");
1038 tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
1039 if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
1040 setsize(self, FLAG_MIN, FLAG_MAX); }
1042 switch(self.ctf_status) // reset flag angles in case warpzones adjust it
1046 self.angles = '0 0 0';
1053 // main think method
1054 switch(self.ctf_status)
1058 if(autocvar_g_ctf_dropped_capture_radius)
1060 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
1061 if(tmp_entity.ctf_status == FLAG_DROPPED)
1062 if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
1063 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
1064 ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
1071 if(autocvar_g_ctf_flag_dropped_floatinwater)
1073 vector midpoint = ((self.absmin + self.absmax) * 0.5);
1074 if(pointcontents(midpoint) == CONTENT_WATER)
1076 self.velocity = self.velocity * 0.5;
1078 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
1079 { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
1081 { self.movetype = MOVETYPE_FLY; }
1083 else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
1085 if(autocvar_g_ctf_flag_return_dropped)
1087 if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
1090 ctf_CheckFlagReturn(self, RETURN_DROPPED);
1094 if(self.ctf_flagdamaged)
1096 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
1097 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1100 else if(autocvar_g_ctf_flag_return_time)
1102 self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
1103 ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
1111 if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
1114 ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
1116 setself(self.owner);
1117 self.impulse = CHIMPULSE_SPEEDRUN; // move the player back to the waypoint they set
1121 if(autocvar_g_ctf_stalemate)
1123 if(time >= wpforenemy_nextthink)
1125 ctf_CheckStalemate();
1126 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1129 if(CTF_SAMETEAM(self, self.owner) && self.team)
1131 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1132 ctf_Handle_Throw(self.owner, world, DROP_THROW);
1133 else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
1134 ctf_Handle_Return(self, self.owner);
1141 vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
1142 targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
1143 WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
1145 if((self.pass_target == world)
1146 || (self.pass_target.deadflag != DEAD_NO)
1147 || (self.pass_target.flagcarried)
1148 || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
1149 || ((trace_fraction < 1) && (trace_ent != self.pass_target))
1150 || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1152 // give up, pass failed
1153 ctf_Handle_Drop(self, world, DROP_PASS);
1157 // still a viable target, go for it
1158 ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
1163 default: // this should never happen
1165 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
1171 void ctf_FlagTouch()
1173 if(gameover) { return; }
1174 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1176 entity toucher = other, tmp_entity;
1177 bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
1179 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1180 if(ITEM_TOUCH_NEEDKILL())
1182 if(!autocvar_g_ctf_flag_return_damage_delay)
1185 ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
1187 if(!self.ctf_flagdamaged) { return; }
1190 FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
1192 // special touch behaviors
1193 if(toucher.frozen) { return; }
1194 else if(IS_VEHICLE(toucher))
1196 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1197 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1199 return; // do nothing
1201 else if(IS_MONSTER(toucher))
1203 if(!autocvar_g_ctf_allow_monster_touch)
1204 return; // do nothing
1206 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1208 if(time > self.wait) // if we haven't in a while, play a sound/effect
1210 Send_Effect_(self.toucheffect, self.origin, '0 0 0', 1);
1211 _sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1212 self.wait = time + FLAG_TOUCHRATE;
1216 else if(toucher.deadflag != DEAD_NO) { return; }
1218 switch(self.ctf_status)
1224 if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1225 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1226 else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1227 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1229 else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
1230 ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1231 else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1232 ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1238 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
1239 ctf_Handle_Return(self, toucher); // toucher just returned his own flag
1240 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1241 ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1247 LOG_TRACE("Someone touched a flag even though it was being carried?\n");
1253 if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
1255 if(DIFF_TEAM(toucher, self.pass_sender))
1256 ctf_Handle_Return(self, toucher);
1258 ctf_Handle_Retrieve(self, toucher);
1265 .float last_respawn;
1266 void ctf_RespawnFlag(entity flag)
1268 // check for flag respawn being called twice in a row
1269 if(flag.last_respawn > time - 0.5)
1270 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1272 flag.last_respawn = time;
1274 // reset the player (if there is one)
1275 if((flag.owner) && (flag.owner.flagcarried == flag))
1277 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1278 WaypointSprite_Kill(flag.wps_flagcarrier);
1280 flag.owner.flagcarried = world;
1282 if(flag.speedrunning)
1283 ctf_FakeTimeLimit(flag.owner, -1);
1286 if((flag.owner) && (flag.owner.vehicle))
1287 flag.scale = FLAG_SCALE;
1289 if(flag.ctf_status == FLAG_DROPPED)
1290 { WaypointSprite_Kill(flag.wps_flagdropped); }
1293 setattachment(flag, world, "");
1294 setorigin(flag, flag.ctf_spawnorigin);
1296 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1297 flag.takedamage = DAMAGE_NO;
1298 flag.health = flag.max_flag_health;
1299 flag.solid = SOLID_TRIGGER;
1300 flag.velocity = '0 0 0';
1301 flag.angles = flag.mangle;
1302 flag.flags = FL_ITEM | FL_NOTARGET;
1304 flag.ctf_status = FLAG_BASE;
1306 flag.pass_distance = 0;
1307 flag.pass_sender = world;
1308 flag.pass_target = world;
1309 flag.ctf_dropper = world;
1310 flag.ctf_pickuptime = 0;
1311 flag.ctf_droptime = 0;
1312 flag.ctf_flagdamaged = 0;
1314 ctf_CheckStalemate();
1320 if(IS_PLAYER(self.owner))
1321 ctf_Handle_Throw(self.owner, world, DROP_RESET);
1323 ctf_RespawnFlag(self);
1326 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
1329 waypoint_spawnforitem_force(self, self.origin);
1330 self.nearestwaypointtimeout = 0; // activate waypointing again
1331 self.bot_basewaypoint = self.nearestwaypoint;
1337 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1338 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1339 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1340 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1341 default: basename = WP_FlagBaseNeutral; break;
1344 entity wp = WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG);
1345 wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 1 1');
1346 WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
1348 // captureshield setup
1349 ctf_CaptureShield_Spawn(self);
1352 void set_flag_string(entity flag, .string field, string value, string teamname)
1354 if(flag.(field) == "")
1355 flag.(field) = strzone(sprintf(value,teamname));
1358 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1361 setself(flag); // for later usage with droptofloor()
1364 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1365 ctf_worldflaglist = flag;
1367 setattachment(flag, world, "");
1369 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1370 flag.team = teamnumber;
1371 flag.classname = "item_flag_team";
1372 flag.target = "###item###"; // wut?
1373 flag.flags = FL_ITEM | FL_NOTARGET;
1374 flag.solid = SOLID_TRIGGER;
1375 flag.takedamage = DAMAGE_NO;
1376 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1377 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1378 flag.health = flag.max_flag_health;
1379 flag.event_damage = ctf_FlagDamage;
1380 flag.pushable = true;
1381 flag.teleportable = TELEPORT_NORMAL;
1382 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
1383 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1384 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1385 flag.velocity = '0 0 0';
1386 flag.mangle = flag.angles;
1387 flag.reset = ctf_Reset;
1388 flag.touch = ctf_FlagTouch;
1389 flag.think = ctf_FlagThink;
1390 flag.nextthink = time + FLAG_THINKRATE;
1391 flag.ctf_status = FLAG_BASE;
1393 string teamname = Static_Team_ColorName_Lower(teamnumber);
1395 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1396 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1397 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1398 set_flag_string(flag, toucheffect, "%sflag_touch", teamname);
1399 set_flag_string(flag, passeffect, "%s_pass", teamname);
1400 set_flag_string(flag, capeffect, "%s_cap", teamname);
1403 flag.snd_flag_taken = SND(CTF_TAKEN(teamnumber));
1404 flag.snd_flag_returned = SND(CTF_RETURNED(teamnumber));
1405 flag.snd_flag_capture = SND(CTF_CAPTURE(teamnumber));
1406 flag.snd_flag_dropped = SND(CTF_DROPPED(teamnumber));
1407 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.
1408 precache_sound(flag.snd_flag_respawn);
1409 if (flag.snd_flag_touch == "") flag.snd_flag_touch = SND(CTF_TOUCH); // again has no team-based sound
1410 precache_sound(flag.snd_flag_touch);
1411 if (flag.snd_flag_pass == "") flag.snd_flag_pass = SND(CTF_PASS); // same story here
1412 precache_sound(flag.snd_flag_pass);
1415 precache_model(flag.model);
1418 _setmodel(flag, flag.model); // precision set below
1419 setsize(flag, FLAG_MIN, FLAG_MAX);
1420 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1422 if(autocvar_g_ctf_flag_glowtrails)
1426 case NUM_TEAM_1: flag.glow_color = 251; break;
1427 case NUM_TEAM_2: flag.glow_color = 210; break;
1428 case NUM_TEAM_3: flag.glow_color = 110; break;
1429 case NUM_TEAM_4: flag.glow_color = 145; break;
1430 default: flag.glow_color = 254; break;
1432 flag.glow_size = 25;
1433 flag.glow_trail = 1;
1436 flag.effects |= EF_LOWPRECISION;
1437 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1438 if(autocvar_g_ctf_dynamiclights)
1442 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1443 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1444 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1445 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1446 default: flag.effects |= EF_DIMLIGHT; break;
1451 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1453 flag.dropped_origin = flag.origin;
1454 flag.noalign = true;
1455 flag.movetype = MOVETYPE_NONE;
1457 else // drop to floor, automatically find a platform and set that as spawn origin
1459 flag.noalign = false;
1462 flag.movetype = MOVETYPE_TOSS;
1465 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1473 // NOTE: LEGACY CODE, needs to be re-written!
1475 void havocbot_calculate_middlepoint()
1479 vector fo = '0 0 0';
1482 f = ctf_worldflaglist;
1487 f = f.ctf_worldflagnext;
1491 havocbot_ctf_middlepoint = s * (1.0 / n);
1492 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1496 entity havocbot_ctf_find_flag(entity bot)
1499 f = ctf_worldflaglist;
1502 if (CTF_SAMETEAM(bot, f))
1504 f = f.ctf_worldflagnext;
1509 entity havocbot_ctf_find_enemy_flag(entity bot)
1512 f = ctf_worldflaglist;
1517 if(CTF_DIFFTEAM(bot, f))
1524 else if(!bot.flagcarried)
1528 else if (CTF_DIFFTEAM(bot, f))
1530 f = f.ctf_worldflagnext;
1535 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1543 FOR_EACH_PLAYER(head)
1545 if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
1548 if(vlen(head.origin - org) < tc_radius)
1555 void havocbot_goalrating_ctf_ourflag(float ratingscale)
1558 head = ctf_worldflaglist;
1561 if (CTF_SAMETEAM(self, head))
1563 head = head.ctf_worldflagnext;
1566 navigation_routerating(head, ratingscale, 10000);
1569 void havocbot_goalrating_ctf_ourbase(float ratingscale)
1572 head = ctf_worldflaglist;
1575 if (CTF_SAMETEAM(self, head))
1577 head = head.ctf_worldflagnext;
1582 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1585 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
1588 head = ctf_worldflaglist;
1593 if(CTF_DIFFTEAM(self, head))
1597 if(self.flagcarried)
1600 else if(!self.flagcarried)
1604 else if(CTF_DIFFTEAM(self, head))
1606 head = head.ctf_worldflagnext;
1609 navigation_routerating(head, ratingscale, 10000);
1612 void havocbot_goalrating_ctf_enemybase(float ratingscale)
1614 if (!bot_waypoints_for_items)
1616 havocbot_goalrating_ctf_enemyflag(ratingscale);
1622 head = havocbot_ctf_find_enemy_flag(self);
1627 navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
1630 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
1634 mf = havocbot_ctf_find_flag(self);
1636 if(mf.ctf_status == FLAG_BASE)
1640 navigation_routerating(mf.tag_entity, ratingscale, 10000);
1643 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float df_radius)
1646 head = ctf_worldflaglist;
1649 // flag is out in the field
1650 if(head.ctf_status != FLAG_BASE)
1651 if(head.tag_entity==world) // dropped
1655 if(vlen(org-head.origin)<df_radius)
1656 navigation_routerating(head, ratingscale, 10000);
1659 navigation_routerating(head, ratingscale, 10000);
1662 head = head.ctf_worldflagnext;
1666 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
1670 head = findchainfloat(bot_pickup, true);
1673 // gather health and armor only
1675 if (head.health || head.armorvalue)
1676 if (vlen(head.origin - org) < sradius)
1678 // get the value of the item
1679 t = head.bot_pickupevalfunc(self, head) * 0.0001;
1681 navigation_routerating(head, t * ratingscale, 500);
1687 void havocbot_ctf_reset_role(entity bot)
1689 float cdefense, cmiddle, coffense;
1690 entity mf, ef, head;
1693 if(bot.deadflag != DEAD_NO)
1696 if(vlen(havocbot_ctf_middlepoint)==0)
1697 havocbot_calculate_middlepoint();
1700 if (bot.flagcarried)
1702 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_CARRIER);
1706 mf = havocbot_ctf_find_flag(bot);
1707 ef = havocbot_ctf_find_enemy_flag(bot);
1709 // Retrieve stolen flag
1710 if(mf.ctf_status!=FLAG_BASE)
1712 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);
1716 // If enemy flag is taken go to the middle to intercept pursuers
1717 if(ef.ctf_status!=FLAG_BASE)
1719 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1723 // if there is only me on the team switch to offense
1725 FOR_EACH_PLAYER(head)
1726 if(SAME_TEAM(head, bot))
1731 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1735 // Evaluate best position to take
1736 // Count mates on middle position
1737 cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1739 // Count mates on defense position
1740 cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1742 // Count mates on offense position
1743 coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1745 if(cdefense<=coffense)
1746 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);
1747 else if(coffense<=cmiddle)
1748 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
1750 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);
1753 void havocbot_role_ctf_carrier()
1755 if(self.deadflag != DEAD_NO)
1757 havocbot_ctf_reset_role(self);
1761 if (self.flagcarried == world)
1763 havocbot_ctf_reset_role(self);
1767 if (self.bot_strategytime < time)
1769 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1771 navigation_goalrating_start();
1773 havocbot_goalrating_ctf_enemybase(50000);
1775 havocbot_goalrating_ctf_ourbase(50000);
1778 havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
1780 navigation_goalrating_end();
1782 if (self.navigation_hasgoals)
1783 self.havocbot_cantfindflag = time + 10;
1784 else if (time > self.havocbot_cantfindflag)
1786 // Can't navigate to my own base, suicide!
1787 // TODO: drop it and wander around
1788 Damage(self, self, self, 100000, DEATH_KILL.m_id, self.origin, '0 0 0');
1794 void havocbot_role_ctf_escort()
1798 if(self.deadflag != DEAD_NO)
1800 havocbot_ctf_reset_role(self);
1804 if (self.flagcarried)
1806 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1810 // If enemy flag is back on the base switch to previous role
1811 ef = havocbot_ctf_find_enemy_flag(self);
1812 if(ef.ctf_status==FLAG_BASE)
1814 self.havocbot_role = self.havocbot_previous_role;
1815 self.havocbot_role_timeout = 0;
1819 // If the flag carrier reached the base switch to defense
1820 mf = havocbot_ctf_find_flag(self);
1821 if(mf.ctf_status!=FLAG_BASE)
1822 if(vlen(ef.origin - mf.dropped_origin) < 300)
1824 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
1828 // Set the role timeout if necessary
1829 if (!self.havocbot_role_timeout)
1831 self.havocbot_role_timeout = time + random() * 30 + 60;
1834 // If nothing happened just switch to previous role
1835 if (time > self.havocbot_role_timeout)
1837 self.havocbot_role = self.havocbot_previous_role;
1838 self.havocbot_role_timeout = 0;
1842 // Chase the flag carrier
1843 if (self.bot_strategytime < time)
1845 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1846 navigation_goalrating_start();
1847 havocbot_goalrating_ctf_enemyflag(30000);
1848 havocbot_goalrating_ctf_ourstolenflag(40000);
1849 havocbot_goalrating_items(10000, self.origin, 10000);
1850 navigation_goalrating_end();
1854 void havocbot_role_ctf_offense()
1859 if(self.deadflag != DEAD_NO)
1861 havocbot_ctf_reset_role(self);
1865 if (self.flagcarried)
1867 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1872 mf = havocbot_ctf_find_flag(self);
1873 ef = havocbot_ctf_find_enemy_flag(self);
1876 if(mf.ctf_status!=FLAG_BASE)
1879 pos = mf.tag_entity.origin;
1883 // Try to get it if closer than the enemy base
1884 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
1886 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
1891 // Escort flag carrier
1892 if(ef.ctf_status!=FLAG_BASE)
1895 pos = ef.tag_entity.origin;
1899 if(vlen(pos-mf.dropped_origin)>700)
1901 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);
1906 // About to fail, switch to middlefield
1909 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);
1913 // Set the role timeout if necessary
1914 if (!self.havocbot_role_timeout)
1915 self.havocbot_role_timeout = time + 120;
1917 if (time > self.havocbot_role_timeout)
1919 havocbot_ctf_reset_role(self);
1923 if (self.bot_strategytime < time)
1925 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1926 navigation_goalrating_start();
1927 havocbot_goalrating_ctf_ourstolenflag(50000);
1928 havocbot_goalrating_ctf_enemybase(20000);
1929 havocbot_goalrating_items(5000, self.origin, 1000);
1930 havocbot_goalrating_items(1000, self.origin, 10000);
1931 navigation_goalrating_end();
1935 // Retriever (temporary role):
1936 void havocbot_role_ctf_retriever()
1940 if(self.deadflag != DEAD_NO)
1942 havocbot_ctf_reset_role(self);
1946 if (self.flagcarried)
1948 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
1952 // If flag is back on the base switch to previous role
1953 mf = havocbot_ctf_find_flag(self);
1954 if(mf.ctf_status==FLAG_BASE)
1956 havocbot_ctf_reset_role(self);
1960 if (!self.havocbot_role_timeout)
1961 self.havocbot_role_timeout = time + 20;
1963 if (time > self.havocbot_role_timeout)
1965 havocbot_ctf_reset_role(self);
1969 if (self.bot_strategytime < time)
1974 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1975 navigation_goalrating_start();
1976 havocbot_goalrating_ctf_ourstolenflag(50000);
1977 havocbot_goalrating_ctf_droppedflags(40000, self.origin, rt_radius);
1978 havocbot_goalrating_ctf_enemybase(30000);
1979 havocbot_goalrating_items(500, self.origin, rt_radius);
1980 navigation_goalrating_end();
1984 void havocbot_role_ctf_middle()
1988 if(self.deadflag != DEAD_NO)
1990 havocbot_ctf_reset_role(self);
1994 if (self.flagcarried)
1996 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2000 mf = havocbot_ctf_find_flag(self);
2001 if(mf.ctf_status!=FLAG_BASE)
2003 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2007 if (!self.havocbot_role_timeout)
2008 self.havocbot_role_timeout = time + 10;
2010 if (time > self.havocbot_role_timeout)
2012 havocbot_ctf_reset_role(self);
2016 if (self.bot_strategytime < time)
2020 org = havocbot_ctf_middlepoint;
2021 org.z = self.origin.z;
2023 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2024 navigation_goalrating_start();
2025 havocbot_goalrating_ctf_ourstolenflag(50000);
2026 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);
2027 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);
2028 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
2029 havocbot_goalrating_items(2500, self.origin, 10000);
2030 havocbot_goalrating_ctf_enemybase(2500);
2031 navigation_goalrating_end();
2035 void havocbot_role_ctf_defense()
2039 if(self.deadflag != DEAD_NO)
2041 havocbot_ctf_reset_role(self);
2045 if (self.flagcarried)
2047 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
2051 // If own flag was captured
2052 mf = havocbot_ctf_find_flag(self);
2053 if(mf.ctf_status!=FLAG_BASE)
2055 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);
2059 if (!self.havocbot_role_timeout)
2060 self.havocbot_role_timeout = time + 30;
2062 if (time > self.havocbot_role_timeout)
2064 havocbot_ctf_reset_role(self);
2067 if (self.bot_strategytime < time)
2072 org = mf.dropped_origin;
2073 mp_radius = havocbot_ctf_middlepoint_radius;
2075 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
2076 navigation_goalrating_start();
2078 // if enemies are closer to our base, go there
2079 entity head, closestplayer = world;
2080 float distance, bestdistance = 10000;
2081 FOR_EACH_PLAYER(head)
2083 if(head.deadflag!=DEAD_NO)
2086 distance = vlen(org - head.origin);
2087 if(distance<bestdistance)
2089 closestplayer = head;
2090 bestdistance = distance;
2095 if(DIFF_TEAM(closestplayer, self))
2096 if(vlen(org - self.origin)>1000)
2097 if(checkpvs(self.origin,closestplayer)||random()<0.5)
2098 havocbot_goalrating_ctf_ourbase(30000);
2100 havocbot_goalrating_ctf_ourstolenflag(20000);
2101 havocbot_goalrating_ctf_droppedflags(20000, org, mp_radius);
2102 havocbot_goalrating_enemyplayers(15000, org, mp_radius);
2103 havocbot_goalrating_items(10000, org, mp_radius);
2104 havocbot_goalrating_items(5000, self.origin, 10000);
2105 navigation_goalrating_end();
2109 void havocbot_role_ctf_setrole(entity bot, int role)
2111 LOG_TRACE(strcat(bot.netname," switched to "));
2114 case HAVOCBOT_CTF_ROLE_CARRIER:
2115 LOG_TRACE("carrier");
2116 bot.havocbot_role = havocbot_role_ctf_carrier;
2117 bot.havocbot_role_timeout = 0;
2118 bot.havocbot_cantfindflag = time + 10;
2119 bot.bot_strategytime = 0;
2121 case HAVOCBOT_CTF_ROLE_DEFENSE:
2122 LOG_TRACE("defense");
2123 bot.havocbot_role = havocbot_role_ctf_defense;
2124 bot.havocbot_role_timeout = 0;
2126 case HAVOCBOT_CTF_ROLE_MIDDLE:
2127 LOG_TRACE("middle");
2128 bot.havocbot_role = havocbot_role_ctf_middle;
2129 bot.havocbot_role_timeout = 0;
2131 case HAVOCBOT_CTF_ROLE_OFFENSE:
2132 LOG_TRACE("offense");
2133 bot.havocbot_role = havocbot_role_ctf_offense;
2134 bot.havocbot_role_timeout = 0;
2136 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2137 LOG_TRACE("retriever");
2138 bot.havocbot_previous_role = bot.havocbot_role;
2139 bot.havocbot_role = havocbot_role_ctf_retriever;
2140 bot.havocbot_role_timeout = time + 10;
2141 bot.bot_strategytime = 0;
2143 case HAVOCBOT_CTF_ROLE_ESCORT:
2144 LOG_TRACE("escort");
2145 bot.havocbot_previous_role = bot.havocbot_role;
2146 bot.havocbot_role = havocbot_role_ctf_escort;
2147 bot.havocbot_role_timeout = time + 30;
2148 bot.bot_strategytime = 0;
2159 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2162 int t = 0, t2 = 0, t3 = 0;
2164 // initially clear items so they can be set as necessary later.
2165 self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2166 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2167 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2168 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2169 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2170 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
2172 // scan through all the flags and notify the client about them
2173 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2175 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2176 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2177 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2178 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2179 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; }
2181 switch(flag.ctf_status)
2186 if((flag.owner == self) || (flag.pass_sender == self))
2187 self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
2189 self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2194 self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2200 // item for stopping players from capturing the flag too often
2201 if(self.ctf_captureshielded)
2202 self.ctf_flagstatus |= CTF_SHIELDED;
2204 // update the health of the flag carrier waypointsprite
2205 if(self.wps_flagcarrier)
2206 WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2211 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2213 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2215 if(frag_target == frag_attacker) // damage done to yourself
2217 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2218 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2220 else // damage done to everyone else
2222 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2223 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2226 else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2228 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)))
2229 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2231 frag_target.wps_helpme_time = time;
2232 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2234 // todo: add notification for when flag carrier needs help?
2239 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2241 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2243 PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
2244 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2247 if(frag_target.flagcarried)
2249 entity tmp_entity = frag_target.flagcarried;
2250 ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
2251 tmp_entity.ctf_dropper = world;
2257 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2260 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2263 void ctf_RemovePlayer(entity player)
2265 if(player.flagcarried)
2266 { ctf_Handle_Throw(player, world, DROP_NORMAL); }
2268 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2270 if(flag.pass_sender == player) { flag.pass_sender = world; }
2271 if(flag.pass_target == player) { flag.pass_target = world; }
2272 if(flag.ctf_dropper == player) { flag.ctf_dropper = world; }
2276 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2278 ctf_RemovePlayer(self);
2282 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2284 ctf_RemovePlayer(self);
2288 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2290 if(self.flagcarried)
2291 if(!autocvar_g_ctf_portalteleport)
2292 { ctf_Handle_Throw(self, world, DROP_NORMAL); }
2297 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2299 if(MUTATOR_RETURNVALUE || gameover) { return false; }
2301 entity player = self;
2303 if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2305 // pass the flag to a team mate
2306 if(autocvar_g_ctf_pass)
2308 entity head, closest_target = world;
2309 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2311 while(head) // find the closest acceptable target to pass to
2313 if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
2314 if(head != player && SAME_TEAM(head, player))
2315 if(!head.speedrunning && !head.vehicle)
2317 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2318 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2319 vector passer_center = CENTER_OR_VIEWOFS(player);
2321 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2323 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2325 if(IS_BOT_CLIENT(head))
2327 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2328 ctf_Handle_Throw(head, player, DROP_PASS);
2332 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2333 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2335 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2338 else if(player.flagcarried)
2342 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2343 if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
2344 { closest_target = head; }
2346 else { closest_target = head; }
2353 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2356 // throw the flag in front of you
2357 if(autocvar_g_ctf_throw && player.flagcarried)
2359 if(player.throw_count == -1)
2361 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2363 player.throw_prevtime = time;
2364 player.throw_count = 1;
2365 ctf_Handle_Throw(player, world, DROP_THROW);
2370 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2376 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2377 else { player.throw_count += 1; }
2378 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2380 player.throw_prevtime = time;
2381 ctf_Handle_Throw(player, world, DROP_THROW);
2390 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2392 if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2394 self.wps_helpme_time = time;
2395 WaypointSprite_HelpMePing(self.wps_flagcarrier);
2397 else // create a normal help me waypointsprite
2399 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, false, RADARICON_HELPME);
2400 WaypointSprite_Ping(self.wps_helpme);
2406 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2408 if(vh_player.flagcarried)
2410 vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
2412 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2414 ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
2418 setattachment(vh_player.flagcarried, vh_vehicle, "");
2419 setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
2420 vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2421 //vh_player.flagcarried.angles = '0 0 0';
2429 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2431 if(vh_player.flagcarried)
2433 setattachment(vh_player.flagcarried, vh_player, "");
2434 setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
2435 vh_player.flagcarried.scale = FLAG_SCALE;
2436 vh_player.flagcarried.angles = '0 0 0';
2437 vh_player.flagcarried.nodrawtoclient = world;
2444 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2446 if(self.flagcarried)
2448 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));
2449 ctf_RespawnFlag(self.flagcarried);
2456 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2458 entity flag; // temporary entity for the search method
2460 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2462 switch(flag.ctf_status)
2467 // lock the flag, game is over
2468 flag.movetype = MOVETYPE_NONE;
2469 flag.takedamage = DAMAGE_NO;
2470 flag.solid = SOLID_NOT;
2471 flag.nextthink = false; // stop thinking
2473 //dprint("stopping the ", flag.netname, " from moving.\n");
2481 // do nothing for these flags
2490 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2492 havocbot_ctf_reset_role(self);
2496 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2498 //ret_float = ctf_teams;
2499 ret_string = "ctf_team";
2503 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2505 self.ctf_flagstatus = other.ctf_flagstatus;
2509 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2511 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2513 if (MapInfo_Get_ByID(i))
2515 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2521 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2522 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2529 bool superspec_Spectate(entity _player); // TODO
2530 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2531 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2533 if(IS_PLAYER(self) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2535 if(cmd_name == "followfc")
2548 case "red": _team = NUM_TEAM_1; break;
2549 case "blue": _team = NUM_TEAM_2; break;
2550 case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
2551 case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
2555 FOR_EACH_PLAYER(_player)
2557 if(_player.flagcarried && (_player.team == _team || _team == 0))
2560 if(_team == 0 && IS_SPEC(self) && self.enemy == _player)
2561 continue; // already spectating a fc, try to find the other fc
2562 return superspec_Spectate(_player);
2567 superspec_msg("", "", self, "No active flag carrier\n", 1);
2574 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2576 if(frag_target.flagcarried)
2577 ctf_Handle_Throw(frag_target, world, DROP_THROW);
2587 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2588 CTF flag for team one (Red).
2590 "angle" Angle the flag will point (minus 90 degrees)...
2591 "model" model to use, note this needs red and blue as skins 0 and 1...
2592 "noise" sound played when flag is picked up...
2593 "noise1" sound played when flag is returned by a teammate...
2594 "noise2" sound played when flag is captured...
2595 "noise3" sound played when flag is lost in the field and respawns itself...
2596 "noise4" sound played when flag is dropped by a player...
2597 "noise5" sound played when flag touches the ground... */
2598 spawnfunc(item_flag_team1)
2600 if(!g_ctf) { remove(self); return; }
2602 ctf_FlagSetup(NUM_TEAM_1, self);
2605 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2606 CTF flag for team two (Blue).
2608 "angle" Angle the flag will point (minus 90 degrees)...
2609 "model" model to use, note this needs red and blue as skins 0 and 1...
2610 "noise" sound played when flag is picked up...
2611 "noise1" sound played when flag is returned by a teammate...
2612 "noise2" sound played when flag is captured...
2613 "noise3" sound played when flag is lost in the field and respawns itself...
2614 "noise4" sound played when flag is dropped by a player...
2615 "noise5" sound played when flag touches the ground... */
2616 spawnfunc(item_flag_team2)
2618 if(!g_ctf) { remove(self); return; }
2620 ctf_FlagSetup(NUM_TEAM_2, self);
2623 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2624 CTF flag for team three (Yellow).
2626 "angle" Angle the flag will point (minus 90 degrees)...
2627 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2628 "noise" sound played when flag is picked up...
2629 "noise1" sound played when flag is returned by a teammate...
2630 "noise2" sound played when flag is captured...
2631 "noise3" sound played when flag is lost in the field and respawns itself...
2632 "noise4" sound played when flag is dropped by a player...
2633 "noise5" sound played when flag touches the ground... */
2634 spawnfunc(item_flag_team3)
2636 if(!g_ctf) { remove(self); return; }
2638 ctf_FlagSetup(NUM_TEAM_3, self);
2641 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2642 CTF flag for team four (Pink).
2644 "angle" Angle the flag will point (minus 90 degrees)...
2645 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2646 "noise" sound played when flag is picked up...
2647 "noise1" sound played when flag is returned by a teammate...
2648 "noise2" sound played when flag is captured...
2649 "noise3" sound played when flag is lost in the field and respawns itself...
2650 "noise4" sound played when flag is dropped by a player...
2651 "noise5" sound played when flag touches the ground... */
2652 spawnfunc(item_flag_team4)
2654 if(!g_ctf) { remove(self); return; }
2656 ctf_FlagSetup(NUM_TEAM_4, self);
2659 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2662 "angle" Angle the flag will point (minus 90 degrees)...
2663 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2664 "noise" sound played when flag is picked up...
2665 "noise1" sound played when flag is returned by a teammate...
2666 "noise2" sound played when flag is captured...
2667 "noise3" sound played when flag is lost in the field and respawns itself...
2668 "noise4" sound played when flag is dropped by a player...
2669 "noise5" sound played when flag touches the ground... */
2670 spawnfunc(item_flag_neutral)
2672 if(!g_ctf) { remove(self); return; }
2673 if(!cvar("g_ctf_oneflag")) { remove(self); return; }
2675 ctf_FlagSetup(0, self);
2678 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2679 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2680 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.
2682 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2683 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2686 if(!g_ctf) { remove(self); return; }
2688 self.classname = "ctf_team";
2689 self.team = self.cnt + 1;
2692 // compatibility for quake maps
2693 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2694 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2695 spawnfunc(info_player_team1);
2696 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2697 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2698 spawnfunc(info_player_team2);
2699 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2700 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2702 void team_CTF_neutralflag() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2703 void team_neutralobelisk() { SELFPARAM(); spawnfunc_item_flag_neutral(self); }
2711 void ctf_ScoreRules(int teams)
2713 CheckAllowedTeams(world);
2714 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2715 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2716 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2717 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2718 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2719 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2720 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2721 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2722 ScoreRules_basics_end();
2725 // code from here on is just to support maps that don't have flag and team entities
2726 void ctf_SpawnTeam (string teamname, int teamcolor)
2728 entity this = new(ctf_team);
2729 this.netname = teamname;
2730 this.cnt = teamcolor;
2731 this.spawnfunc_checked = true;
2732 WITH(entity, self, this, spawnfunc_ctf_team(this));
2735 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
2740 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2742 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2743 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2744 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2747 ctf_teams = bound(2, ctf_teams, 4);
2749 // if no teams are found, spawn defaults
2750 if(find(world, classname, "ctf_team") == world)
2752 LOG_INFO("No ""ctf_team"" entities found on this map, creating them anyway.\n");
2753 ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
2754 ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
2756 ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
2758 ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
2761 ctf_ScoreRules(ctf_teams);
2764 void ctf_Initialize()
2766 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2768 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2769 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2770 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2772 addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
2774 InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);