1 #include "gamemode_ctf.qh"
7 REGISTER_MUTATOR(ctf, false)
11 if (time > 1) // game loads at time 1
12 error("This is a game type and it cannot be added at runtime.");
16 SetLimits(autocvar_capturelimit_override, autocvar_captureleadlimit_override, autocvar_timelimit_override, -1);
17 have_team_spawns = -1; // request team spawns
20 MUTATOR_ONROLLBACK_OR_REMOVE
22 // we actually cannot roll back ctf_Initialize here
23 // BUT: we don't need to! If this gets called, adding always
29 LOG_INFO("This is a game type and it cannot be removed at runtime.");
38 #include <common/vehicles/all.qh>
39 #include <server/teamplay.qh>
42 #include <lib/warpzone/common.qh>
44 bool autocvar_g_ctf_allow_vehicle_carry;
45 bool autocvar_g_ctf_allow_vehicle_touch;
46 bool autocvar_g_ctf_allow_monster_touch;
47 bool autocvar_g_ctf_throw;
48 float autocvar_g_ctf_throw_angle_max;
49 float autocvar_g_ctf_throw_angle_min;
50 int autocvar_g_ctf_throw_punish_count;
51 float autocvar_g_ctf_throw_punish_delay;
52 float autocvar_g_ctf_throw_punish_time;
53 float autocvar_g_ctf_throw_strengthmultiplier;
54 float autocvar_g_ctf_throw_velocity_forward;
55 float autocvar_g_ctf_throw_velocity_up;
56 float autocvar_g_ctf_drop_velocity_up;
57 float autocvar_g_ctf_drop_velocity_side;
58 bool autocvar_g_ctf_oneflag_reverse;
59 bool autocvar_g_ctf_portalteleport;
60 bool autocvar_g_ctf_pass;
61 float autocvar_g_ctf_pass_arc;
62 float autocvar_g_ctf_pass_arc_max;
63 float autocvar_g_ctf_pass_directional_max;
64 float autocvar_g_ctf_pass_directional_min;
65 float autocvar_g_ctf_pass_radius;
66 float autocvar_g_ctf_pass_wait;
67 bool autocvar_g_ctf_pass_request;
68 float autocvar_g_ctf_pass_turnrate;
69 float autocvar_g_ctf_pass_timelimit;
70 float autocvar_g_ctf_pass_velocity;
71 bool autocvar_g_ctf_dynamiclights;
72 float autocvar_g_ctf_flag_collect_delay;
73 float autocvar_g_ctf_flag_damageforcescale;
74 bool autocvar_g_ctf_flag_dropped_waypoint;
75 bool autocvar_g_ctf_flag_dropped_floatinwater;
76 bool autocvar_g_ctf_flag_glowtrails;
77 int autocvar_g_ctf_flag_health;
78 bool autocvar_g_ctf_flag_return;
79 bool autocvar_g_ctf_flag_return_carrying;
80 float autocvar_g_ctf_flag_return_carried_radius;
81 float autocvar_g_ctf_flag_return_time;
82 bool autocvar_g_ctf_flag_return_when_unreachable;
83 float autocvar_g_ctf_flag_return_damage;
84 float autocvar_g_ctf_flag_return_damage_delay;
85 float autocvar_g_ctf_flag_return_dropped;
86 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
87 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
88 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
89 float autocvar_g_ctf_flagcarrier_selfforcefactor;
90 float autocvar_g_ctf_flagcarrier_damagefactor;
91 float autocvar_g_ctf_flagcarrier_forcefactor;
92 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
93 bool autocvar_g_ctf_fullbrightflags;
94 bool autocvar_g_ctf_ignore_frags;
95 int autocvar_g_ctf_score_capture;
96 int autocvar_g_ctf_score_capture_assist;
97 int autocvar_g_ctf_score_kill;
98 int autocvar_g_ctf_score_penalty_drop;
99 int autocvar_g_ctf_score_penalty_returned;
100 int autocvar_g_ctf_score_pickup_base;
101 int autocvar_g_ctf_score_pickup_dropped_early;
102 int autocvar_g_ctf_score_pickup_dropped_late;
103 int autocvar_g_ctf_score_return;
104 float autocvar_g_ctf_shield_force;
105 float autocvar_g_ctf_shield_max_ratio;
106 int autocvar_g_ctf_shield_min_negscore;
107 bool autocvar_g_ctf_stalemate;
108 int autocvar_g_ctf_stalemate_endcondition;
109 float autocvar_g_ctf_stalemate_time;
110 bool autocvar_g_ctf_reverse;
111 float autocvar_g_ctf_dropped_capture_delay;
112 float autocvar_g_ctf_dropped_capture_radius;
114 void ctf_FakeTimeLimit(entity e, float t)
117 WriteByte(MSG_ONE, 3); // svc_updatestat
118 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
120 WriteCoord(MSG_ONE, autocvar_timelimit);
122 WriteCoord(MSG_ONE, (t + 1) / 60);
125 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
127 if(autocvar_sv_eventlog)
128 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
129 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
132 void ctf_CaptureRecord(entity flag, entity player)
134 float cap_record = ctf_captimerecord;
135 float cap_time = (time - flag.ctf_pickuptime);
136 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
139 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
140 else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_CAPTURE_TIME), player.netname, (cap_time * 100)); }
141 else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
142 else { Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
144 // write that shit in the database
145 if(!ctf_oneflag) // but not in 1-flag mode
146 if((!ctf_captimerecord) || (cap_time < cap_record))
148 ctf_captimerecord = cap_time;
149 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
150 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
151 write_recordmarker(player, (time - cap_time), cap_time);
155 void ctf_FlagcarrierWaypoints(entity player)
157 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
158 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
159 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
160 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
163 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
165 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
166 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
167 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
168 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
171 if(current_height) // make sure we can actually do this arcing path
173 targpos = (to + ('0 0 1' * current_height));
174 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
175 if(trace_fraction < 1)
177 //print("normal arc line failed, trying to find new pos...");
178 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
179 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
180 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
181 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
182 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
185 else { targpos = to; }
187 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
189 vector desired_direction = normalize(targpos - from);
190 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
191 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
194 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
196 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
198 // directional tracing only
200 makevectors(passer_angle);
202 // find the closest point on the enemy to the center of the attack
203 float h; // hypotenuse, which is the distance between attacker to head
204 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
206 h = vlen(head_center - passer_center);
207 a = h * (normalize(head_center - passer_center) * v_forward);
209 vector nearest_on_line = (passer_center + a * v_forward);
210 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
212 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
213 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
215 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
220 else { return true; }
224 // =======================
225 // CaptureShield Functions
226 // =======================
228 bool ctf_CaptureShield_CheckStatus(entity p)
230 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
231 int players_worseeq, players_total;
233 if(ctf_captureshield_max_ratio <= 0)
236 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
237 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
238 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
239 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
241 sr = ((s - s2) + (s3 + s4));
243 if(sr >= -ctf_captureshield_min_negscore)
246 players_total = players_worseeq = 0;
247 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
250 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
251 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
252 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
253 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
255 ser = ((se - se2) + (se3 + se4));
262 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
263 // use this rule here
265 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
271 void ctf_CaptureShield_Update(entity player, bool wanted_status)
273 bool updated_status = ctf_CaptureShield_CheckStatus(player);
274 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
276 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
277 player.ctf_captureshielded = updated_status;
281 bool ctf_CaptureShield_Customize(entity this, entity client)
283 if(!client.ctf_captureshielded) { return false; }
284 if(CTF_SAMETEAM(this, client)) { return false; }
289 void ctf_CaptureShield_Touch(entity this, entity toucher)
291 if(!toucher.ctf_captureshielded) { return; }
292 if(CTF_SAMETEAM(this, toucher)) { return; }
294 vector mymid = (this.absmin + this.absmax) * 0.5;
295 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
297 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
298 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
301 void ctf_CaptureShield_Spawn(entity flag)
303 entity shield = new(ctf_captureshield);
306 shield.team = flag.team;
307 settouch(shield, ctf_CaptureShield_Touch);
308 setcefc(shield, ctf_CaptureShield_Customize);
309 shield.effects = EF_ADDITIVE;
310 shield.movetype = MOVETYPE_NOCLIP;
311 shield.solid = SOLID_TRIGGER;
312 shield.avelocity = '7 0 11';
315 setorigin(shield, flag.origin);
316 setmodel(shield, MDL_CTF_SHIELD);
317 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
321 // ====================
322 // Drop/Pass/Throw Code
323 // ====================
325 void ctf_Handle_Drop(entity flag, entity player, int droptype)
328 player = (player ? player : flag.pass_sender);
331 flag.movetype = MOVETYPE_TOSS;
332 flag.takedamage = DAMAGE_YES;
333 flag.angles = '0 0 0';
334 flag.health = flag.max_flag_health;
335 flag.ctf_droptime = time;
336 flag.ctf_dropper = player;
337 flag.ctf_status = FLAG_DROPPED;
339 // messages and sounds
340 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_LOST) : INFO_CTF_LOST_NEUTRAL), player.netname);
341 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
342 ctf_EventLog("dropped", player.team, player);
345 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
346 PlayerScore_Add(player, SP_CTF_DROPS, 1);
349 if(autocvar_g_ctf_flag_dropped_waypoint) {
350 entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
351 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
354 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
356 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
357 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
360 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
362 if(droptype == DROP_PASS)
364 flag.pass_distance = 0;
365 flag.pass_sender = NULL;
366 flag.pass_target = NULL;
370 void ctf_Handle_Retrieve(entity flag, entity player)
372 entity sender = flag.pass_sender;
374 // transfer flag to player
376 flag.owner.flagcarried = flag;
381 setattachment(flag, player.vehicle, "");
382 setorigin(flag, VEHICLE_FLAG_OFFSET);
383 flag.scale = VEHICLE_FLAG_SCALE;
387 setattachment(flag, player, "");
388 setorigin(flag, FLAG_CARRY_OFFSET);
390 flag.movetype = MOVETYPE_NONE;
391 flag.takedamage = DAMAGE_NO;
392 flag.solid = SOLID_NOT;
393 flag.angles = '0 0 0';
394 flag.ctf_status = FLAG_CARRY;
396 // messages and sounds
397 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
398 ctf_EventLog("receive", flag.team, player);
400 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(
402 Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT(flag, CENTER_CTF_PASS_SENT) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
403 else if(it == player)
404 Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT(flag, CENTER_CTF_PASS_RECEIVED) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
405 else if(SAME_TEAM(it, sender))
406 Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT(flag, CENTER_CTF_PASS_OTHER) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname);
409 // create new waypoint
410 ctf_FlagcarrierWaypoints(player);
412 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
413 player.throw_antispam = sender.throw_antispam;
415 flag.pass_distance = 0;
416 flag.pass_sender = NULL;
417 flag.pass_target = NULL;
420 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
422 entity flag = player.flagcarried;
423 vector targ_origin, flag_velocity;
425 if(!flag) { return; }
426 if((droptype == DROP_PASS) && !receiver) { return; }
428 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
431 setattachment(flag, NULL, "");
432 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
433 flag.owner.flagcarried = NULL;
435 flag.solid = SOLID_TRIGGER;
436 flag.ctf_dropper = player;
437 flag.ctf_droptime = time;
439 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
446 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
447 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
448 WarpZone_RefSys_Copy(flag, receiver);
449 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
450 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
452 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
453 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
456 flag.movetype = MOVETYPE_FLY;
457 flag.takedamage = DAMAGE_NO;
458 flag.pass_sender = player;
459 flag.pass_target = receiver;
460 flag.ctf_status = FLAG_PASSING;
463 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
464 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
465 ctf_EventLog("pass", flag.team, player);
471 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'));
473 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)));
474 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
475 ctf_Handle_Drop(flag, player, droptype);
481 flag.velocity = '0 0 0'; // do nothing
488 flag.velocity = W_CalculateProjectileVelocity(player, 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);
489 ctf_Handle_Drop(flag, player, droptype);
494 // kill old waypointsprite
495 WaypointSprite_Ping(player.wps_flagcarrier);
496 WaypointSprite_Kill(player.wps_flagcarrier);
498 if(player.wps_enemyflagcarrier)
499 WaypointSprite_Kill(player.wps_enemyflagcarrier);
502 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
505 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
507 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
514 void nades_GiveBonus(entity player, float score);
516 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
518 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
519 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
520 entity player_team_flag = NULL, tmp_entity;
521 float old_time, new_time;
523 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
524 if(CTF_DIFFTEAM(player, flag)) { return; }
527 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
528 if(SAME_TEAM(tmp_entity, player))
530 player_team_flag = tmp_entity;
534 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
536 player.throw_prevtime = time;
537 player.throw_count = 0;
539 // messages and sounds
540 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT(enemy_flag, CENTER_CTF_CAPTURE) : CENTER_CTF_CAPTURE_NEUTRAL));
541 ctf_CaptureRecord(enemy_flag, player);
542 _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);
546 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
547 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
552 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
553 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
555 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
556 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
557 if(!old_time || new_time < old_time)
558 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
561 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
562 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
565 if(capturetype == CAPTURE_NORMAL)
567 WaypointSprite_Kill(player.wps_flagcarrier);
568 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
570 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
571 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
575 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
576 ctf_RespawnFlag(enemy_flag);
579 void ctf_Handle_Return(entity flag, entity player)
581 // messages and sounds
582 if(IS_MONSTER(player))
584 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_ENT(flag, INFO_CTF_RETURN_MONSTER), player.monster_name);
588 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT(flag, CENTER_CTF_RETURN));
589 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_ENT(flag, INFO_CTF_RETURN), player.netname);
591 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
592 ctf_EventLog("return", flag.team, player);
595 if(IS_PLAYER(player))
597 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
598 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
600 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
603 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
607 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
608 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
609 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
613 if(player.flagcarried == flag)
614 WaypointSprite_Kill(player.wps_flagcarrier);
617 ctf_RespawnFlag(flag);
620 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
623 float pickup_dropped_score; // used to calculate dropped pickup score
625 // attach the flag to the player
627 player.flagcarried = flag;
630 setattachment(flag, player.vehicle, "");
631 setorigin(flag, VEHICLE_FLAG_OFFSET);
632 flag.scale = VEHICLE_FLAG_SCALE;
636 setattachment(flag, player, "");
637 setorigin(flag, FLAG_CARRY_OFFSET);
641 flag.movetype = MOVETYPE_NONE;
642 flag.takedamage = DAMAGE_NO;
643 flag.solid = SOLID_NOT;
644 flag.angles = '0 0 0';
645 flag.ctf_status = FLAG_CARRY;
649 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
650 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
654 // messages and sounds
655 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_PICKUP) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
656 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
657 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
658 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT(flag, CENTER_CTF_PICKUP)); }
659 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)); }
661 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT(flag, CHOICE_CTF_PICKUP_TEAM) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname);
664 FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), LAMBDA(Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname)));
667 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
668 if(CTF_SAMETEAM(flag, it))
669 if(SAME_TEAM(player, it))
670 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
672 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
675 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
678 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
679 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
684 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
685 ctf_EventLog("steal", flag.team, player);
691 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);
692 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);
693 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
694 PlayerTeamScore_AddScore(player, pickup_dropped_score);
695 ctf_EventLog("pickup", flag.team, player);
703 if(pickuptype == PICKUP_BASE)
705 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
706 if((player.speedrunning) && (ctf_captimerecord))
707 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
711 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
714 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
715 ctf_FlagcarrierWaypoints(player);
716 WaypointSprite_Ping(player.wps_flagcarrier);
720 // ===================
721 // Main Flag Functions
722 // ===================
724 void ctf_CheckFlagReturn(entity flag, int returntype)
726 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
728 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
730 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
734 case RETURN_DROPPED: Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_DROPPED) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break;
735 case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_DAMAGED) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break;
736 case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_SPEEDRUN) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break;
737 case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_NEEDKILL) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break;
741 { Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_TIMEOUT) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
743 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
744 ctf_EventLog("returned", flag.team, NULL);
745 ctf_RespawnFlag(flag);
750 bool ctf_Stalemate_Customize(entity this, entity client)
752 // make spectators see what the player would see
754 e = WaypointSprite_getviewentity(client);
755 wp_owner = this.owner;
758 if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
759 if(SAME_TEAM(wp_owner, e)) { return false; }
760 if(!IS_PLAYER(e)) { return false; }
765 void ctf_CheckStalemate()
768 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
771 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
773 // build list of stale flags
774 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
776 if(autocvar_g_ctf_stalemate)
777 if(tmp_entity.ctf_status != FLAG_BASE)
778 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
780 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
781 ctf_staleflaglist = tmp_entity;
783 switch(tmp_entity.team)
785 case NUM_TEAM_1: ++stale_red_flags; break;
786 case NUM_TEAM_2: ++stale_blue_flags; break;
787 case NUM_TEAM_3: ++stale_yellow_flags; break;
788 case NUM_TEAM_4: ++stale_pink_flags; break;
789 default: ++stale_neutral_flags; break;
795 stale_flags = (stale_neutral_flags >= 1);
797 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
799 if(ctf_oneflag && stale_flags == 1)
800 ctf_stalemate = true;
801 else if(stale_flags >= 2)
802 ctf_stalemate = true;
803 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
804 { ctf_stalemate = false; wpforenemy_announced = false; }
805 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
806 { ctf_stalemate = false; wpforenemy_announced = false; }
808 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
811 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
813 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
815 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, NULL, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG);
816 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
817 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
821 if (!wpforenemy_announced)
823 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER))));
825 wpforenemy_announced = true;
830 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
832 if(ITEM_DAMAGE_NEEDKILL(deathtype))
834 if(autocvar_g_ctf_flag_return_damage_delay)
836 this.ctf_flagdamaged = true;
841 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
845 if(autocvar_g_ctf_flag_return_damage)
847 // reduce health and check if it should be returned
848 this.health = this.health - damage;
849 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
854 void ctf_FlagThink(entity this)
859 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
862 if(this == ctf_worldflaglist) // only for the first flag
863 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
866 if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
867 LOG_TRACE("wtf the flag got squashed?\n");
868 tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
869 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
870 setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs); }
872 switch(this.ctf_status) // reset flag angles in case warpzones adjust it
876 this.angles = '0 0 0';
884 switch(this.ctf_status)
888 if(autocvar_g_ctf_dropped_capture_radius)
890 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
891 if(tmp_entity.ctf_status == FLAG_DROPPED)
892 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
893 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
894 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
901 if(autocvar_g_ctf_flag_dropped_floatinwater)
903 vector midpoint = ((this.absmin + this.absmax) * 0.5);
904 if(pointcontents(midpoint) == CONTENT_WATER)
906 this.velocity = this.velocity * 0.5;
908 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
909 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
911 { this.movetype = MOVETYPE_FLY; }
913 else if(this.movetype == MOVETYPE_FLY) { this.movetype = MOVETYPE_TOSS; }
915 if(autocvar_g_ctf_flag_return_dropped)
917 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
920 ctf_CheckFlagReturn(this, RETURN_DROPPED);
924 if(this.ctf_flagdamaged)
926 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
927 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
930 else if(autocvar_g_ctf_flag_return_time)
932 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
933 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
941 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
944 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
946 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
947 ImpulseCommands(this.owner);
949 if(autocvar_g_ctf_stalemate)
951 if(time >= wpforenemy_nextthink)
953 ctf_CheckStalemate();
954 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
957 if(CTF_SAMETEAM(this, this.owner) && this.team)
959 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
960 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
961 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
962 ctf_Handle_Return(this, this.owner);
969 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
970 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
971 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
973 if((this.pass_target == NULL)
974 || (IS_DEAD(this.pass_target))
975 || (this.pass_target.flagcarried)
976 || (vdist(this.origin - targ_origin, <, autocvar_g_ctf_pass_radius))
977 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
978 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
980 // give up, pass failed
981 ctf_Handle_Drop(this, NULL, DROP_PASS);
985 // still a viable target, go for it
986 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
991 default: // this should never happen
993 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
999 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1002 if(gameover) { return; }
1003 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1005 bool is_not_monster = (!IS_MONSTER(toucher));
1007 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1008 if(ITEM_TOUCH_NEEDKILL())
1010 if(!autocvar_g_ctf_flag_return_damage_delay)
1013 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1015 if(!flag.ctf_flagdamaged) { return; }
1018 int num_perteam = 0;
1019 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), LAMBDA(++num_perteam));
1021 // special touch behaviors
1022 if(STAT(FROZEN, toucher)) { return; }
1023 else if(IS_VEHICLE(toucher))
1025 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1026 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1028 return; // do nothing
1030 else if(IS_MONSTER(toucher))
1032 if(!autocvar_g_ctf_allow_monster_touch)
1033 return; // do nothing
1035 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1037 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1039 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1040 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1041 flag.wait = time + FLAG_TOUCHRATE;
1045 else if(IS_DEAD(toucher)) { return; }
1047 switch(flag.ctf_status)
1053 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1054 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1055 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1056 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1058 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1059 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1060 else if(CTF_DIFFTEAM(toucher, flag) && (toucher.flagcarried) && CTF_SAMETEAM(toucher.flagcarried, toucher) && (!toucher.ctf_captureshielded) && autocvar_g_ctf_flag_return_carrying && (time > toucher.next_take_time) && is_not_monster)
1062 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1063 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1065 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1066 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1072 if(CTF_SAMETEAM(toucher, flag) && (autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried)) && flag.team) // automatically return if there's only 1 player on the team
1073 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1074 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1075 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1081 LOG_TRACE("Someone touched a flag even though it was being carried?\n");
1087 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1089 if(DIFF_TEAM(toucher, flag.pass_sender))
1090 ctf_Handle_Return(flag, toucher);
1092 ctf_Handle_Retrieve(flag, toucher);
1099 .float last_respawn;
1100 void ctf_RespawnFlag(entity flag)
1102 // check for flag respawn being called twice in a row
1103 if(flag.last_respawn > time - 0.5)
1104 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1106 flag.last_respawn = time;
1108 // reset the player (if there is one)
1109 if((flag.owner) && (flag.owner.flagcarried == flag))
1111 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1112 WaypointSprite_Kill(flag.wps_flagcarrier);
1114 flag.owner.flagcarried = NULL;
1116 if(flag.speedrunning)
1117 ctf_FakeTimeLimit(flag.owner, -1);
1120 if((flag.owner) && (flag.owner.vehicle))
1121 flag.scale = FLAG_SCALE;
1123 if(flag.ctf_status == FLAG_DROPPED)
1124 { WaypointSprite_Kill(flag.wps_flagdropped); }
1127 setattachment(flag, NULL, "");
1128 setorigin(flag, flag.ctf_spawnorigin);
1130 flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
1131 flag.takedamage = DAMAGE_NO;
1132 flag.health = flag.max_flag_health;
1133 flag.solid = SOLID_TRIGGER;
1134 flag.velocity = '0 0 0';
1135 flag.angles = flag.mangle;
1136 flag.flags = FL_ITEM | FL_NOTARGET;
1138 flag.ctf_status = FLAG_BASE;
1140 flag.pass_distance = 0;
1141 flag.pass_sender = NULL;
1142 flag.pass_target = NULL;
1143 flag.ctf_dropper = NULL;
1144 flag.ctf_pickuptime = 0;
1145 flag.ctf_droptime = 0;
1146 flag.ctf_flagdamaged = 0;
1148 ctf_CheckStalemate();
1151 void ctf_Reset(entity this)
1153 if(this.owner && IS_PLAYER(this.owner))
1154 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1156 ctf_RespawnFlag(this);
1159 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1162 waypoint_spawnforitem_force(this, this.origin);
1163 this.nearestwaypointtimeout = 0; // activate waypointing again
1164 this.bot_basewaypoint = this.nearestwaypoint;
1170 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1171 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1172 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1173 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1174 default: basename = WP_FlagBaseNeutral; break;
1177 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1178 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1179 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1181 // captureshield setup
1182 ctf_CaptureShield_Spawn(this);
1187 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1190 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1191 ctf_worldflaglist = flag;
1193 setattachment(flag, NULL, "");
1195 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1196 flag.team = teamnumber;
1197 flag.classname = "item_flag_team";
1198 flag.target = "###item###"; // wut?
1199 flag.flags = FL_ITEM | FL_NOTARGET;
1200 flag.solid = SOLID_TRIGGER;
1201 flag.takedamage = DAMAGE_NO;
1202 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1203 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1204 flag.health = flag.max_flag_health;
1205 flag.event_damage = ctf_FlagDamage;
1206 flag.pushable = true;
1207 flag.teleportable = TELEPORT_NORMAL;
1208 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1209 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1210 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1211 flag.velocity = '0 0 0';
1212 flag.mangle = flag.angles;
1213 flag.reset = ctf_Reset;
1214 settouch(flag, ctf_FlagTouch);
1215 setthink(flag, ctf_FlagThink);
1216 flag.nextthink = time + FLAG_THINKRATE;
1217 flag.ctf_status = FLAG_BASE;
1219 string teamname = Static_Team_ColorName_Lower(teamnumber);
1221 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1222 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1223 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1224 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1225 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1226 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1230 if(flag.s == "") flag.s = b; \
1231 precache_sound(flag.s);
1233 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1234 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1235 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1236 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1237 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1238 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1239 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1243 precache_model(flag.model);
1246 _setmodel(flag, flag.model); // precision set below
1247 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1248 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1250 if(autocvar_g_ctf_flag_glowtrails)
1254 case NUM_TEAM_1: flag.glow_color = 251; break;
1255 case NUM_TEAM_2: flag.glow_color = 210; break;
1256 case NUM_TEAM_3: flag.glow_color = 110; break;
1257 case NUM_TEAM_4: flag.glow_color = 145; break;
1258 default: flag.glow_color = 254; break;
1260 flag.glow_size = 25;
1261 flag.glow_trail = 1;
1264 flag.effects |= EF_LOWPRECISION;
1265 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1266 if(autocvar_g_ctf_dynamiclights)
1270 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1271 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1272 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1273 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1274 default: flag.effects |= EF_DIMLIGHT; break;
1279 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1281 flag.dropped_origin = flag.origin;
1282 flag.noalign = true;
1283 flag.movetype = MOVETYPE_NONE;
1285 else // drop to floor, automatically find a platform and set that as spawn origin
1287 flag.noalign = false;
1289 flag.movetype = MOVETYPE_TOSS;
1292 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1300 // NOTE: LEGACY CODE, needs to be re-written!
1302 void havocbot_calculate_middlepoint()
1306 vector fo = '0 0 0';
1309 f = ctf_worldflaglist;
1314 f = f.ctf_worldflagnext;
1318 havocbot_ctf_middlepoint = s * (1.0 / n);
1319 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1323 entity havocbot_ctf_find_flag(entity bot)
1326 f = ctf_worldflaglist;
1329 if (CTF_SAMETEAM(bot, f))
1331 f = f.ctf_worldflagnext;
1336 entity havocbot_ctf_find_enemy_flag(entity bot)
1339 f = ctf_worldflaglist;
1344 if(CTF_DIFFTEAM(bot, f))
1351 else if(!bot.flagcarried)
1355 else if (CTF_DIFFTEAM(bot, f))
1357 f = f.ctf_worldflagnext;
1362 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1369 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1370 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1373 if(vdist(it.origin - org, <, tc_radius))
1380 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1383 head = ctf_worldflaglist;
1386 if (CTF_SAMETEAM(this, head))
1388 head = head.ctf_worldflagnext;
1391 navigation_routerating(this, head, ratingscale, 10000);
1394 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1397 head = ctf_worldflaglist;
1400 if (CTF_SAMETEAM(this, head))
1402 head = head.ctf_worldflagnext;
1407 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1410 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1413 head = ctf_worldflaglist;
1418 if(CTF_DIFFTEAM(this, head))
1422 if(this.flagcarried)
1425 else if(!this.flagcarried)
1429 else if(CTF_DIFFTEAM(this, head))
1431 head = head.ctf_worldflagnext;
1434 navigation_routerating(this, head, ratingscale, 10000);
1437 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1439 if (!bot_waypoints_for_items)
1441 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1447 head = havocbot_ctf_find_enemy_flag(this);
1452 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1455 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1459 mf = havocbot_ctf_find_flag(this);
1461 if(mf.ctf_status == FLAG_BASE)
1465 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1468 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1471 head = ctf_worldflaglist;
1474 // flag is out in the field
1475 if(head.ctf_status != FLAG_BASE)
1476 if(head.tag_entity==NULL) // dropped
1480 if(vdist(org - head.origin, <, df_radius))
1481 navigation_routerating(this, head, ratingscale, 10000);
1484 navigation_routerating(this, head, ratingscale, 10000);
1487 head = head.ctf_worldflagnext;
1491 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1495 head = findchainfloat(bot_pickup, true);
1498 // gather health and armor only
1500 if (head.health || head.armorvalue)
1501 if (vdist(head.origin - org, <, sradius))
1503 // get the value of the item
1504 t = head.bot_pickupevalfunc(this, head) * 0.0001;
1506 navigation_routerating(this, head, t * ratingscale, 500);
1512 void havocbot_ctf_reset_role(entity this)
1514 float cdefense, cmiddle, coffense;
1521 if(havocbot_ctf_middlepoint == '0 0 0')
1522 havocbot_calculate_middlepoint();
1525 if (this.flagcarried)
1527 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1531 mf = havocbot_ctf_find_flag(this);
1532 ef = havocbot_ctf_find_enemy_flag(this);
1534 // Retrieve stolen flag
1535 if(mf.ctf_status!=FLAG_BASE)
1537 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1541 // If enemy flag is taken go to the middle to intercept pursuers
1542 if(ef.ctf_status!=FLAG_BASE)
1544 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1548 // if there is only me on the team switch to offense
1550 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1554 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1558 // Evaluate best position to take
1559 // Count mates on middle position
1560 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1562 // Count mates on defense position
1563 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1565 // Count mates on offense position
1566 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1568 if(cdefense<=coffense)
1569 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1570 else if(coffense<=cmiddle)
1571 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1573 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1576 void havocbot_role_ctf_carrier(entity this)
1580 havocbot_ctf_reset_role(this);
1584 if (this.flagcarried == NULL)
1586 havocbot_ctf_reset_role(this);
1590 if (this.bot_strategytime < time)
1592 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1594 navigation_goalrating_start(this);
1596 havocbot_goalrating_ctf_enemybase(this, 50000);
1598 havocbot_goalrating_ctf_ourbase(this, 50000);
1601 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1603 navigation_goalrating_end(this);
1605 if (this.navigation_hasgoals)
1606 this.havocbot_cantfindflag = time + 10;
1607 else if (time > this.havocbot_cantfindflag)
1609 // Can't navigate to my own base, suicide!
1610 // TODO: drop it and wander around
1611 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1617 void havocbot_role_ctf_escort(entity this)
1623 havocbot_ctf_reset_role(this);
1627 if (this.flagcarried)
1629 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1633 // If enemy flag is back on the base switch to previous role
1634 ef = havocbot_ctf_find_enemy_flag(this);
1635 if(ef.ctf_status==FLAG_BASE)
1637 this.havocbot_role = this.havocbot_previous_role;
1638 this.havocbot_role_timeout = 0;
1642 // If the flag carrier reached the base switch to defense
1643 mf = havocbot_ctf_find_flag(this);
1644 if(mf.ctf_status!=FLAG_BASE)
1645 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1647 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1651 // Set the role timeout if necessary
1652 if (!this.havocbot_role_timeout)
1654 this.havocbot_role_timeout = time + random() * 30 + 60;
1657 // If nothing happened just switch to previous role
1658 if (time > this.havocbot_role_timeout)
1660 this.havocbot_role = this.havocbot_previous_role;
1661 this.havocbot_role_timeout = 0;
1665 // Chase the flag carrier
1666 if (this.bot_strategytime < time)
1668 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1669 navigation_goalrating_start(this);
1670 havocbot_goalrating_ctf_enemyflag(this, 30000);
1671 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1672 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1673 navigation_goalrating_end(this);
1677 void havocbot_role_ctf_offense(entity this)
1684 havocbot_ctf_reset_role(this);
1688 if (this.flagcarried)
1690 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1695 mf = havocbot_ctf_find_flag(this);
1696 ef = havocbot_ctf_find_enemy_flag(this);
1699 if(mf.ctf_status!=FLAG_BASE)
1702 pos = mf.tag_entity.origin;
1706 // Try to get it if closer than the enemy base
1707 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1709 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1714 // Escort flag carrier
1715 if(ef.ctf_status!=FLAG_BASE)
1718 pos = ef.tag_entity.origin;
1722 if(vdist(pos - mf.dropped_origin, >, 700))
1724 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1729 // About to fail, switch to middlefield
1732 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1736 // Set the role timeout if necessary
1737 if (!this.havocbot_role_timeout)
1738 this.havocbot_role_timeout = time + 120;
1740 if (time > this.havocbot_role_timeout)
1742 havocbot_ctf_reset_role(this);
1746 if (this.bot_strategytime < time)
1748 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1749 navigation_goalrating_start(this);
1750 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1751 havocbot_goalrating_ctf_enemybase(this, 20000);
1752 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1753 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1754 navigation_goalrating_end(this);
1758 // Retriever (temporary role):
1759 void havocbot_role_ctf_retriever(entity this)
1765 havocbot_ctf_reset_role(this);
1769 if (this.flagcarried)
1771 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1775 // If flag is back on the base switch to previous role
1776 mf = havocbot_ctf_find_flag(this);
1777 if(mf.ctf_status==FLAG_BASE)
1779 havocbot_ctf_reset_role(this);
1783 if (!this.havocbot_role_timeout)
1784 this.havocbot_role_timeout = time + 20;
1786 if (time > this.havocbot_role_timeout)
1788 havocbot_ctf_reset_role(this);
1792 if (this.bot_strategytime < time)
1797 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1798 navigation_goalrating_start(this);
1799 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1800 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1801 havocbot_goalrating_ctf_enemybase(this, 30000);
1802 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1803 navigation_goalrating_end(this);
1807 void havocbot_role_ctf_middle(entity this)
1813 havocbot_ctf_reset_role(this);
1817 if (this.flagcarried)
1819 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1823 mf = havocbot_ctf_find_flag(this);
1824 if(mf.ctf_status!=FLAG_BASE)
1826 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1830 if (!this.havocbot_role_timeout)
1831 this.havocbot_role_timeout = time + 10;
1833 if (time > this.havocbot_role_timeout)
1835 havocbot_ctf_reset_role(this);
1839 if (this.bot_strategytime < time)
1843 org = havocbot_ctf_middlepoint;
1844 org.z = this.origin.z;
1846 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1847 navigation_goalrating_start(this);
1848 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1849 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1850 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1851 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1852 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1853 havocbot_goalrating_ctf_enemybase(this, 2500);
1854 navigation_goalrating_end(this);
1858 void havocbot_role_ctf_defense(entity this)
1864 havocbot_ctf_reset_role(this);
1868 if (this.flagcarried)
1870 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1874 // If own flag was captured
1875 mf = havocbot_ctf_find_flag(this);
1876 if(mf.ctf_status!=FLAG_BASE)
1878 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1882 if (!this.havocbot_role_timeout)
1883 this.havocbot_role_timeout = time + 30;
1885 if (time > this.havocbot_role_timeout)
1887 havocbot_ctf_reset_role(this);
1890 if (this.bot_strategytime < time)
1895 org = mf.dropped_origin;
1896 mp_radius = havocbot_ctf_middlepoint_radius;
1898 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1899 navigation_goalrating_start(this);
1901 // if enemies are closer to our base, go there
1902 entity closestplayer = NULL;
1903 float distance, bestdistance = 10000;
1904 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1905 distance = vlen(org - it.origin);
1906 if(distance<bestdistance)
1909 bestdistance = distance;
1914 if(DIFF_TEAM(closestplayer, this))
1915 if(vdist(org - this.origin, >, 1000))
1916 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1917 havocbot_goalrating_ctf_ourbase(this, 30000);
1919 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1920 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1921 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1922 havocbot_goalrating_items(this, 10000, org, mp_radius);
1923 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1924 navigation_goalrating_end(this);
1928 void havocbot_role_ctf_setrole(entity bot, int role)
1930 LOG_TRACE(strcat(bot.netname," switched to "));
1933 case HAVOCBOT_CTF_ROLE_CARRIER:
1934 LOG_TRACE("carrier");
1935 bot.havocbot_role = havocbot_role_ctf_carrier;
1936 bot.havocbot_role_timeout = 0;
1937 bot.havocbot_cantfindflag = time + 10;
1938 bot.bot_strategytime = 0;
1940 case HAVOCBOT_CTF_ROLE_DEFENSE:
1941 LOG_TRACE("defense");
1942 bot.havocbot_role = havocbot_role_ctf_defense;
1943 bot.havocbot_role_timeout = 0;
1945 case HAVOCBOT_CTF_ROLE_MIDDLE:
1946 LOG_TRACE("middle");
1947 bot.havocbot_role = havocbot_role_ctf_middle;
1948 bot.havocbot_role_timeout = 0;
1950 case HAVOCBOT_CTF_ROLE_OFFENSE:
1951 LOG_TRACE("offense");
1952 bot.havocbot_role = havocbot_role_ctf_offense;
1953 bot.havocbot_role_timeout = 0;
1955 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1956 LOG_TRACE("retriever");
1957 bot.havocbot_previous_role = bot.havocbot_role;
1958 bot.havocbot_role = havocbot_role_ctf_retriever;
1959 bot.havocbot_role_timeout = time + 10;
1960 bot.bot_strategytime = 0;
1962 case HAVOCBOT_CTF_ROLE_ESCORT:
1963 LOG_TRACE("escort");
1964 bot.havocbot_previous_role = bot.havocbot_role;
1965 bot.havocbot_role = havocbot_role_ctf_escort;
1966 bot.havocbot_role_timeout = time + 30;
1967 bot.bot_strategytime = 0;
1978 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
1980 entity player = M_ARGV(0, entity);
1982 int t = 0, t2 = 0, t3 = 0;
1984 // initially clear items so they can be set as necessary later.
1985 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
1986 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
1987 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
1988 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
1989 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
1990 | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
1992 // scan through all the flags and notify the client about them
1993 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
1995 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
1996 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
1997 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
1998 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
1999 if(flag.team == 0) { t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; player.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
2001 switch(flag.ctf_status)
2006 if((flag.owner == player) || (flag.pass_sender == player))
2007 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2009 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2014 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2020 // item for stopping players from capturing the flag too often
2021 if(player.ctf_captureshielded)
2022 player.ctf_flagstatus |= CTF_SHIELDED;
2024 // update the health of the flag carrier waypointsprite
2025 if(player.wps_flagcarrier)
2026 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2029 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2031 entity frag_attacker = M_ARGV(1, entity);
2032 entity frag_target = M_ARGV(2, entity);
2033 float frag_damage = M_ARGV(4, float);
2034 vector frag_force = M_ARGV(6, vector);
2036 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2038 if(frag_target == frag_attacker) // damage done to yourself
2040 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2041 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2043 else // damage done to everyone else
2045 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2046 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2049 M_ARGV(4, float) = frag_damage;
2050 M_ARGV(6, vector) = frag_force;
2052 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2054 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)))
2055 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2057 frag_target.wps_helpme_time = time;
2058 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2060 // todo: add notification for when flag carrier needs help?
2064 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2066 entity frag_attacker = M_ARGV(1, entity);
2067 entity frag_target = M_ARGV(2, entity);
2069 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2071 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2072 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2075 if(frag_target.flagcarried)
2077 entity tmp_entity = frag_target.flagcarried;
2078 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2079 tmp_entity.ctf_dropper = NULL;
2083 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2085 M_ARGV(2, float) = 0; // frag score
2086 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2089 void ctf_RemovePlayer(entity player)
2091 if(player.flagcarried)
2092 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2094 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2096 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2097 if(flag.pass_target == player) { flag.pass_target = NULL; }
2098 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2102 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2104 entity player = M_ARGV(0, entity);
2106 ctf_RemovePlayer(player);
2109 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2111 entity player = M_ARGV(0, entity);
2113 ctf_RemovePlayer(player);
2116 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2118 entity player = M_ARGV(0, entity);
2120 if(player.flagcarried)
2121 if(!autocvar_g_ctf_portalteleport)
2122 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2125 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2127 if(MUTATOR_RETURNVALUE || gameover) { return; }
2129 entity player = M_ARGV(0, entity);
2131 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2133 // pass the flag to a team mate
2134 if(autocvar_g_ctf_pass)
2136 entity head, closest_target = NULL;
2137 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2139 while(head) // find the closest acceptable target to pass to
2141 if(IS_PLAYER(head) && !IS_DEAD(head))
2142 if(head != player && SAME_TEAM(head, player))
2143 if(!head.speedrunning && !head.vehicle)
2145 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2146 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2147 vector passer_center = CENTER_OR_VIEWOFS(player);
2149 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2151 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2153 if(IS_BOT_CLIENT(head))
2155 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2156 ctf_Handle_Throw(head, player, DROP_PASS);
2160 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2161 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2163 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2166 else if(player.flagcarried)
2170 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2171 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2172 { closest_target = head; }
2174 else { closest_target = head; }
2181 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2184 // throw the flag in front of you
2185 if(autocvar_g_ctf_throw && player.flagcarried)
2187 if(player.throw_count == -1)
2189 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2191 player.throw_prevtime = time;
2192 player.throw_count = 1;
2193 ctf_Handle_Throw(player, NULL, DROP_THROW);
2198 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2204 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2205 else { player.throw_count += 1; }
2206 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2208 player.throw_prevtime = time;
2209 ctf_Handle_Throw(player, NULL, DROP_THROW);
2216 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2218 entity player = M_ARGV(0, entity);
2220 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2222 player.wps_helpme_time = time;
2223 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2225 else // create a normal help me waypointsprite
2227 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2228 WaypointSprite_Ping(player.wps_helpme);
2234 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2236 entity player = M_ARGV(0, entity);
2237 entity veh = M_ARGV(1, entity);
2239 if(player.flagcarried)
2241 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2243 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2247 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2248 setattachment(player.flagcarried, veh, "");
2249 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2250 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2251 //player.flagcarried.angles = '0 0 0';
2257 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2259 entity player = M_ARGV(0, entity);
2261 if(player.flagcarried)
2263 setattachment(player.flagcarried, player, "");
2264 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2265 player.flagcarried.scale = FLAG_SCALE;
2266 player.flagcarried.angles = '0 0 0';
2267 player.flagcarried.nodrawtoclient = NULL;
2272 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2274 entity player = M_ARGV(0, entity);
2276 if(player.flagcarried)
2278 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((player.flagcarried.team) ? APP_TEAM_ENT(player.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
2279 ctf_RespawnFlag(player.flagcarried);
2284 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2286 entity flag; // temporary entity for the search method
2288 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2290 switch(flag.ctf_status)
2295 // lock the flag, game is over
2296 flag.movetype = MOVETYPE_NONE;
2297 flag.takedamage = DAMAGE_NO;
2298 flag.solid = SOLID_NOT;
2299 flag.nextthink = false; // stop thinking
2301 //dprint("stopping the ", flag.netname, " from moving.\n");
2309 // do nothing for these flags
2316 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2318 entity bot = M_ARGV(0, entity);
2320 havocbot_ctf_reset_role(bot);
2324 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2326 //M_ARGV(0, float) = ctf_teams;
2327 M_ARGV(1, string) = "ctf_team";
2331 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2333 entity spectatee = M_ARGV(0, entity);
2334 entity client = M_ARGV(1, entity);
2336 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2339 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2341 int record_page = M_ARGV(0, int);
2342 string ret_string = M_ARGV(1, string);
2344 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2346 if (MapInfo_Get_ByID(i))
2348 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2354 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2355 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2359 M_ARGV(1, string) = ret_string;
2362 bool superspec_Spectate(entity this, entity targ); // TODO
2363 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2364 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2366 entity player = M_ARGV(0, entity);
2367 string cmd_name = M_ARGV(1, string);
2368 int cmd_argc = M_ARGV(2, int);
2370 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2372 if(cmd_name == "followfc")
2384 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2385 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2386 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2387 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2391 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2392 if(it.flagcarried && (it.team == _team || _team == 0))
2395 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2396 continue; // already spectating this fc, try another
2397 return superspec_Spectate(player, it);
2402 superspec_msg("", "", player, "No active flag carrier\n", 1);
2407 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2409 entity frag_target = M_ARGV(0, entity);
2411 if(frag_target.flagcarried)
2412 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2420 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2421 CTF flag for team one (Red).
2423 "angle" Angle the flag will point (minus 90 degrees)...
2424 "model" model to use, note this needs red and blue as skins 0 and 1...
2425 "noise" sound played when flag is picked up...
2426 "noise1" sound played when flag is returned by a teammate...
2427 "noise2" sound played when flag is captured...
2428 "noise3" sound played when flag is lost in the field and respawns itself...
2429 "noise4" sound played when flag is dropped by a player...
2430 "noise5" sound played when flag touches the ground... */
2431 spawnfunc(item_flag_team1)
2433 if(!g_ctf) { remove(this); return; }
2435 ctf_FlagSetup(NUM_TEAM_1, this);
2438 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2439 CTF flag for team two (Blue).
2441 "angle" Angle the flag will point (minus 90 degrees)...
2442 "model" model to use, note this needs red and blue as skins 0 and 1...
2443 "noise" sound played when flag is picked up...
2444 "noise1" sound played when flag is returned by a teammate...
2445 "noise2" sound played when flag is captured...
2446 "noise3" sound played when flag is lost in the field and respawns itself...
2447 "noise4" sound played when flag is dropped by a player...
2448 "noise5" sound played when flag touches the ground... */
2449 spawnfunc(item_flag_team2)
2451 if(!g_ctf) { remove(this); return; }
2453 ctf_FlagSetup(NUM_TEAM_2, this);
2456 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2457 CTF flag for team three (Yellow).
2459 "angle" Angle the flag will point (minus 90 degrees)...
2460 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2461 "noise" sound played when flag is picked up...
2462 "noise1" sound played when flag is returned by a teammate...
2463 "noise2" sound played when flag is captured...
2464 "noise3" sound played when flag is lost in the field and respawns itself...
2465 "noise4" sound played when flag is dropped by a player...
2466 "noise5" sound played when flag touches the ground... */
2467 spawnfunc(item_flag_team3)
2469 if(!g_ctf) { remove(this); return; }
2471 ctf_FlagSetup(NUM_TEAM_3, this);
2474 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2475 CTF flag for team four (Pink).
2477 "angle" Angle the flag will point (minus 90 degrees)...
2478 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2479 "noise" sound played when flag is picked up...
2480 "noise1" sound played when flag is returned by a teammate...
2481 "noise2" sound played when flag is captured...
2482 "noise3" sound played when flag is lost in the field and respawns itself...
2483 "noise4" sound played when flag is dropped by a player...
2484 "noise5" sound played when flag touches the ground... */
2485 spawnfunc(item_flag_team4)
2487 if(!g_ctf) { remove(this); return; }
2489 ctf_FlagSetup(NUM_TEAM_4, this);
2492 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2495 "angle" Angle the flag will point (minus 90 degrees)...
2496 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2497 "noise" sound played when flag is picked up...
2498 "noise1" sound played when flag is returned by a teammate...
2499 "noise2" sound played when flag is captured...
2500 "noise3" sound played when flag is lost in the field and respawns itself...
2501 "noise4" sound played when flag is dropped by a player...
2502 "noise5" sound played when flag touches the ground... */
2503 spawnfunc(item_flag_neutral)
2505 if(!g_ctf) { remove(this); return; }
2506 if(!cvar("g_ctf_oneflag")) { remove(this); return; }
2508 ctf_FlagSetup(0, this);
2511 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2512 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2513 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.
2515 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2516 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2519 if(!g_ctf) { remove(this); return; }
2521 this.classname = "ctf_team";
2522 this.team = this.cnt + 1;
2525 // compatibility for quake maps
2526 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2527 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2528 spawnfunc(info_player_team1);
2529 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2530 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2531 spawnfunc(info_player_team2);
2532 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2533 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2535 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2536 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2544 void ctf_ScoreRules(int teams)
2546 CheckAllowedTeams(NULL);
2547 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2548 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2549 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2550 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2551 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2552 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2553 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2554 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2555 ScoreRules_basics_end();
2558 // code from here on is just to support maps that don't have flag and team entities
2559 void ctf_SpawnTeam (string teamname, int teamcolor)
2561 entity this = new_pure(ctf_team);
2562 this.netname = teamname;
2563 this.cnt = teamcolor - 1;
2564 this.spawnfunc_checked = true;
2565 this.team = teamcolor;
2568 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2573 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2575 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2576 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2578 switch(tmp_entity.team)
2580 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2581 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2582 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2583 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2585 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2588 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2590 ctf_teams = 0; // so set the default red and blue teams
2591 BITSET_ASSIGN(ctf_teams, BIT(0));
2592 BITSET_ASSIGN(ctf_teams, BIT(1));
2595 //ctf_teams = bound(2, ctf_teams, 4);
2597 // if no teams are found, spawn defaults
2598 if(find(NULL, classname, "ctf_team") == NULL)
2600 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.\n");
2601 if(ctf_teams & BIT(0))
2602 ctf_SpawnTeam("Red", NUM_TEAM_1);
2603 if(ctf_teams & BIT(1))
2604 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2605 if(ctf_teams & BIT(2))
2606 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2607 if(ctf_teams & BIT(3))
2608 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2611 ctf_ScoreRules(ctf_teams);
2614 void ctf_Initialize()
2616 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2618 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2619 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2620 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2622 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);