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 bool ctf_Return_Customize(entity this, entity client)
157 // only to the carrier
158 return boolean(client == this.owner);
161 void ctf_FlagcarrierWaypoints(entity player)
163 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
164 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
165 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
166 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
168 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
170 if(!player.wps_enemyflagcarrier)
172 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
173 wp.colormod = WPCOLOR_ENEMYFC(player.team);
174 setcefc(wp, ctf_Stalemate_Customize);
176 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
177 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
180 if(!player.wps_flagreturn)
182 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
183 owp.colormod = '0 0.8 0.8';
184 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
185 setcefc(owp, ctf_Return_Customize);
190 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
192 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
193 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
194 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
195 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
198 if(current_height) // make sure we can actually do this arcing path
200 targpos = (to + ('0 0 1' * current_height));
201 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
202 if(trace_fraction < 1)
204 //print("normal arc line failed, trying to find new pos...");
205 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
206 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
207 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
208 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
209 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
212 else { targpos = to; }
214 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
216 vector desired_direction = normalize(targpos - from);
217 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
218 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
221 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
223 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
225 // directional tracing only
227 makevectors(passer_angle);
229 // find the closest point on the enemy to the center of the attack
230 float h; // hypotenuse, which is the distance between attacker to head
231 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
233 h = vlen(head_center - passer_center);
234 a = h * (normalize(head_center - passer_center) * v_forward);
236 vector nearest_on_line = (passer_center + a * v_forward);
237 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
239 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
240 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
242 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
247 else { return true; }
251 // =======================
252 // CaptureShield Functions
253 // =======================
255 bool ctf_CaptureShield_CheckStatus(entity p)
257 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
258 int players_worseeq, players_total;
260 if(ctf_captureshield_max_ratio <= 0)
263 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
264 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
265 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
266 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
268 sr = ((s - s2) + (s3 + s4));
270 if(sr >= -ctf_captureshield_min_negscore)
273 players_total = players_worseeq = 0;
274 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
277 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
278 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
279 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
280 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
282 ser = ((se - se2) + (se3 + se4));
289 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
290 // use this rule here
292 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
298 void ctf_CaptureShield_Update(entity player, bool wanted_status)
300 bool updated_status = ctf_CaptureShield_CheckStatus(player);
301 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
303 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
304 player.ctf_captureshielded = updated_status;
308 bool ctf_CaptureShield_Customize(entity this, entity client)
310 if(!client.ctf_captureshielded) { return false; }
311 if(CTF_SAMETEAM(this, client)) { return false; }
316 void ctf_CaptureShield_Touch(entity this, entity toucher)
318 if(!toucher.ctf_captureshielded) { return; }
319 if(CTF_SAMETEAM(this, toucher)) { return; }
321 vector mymid = (this.absmin + this.absmax) * 0.5;
322 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
324 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
325 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
328 void ctf_CaptureShield_Spawn(entity flag)
330 entity shield = new(ctf_captureshield);
333 shield.team = flag.team;
334 settouch(shield, ctf_CaptureShield_Touch);
335 setcefc(shield, ctf_CaptureShield_Customize);
336 shield.effects = EF_ADDITIVE;
337 set_movetype(shield, MOVETYPE_NOCLIP);
338 shield.solid = SOLID_TRIGGER;
339 shield.avelocity = '7 0 11';
342 setorigin(shield, flag.origin);
343 setmodel(shield, MDL_CTF_SHIELD);
344 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
348 // ====================
349 // Drop/Pass/Throw Code
350 // ====================
352 void ctf_Handle_Drop(entity flag, entity player, int droptype)
355 player = (player ? player : flag.pass_sender);
358 set_movetype(flag, MOVETYPE_TOSS);
359 flag.takedamage = DAMAGE_YES;
360 flag.angles = '0 0 0';
361 flag.health = flag.max_flag_health;
362 flag.ctf_droptime = time;
363 flag.ctf_dropper = player;
364 flag.ctf_status = FLAG_DROPPED;
366 // messages and sounds
367 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_LOST) : INFO_CTF_LOST_NEUTRAL), player.netname);
368 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
369 ctf_EventLog("dropped", player.team, player);
372 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
373 PlayerScore_Add(player, SP_CTF_DROPS, 1);
376 if(autocvar_g_ctf_flag_dropped_waypoint) {
377 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);
378 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
381 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
383 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
384 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
387 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
389 if(droptype == DROP_PASS)
391 flag.pass_distance = 0;
392 flag.pass_sender = NULL;
393 flag.pass_target = NULL;
397 void ctf_Handle_Retrieve(entity flag, entity player)
399 entity sender = flag.pass_sender;
401 // transfer flag to player
403 flag.owner.flagcarried = flag;
408 setattachment(flag, player.vehicle, "");
409 setorigin(flag, VEHICLE_FLAG_OFFSET);
410 flag.scale = VEHICLE_FLAG_SCALE;
414 setattachment(flag, player, "");
415 setorigin(flag, FLAG_CARRY_OFFSET);
417 set_movetype(flag, MOVETYPE_NONE);
418 flag.takedamage = DAMAGE_NO;
419 flag.solid = SOLID_NOT;
420 flag.angles = '0 0 0';
421 flag.ctf_status = FLAG_CARRY;
423 // messages and sounds
424 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
425 ctf_EventLog("receive", flag.team, player);
427 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(
429 Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT(flag, CENTER_CTF_PASS_SENT) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
430 else if(it == player)
431 Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT(flag, CENTER_CTF_PASS_RECEIVED) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
432 else if(SAME_TEAM(it, sender))
433 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);
436 // create new waypoint
437 ctf_FlagcarrierWaypoints(player);
439 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
440 player.throw_antispam = sender.throw_antispam;
442 flag.pass_distance = 0;
443 flag.pass_sender = NULL;
444 flag.pass_target = NULL;
447 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
449 entity flag = player.flagcarried;
450 vector targ_origin, flag_velocity;
452 if(!flag) { return; }
453 if((droptype == DROP_PASS) && !receiver) { return; }
455 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
458 setattachment(flag, NULL, "");
459 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
460 flag.owner.flagcarried = NULL;
462 flag.solid = SOLID_TRIGGER;
463 flag.ctf_dropper = player;
464 flag.ctf_droptime = time;
466 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
473 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
474 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
475 WarpZone_RefSys_Copy(flag, receiver);
476 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
477 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
479 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
480 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
483 set_movetype(flag, MOVETYPE_FLY);
484 flag.takedamage = DAMAGE_NO;
485 flag.pass_sender = player;
486 flag.pass_target = receiver;
487 flag.ctf_status = FLAG_PASSING;
490 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
491 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
492 ctf_EventLog("pass", flag.team, player);
498 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'));
500 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)));
501 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
502 ctf_Handle_Drop(flag, player, droptype);
508 flag.velocity = '0 0 0'; // do nothing
515 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);
516 ctf_Handle_Drop(flag, player, droptype);
521 // kill old waypointsprite
522 WaypointSprite_Ping(player.wps_flagcarrier);
523 WaypointSprite_Kill(player.wps_flagcarrier);
525 if(player.wps_enemyflagcarrier)
526 WaypointSprite_Kill(player.wps_enemyflagcarrier);
528 if(player.wps_flagreturn)
529 WaypointSprite_Kill(player.wps_flagreturn);
532 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
535 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
537 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
544 void nades_GiveBonus(entity player, float score);
546 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
548 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
549 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
550 entity player_team_flag = NULL, tmp_entity;
551 float old_time, new_time;
553 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
554 if(CTF_DIFFTEAM(player, flag)) { return; }
557 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
558 if(SAME_TEAM(tmp_entity, player))
560 player_team_flag = tmp_entity;
564 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
566 player.throw_prevtime = time;
567 player.throw_count = 0;
569 // messages and sounds
570 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT(enemy_flag, CENTER_CTF_CAPTURE) : CENTER_CTF_CAPTURE_NEUTRAL));
571 ctf_CaptureRecord(enemy_flag, player);
572 _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);
576 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
577 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
582 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
583 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
585 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
586 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
587 if(!old_time || new_time < old_time)
588 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
591 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
592 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
595 if(capturetype == CAPTURE_NORMAL)
597 WaypointSprite_Kill(player.wps_flagcarrier);
598 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
600 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
601 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
605 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
606 ctf_RespawnFlag(enemy_flag);
609 void ctf_Handle_Return(entity flag, entity player)
611 // messages and sounds
612 if(IS_MONSTER(player))
614 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_ENT(flag, INFO_CTF_RETURN_MONSTER), player.monster_name);
618 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT(flag, CENTER_CTF_RETURN));
619 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_ENT(flag, INFO_CTF_RETURN), player.netname);
621 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
622 ctf_EventLog("return", flag.team, player);
625 if(IS_PLAYER(player))
627 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
628 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
630 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
633 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
637 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
638 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
639 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
643 if(player.flagcarried == flag)
644 WaypointSprite_Kill(player.wps_flagcarrier);
647 ctf_RespawnFlag(flag);
650 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
653 float pickup_dropped_score; // used to calculate dropped pickup score
655 // attach the flag to the player
657 player.flagcarried = flag;
660 setattachment(flag, player.vehicle, "");
661 setorigin(flag, VEHICLE_FLAG_OFFSET);
662 flag.scale = VEHICLE_FLAG_SCALE;
666 setattachment(flag, player, "");
667 setorigin(flag, FLAG_CARRY_OFFSET);
671 set_movetype(flag, MOVETYPE_NONE);
672 flag.takedamage = DAMAGE_NO;
673 flag.solid = SOLID_NOT;
674 flag.angles = '0 0 0';
675 flag.ctf_status = FLAG_CARRY;
679 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
680 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
684 // messages and sounds
685 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_PICKUP) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
686 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
687 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
688 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT(flag, CENTER_CTF_PICKUP)); }
689 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)); }
691 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);
694 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)));
697 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
698 if(CTF_SAMETEAM(flag, it))
699 if(SAME_TEAM(player, it))
700 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
702 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);
705 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
708 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
709 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
714 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
715 ctf_EventLog("steal", flag.team, player);
721 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);
722 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);
723 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score), "\n");
724 PlayerTeamScore_AddScore(player, pickup_dropped_score);
725 ctf_EventLog("pickup", flag.team, player);
733 if(pickuptype == PICKUP_BASE)
735 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
736 if((player.speedrunning) && (ctf_captimerecord))
737 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
741 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
744 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
745 ctf_FlagcarrierWaypoints(player);
746 WaypointSprite_Ping(player.wps_flagcarrier);
750 // ===================
751 // Main Flag Functions
752 // ===================
754 void ctf_CheckFlagReturn(entity flag, int returntype)
756 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
758 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
760 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
764 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;
765 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;
766 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;
767 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;
771 { Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_TIMEOUT) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
773 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
774 ctf_EventLog("returned", flag.team, NULL);
775 ctf_RespawnFlag(flag);
780 bool ctf_Stalemate_Customize(entity this, entity client)
782 // make spectators see what the player would see
783 entity e = WaypointSprite_getviewentity(client);
784 entity wp_owner = this.owner;
787 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
788 if(SAME_TEAM(wp_owner, e)) { return false; }
789 if(!IS_PLAYER(e)) { return false; }
794 void ctf_CheckStalemate()
797 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
800 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
802 // build list of stale flags
803 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
805 if(autocvar_g_ctf_stalemate)
806 if(tmp_entity.ctf_status != FLAG_BASE)
807 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
809 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
810 ctf_staleflaglist = tmp_entity;
812 switch(tmp_entity.team)
814 case NUM_TEAM_1: ++stale_red_flags; break;
815 case NUM_TEAM_2: ++stale_blue_flags; break;
816 case NUM_TEAM_3: ++stale_yellow_flags; break;
817 case NUM_TEAM_4: ++stale_pink_flags; break;
818 default: ++stale_neutral_flags; break;
824 stale_flags = (stale_neutral_flags >= 1);
826 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
828 if(ctf_oneflag && stale_flags == 1)
829 ctf_stalemate = true;
830 else if(stale_flags >= 2)
831 ctf_stalemate = true;
832 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
833 { ctf_stalemate = false; wpforenemy_announced = false; }
834 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
835 { ctf_stalemate = false; wpforenemy_announced = false; }
837 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
840 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
842 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
844 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);
845 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
846 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
850 if (!wpforenemy_announced)
852 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))));
854 wpforenemy_announced = true;
859 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
861 if(ITEM_DAMAGE_NEEDKILL(deathtype))
863 if(autocvar_g_ctf_flag_return_damage_delay)
865 this.ctf_flagdamaged = true;
870 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
874 if(autocvar_g_ctf_flag_return_damage)
876 // reduce health and check if it should be returned
877 this.health = this.health - damage;
878 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
883 void ctf_FlagThink(entity this)
888 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
891 if(this == ctf_worldflaglist) // only for the first flag
892 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
895 if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
896 LOG_TRACE("wtf the flag got squashed?\n");
897 tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
898 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
899 setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs); }
901 switch(this.ctf_status) // reset flag angles in case warpzones adjust it
905 this.angles = '0 0 0';
913 switch(this.ctf_status)
917 if(autocvar_g_ctf_dropped_capture_radius)
919 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
920 if(tmp_entity.ctf_status == FLAG_DROPPED)
921 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
922 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
923 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
930 if(autocvar_g_ctf_flag_dropped_floatinwater)
932 vector midpoint = ((this.absmin + this.absmax) * 0.5);
933 if(pointcontents(midpoint) == CONTENT_WATER)
935 this.velocity = this.velocity * 0.5;
937 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
938 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
940 { set_movetype(this, MOVETYPE_FLY); }
942 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
944 if(autocvar_g_ctf_flag_return_dropped)
946 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
949 ctf_CheckFlagReturn(this, RETURN_DROPPED);
953 if(this.ctf_flagdamaged)
955 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
956 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
959 else if(autocvar_g_ctf_flag_return_time)
961 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
962 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
970 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
973 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
975 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
976 ImpulseCommands(this.owner);
978 if(autocvar_g_ctf_stalemate)
980 if(time >= wpforenemy_nextthink)
982 ctf_CheckStalemate();
983 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
986 if(CTF_SAMETEAM(this, this.owner) && this.team)
988 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
989 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
990 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
991 ctf_Handle_Return(this, this.owner);
998 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
999 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1000 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1002 if((this.pass_target == NULL)
1003 || (IS_DEAD(this.pass_target))
1004 || (this.pass_target.flagcarried)
1005 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1006 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1007 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1009 // give up, pass failed
1010 ctf_Handle_Drop(this, NULL, DROP_PASS);
1014 // still a viable target, go for it
1015 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1020 default: // this should never happen
1022 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?\n");
1028 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1031 if(gameover) { return; }
1032 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1034 bool is_not_monster = (!IS_MONSTER(toucher));
1036 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1037 if(ITEM_TOUCH_NEEDKILL())
1039 if(!autocvar_g_ctf_flag_return_damage_delay)
1042 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1044 if(!flag.ctf_flagdamaged) { return; }
1047 int num_perteam = 0;
1048 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), LAMBDA(++num_perteam));
1050 // special touch behaviors
1051 if(STAT(FROZEN, toucher)) { return; }
1052 else if(IS_VEHICLE(toucher))
1054 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1055 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1057 return; // do nothing
1059 else if(IS_MONSTER(toucher))
1061 if(!autocvar_g_ctf_allow_monster_touch)
1062 return; // do nothing
1064 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1066 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1068 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1069 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1070 flag.wait = time + FLAG_TOUCHRATE;
1074 else if(IS_DEAD(toucher)) { return; }
1076 switch(flag.ctf_status)
1082 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1083 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1084 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1085 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1087 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1088 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1089 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)
1091 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1092 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1094 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1095 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1101 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
1102 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1103 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1104 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1110 LOG_TRACE("Someone touched a flag even though it was being carried?\n");
1116 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1118 if(DIFF_TEAM(toucher, flag.pass_sender))
1119 ctf_Handle_Return(flag, toucher);
1121 ctf_Handle_Retrieve(flag, toucher);
1128 .float last_respawn;
1129 void ctf_RespawnFlag(entity flag)
1131 // check for flag respawn being called twice in a row
1132 if(flag.last_respawn > time - 0.5)
1133 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1135 flag.last_respawn = time;
1137 // reset the player (if there is one)
1138 if((flag.owner) && (flag.owner.flagcarried == flag))
1140 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1141 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1142 WaypointSprite_Kill(flag.wps_flagcarrier);
1144 flag.owner.flagcarried = NULL;
1146 if(flag.speedrunning)
1147 ctf_FakeTimeLimit(flag.owner, -1);
1150 if((flag.owner) && (flag.owner.vehicle))
1151 flag.scale = FLAG_SCALE;
1153 if(flag.ctf_status == FLAG_DROPPED)
1154 { WaypointSprite_Kill(flag.wps_flagdropped); }
1157 setattachment(flag, NULL, "");
1158 setorigin(flag, flag.ctf_spawnorigin);
1160 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1161 flag.takedamage = DAMAGE_NO;
1162 flag.health = flag.max_flag_health;
1163 flag.solid = SOLID_TRIGGER;
1164 flag.velocity = '0 0 0';
1165 flag.angles = flag.mangle;
1166 flag.flags = FL_ITEM | FL_NOTARGET;
1168 flag.ctf_status = FLAG_BASE;
1170 flag.pass_distance = 0;
1171 flag.pass_sender = NULL;
1172 flag.pass_target = NULL;
1173 flag.ctf_dropper = NULL;
1174 flag.ctf_pickuptime = 0;
1175 flag.ctf_droptime = 0;
1176 flag.ctf_flagdamaged = 0;
1178 ctf_CheckStalemate();
1181 void ctf_Reset(entity this)
1183 if(this.owner && IS_PLAYER(this.owner))
1184 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1186 ctf_RespawnFlag(this);
1189 bool ctf_FlagBase_Customize(entity this, entity client)
1191 if(client.flagcarried && CTF_SAMETEAM(client, client.flagcarried))
1196 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1199 waypoint_spawnforitem_force(this, this.origin);
1200 this.nearestwaypointtimeout = 0; // activate waypointing again
1201 this.bot_basewaypoint = this.nearestwaypoint;
1207 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1208 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1209 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1210 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1211 default: basename = WP_FlagBaseNeutral; break;
1214 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1215 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1216 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1217 setcefc(wp, ctf_FlagBase_Customize);
1219 // captureshield setup
1220 ctf_CaptureShield_Spawn(this);
1225 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1228 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1229 ctf_worldflaglist = flag;
1231 setattachment(flag, NULL, "");
1233 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1234 flag.team = teamnumber;
1235 flag.classname = "item_flag_team";
1236 flag.target = "###item###"; // wut?
1237 flag.flags = FL_ITEM | FL_NOTARGET;
1238 flag.solid = SOLID_TRIGGER;
1239 flag.takedamage = DAMAGE_NO;
1240 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1241 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1242 flag.health = flag.max_flag_health;
1243 flag.event_damage = ctf_FlagDamage;
1244 flag.pushable = true;
1245 flag.teleportable = TELEPORT_NORMAL;
1246 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1247 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1248 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1249 flag.velocity = '0 0 0';
1250 flag.mangle = flag.angles;
1251 flag.reset = ctf_Reset;
1252 settouch(flag, ctf_FlagTouch);
1253 setthink(flag, ctf_FlagThink);
1254 flag.nextthink = time + FLAG_THINKRATE;
1255 flag.ctf_status = FLAG_BASE;
1257 string teamname = Static_Team_ColorName_Lower(teamnumber);
1259 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1260 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1261 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1262 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1263 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1264 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1268 if(flag.s == "") flag.s = b; \
1269 precache_sound(flag.s);
1271 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1272 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1273 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1274 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1275 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1276 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1277 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1281 precache_model(flag.model);
1284 _setmodel(flag, flag.model); // precision set below
1285 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1286 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1288 if(autocvar_g_ctf_flag_glowtrails)
1292 case NUM_TEAM_1: flag.glow_color = 251; break;
1293 case NUM_TEAM_2: flag.glow_color = 210; break;
1294 case NUM_TEAM_3: flag.glow_color = 110; break;
1295 case NUM_TEAM_4: flag.glow_color = 145; break;
1296 default: flag.glow_color = 254; break;
1298 flag.glow_size = 25;
1299 flag.glow_trail = 1;
1302 flag.effects |= EF_LOWPRECISION;
1303 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1304 if(autocvar_g_ctf_dynamiclights)
1308 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1309 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1310 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1311 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1312 default: flag.effects |= EF_DIMLIGHT; break;
1317 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1319 flag.dropped_origin = flag.origin;
1320 flag.noalign = true;
1321 set_movetype(flag, MOVETYPE_NONE);
1323 else // drop to floor, automatically find a platform and set that as spawn origin
1325 flag.noalign = false;
1327 set_movetype(flag, MOVETYPE_NONE);
1330 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1338 // NOTE: LEGACY CODE, needs to be re-written!
1340 void havocbot_calculate_middlepoint()
1344 vector fo = '0 0 0';
1347 f = ctf_worldflaglist;
1352 f = f.ctf_worldflagnext;
1356 havocbot_ctf_middlepoint = s * (1.0 / n);
1357 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1361 entity havocbot_ctf_find_flag(entity bot)
1364 f = ctf_worldflaglist;
1367 if (CTF_SAMETEAM(bot, f))
1369 f = f.ctf_worldflagnext;
1374 entity havocbot_ctf_find_enemy_flag(entity bot)
1377 f = ctf_worldflaglist;
1382 if(CTF_DIFFTEAM(bot, f))
1389 else if(!bot.flagcarried)
1393 else if (CTF_DIFFTEAM(bot, f))
1395 f = f.ctf_worldflagnext;
1400 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1407 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1408 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1411 if(vdist(it.origin - org, <, tc_radius))
1418 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1421 head = ctf_worldflaglist;
1424 if (CTF_SAMETEAM(this, head))
1426 head = head.ctf_worldflagnext;
1429 navigation_routerating(this, head, ratingscale, 10000);
1432 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1435 head = ctf_worldflaglist;
1438 if (CTF_SAMETEAM(this, head))
1440 head = head.ctf_worldflagnext;
1445 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1448 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1451 head = ctf_worldflaglist;
1456 if(CTF_DIFFTEAM(this, head))
1460 if(this.flagcarried)
1463 else if(!this.flagcarried)
1467 else if(CTF_DIFFTEAM(this, head))
1469 head = head.ctf_worldflagnext;
1472 navigation_routerating(this, head, ratingscale, 10000);
1475 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1477 if (!bot_waypoints_for_items)
1479 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1485 head = havocbot_ctf_find_enemy_flag(this);
1490 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1493 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1497 mf = havocbot_ctf_find_flag(this);
1499 if(mf.ctf_status == FLAG_BASE)
1503 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1506 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1509 head = ctf_worldflaglist;
1512 // flag is out in the field
1513 if(head.ctf_status != FLAG_BASE)
1514 if(head.tag_entity==NULL) // dropped
1518 if(vdist(org - head.origin, <, df_radius))
1519 navigation_routerating(this, head, ratingscale, 10000);
1522 navigation_routerating(this, head, ratingscale, 10000);
1525 head = head.ctf_worldflagnext;
1529 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1531 FOREACH_ENTITY_FLOAT(bot_pickup, true,
1533 // gather health and armor only
1535 if (it.health || it.armorvalue)
1536 if (vdist(it.origin - org, <, sradius))
1538 // get the value of the item
1539 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1541 navigation_routerating(this, it, t * ratingscale, 500);
1546 void havocbot_ctf_reset_role(entity this)
1548 float cdefense, cmiddle, coffense;
1555 if(havocbot_ctf_middlepoint == '0 0 0')
1556 havocbot_calculate_middlepoint();
1559 if (this.flagcarried)
1561 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1565 mf = havocbot_ctf_find_flag(this);
1566 ef = havocbot_ctf_find_enemy_flag(this);
1568 // Retrieve stolen flag
1569 if(mf.ctf_status!=FLAG_BASE)
1571 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1575 // If enemy flag is taken go to the middle to intercept pursuers
1576 if(ef.ctf_status!=FLAG_BASE)
1578 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1582 // if there is only me on the team switch to offense
1584 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1588 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1592 // Evaluate best position to take
1593 // Count mates on middle position
1594 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1596 // Count mates on defense position
1597 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1599 // Count mates on offense position
1600 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1602 if(cdefense<=coffense)
1603 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1604 else if(coffense<=cmiddle)
1605 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1607 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1610 void havocbot_role_ctf_carrier(entity this)
1614 havocbot_ctf_reset_role(this);
1618 if (this.flagcarried == NULL)
1620 havocbot_ctf_reset_role(this);
1624 if (this.bot_strategytime < time)
1626 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1628 navigation_goalrating_start(this);
1630 havocbot_goalrating_ctf_enemybase(this, 50000);
1632 havocbot_goalrating_ctf_ourbase(this, 50000);
1635 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1637 navigation_goalrating_end(this);
1639 if (this.navigation_hasgoals)
1640 this.havocbot_cantfindflag = time + 10;
1641 else if (time > this.havocbot_cantfindflag)
1643 // Can't navigate to my own base, suicide!
1644 // TODO: drop it and wander around
1645 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1651 void havocbot_role_ctf_escort(entity this)
1657 havocbot_ctf_reset_role(this);
1661 if (this.flagcarried)
1663 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1667 // If enemy flag is back on the base switch to previous role
1668 ef = havocbot_ctf_find_enemy_flag(this);
1669 if(ef.ctf_status==FLAG_BASE)
1671 this.havocbot_role = this.havocbot_previous_role;
1672 this.havocbot_role_timeout = 0;
1676 // If the flag carrier reached the base switch to defense
1677 mf = havocbot_ctf_find_flag(this);
1678 if(mf.ctf_status!=FLAG_BASE)
1679 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1681 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1685 // Set the role timeout if necessary
1686 if (!this.havocbot_role_timeout)
1688 this.havocbot_role_timeout = time + random() * 30 + 60;
1691 // If nothing happened just switch to previous role
1692 if (time > this.havocbot_role_timeout)
1694 this.havocbot_role = this.havocbot_previous_role;
1695 this.havocbot_role_timeout = 0;
1699 // Chase the flag carrier
1700 if (this.bot_strategytime < time)
1702 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1703 navigation_goalrating_start(this);
1704 havocbot_goalrating_ctf_enemyflag(this, 30000);
1705 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1706 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1707 navigation_goalrating_end(this);
1711 void havocbot_role_ctf_offense(entity this)
1718 havocbot_ctf_reset_role(this);
1722 if (this.flagcarried)
1724 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1729 mf = havocbot_ctf_find_flag(this);
1730 ef = havocbot_ctf_find_enemy_flag(this);
1733 if(mf.ctf_status!=FLAG_BASE)
1736 pos = mf.tag_entity.origin;
1740 // Try to get it if closer than the enemy base
1741 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1743 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1748 // Escort flag carrier
1749 if(ef.ctf_status!=FLAG_BASE)
1752 pos = ef.tag_entity.origin;
1756 if(vdist(pos - mf.dropped_origin, >, 700))
1758 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1763 // About to fail, switch to middlefield
1766 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1770 // Set the role timeout if necessary
1771 if (!this.havocbot_role_timeout)
1772 this.havocbot_role_timeout = time + 120;
1774 if (time > this.havocbot_role_timeout)
1776 havocbot_ctf_reset_role(this);
1780 if (this.bot_strategytime < time)
1782 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1783 navigation_goalrating_start(this);
1784 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1785 havocbot_goalrating_ctf_enemybase(this, 20000);
1786 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1787 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1788 navigation_goalrating_end(this);
1792 // Retriever (temporary role):
1793 void havocbot_role_ctf_retriever(entity this)
1799 havocbot_ctf_reset_role(this);
1803 if (this.flagcarried)
1805 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1809 // If flag is back on the base switch to previous role
1810 mf = havocbot_ctf_find_flag(this);
1811 if(mf.ctf_status==FLAG_BASE)
1813 havocbot_ctf_reset_role(this);
1817 if (!this.havocbot_role_timeout)
1818 this.havocbot_role_timeout = time + 20;
1820 if (time > this.havocbot_role_timeout)
1822 havocbot_ctf_reset_role(this);
1826 if (this.bot_strategytime < time)
1831 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1832 navigation_goalrating_start(this);
1833 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1834 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1835 havocbot_goalrating_ctf_enemybase(this, 30000);
1836 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1837 navigation_goalrating_end(this);
1841 void havocbot_role_ctf_middle(entity this)
1847 havocbot_ctf_reset_role(this);
1851 if (this.flagcarried)
1853 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1857 mf = havocbot_ctf_find_flag(this);
1858 if(mf.ctf_status!=FLAG_BASE)
1860 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1864 if (!this.havocbot_role_timeout)
1865 this.havocbot_role_timeout = time + 10;
1867 if (time > this.havocbot_role_timeout)
1869 havocbot_ctf_reset_role(this);
1873 if (this.bot_strategytime < time)
1877 org = havocbot_ctf_middlepoint;
1878 org.z = this.origin.z;
1880 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1881 navigation_goalrating_start(this);
1882 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1883 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1884 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1885 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1886 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1887 havocbot_goalrating_ctf_enemybase(this, 2500);
1888 navigation_goalrating_end(this);
1892 void havocbot_role_ctf_defense(entity this)
1898 havocbot_ctf_reset_role(this);
1902 if (this.flagcarried)
1904 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1908 // If own flag was captured
1909 mf = havocbot_ctf_find_flag(this);
1910 if(mf.ctf_status!=FLAG_BASE)
1912 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1916 if (!this.havocbot_role_timeout)
1917 this.havocbot_role_timeout = time + 30;
1919 if (time > this.havocbot_role_timeout)
1921 havocbot_ctf_reset_role(this);
1924 if (this.bot_strategytime < time)
1929 org = mf.dropped_origin;
1930 mp_radius = havocbot_ctf_middlepoint_radius;
1932 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1933 navigation_goalrating_start(this);
1935 // if enemies are closer to our base, go there
1936 entity closestplayer = NULL;
1937 float distance, bestdistance = 10000;
1938 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1939 distance = vlen(org - it.origin);
1940 if(distance<bestdistance)
1943 bestdistance = distance;
1948 if(DIFF_TEAM(closestplayer, this))
1949 if(vdist(org - this.origin, >, 1000))
1950 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1951 havocbot_goalrating_ctf_ourbase(this, 30000);
1953 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1954 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1955 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1956 havocbot_goalrating_items(this, 10000, org, mp_radius);
1957 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1958 navigation_goalrating_end(this);
1962 void havocbot_role_ctf_setrole(entity bot, int role)
1964 LOG_TRACE(strcat(bot.netname," switched to "));
1967 case HAVOCBOT_CTF_ROLE_CARRIER:
1968 LOG_TRACE("carrier");
1969 bot.havocbot_role = havocbot_role_ctf_carrier;
1970 bot.havocbot_role_timeout = 0;
1971 bot.havocbot_cantfindflag = time + 10;
1972 bot.bot_strategytime = 0;
1974 case HAVOCBOT_CTF_ROLE_DEFENSE:
1975 LOG_TRACE("defense");
1976 bot.havocbot_role = havocbot_role_ctf_defense;
1977 bot.havocbot_role_timeout = 0;
1979 case HAVOCBOT_CTF_ROLE_MIDDLE:
1980 LOG_TRACE("middle");
1981 bot.havocbot_role = havocbot_role_ctf_middle;
1982 bot.havocbot_role_timeout = 0;
1984 case HAVOCBOT_CTF_ROLE_OFFENSE:
1985 LOG_TRACE("offense");
1986 bot.havocbot_role = havocbot_role_ctf_offense;
1987 bot.havocbot_role_timeout = 0;
1989 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1990 LOG_TRACE("retriever");
1991 bot.havocbot_previous_role = bot.havocbot_role;
1992 bot.havocbot_role = havocbot_role_ctf_retriever;
1993 bot.havocbot_role_timeout = time + 10;
1994 bot.bot_strategytime = 0;
1996 case HAVOCBOT_CTF_ROLE_ESCORT:
1997 LOG_TRACE("escort");
1998 bot.havocbot_previous_role = bot.havocbot_role;
1999 bot.havocbot_role = havocbot_role_ctf_escort;
2000 bot.havocbot_role_timeout = time + 30;
2001 bot.bot_strategytime = 0;
2012 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2014 entity player = M_ARGV(0, entity);
2016 int t = 0, t2 = 0, t3 = 0;
2018 // initially clear items so they can be set as necessary later.
2019 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2020 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2021 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2022 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2023 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2024 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2026 // scan through all the flags and notify the client about them
2027 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2029 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2030 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2031 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2032 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2033 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; }
2035 switch(flag.ctf_status)
2040 if((flag.owner == player) || (flag.pass_sender == player))
2041 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2043 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2048 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2054 // item for stopping players from capturing the flag too often
2055 if(player.ctf_captureshielded)
2056 player.ctf_flagstatus |= CTF_SHIELDED;
2059 player.ctf_flagstatus |= CTF_STALEMATE;
2061 // update the health of the flag carrier waypointsprite
2062 if(player.wps_flagcarrier)
2063 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2066 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2068 entity frag_attacker = M_ARGV(1, entity);
2069 entity frag_target = M_ARGV(2, entity);
2070 float frag_damage = M_ARGV(4, float);
2071 vector frag_force = M_ARGV(6, vector);
2073 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2075 if(frag_target == frag_attacker) // damage done to yourself
2077 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2078 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2080 else // damage done to everyone else
2082 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2083 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2086 M_ARGV(4, float) = frag_damage;
2087 M_ARGV(6, vector) = frag_force;
2089 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2091 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)))
2092 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2094 frag_target.wps_helpme_time = time;
2095 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2097 // todo: add notification for when flag carrier needs help?
2101 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2103 entity frag_attacker = M_ARGV(1, entity);
2104 entity frag_target = M_ARGV(2, entity);
2106 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2108 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2109 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2112 if(frag_target.flagcarried)
2114 entity tmp_entity = frag_target.flagcarried;
2115 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2116 tmp_entity.ctf_dropper = NULL;
2120 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2122 M_ARGV(2, float) = 0; // frag score
2123 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2126 void ctf_RemovePlayer(entity player)
2128 if(player.flagcarried)
2129 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2131 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2133 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2134 if(flag.pass_target == player) { flag.pass_target = NULL; }
2135 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2139 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2141 entity player = M_ARGV(0, entity);
2143 ctf_RemovePlayer(player);
2146 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2148 entity player = M_ARGV(0, entity);
2150 ctf_RemovePlayer(player);
2153 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2155 entity player = M_ARGV(0, entity);
2157 if(player.flagcarried)
2158 if(!autocvar_g_ctf_portalteleport)
2159 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2162 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2164 if(MUTATOR_RETURNVALUE || gameover) { return; }
2166 entity player = M_ARGV(0, entity);
2168 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2170 // pass the flag to a team mate
2171 if(autocvar_g_ctf_pass)
2173 entity head, closest_target = NULL;
2174 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2176 while(head) // find the closest acceptable target to pass to
2178 if(IS_PLAYER(head) && !IS_DEAD(head))
2179 if(head != player && SAME_TEAM(head, player))
2180 if(!head.speedrunning && !head.vehicle)
2182 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2183 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2184 vector passer_center = CENTER_OR_VIEWOFS(player);
2186 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2188 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2190 if(IS_BOT_CLIENT(head))
2192 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2193 ctf_Handle_Throw(head, player, DROP_PASS);
2197 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2198 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2200 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2203 else if(player.flagcarried)
2207 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2208 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2209 { closest_target = head; }
2211 else { closest_target = head; }
2218 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2221 // throw the flag in front of you
2222 if(autocvar_g_ctf_throw && player.flagcarried)
2224 if(player.throw_count == -1)
2226 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2228 player.throw_prevtime = time;
2229 player.throw_count = 1;
2230 ctf_Handle_Throw(player, NULL, DROP_THROW);
2235 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2241 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2242 else { player.throw_count += 1; }
2243 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2245 player.throw_prevtime = time;
2246 ctf_Handle_Throw(player, NULL, DROP_THROW);
2253 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2255 entity player = M_ARGV(0, entity);
2257 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2259 player.wps_helpme_time = time;
2260 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2262 else // create a normal help me waypointsprite
2264 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2265 WaypointSprite_Ping(player.wps_helpme);
2271 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2273 entity player = M_ARGV(0, entity);
2274 entity veh = M_ARGV(1, entity);
2276 if(player.flagcarried)
2278 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2280 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2284 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2285 setattachment(player.flagcarried, veh, "");
2286 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2287 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2288 //player.flagcarried.angles = '0 0 0';
2294 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2296 entity player = M_ARGV(0, entity);
2298 if(player.flagcarried)
2300 setattachment(player.flagcarried, player, "");
2301 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2302 player.flagcarried.scale = FLAG_SCALE;
2303 player.flagcarried.angles = '0 0 0';
2304 player.flagcarried.nodrawtoclient = NULL;
2309 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2311 entity player = M_ARGV(0, entity);
2313 if(player.flagcarried)
2315 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((player.flagcarried.team) ? APP_TEAM_ENT(player.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
2316 ctf_RespawnFlag(player.flagcarried);
2321 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2323 entity flag; // temporary entity for the search method
2325 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2327 switch(flag.ctf_status)
2332 // lock the flag, game is over
2333 set_movetype(flag, MOVETYPE_NONE);
2334 flag.takedamage = DAMAGE_NO;
2335 flag.solid = SOLID_NOT;
2336 flag.nextthink = false; // stop thinking
2338 //dprint("stopping the ", flag.netname, " from moving.\n");
2346 // do nothing for these flags
2353 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2355 entity bot = M_ARGV(0, entity);
2357 havocbot_ctf_reset_role(bot);
2361 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2363 //M_ARGV(0, float) = ctf_teams;
2364 M_ARGV(1, string) = "ctf_team";
2368 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2370 entity spectatee = M_ARGV(0, entity);
2371 entity client = M_ARGV(1, entity);
2373 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2376 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2378 int record_page = M_ARGV(0, int);
2379 string ret_string = M_ARGV(1, string);
2381 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2383 if (MapInfo_Get_ByID(i))
2385 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2391 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2392 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2396 M_ARGV(1, string) = ret_string;
2399 bool superspec_Spectate(entity this, entity targ); // TODO
2400 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2401 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2403 entity player = M_ARGV(0, entity);
2404 string cmd_name = M_ARGV(1, string);
2405 int cmd_argc = M_ARGV(2, int);
2407 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2409 if(cmd_name == "followfc")
2421 case "red": _team = NUM_TEAM_1; break;
2422 case "blue": _team = NUM_TEAM_2; break;
2423 case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
2424 case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
2428 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2429 if(it.flagcarried && (it.team == _team || _team == 0))
2432 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2433 continue; // already spectating this fc, try another
2434 return superspec_Spectate(player, it);
2439 superspec_msg("", "", player, "No active flag carrier\n", 1);
2444 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2446 entity frag_target = M_ARGV(0, entity);
2448 if(frag_target.flagcarried)
2449 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2457 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2458 CTF flag for team one (Red).
2460 "angle" Angle the flag will point (minus 90 degrees)...
2461 "model" model to use, note this needs red and blue as skins 0 and 1...
2462 "noise" sound played when flag is picked up...
2463 "noise1" sound played when flag is returned by a teammate...
2464 "noise2" sound played when flag is captured...
2465 "noise3" sound played when flag is lost in the field and respawns itself...
2466 "noise4" sound played when flag is dropped by a player...
2467 "noise5" sound played when flag touches the ground... */
2468 spawnfunc(item_flag_team1)
2470 if(!g_ctf) { remove(this); return; }
2472 ctf_FlagSetup(NUM_TEAM_1, this);
2475 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2476 CTF flag for team two (Blue).
2478 "angle" Angle the flag will point (minus 90 degrees)...
2479 "model" model to use, note this needs red and blue as skins 0 and 1...
2480 "noise" sound played when flag is picked up...
2481 "noise1" sound played when flag is returned by a teammate...
2482 "noise2" sound played when flag is captured...
2483 "noise3" sound played when flag is lost in the field and respawns itself...
2484 "noise4" sound played when flag is dropped by a player...
2485 "noise5" sound played when flag touches the ground... */
2486 spawnfunc(item_flag_team2)
2488 if(!g_ctf) { remove(this); return; }
2490 ctf_FlagSetup(NUM_TEAM_2, this);
2493 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2494 CTF flag for team three (Yellow).
2496 "angle" Angle the flag will point (minus 90 degrees)...
2497 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2498 "noise" sound played when flag is picked up...
2499 "noise1" sound played when flag is returned by a teammate...
2500 "noise2" sound played when flag is captured...
2501 "noise3" sound played when flag is lost in the field and respawns itself...
2502 "noise4" sound played when flag is dropped by a player...
2503 "noise5" sound played when flag touches the ground... */
2504 spawnfunc(item_flag_team3)
2506 if(!g_ctf) { remove(this); return; }
2508 ctf_FlagSetup(NUM_TEAM_3, this);
2511 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2512 CTF flag for team four (Pink).
2514 "angle" Angle the flag will point (minus 90 degrees)...
2515 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2516 "noise" sound played when flag is picked up...
2517 "noise1" sound played when flag is returned by a teammate...
2518 "noise2" sound played when flag is captured...
2519 "noise3" sound played when flag is lost in the field and respawns itself...
2520 "noise4" sound played when flag is dropped by a player...
2521 "noise5" sound played when flag touches the ground... */
2522 spawnfunc(item_flag_team4)
2524 if(!g_ctf) { remove(this); return; }
2526 ctf_FlagSetup(NUM_TEAM_4, this);
2529 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2532 "angle" Angle the flag will point (minus 90 degrees)...
2533 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2534 "noise" sound played when flag is picked up...
2535 "noise1" sound played when flag is returned by a teammate...
2536 "noise2" sound played when flag is captured...
2537 "noise3" sound played when flag is lost in the field and respawns itself...
2538 "noise4" sound played when flag is dropped by a player...
2539 "noise5" sound played when flag touches the ground... */
2540 spawnfunc(item_flag_neutral)
2542 if(!g_ctf) { remove(this); return; }
2543 if(!cvar("g_ctf_oneflag")) { remove(this); return; }
2545 ctf_FlagSetup(0, this);
2548 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2549 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2550 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.
2552 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2553 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2556 if(!g_ctf) { remove(this); return; }
2558 this.classname = "ctf_team";
2559 this.team = this.cnt + 1;
2562 // compatibility for quake maps
2563 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2564 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2565 spawnfunc(info_player_team1);
2566 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2567 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2568 spawnfunc(info_player_team2);
2569 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2570 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2572 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2573 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2581 void ctf_ScoreRules(int teams)
2583 CheckAllowedTeams(NULL);
2584 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2585 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2586 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2587 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2588 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2589 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2590 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2591 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2592 ScoreRules_basics_end();
2595 // code from here on is just to support maps that don't have flag and team entities
2596 void ctf_SpawnTeam (string teamname, int teamcolor)
2598 entity this = new_pure(ctf_team);
2599 this.netname = teamname;
2600 this.cnt = teamcolor - 1;
2601 this.spawnfunc_checked = true;
2602 this.team = teamcolor;
2605 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2610 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2612 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2613 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2614 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2617 ctf_teams = bound(2, ctf_teams, 4);
2619 // if no teams are found, spawn defaults
2620 if(find(NULL, classname, "ctf_team") == NULL)
2622 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.\n");
2623 ctf_SpawnTeam("Red", NUM_TEAM_1);
2624 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2626 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2628 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2631 ctf_ScoreRules(ctf_teams);
2634 void ctf_Initialize()
2636 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2638 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2639 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2640 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2642 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);