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)
1533 head = findchainfloat(bot_pickup, true);
1536 // gather health and armor only
1538 if (head.health || head.armorvalue)
1539 if (vdist(head.origin - org, <, sradius))
1541 // get the value of the item
1542 t = head.bot_pickupevalfunc(this, head) * 0.0001;
1544 navigation_routerating(this, head, t * ratingscale, 500);
1550 void havocbot_ctf_reset_role(entity this)
1552 float cdefense, cmiddle, coffense;
1559 if(havocbot_ctf_middlepoint == '0 0 0')
1560 havocbot_calculate_middlepoint();
1563 if (this.flagcarried)
1565 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1569 mf = havocbot_ctf_find_flag(this);
1570 ef = havocbot_ctf_find_enemy_flag(this);
1572 // Retrieve stolen flag
1573 if(mf.ctf_status!=FLAG_BASE)
1575 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1579 // If enemy flag is taken go to the middle to intercept pursuers
1580 if(ef.ctf_status!=FLAG_BASE)
1582 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1586 // if there is only me on the team switch to offense
1588 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1592 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1596 // Evaluate best position to take
1597 // Count mates on middle position
1598 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1600 // Count mates on defense position
1601 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1603 // Count mates on offense position
1604 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1606 if(cdefense<=coffense)
1607 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1608 else if(coffense<=cmiddle)
1609 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1611 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1614 void havocbot_role_ctf_carrier(entity this)
1618 havocbot_ctf_reset_role(this);
1622 if (this.flagcarried == NULL)
1624 havocbot_ctf_reset_role(this);
1628 if (this.bot_strategytime < time)
1630 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1632 navigation_goalrating_start(this);
1634 havocbot_goalrating_ctf_enemybase(this, 50000);
1636 havocbot_goalrating_ctf_ourbase(this, 50000);
1639 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1641 navigation_goalrating_end(this);
1643 if (this.navigation_hasgoals)
1644 this.havocbot_cantfindflag = time + 10;
1645 else if (time > this.havocbot_cantfindflag)
1647 // Can't navigate to my own base, suicide!
1648 // TODO: drop it and wander around
1649 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1655 void havocbot_role_ctf_escort(entity this)
1661 havocbot_ctf_reset_role(this);
1665 if (this.flagcarried)
1667 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1671 // If enemy flag is back on the base switch to previous role
1672 ef = havocbot_ctf_find_enemy_flag(this);
1673 if(ef.ctf_status==FLAG_BASE)
1675 this.havocbot_role = this.havocbot_previous_role;
1676 this.havocbot_role_timeout = 0;
1680 // If the flag carrier reached the base switch to defense
1681 mf = havocbot_ctf_find_flag(this);
1682 if(mf.ctf_status!=FLAG_BASE)
1683 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1685 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1689 // Set the role timeout if necessary
1690 if (!this.havocbot_role_timeout)
1692 this.havocbot_role_timeout = time + random() * 30 + 60;
1695 // If nothing happened just switch to previous role
1696 if (time > this.havocbot_role_timeout)
1698 this.havocbot_role = this.havocbot_previous_role;
1699 this.havocbot_role_timeout = 0;
1703 // Chase the flag carrier
1704 if (this.bot_strategytime < time)
1706 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1707 navigation_goalrating_start(this);
1708 havocbot_goalrating_ctf_enemyflag(this, 30000);
1709 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1710 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1711 navigation_goalrating_end(this);
1715 void havocbot_role_ctf_offense(entity this)
1722 havocbot_ctf_reset_role(this);
1726 if (this.flagcarried)
1728 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1733 mf = havocbot_ctf_find_flag(this);
1734 ef = havocbot_ctf_find_enemy_flag(this);
1737 if(mf.ctf_status!=FLAG_BASE)
1740 pos = mf.tag_entity.origin;
1744 // Try to get it if closer than the enemy base
1745 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1747 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1752 // Escort flag carrier
1753 if(ef.ctf_status!=FLAG_BASE)
1756 pos = ef.tag_entity.origin;
1760 if(vdist(pos - mf.dropped_origin, >, 700))
1762 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1767 // About to fail, switch to middlefield
1770 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1774 // Set the role timeout if necessary
1775 if (!this.havocbot_role_timeout)
1776 this.havocbot_role_timeout = time + 120;
1778 if (time > this.havocbot_role_timeout)
1780 havocbot_ctf_reset_role(this);
1784 if (this.bot_strategytime < time)
1786 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1787 navigation_goalrating_start(this);
1788 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1789 havocbot_goalrating_ctf_enemybase(this, 20000);
1790 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1791 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1792 navigation_goalrating_end(this);
1796 // Retriever (temporary role):
1797 void havocbot_role_ctf_retriever(entity this)
1803 havocbot_ctf_reset_role(this);
1807 if (this.flagcarried)
1809 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1813 // If flag is back on the base switch to previous role
1814 mf = havocbot_ctf_find_flag(this);
1815 if(mf.ctf_status==FLAG_BASE)
1817 havocbot_ctf_reset_role(this);
1821 if (!this.havocbot_role_timeout)
1822 this.havocbot_role_timeout = time + 20;
1824 if (time > this.havocbot_role_timeout)
1826 havocbot_ctf_reset_role(this);
1830 if (this.bot_strategytime < time)
1835 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1836 navigation_goalrating_start(this);
1837 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1838 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1839 havocbot_goalrating_ctf_enemybase(this, 30000);
1840 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1841 navigation_goalrating_end(this);
1845 void havocbot_role_ctf_middle(entity this)
1851 havocbot_ctf_reset_role(this);
1855 if (this.flagcarried)
1857 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1861 mf = havocbot_ctf_find_flag(this);
1862 if(mf.ctf_status!=FLAG_BASE)
1864 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1868 if (!this.havocbot_role_timeout)
1869 this.havocbot_role_timeout = time + 10;
1871 if (time > this.havocbot_role_timeout)
1873 havocbot_ctf_reset_role(this);
1877 if (this.bot_strategytime < time)
1881 org = havocbot_ctf_middlepoint;
1882 org.z = this.origin.z;
1884 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1885 navigation_goalrating_start(this);
1886 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1887 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1888 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1889 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1890 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1891 havocbot_goalrating_ctf_enemybase(this, 2500);
1892 navigation_goalrating_end(this);
1896 void havocbot_role_ctf_defense(entity this)
1902 havocbot_ctf_reset_role(this);
1906 if (this.flagcarried)
1908 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1912 // If own flag was captured
1913 mf = havocbot_ctf_find_flag(this);
1914 if(mf.ctf_status!=FLAG_BASE)
1916 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1920 if (!this.havocbot_role_timeout)
1921 this.havocbot_role_timeout = time + 30;
1923 if (time > this.havocbot_role_timeout)
1925 havocbot_ctf_reset_role(this);
1928 if (this.bot_strategytime < time)
1933 org = mf.dropped_origin;
1934 mp_radius = havocbot_ctf_middlepoint_radius;
1936 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1937 navigation_goalrating_start(this);
1939 // if enemies are closer to our base, go there
1940 entity closestplayer = NULL;
1941 float distance, bestdistance = 10000;
1942 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1943 distance = vlen(org - it.origin);
1944 if(distance<bestdistance)
1947 bestdistance = distance;
1952 if(DIFF_TEAM(closestplayer, this))
1953 if(vdist(org - this.origin, >, 1000))
1954 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1955 havocbot_goalrating_ctf_ourbase(this, 30000);
1957 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1958 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1959 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1960 havocbot_goalrating_items(this, 10000, org, mp_radius);
1961 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1962 navigation_goalrating_end(this);
1966 void havocbot_role_ctf_setrole(entity bot, int role)
1968 LOG_TRACE(strcat(bot.netname," switched to "));
1971 case HAVOCBOT_CTF_ROLE_CARRIER:
1972 LOG_TRACE("carrier");
1973 bot.havocbot_role = havocbot_role_ctf_carrier;
1974 bot.havocbot_role_timeout = 0;
1975 bot.havocbot_cantfindflag = time + 10;
1976 bot.bot_strategytime = 0;
1978 case HAVOCBOT_CTF_ROLE_DEFENSE:
1979 LOG_TRACE("defense");
1980 bot.havocbot_role = havocbot_role_ctf_defense;
1981 bot.havocbot_role_timeout = 0;
1983 case HAVOCBOT_CTF_ROLE_MIDDLE:
1984 LOG_TRACE("middle");
1985 bot.havocbot_role = havocbot_role_ctf_middle;
1986 bot.havocbot_role_timeout = 0;
1988 case HAVOCBOT_CTF_ROLE_OFFENSE:
1989 LOG_TRACE("offense");
1990 bot.havocbot_role = havocbot_role_ctf_offense;
1991 bot.havocbot_role_timeout = 0;
1993 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1994 LOG_TRACE("retriever");
1995 bot.havocbot_previous_role = bot.havocbot_role;
1996 bot.havocbot_role = havocbot_role_ctf_retriever;
1997 bot.havocbot_role_timeout = time + 10;
1998 bot.bot_strategytime = 0;
2000 case HAVOCBOT_CTF_ROLE_ESCORT:
2001 LOG_TRACE("escort");
2002 bot.havocbot_previous_role = bot.havocbot_role;
2003 bot.havocbot_role = havocbot_role_ctf_escort;
2004 bot.havocbot_role_timeout = time + 30;
2005 bot.bot_strategytime = 0;
2016 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2018 entity player = M_ARGV(0, entity);
2020 int t = 0, t2 = 0, t3 = 0;
2022 // initially clear items so they can be set as necessary later.
2023 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2024 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2025 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2026 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2027 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2028 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2030 // scan through all the flags and notify the client about them
2031 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2033 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2034 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2035 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2036 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2037 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; }
2039 switch(flag.ctf_status)
2044 if((flag.owner == player) || (flag.pass_sender == player))
2045 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2047 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2052 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2058 // item for stopping players from capturing the flag too often
2059 if(player.ctf_captureshielded)
2060 player.ctf_flagstatus |= CTF_SHIELDED;
2063 player.ctf_flagstatus |= CTF_STALEMATE;
2065 // update the health of the flag carrier waypointsprite
2066 if(player.wps_flagcarrier)
2067 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2070 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2072 entity frag_attacker = M_ARGV(1, entity);
2073 entity frag_target = M_ARGV(2, entity);
2074 float frag_damage = M_ARGV(4, float);
2075 vector frag_force = M_ARGV(6, vector);
2077 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2079 if(frag_target == frag_attacker) // damage done to yourself
2081 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2082 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2084 else // damage done to everyone else
2086 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2087 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2090 M_ARGV(4, float) = frag_damage;
2091 M_ARGV(6, vector) = frag_force;
2093 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2095 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)))
2096 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2098 frag_target.wps_helpme_time = time;
2099 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2101 // todo: add notification for when flag carrier needs help?
2105 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2107 entity frag_attacker = M_ARGV(1, entity);
2108 entity frag_target = M_ARGV(2, entity);
2110 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2112 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2113 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2116 if(frag_target.flagcarried)
2118 entity tmp_entity = frag_target.flagcarried;
2119 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2120 tmp_entity.ctf_dropper = NULL;
2124 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2126 M_ARGV(2, float) = 0; // frag score
2127 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2130 void ctf_RemovePlayer(entity player)
2132 if(player.flagcarried)
2133 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2135 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2137 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2138 if(flag.pass_target == player) { flag.pass_target = NULL; }
2139 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2143 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2145 entity player = M_ARGV(0, entity);
2147 ctf_RemovePlayer(player);
2150 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2152 entity player = M_ARGV(0, entity);
2154 ctf_RemovePlayer(player);
2157 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2159 entity player = M_ARGV(0, entity);
2161 if(player.flagcarried)
2162 if(!autocvar_g_ctf_portalteleport)
2163 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2166 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2168 if(MUTATOR_RETURNVALUE || gameover) { return; }
2170 entity player = M_ARGV(0, entity);
2172 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2174 // pass the flag to a team mate
2175 if(autocvar_g_ctf_pass)
2177 entity head, closest_target = NULL;
2178 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2180 while(head) // find the closest acceptable target to pass to
2182 if(IS_PLAYER(head) && !IS_DEAD(head))
2183 if(head != player && SAME_TEAM(head, player))
2184 if(!head.speedrunning && !head.vehicle)
2186 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2187 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2188 vector passer_center = CENTER_OR_VIEWOFS(player);
2190 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2192 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2194 if(IS_BOT_CLIENT(head))
2196 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2197 ctf_Handle_Throw(head, player, DROP_PASS);
2201 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2202 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2204 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2207 else if(player.flagcarried)
2211 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2212 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2213 { closest_target = head; }
2215 else { closest_target = head; }
2222 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2225 // throw the flag in front of you
2226 if(autocvar_g_ctf_throw && player.flagcarried)
2228 if(player.throw_count == -1)
2230 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2232 player.throw_prevtime = time;
2233 player.throw_count = 1;
2234 ctf_Handle_Throw(player, NULL, DROP_THROW);
2239 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2245 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2246 else { player.throw_count += 1; }
2247 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2249 player.throw_prevtime = time;
2250 ctf_Handle_Throw(player, NULL, DROP_THROW);
2257 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2259 entity player = M_ARGV(0, entity);
2261 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2263 player.wps_helpme_time = time;
2264 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2266 else // create a normal help me waypointsprite
2268 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2269 WaypointSprite_Ping(player.wps_helpme);
2275 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2277 entity player = M_ARGV(0, entity);
2278 entity veh = M_ARGV(1, entity);
2280 if(player.flagcarried)
2282 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2284 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2288 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2289 setattachment(player.flagcarried, veh, "");
2290 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2291 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2292 //player.flagcarried.angles = '0 0 0';
2298 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2300 entity player = M_ARGV(0, entity);
2302 if(player.flagcarried)
2304 setattachment(player.flagcarried, player, "");
2305 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2306 player.flagcarried.scale = FLAG_SCALE;
2307 player.flagcarried.angles = '0 0 0';
2308 player.flagcarried.nodrawtoclient = NULL;
2313 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2315 entity player = M_ARGV(0, entity);
2317 if(player.flagcarried)
2319 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((player.flagcarried.team) ? APP_TEAM_ENT(player.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
2320 ctf_RespawnFlag(player.flagcarried);
2325 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2327 entity flag; // temporary entity for the search method
2329 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2331 switch(flag.ctf_status)
2336 // lock the flag, game is over
2337 set_movetype(flag, MOVETYPE_NONE);
2338 flag.takedamage = DAMAGE_NO;
2339 flag.solid = SOLID_NOT;
2340 flag.nextthink = false; // stop thinking
2342 //dprint("stopping the ", flag.netname, " from moving.\n");
2350 // do nothing for these flags
2357 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2359 entity bot = M_ARGV(0, entity);
2361 havocbot_ctf_reset_role(bot);
2365 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2367 //M_ARGV(0, float) = ctf_teams;
2368 M_ARGV(1, string) = "ctf_team";
2372 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2374 entity spectatee = M_ARGV(0, entity);
2375 entity client = M_ARGV(1, entity);
2377 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2380 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2382 int record_page = M_ARGV(0, int);
2383 string ret_string = M_ARGV(1, string);
2385 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2387 if (MapInfo_Get_ByID(i))
2389 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2395 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2396 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2400 M_ARGV(1, string) = ret_string;
2403 bool superspec_Spectate(entity this, entity targ); // TODO
2404 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2405 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2407 entity player = M_ARGV(0, entity);
2408 string cmd_name = M_ARGV(1, string);
2409 int cmd_argc = M_ARGV(2, int);
2411 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2413 if(cmd_name == "followfc")
2425 case "red": _team = NUM_TEAM_1; break;
2426 case "blue": _team = NUM_TEAM_2; break;
2427 case "yellow": if(ctf_teams >= 3) _team = NUM_TEAM_3; break;
2428 case "pink": if(ctf_teams >= 4) _team = NUM_TEAM_4; break;
2432 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2433 if(it.flagcarried && (it.team == _team || _team == 0))
2436 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2437 continue; // already spectating this fc, try another
2438 return superspec_Spectate(player, it);
2443 superspec_msg("", "", player, "No active flag carrier\n", 1);
2448 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2450 entity frag_target = M_ARGV(0, entity);
2452 if(frag_target.flagcarried)
2453 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2461 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2462 CTF flag for team one (Red).
2464 "angle" Angle the flag will point (minus 90 degrees)...
2465 "model" model to use, note this needs red and blue as skins 0 and 1...
2466 "noise" sound played when flag is picked up...
2467 "noise1" sound played when flag is returned by a teammate...
2468 "noise2" sound played when flag is captured...
2469 "noise3" sound played when flag is lost in the field and respawns itself...
2470 "noise4" sound played when flag is dropped by a player...
2471 "noise5" sound played when flag touches the ground... */
2472 spawnfunc(item_flag_team1)
2474 if(!g_ctf) { remove(this); return; }
2476 ctf_FlagSetup(NUM_TEAM_1, this);
2479 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2480 CTF flag for team two (Blue).
2482 "angle" Angle the flag will point (minus 90 degrees)...
2483 "model" model to use, note this needs red and blue as skins 0 and 1...
2484 "noise" sound played when flag is picked up...
2485 "noise1" sound played when flag is returned by a teammate...
2486 "noise2" sound played when flag is captured...
2487 "noise3" sound played when flag is lost in the field and respawns itself...
2488 "noise4" sound played when flag is dropped by a player...
2489 "noise5" sound played when flag touches the ground... */
2490 spawnfunc(item_flag_team2)
2492 if(!g_ctf) { remove(this); return; }
2494 ctf_FlagSetup(NUM_TEAM_2, this);
2497 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2498 CTF flag for team three (Yellow).
2500 "angle" Angle the flag will point (minus 90 degrees)...
2501 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2502 "noise" sound played when flag is picked up...
2503 "noise1" sound played when flag is returned by a teammate...
2504 "noise2" sound played when flag is captured...
2505 "noise3" sound played when flag is lost in the field and respawns itself...
2506 "noise4" sound played when flag is dropped by a player...
2507 "noise5" sound played when flag touches the ground... */
2508 spawnfunc(item_flag_team3)
2510 if(!g_ctf) { remove(this); return; }
2512 ctf_FlagSetup(NUM_TEAM_3, this);
2515 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2516 CTF flag for team four (Pink).
2518 "angle" Angle the flag will point (minus 90 degrees)...
2519 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2520 "noise" sound played when flag is picked up...
2521 "noise1" sound played when flag is returned by a teammate...
2522 "noise2" sound played when flag is captured...
2523 "noise3" sound played when flag is lost in the field and respawns itself...
2524 "noise4" sound played when flag is dropped by a player...
2525 "noise5" sound played when flag touches the ground... */
2526 spawnfunc(item_flag_team4)
2528 if(!g_ctf) { remove(this); return; }
2530 ctf_FlagSetup(NUM_TEAM_4, this);
2533 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2536 "angle" Angle the flag will point (minus 90 degrees)...
2537 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2538 "noise" sound played when flag is picked up...
2539 "noise1" sound played when flag is returned by a teammate...
2540 "noise2" sound played when flag is captured...
2541 "noise3" sound played when flag is lost in the field and respawns itself...
2542 "noise4" sound played when flag is dropped by a player...
2543 "noise5" sound played when flag touches the ground... */
2544 spawnfunc(item_flag_neutral)
2546 if(!g_ctf) { remove(this); return; }
2547 if(!cvar("g_ctf_oneflag")) { remove(this); return; }
2549 ctf_FlagSetup(0, this);
2552 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2553 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2554 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.
2556 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2557 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2560 if(!g_ctf) { remove(this); return; }
2562 this.classname = "ctf_team";
2563 this.team = this.cnt + 1;
2566 // compatibility for quake maps
2567 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2568 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2569 spawnfunc(info_player_team1);
2570 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2571 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2572 spawnfunc(info_player_team2);
2573 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2574 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2576 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2577 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2585 void ctf_ScoreRules(int teams)
2587 CheckAllowedTeams(NULL);
2588 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2589 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2590 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2591 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2592 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2593 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2594 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2595 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2596 ScoreRules_basics_end();
2599 // code from here on is just to support maps that don't have flag and team entities
2600 void ctf_SpawnTeam (string teamname, int teamcolor)
2602 entity this = new_pure(ctf_team);
2603 this.netname = teamname;
2604 this.cnt = teamcolor - 1;
2605 this.spawnfunc_checked = true;
2606 this.team = teamcolor;
2609 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2614 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2616 if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2617 if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2618 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2621 ctf_teams = bound(2, ctf_teams, 4);
2623 // if no teams are found, spawn defaults
2624 if(find(NULL, classname, "ctf_team") == NULL)
2626 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.\n");
2627 ctf_SpawnTeam("Red", NUM_TEAM_1);
2628 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2630 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2632 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2635 ctf_ScoreRules(ctf_teams);
2638 void ctf_Initialize()
2640 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2642 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2643 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2644 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2646 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);