1 #include "gamemode_ctf.qh"
6 REGISTER_MUTATOR(ctf, false)
10 if (time > 1) // game loads at time 1
11 error("This is a game type and it cannot be added at runtime.");
15 SetLimits(autocvar_capturelimit_override, autocvar_captureleadlimit_override, autocvar_timelimit_override, -1);
16 have_team_spawns = -1; // request team spawns
19 MUTATOR_ONROLLBACK_OR_REMOVE
21 // we actually cannot roll back ctf_Initialize here
22 // BUT: we don't need to! If this gets called, adding always
28 LOG_INFO("This is a game type and it cannot be removed at runtime.");
37 #include <common/vehicles/all.qh>
38 #include <server/teamplay.qh>
41 #include <lib/warpzone/common.qh>
43 bool autocvar_g_ctf_allow_vehicle_carry;
44 bool autocvar_g_ctf_allow_vehicle_touch;
45 bool autocvar_g_ctf_allow_monster_touch;
46 bool autocvar_g_ctf_throw;
47 float autocvar_g_ctf_throw_angle_max;
48 float autocvar_g_ctf_throw_angle_min;
49 int autocvar_g_ctf_throw_punish_count;
50 float autocvar_g_ctf_throw_punish_delay;
51 float autocvar_g_ctf_throw_punish_time;
52 float autocvar_g_ctf_throw_strengthmultiplier;
53 float autocvar_g_ctf_throw_velocity_forward;
54 float autocvar_g_ctf_throw_velocity_up;
55 float autocvar_g_ctf_drop_velocity_up;
56 float autocvar_g_ctf_drop_velocity_side;
57 bool autocvar_g_ctf_oneflag_reverse;
58 bool autocvar_g_ctf_portalteleport;
59 bool autocvar_g_ctf_pass;
60 float autocvar_g_ctf_pass_arc;
61 float autocvar_g_ctf_pass_arc_max;
62 float autocvar_g_ctf_pass_directional_max;
63 float autocvar_g_ctf_pass_directional_min;
64 float autocvar_g_ctf_pass_radius;
65 float autocvar_g_ctf_pass_wait;
66 bool autocvar_g_ctf_pass_request;
67 float autocvar_g_ctf_pass_turnrate;
68 float autocvar_g_ctf_pass_timelimit;
69 float autocvar_g_ctf_pass_velocity;
70 bool autocvar_g_ctf_dynamiclights;
71 float autocvar_g_ctf_flag_collect_delay;
72 float autocvar_g_ctf_flag_damageforcescale;
73 bool autocvar_g_ctf_flag_dropped_waypoint;
74 bool autocvar_g_ctf_flag_dropped_floatinwater;
75 bool autocvar_g_ctf_flag_glowtrails;
76 int autocvar_g_ctf_flag_health;
77 bool autocvar_g_ctf_flag_return;
78 bool autocvar_g_ctf_flag_return_carrying;
79 float autocvar_g_ctf_flag_return_carried_radius;
80 float autocvar_g_ctf_flag_return_time;
81 bool autocvar_g_ctf_flag_return_when_unreachable;
82 float autocvar_g_ctf_flag_return_damage;
83 float autocvar_g_ctf_flag_return_damage_delay;
84 float autocvar_g_ctf_flag_return_dropped;
85 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
86 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
87 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
88 float autocvar_g_ctf_flagcarrier_selfforcefactor;
89 float autocvar_g_ctf_flagcarrier_damagefactor;
90 float autocvar_g_ctf_flagcarrier_forcefactor;
91 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
92 bool autocvar_g_ctf_fullbrightflags;
93 bool autocvar_g_ctf_ignore_frags;
94 int autocvar_g_ctf_score_capture;
95 int autocvar_g_ctf_score_capture_assist;
96 int autocvar_g_ctf_score_kill;
97 int autocvar_g_ctf_score_penalty_drop;
98 int autocvar_g_ctf_score_penalty_returned;
99 int autocvar_g_ctf_score_pickup_base;
100 int autocvar_g_ctf_score_pickup_dropped_early;
101 int autocvar_g_ctf_score_pickup_dropped_late;
102 int autocvar_g_ctf_score_return;
103 float autocvar_g_ctf_shield_force;
104 float autocvar_g_ctf_shield_max_ratio;
105 int autocvar_g_ctf_shield_min_negscore;
106 bool autocvar_g_ctf_stalemate;
107 int autocvar_g_ctf_stalemate_endcondition;
108 float autocvar_g_ctf_stalemate_time;
109 bool autocvar_g_ctf_reverse;
110 float autocvar_g_ctf_dropped_capture_delay;
111 float autocvar_g_ctf_dropped_capture_radius;
113 void ctf_FakeTimeLimit(entity e, float t)
116 WriteByte(MSG_ONE, 3); // svc_updatestat
117 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
119 WriteCoord(MSG_ONE, autocvar_timelimit);
121 WriteCoord(MSG_ONE, (t + 1) / 60);
124 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
126 if(autocvar_sv_eventlog)
127 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
128 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
131 void ctf_CaptureRecord(entity flag, entity player)
133 float cap_record = ctf_captimerecord;
134 float cap_time = (time - flag.ctf_pickuptime);
135 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
138 if(ctf_oneflag) { Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
139 else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_CAPTURE_TIME), player.netname, (cap_time * 100)); }
140 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)); }
141 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)); }
143 // write that shit in the database
144 if(!ctf_oneflag) // but not in 1-flag mode
145 if((!ctf_captimerecord) || (cap_time < cap_record))
147 ctf_captimerecord = cap_time;
148 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
149 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
150 write_recordmarker(player, (time - cap_time), cap_time);
154 bool ctf_Return_Customize(entity this, entity client)
156 // only to the carrier
157 return boolean(client == this.owner);
160 void ctf_FlagcarrierWaypoints(entity player)
162 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
163 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
164 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
165 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
167 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
169 if(!player.wps_enemyflagcarrier)
171 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
172 wp.colormod = WPCOLOR_ENEMYFC(player.team);
173 setcefc(wp, ctf_Stalemate_Customize);
175 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
176 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
179 if(!player.wps_flagreturn)
181 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
182 owp.colormod = '0 0.8 0.8';
183 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
184 setcefc(owp, ctf_Return_Customize);
189 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
191 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
192 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
193 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
194 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
197 if(current_height) // make sure we can actually do this arcing path
199 targpos = (to + ('0 0 1' * current_height));
200 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
201 if(trace_fraction < 1)
203 //print("normal arc line failed, trying to find new pos...");
204 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
205 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
206 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
207 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
208 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
211 else { targpos = to; }
213 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
215 vector desired_direction = normalize(targpos - from);
216 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
217 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
220 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
222 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
224 // directional tracing only
226 makevectors(passer_angle);
228 // find the closest point on the enemy to the center of the attack
229 float h; // hypotenuse, which is the distance between attacker to head
230 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
232 h = vlen(head_center - passer_center);
233 a = h * (normalize(head_center - passer_center) * v_forward);
235 vector nearest_on_line = (passer_center + a * v_forward);
236 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
238 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
239 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
241 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
246 else { return true; }
250 // =======================
251 // CaptureShield Functions
252 // =======================
254 bool ctf_CaptureShield_CheckStatus(entity p)
256 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
257 int players_worseeq, players_total;
259 if(ctf_captureshield_max_ratio <= 0)
262 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
263 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
264 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
265 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
267 sr = ((s - s2) + (s3 + s4));
269 if(sr >= -ctf_captureshield_min_negscore)
272 players_total = players_worseeq = 0;
273 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
276 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
277 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
278 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
279 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
281 ser = ((se - se2) + (se3 + se4));
288 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
289 // use this rule here
291 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
297 void ctf_CaptureShield_Update(entity player, bool wanted_status)
299 bool updated_status = ctf_CaptureShield_CheckStatus(player);
300 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
302 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
303 player.ctf_captureshielded = updated_status;
307 bool ctf_CaptureShield_Customize(entity this, entity client)
309 if(!client.ctf_captureshielded) { return false; }
310 if(CTF_SAMETEAM(this, client)) { return false; }
315 void ctf_CaptureShield_Touch(entity this, entity toucher)
317 if(!toucher.ctf_captureshielded) { return; }
318 if(CTF_SAMETEAM(this, toucher)) { return; }
320 vector mymid = (this.absmin + this.absmax) * 0.5;
321 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
323 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
324 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
327 void ctf_CaptureShield_Spawn(entity flag)
329 entity shield = new(ctf_captureshield);
332 shield.team = flag.team;
333 settouch(shield, ctf_CaptureShield_Touch);
334 setcefc(shield, ctf_CaptureShield_Customize);
335 shield.effects = EF_ADDITIVE;
336 set_movetype(shield, MOVETYPE_NOCLIP);
337 shield.solid = SOLID_TRIGGER;
338 shield.avelocity = '7 0 11';
341 setorigin(shield, flag.origin);
342 setmodel(shield, MDL_CTF_SHIELD);
343 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
347 // ====================
348 // Drop/Pass/Throw Code
349 // ====================
351 void ctf_Handle_Drop(entity flag, entity player, int droptype)
354 player = (player ? player : flag.pass_sender);
357 set_movetype(flag, MOVETYPE_TOSS);
358 flag.takedamage = DAMAGE_YES;
359 flag.angles = '0 0 0';
360 flag.health = flag.max_flag_health;
361 flag.ctf_droptime = time;
362 flag.ctf_dropper = player;
363 flag.ctf_status = FLAG_DROPPED;
365 // messages and sounds
366 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_LOST) : INFO_CTF_LOST_NEUTRAL), player.netname);
367 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
368 ctf_EventLog("dropped", player.team, player);
371 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
372 PlayerScore_Add(player, SP_CTF_DROPS, 1);
375 if(autocvar_g_ctf_flag_dropped_waypoint) {
376 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);
377 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
380 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
382 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
383 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
386 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
388 if(droptype == DROP_PASS)
390 flag.pass_distance = 0;
391 flag.pass_sender = NULL;
392 flag.pass_target = NULL;
396 void ctf_Handle_Retrieve(entity flag, entity player)
398 entity sender = flag.pass_sender;
400 // transfer flag to player
402 flag.owner.flagcarried = flag;
407 setattachment(flag, player.vehicle, "");
408 setorigin(flag, VEHICLE_FLAG_OFFSET);
409 flag.scale = VEHICLE_FLAG_SCALE;
413 setattachment(flag, player, "");
414 setorigin(flag, FLAG_CARRY_OFFSET);
416 set_movetype(flag, MOVETYPE_NONE);
417 flag.takedamage = DAMAGE_NO;
418 flag.solid = SOLID_NOT;
419 flag.angles = '0 0 0';
420 flag.ctf_status = FLAG_CARRY;
422 // messages and sounds
423 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
424 ctf_EventLog("receive", flag.team, player);
426 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(
428 Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT(flag, CENTER_CTF_PASS_SENT) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
429 else if(it == player)
430 Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT(flag, CENTER_CTF_PASS_RECEIVED) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
431 else if(SAME_TEAM(it, sender))
432 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);
435 // create new waypoint
436 ctf_FlagcarrierWaypoints(player);
438 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
439 player.throw_antispam = sender.throw_antispam;
441 flag.pass_distance = 0;
442 flag.pass_sender = NULL;
443 flag.pass_target = NULL;
446 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
448 entity flag = player.flagcarried;
449 vector targ_origin, flag_velocity;
451 if(!flag) { return; }
452 if((droptype == DROP_PASS) && !receiver) { return; }
454 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
457 setattachment(flag, NULL, "");
458 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
459 flag.owner.flagcarried = NULL;
461 flag.solid = SOLID_TRIGGER;
462 flag.ctf_dropper = player;
463 flag.ctf_droptime = time;
465 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
472 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
473 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
474 WarpZone_RefSys_Copy(flag, receiver);
475 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
476 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
478 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
479 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
482 set_movetype(flag, MOVETYPE_FLY);
483 flag.takedamage = DAMAGE_NO;
484 flag.pass_sender = player;
485 flag.pass_target = receiver;
486 flag.ctf_status = FLAG_PASSING;
489 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
490 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
491 ctf_EventLog("pass", flag.team, player);
497 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'));
499 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)));
500 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
501 ctf_Handle_Drop(flag, player, droptype);
507 flag.velocity = '0 0 0'; // do nothing
514 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);
515 ctf_Handle_Drop(flag, player, droptype);
520 // kill old waypointsprite
521 WaypointSprite_Ping(player.wps_flagcarrier);
522 WaypointSprite_Kill(player.wps_flagcarrier);
524 if(player.wps_enemyflagcarrier)
525 WaypointSprite_Kill(player.wps_enemyflagcarrier);
527 if(player.wps_flagreturn)
528 WaypointSprite_Kill(player.wps_flagreturn);
531 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
534 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
536 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
543 void nades_GiveBonus(entity player, float score);
545 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
547 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
548 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
549 entity player_team_flag = NULL, tmp_entity;
550 float old_time, new_time;
552 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
553 if(CTF_DIFFTEAM(player, flag)) { return; }
556 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
557 if(SAME_TEAM(tmp_entity, player))
559 player_team_flag = tmp_entity;
563 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
565 player.throw_prevtime = time;
566 player.throw_count = 0;
568 // messages and sounds
569 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT(enemy_flag, CENTER_CTF_CAPTURE) : CENTER_CTF_CAPTURE_NEUTRAL));
570 ctf_CaptureRecord(enemy_flag, player);
571 _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);
575 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
576 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
581 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
582 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
584 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
585 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
586 if(!old_time || new_time < old_time)
587 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
590 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
591 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
594 if(capturetype == CAPTURE_NORMAL)
596 WaypointSprite_Kill(player.wps_flagcarrier);
597 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
599 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
600 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
604 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
605 ctf_RespawnFlag(enemy_flag);
608 void ctf_Handle_Return(entity flag, entity player)
610 // messages and sounds
611 if(IS_MONSTER(player))
613 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_ENT(flag, INFO_CTF_RETURN_MONSTER), player.monster_name);
617 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT(flag, CENTER_CTF_RETURN));
618 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_ENT(flag, INFO_CTF_RETURN), player.netname);
620 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
621 ctf_EventLog("return", flag.team, player);
624 if(IS_PLAYER(player))
626 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
627 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
629 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
632 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
636 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
637 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
638 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
642 if(player.flagcarried == flag)
643 WaypointSprite_Kill(player.wps_flagcarrier);
646 ctf_RespawnFlag(flag);
649 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
652 float pickup_dropped_score; // used to calculate dropped pickup score
654 // attach the flag to the player
656 player.flagcarried = flag;
659 setattachment(flag, player.vehicle, "");
660 setorigin(flag, VEHICLE_FLAG_OFFSET);
661 flag.scale = VEHICLE_FLAG_SCALE;
665 setattachment(flag, player, "");
666 setorigin(flag, FLAG_CARRY_OFFSET);
670 set_movetype(flag, MOVETYPE_NONE);
671 flag.takedamage = DAMAGE_NO;
672 flag.solid = SOLID_NOT;
673 flag.angles = '0 0 0';
674 flag.ctf_status = FLAG_CARRY;
678 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
679 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
683 // messages and sounds
684 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_PICKUP) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
685 if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
686 if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
687 else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT(flag, CENTER_CTF_PICKUP)); }
688 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)); }
690 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);
693 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)));
696 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
697 if(CTF_SAMETEAM(flag, it))
698 if(SAME_TEAM(player, it))
699 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_ENT(flag, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
701 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);
704 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
707 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
708 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
713 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
714 ctf_EventLog("steal", flag.team, player);
720 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);
721 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);
722 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
723 PlayerTeamScore_AddScore(player, pickup_dropped_score);
724 ctf_EventLog("pickup", flag.team, player);
732 if(pickuptype == PICKUP_BASE)
734 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
735 if((player.speedrunning) && (ctf_captimerecord))
736 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
740 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
743 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
744 ctf_FlagcarrierWaypoints(player);
745 WaypointSprite_Ping(player.wps_flagcarrier);
749 // ===================
750 // Main Flag Functions
751 // ===================
753 void ctf_CheckFlagReturn(entity flag, int returntype)
755 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
757 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
759 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
763 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;
764 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;
765 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;
766 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;
770 { Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((flag.team) ? APP_TEAM_ENT(flag, INFO_CTF_FLAGRETURN_TIMEOUT) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
772 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
773 ctf_EventLog("returned", flag.team, NULL);
774 ctf_RespawnFlag(flag);
779 bool ctf_Stalemate_Customize(entity this, entity client)
781 // make spectators see what the player would see
782 entity e = WaypointSprite_getviewentity(client);
783 entity wp_owner = this.owner;
786 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
787 if(SAME_TEAM(wp_owner, e)) { return false; }
788 if(!IS_PLAYER(e)) { return false; }
793 void ctf_CheckStalemate()
796 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
799 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
801 // build list of stale flags
802 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
804 if(autocvar_g_ctf_stalemate)
805 if(tmp_entity.ctf_status != FLAG_BASE)
806 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
808 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
809 ctf_staleflaglist = tmp_entity;
811 switch(tmp_entity.team)
813 case NUM_TEAM_1: ++stale_red_flags; break;
814 case NUM_TEAM_2: ++stale_blue_flags; break;
815 case NUM_TEAM_3: ++stale_yellow_flags; break;
816 case NUM_TEAM_4: ++stale_pink_flags; break;
817 default: ++stale_neutral_flags; break;
823 stale_flags = (stale_neutral_flags >= 1);
825 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
827 if(ctf_oneflag && stale_flags == 1)
828 ctf_stalemate = true;
829 else if(stale_flags >= 2)
830 ctf_stalemate = true;
831 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
832 { ctf_stalemate = false; wpforenemy_announced = false; }
833 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
834 { ctf_stalemate = false; wpforenemy_announced = false; }
836 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
839 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
841 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
843 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);
844 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
845 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
849 if (!wpforenemy_announced)
851 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))));
853 wpforenemy_announced = true;
858 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
860 if(ITEM_DAMAGE_NEEDKILL(deathtype))
862 if(autocvar_g_ctf_flag_return_damage_delay)
864 this.ctf_flagdamaged = true;
869 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
873 if(autocvar_g_ctf_flag_return_damage)
875 // reduce health and check if it should be returned
876 this.health = this.health - damage;
877 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
882 void ctf_FlagThink(entity this)
887 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
890 if(this == ctf_worldflaglist) // only for the first flag
891 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
894 if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
895 LOG_TRACE("wtf the flag got squashed?");
896 tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
897 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
898 setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs); }
900 switch(this.ctf_status) // reset flag angles in case warpzones adjust it
904 this.angles = '0 0 0';
912 switch(this.ctf_status)
916 if(autocvar_g_ctf_dropped_capture_radius)
918 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
919 if(tmp_entity.ctf_status == FLAG_DROPPED)
920 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
921 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
922 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
929 if(autocvar_g_ctf_flag_dropped_floatinwater)
931 vector midpoint = ((this.absmin + this.absmax) * 0.5);
932 if(pointcontents(midpoint) == CONTENT_WATER)
934 this.velocity = this.velocity * 0.5;
936 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
937 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
939 { set_movetype(this, MOVETYPE_FLY); }
941 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
943 if(autocvar_g_ctf_flag_return_dropped)
945 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
948 ctf_CheckFlagReturn(this, RETURN_DROPPED);
952 if(this.ctf_flagdamaged)
954 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
955 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
958 else if(autocvar_g_ctf_flag_return_time)
960 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
961 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
969 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
972 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
974 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
975 ImpulseCommands(this.owner);
977 if(autocvar_g_ctf_stalemate)
979 if(time >= wpforenemy_nextthink)
981 ctf_CheckStalemate();
982 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
985 if(CTF_SAMETEAM(this, this.owner) && this.team)
987 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
988 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
989 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
990 ctf_Handle_Return(this, this.owner);
997 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
998 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
999 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1001 if((this.pass_target == NULL)
1002 || (IS_DEAD(this.pass_target))
1003 || (this.pass_target.flagcarried)
1004 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1005 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1006 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1008 // give up, pass failed
1009 ctf_Handle_Drop(this, NULL, DROP_PASS);
1013 // still a viable target, go for it
1014 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1019 default: // this should never happen
1021 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1027 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1030 if(gameover) { return; }
1031 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1033 bool is_not_monster = (!IS_MONSTER(toucher));
1035 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1036 if(ITEM_TOUCH_NEEDKILL())
1038 if(!autocvar_g_ctf_flag_return_damage_delay)
1041 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1043 if(!flag.ctf_flagdamaged) { return; }
1046 int num_perteam = 0;
1047 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), LAMBDA(++num_perteam));
1049 // special touch behaviors
1050 if(STAT(FROZEN, toucher)) { return; }
1051 else if(IS_VEHICLE(toucher))
1053 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1054 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1056 return; // do nothing
1058 else if(IS_MONSTER(toucher))
1060 if(!autocvar_g_ctf_allow_monster_touch)
1061 return; // do nothing
1063 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1065 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1067 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1068 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1069 flag.wait = time + FLAG_TOUCHRATE;
1073 else if(IS_DEAD(toucher)) { return; }
1075 switch(flag.ctf_status)
1081 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1082 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1083 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1084 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1086 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1087 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1088 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)
1090 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1091 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1093 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1094 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1100 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
1101 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1102 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1103 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1109 LOG_TRACE("Someone touched a flag even though it was being carried?");
1115 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1117 if(DIFF_TEAM(toucher, flag.pass_sender))
1118 ctf_Handle_Return(flag, toucher);
1120 ctf_Handle_Retrieve(flag, toucher);
1127 .float last_respawn;
1128 void ctf_RespawnFlag(entity flag)
1130 // check for flag respawn being called twice in a row
1131 if(flag.last_respawn > time - 0.5)
1132 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1134 flag.last_respawn = time;
1136 // reset the player (if there is one)
1137 if((flag.owner) && (flag.owner.flagcarried == flag))
1139 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1140 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1141 WaypointSprite_Kill(flag.wps_flagcarrier);
1143 flag.owner.flagcarried = NULL;
1145 if(flag.speedrunning)
1146 ctf_FakeTimeLimit(flag.owner, -1);
1149 if((flag.owner) && (flag.owner.vehicle))
1150 flag.scale = FLAG_SCALE;
1152 if(flag.ctf_status == FLAG_DROPPED)
1153 { WaypointSprite_Kill(flag.wps_flagdropped); }
1156 setattachment(flag, NULL, "");
1157 setorigin(flag, flag.ctf_spawnorigin);
1159 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1160 flag.takedamage = DAMAGE_NO;
1161 flag.health = flag.max_flag_health;
1162 flag.solid = SOLID_TRIGGER;
1163 flag.velocity = '0 0 0';
1164 flag.angles = flag.mangle;
1165 flag.flags = FL_ITEM | FL_NOTARGET;
1167 flag.ctf_status = FLAG_BASE;
1169 flag.pass_distance = 0;
1170 flag.pass_sender = NULL;
1171 flag.pass_target = NULL;
1172 flag.ctf_dropper = NULL;
1173 flag.ctf_pickuptime = 0;
1174 flag.ctf_droptime = 0;
1175 flag.ctf_flagdamaged = 0;
1177 ctf_CheckStalemate();
1180 void ctf_Reset(entity this)
1182 if(this.owner && IS_PLAYER(this.owner))
1183 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1185 ctf_RespawnFlag(this);
1188 bool ctf_FlagBase_Customize(entity this, entity client)
1190 if(client.flagcarried && CTF_SAMETEAM(client, client.flagcarried))
1195 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1198 waypoint_spawnforitem_force(this, this.origin);
1199 this.nearestwaypointtimeout = 0; // activate waypointing again
1200 this.bot_basewaypoint = this.nearestwaypoint;
1206 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1207 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1208 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1209 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1210 default: basename = WP_FlagBaseNeutral; break;
1213 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1214 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1215 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1216 setcefc(wp, ctf_FlagBase_Customize);
1218 // captureshield setup
1219 ctf_CaptureShield_Spawn(this);
1224 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1227 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1228 ctf_worldflaglist = flag;
1230 setattachment(flag, NULL, "");
1232 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1233 flag.team = teamnumber;
1234 flag.classname = "item_flag_team";
1235 flag.target = "###item###"; // wut?
1236 flag.flags = FL_ITEM | FL_NOTARGET;
1237 IL_PUSH(g_items, flag);
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 if(flag.damagedbycontents)
1250 IL_PUSH(g_damagedbycontents, flag);
1251 flag.velocity = '0 0 0';
1252 flag.mangle = flag.angles;
1253 flag.reset = ctf_Reset;
1254 settouch(flag, ctf_FlagTouch);
1255 setthink(flag, ctf_FlagThink);
1256 flag.nextthink = time + FLAG_THINKRATE;
1257 flag.ctf_status = FLAG_BASE;
1259 string teamname = Static_Team_ColorName_Lower(teamnumber);
1261 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1262 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1263 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1264 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1265 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1266 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1270 if(flag.s == "") flag.s = b; \
1271 precache_sound(flag.s);
1273 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1274 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1275 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1276 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1277 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1278 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1279 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1283 precache_model(flag.model);
1286 _setmodel(flag, flag.model); // precision set below
1287 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1288 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1290 if(autocvar_g_ctf_flag_glowtrails)
1294 case NUM_TEAM_1: flag.glow_color = 251; break;
1295 case NUM_TEAM_2: flag.glow_color = 210; break;
1296 case NUM_TEAM_3: flag.glow_color = 110; break;
1297 case NUM_TEAM_4: flag.glow_color = 145; break;
1298 default: flag.glow_color = 254; break;
1300 flag.glow_size = 25;
1301 flag.glow_trail = 1;
1304 flag.effects |= EF_LOWPRECISION;
1305 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1306 if(autocvar_g_ctf_dynamiclights)
1310 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1311 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1312 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1313 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1314 default: flag.effects |= EF_DIMLIGHT; break;
1319 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1321 flag.dropped_origin = flag.origin;
1322 flag.noalign = true;
1323 set_movetype(flag, MOVETYPE_NONE);
1325 else // drop to floor, automatically find a platform and set that as spawn origin
1327 flag.noalign = false;
1329 set_movetype(flag, MOVETYPE_NONE);
1332 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1340 // NOTE: LEGACY CODE, needs to be re-written!
1342 void havocbot_calculate_middlepoint()
1346 vector fo = '0 0 0';
1349 f = ctf_worldflaglist;
1354 f = f.ctf_worldflagnext;
1358 havocbot_ctf_middlepoint = s * (1.0 / n);
1359 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1363 entity havocbot_ctf_find_flag(entity bot)
1366 f = ctf_worldflaglist;
1369 if (CTF_SAMETEAM(bot, f))
1371 f = f.ctf_worldflagnext;
1376 entity havocbot_ctf_find_enemy_flag(entity bot)
1379 f = ctf_worldflaglist;
1384 if(CTF_DIFFTEAM(bot, f))
1391 else if(!bot.flagcarried)
1395 else if (CTF_DIFFTEAM(bot, f))
1397 f = f.ctf_worldflagnext;
1402 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1409 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1410 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1413 if(vdist(it.origin - org, <, tc_radius))
1420 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1423 head = ctf_worldflaglist;
1426 if (CTF_SAMETEAM(this, head))
1428 head = head.ctf_worldflagnext;
1431 navigation_routerating(this, head, ratingscale, 10000);
1434 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1437 head = ctf_worldflaglist;
1440 if (CTF_SAMETEAM(this, head))
1442 head = head.ctf_worldflagnext;
1447 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1450 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1453 head = ctf_worldflaglist;
1458 if(CTF_DIFFTEAM(this, head))
1462 if(this.flagcarried)
1465 else if(!this.flagcarried)
1469 else if(CTF_DIFFTEAM(this, head))
1471 head = head.ctf_worldflagnext;
1474 navigation_routerating(this, head, ratingscale, 10000);
1477 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1479 if (!bot_waypoints_for_items)
1481 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1487 head = havocbot_ctf_find_enemy_flag(this);
1492 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1495 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1499 mf = havocbot_ctf_find_flag(this);
1501 if(mf.ctf_status == FLAG_BASE)
1505 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1508 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1511 head = ctf_worldflaglist;
1514 // flag is out in the field
1515 if(head.ctf_status != FLAG_BASE)
1516 if(head.tag_entity==NULL) // dropped
1520 if(vdist(org - head.origin, <, df_radius))
1521 navigation_routerating(this, head, ratingscale, 10000);
1524 navigation_routerating(this, head, ratingscale, 10000);
1527 head = head.ctf_worldflagnext;
1531 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1533 IL_EACH(g_items, it.bot_pickup,
1535 // gather health and armor only
1537 if (it.health || it.armorvalue)
1538 if (vdist(it.origin - org, <, sradius))
1540 // get the value of the item
1541 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1543 navigation_routerating(this, it, t * ratingscale, 500);
1548 void havocbot_ctf_reset_role(entity this)
1550 float cdefense, cmiddle, coffense;
1557 if(havocbot_ctf_middlepoint == '0 0 0')
1558 havocbot_calculate_middlepoint();
1561 if (this.flagcarried)
1563 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1567 mf = havocbot_ctf_find_flag(this);
1568 ef = havocbot_ctf_find_enemy_flag(this);
1570 // Retrieve stolen flag
1571 if(mf.ctf_status!=FLAG_BASE)
1573 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1577 // If enemy flag is taken go to the middle to intercept pursuers
1578 if(ef.ctf_status!=FLAG_BASE)
1580 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1584 // if there is only me on the team switch to offense
1586 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1590 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1594 // Evaluate best position to take
1595 // Count mates on middle position
1596 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1598 // Count mates on defense position
1599 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1601 // Count mates on offense position
1602 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1604 if(cdefense<=coffense)
1605 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1606 else if(coffense<=cmiddle)
1607 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1609 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1612 void havocbot_role_ctf_carrier(entity this)
1616 havocbot_ctf_reset_role(this);
1620 if (this.flagcarried == NULL)
1622 havocbot_ctf_reset_role(this);
1626 if (this.bot_strategytime < time)
1628 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1630 navigation_goalrating_start(this);
1632 havocbot_goalrating_ctf_enemybase(this, 50000);
1634 havocbot_goalrating_ctf_ourbase(this, 50000);
1637 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1639 navigation_goalrating_end(this);
1641 if (this.navigation_hasgoals)
1642 this.havocbot_cantfindflag = time + 10;
1643 else if (time > this.havocbot_cantfindflag)
1645 // Can't navigate to my own base, suicide!
1646 // TODO: drop it and wander around
1647 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1653 void havocbot_role_ctf_escort(entity this)
1659 havocbot_ctf_reset_role(this);
1663 if (this.flagcarried)
1665 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1669 // If enemy flag is back on the base switch to previous role
1670 ef = havocbot_ctf_find_enemy_flag(this);
1671 if(ef.ctf_status==FLAG_BASE)
1673 this.havocbot_role = this.havocbot_previous_role;
1674 this.havocbot_role_timeout = 0;
1678 // If the flag carrier reached the base switch to defense
1679 mf = havocbot_ctf_find_flag(this);
1680 if(mf.ctf_status!=FLAG_BASE)
1681 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1683 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1687 // Set the role timeout if necessary
1688 if (!this.havocbot_role_timeout)
1690 this.havocbot_role_timeout = time + random() * 30 + 60;
1693 // If nothing happened just switch to previous role
1694 if (time > this.havocbot_role_timeout)
1696 this.havocbot_role = this.havocbot_previous_role;
1697 this.havocbot_role_timeout = 0;
1701 // Chase the flag carrier
1702 if (this.bot_strategytime < time)
1704 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1705 navigation_goalrating_start(this);
1706 havocbot_goalrating_ctf_enemyflag(this, 30000);
1707 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1708 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1709 navigation_goalrating_end(this);
1713 void havocbot_role_ctf_offense(entity this)
1720 havocbot_ctf_reset_role(this);
1724 if (this.flagcarried)
1726 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1731 mf = havocbot_ctf_find_flag(this);
1732 ef = havocbot_ctf_find_enemy_flag(this);
1735 if(mf.ctf_status!=FLAG_BASE)
1738 pos = mf.tag_entity.origin;
1742 // Try to get it if closer than the enemy base
1743 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1745 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1750 // Escort flag carrier
1751 if(ef.ctf_status!=FLAG_BASE)
1754 pos = ef.tag_entity.origin;
1758 if(vdist(pos - mf.dropped_origin, >, 700))
1760 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1765 // About to fail, switch to middlefield
1768 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1772 // Set the role timeout if necessary
1773 if (!this.havocbot_role_timeout)
1774 this.havocbot_role_timeout = time + 120;
1776 if (time > this.havocbot_role_timeout)
1778 havocbot_ctf_reset_role(this);
1782 if (this.bot_strategytime < time)
1784 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1785 navigation_goalrating_start(this);
1786 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1787 havocbot_goalrating_ctf_enemybase(this, 20000);
1788 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1789 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1790 navigation_goalrating_end(this);
1794 // Retriever (temporary role):
1795 void havocbot_role_ctf_retriever(entity this)
1801 havocbot_ctf_reset_role(this);
1805 if (this.flagcarried)
1807 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1811 // If flag is back on the base switch to previous role
1812 mf = havocbot_ctf_find_flag(this);
1813 if(mf.ctf_status==FLAG_BASE)
1815 havocbot_ctf_reset_role(this);
1819 if (!this.havocbot_role_timeout)
1820 this.havocbot_role_timeout = time + 20;
1822 if (time > this.havocbot_role_timeout)
1824 havocbot_ctf_reset_role(this);
1828 if (this.bot_strategytime < time)
1833 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1834 navigation_goalrating_start(this);
1835 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1836 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1837 havocbot_goalrating_ctf_enemybase(this, 30000);
1838 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1839 navigation_goalrating_end(this);
1843 void havocbot_role_ctf_middle(entity this)
1849 havocbot_ctf_reset_role(this);
1853 if (this.flagcarried)
1855 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1859 mf = havocbot_ctf_find_flag(this);
1860 if(mf.ctf_status!=FLAG_BASE)
1862 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1866 if (!this.havocbot_role_timeout)
1867 this.havocbot_role_timeout = time + 10;
1869 if (time > this.havocbot_role_timeout)
1871 havocbot_ctf_reset_role(this);
1875 if (this.bot_strategytime < time)
1879 org = havocbot_ctf_middlepoint;
1880 org.z = this.origin.z;
1882 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1883 navigation_goalrating_start(this);
1884 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1885 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1886 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1887 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1888 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1889 havocbot_goalrating_ctf_enemybase(this, 2500);
1890 navigation_goalrating_end(this);
1894 void havocbot_role_ctf_defense(entity this)
1900 havocbot_ctf_reset_role(this);
1904 if (this.flagcarried)
1906 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1910 // If own flag was captured
1911 mf = havocbot_ctf_find_flag(this);
1912 if(mf.ctf_status!=FLAG_BASE)
1914 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1918 if (!this.havocbot_role_timeout)
1919 this.havocbot_role_timeout = time + 30;
1921 if (time > this.havocbot_role_timeout)
1923 havocbot_ctf_reset_role(this);
1926 if (this.bot_strategytime < time)
1931 org = mf.dropped_origin;
1932 mp_radius = havocbot_ctf_middlepoint_radius;
1934 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1935 navigation_goalrating_start(this);
1937 // if enemies are closer to our base, go there
1938 entity closestplayer = NULL;
1939 float distance, bestdistance = 10000;
1940 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1941 distance = vlen(org - it.origin);
1942 if(distance<bestdistance)
1945 bestdistance = distance;
1950 if(DIFF_TEAM(closestplayer, this))
1951 if(vdist(org - this.origin, >, 1000))
1952 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1953 havocbot_goalrating_ctf_ourbase(this, 30000);
1955 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1956 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1957 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1958 havocbot_goalrating_items(this, 10000, org, mp_radius);
1959 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1960 navigation_goalrating_end(this);
1964 void havocbot_role_ctf_setrole(entity bot, int role)
1966 string s = "(null)";
1969 case HAVOCBOT_CTF_ROLE_CARRIER:
1971 bot.havocbot_role = havocbot_role_ctf_carrier;
1972 bot.havocbot_role_timeout = 0;
1973 bot.havocbot_cantfindflag = time + 10;
1974 bot.bot_strategytime = 0;
1976 case HAVOCBOT_CTF_ROLE_DEFENSE:
1978 bot.havocbot_role = havocbot_role_ctf_defense;
1979 bot.havocbot_role_timeout = 0;
1981 case HAVOCBOT_CTF_ROLE_MIDDLE:
1983 bot.havocbot_role = havocbot_role_ctf_middle;
1984 bot.havocbot_role_timeout = 0;
1986 case HAVOCBOT_CTF_ROLE_OFFENSE:
1988 bot.havocbot_role = havocbot_role_ctf_offense;
1989 bot.havocbot_role_timeout = 0;
1991 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1993 bot.havocbot_previous_role = bot.havocbot_role;
1994 bot.havocbot_role = havocbot_role_ctf_retriever;
1995 bot.havocbot_role_timeout = time + 10;
1996 bot.bot_strategytime = 0;
1998 case HAVOCBOT_CTF_ROLE_ESCORT:
2000 bot.havocbot_previous_role = bot.havocbot_role;
2001 bot.havocbot_role = havocbot_role_ctf_escort;
2002 bot.havocbot_role_timeout = time + 30;
2003 bot.bot_strategytime = 0;
2006 LOG_TRACE(bot.netname, " switched to ", s);
2014 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2016 entity player = M_ARGV(0, entity);
2018 int t = 0, t2 = 0, t3 = 0;
2020 // initially clear items so they can be set as necessary later.
2021 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2022 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2023 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2024 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2025 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2026 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2028 // scan through all the flags and notify the client about them
2029 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2031 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2032 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2033 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2034 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2035 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; }
2037 switch(flag.ctf_status)
2042 if((flag.owner == player) || (flag.pass_sender == player))
2043 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2045 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2050 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2056 // item for stopping players from capturing the flag too often
2057 if(player.ctf_captureshielded)
2058 player.ctf_flagstatus |= CTF_SHIELDED;
2061 player.ctf_flagstatus |= CTF_STALEMATE;
2063 // update the health of the flag carrier waypointsprite
2064 if(player.wps_flagcarrier)
2065 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2068 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2070 entity frag_attacker = M_ARGV(1, entity);
2071 entity frag_target = M_ARGV(2, entity);
2072 float frag_damage = M_ARGV(4, float);
2073 vector frag_force = M_ARGV(6, vector);
2075 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2077 if(frag_target == frag_attacker) // damage done to yourself
2079 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2080 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2082 else // damage done to everyone else
2084 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2085 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2088 M_ARGV(4, float) = frag_damage;
2089 M_ARGV(6, vector) = frag_force;
2091 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2093 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)))
2094 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2096 frag_target.wps_helpme_time = time;
2097 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2099 // todo: add notification for when flag carrier needs help?
2103 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2105 entity frag_attacker = M_ARGV(1, entity);
2106 entity frag_target = M_ARGV(2, entity);
2108 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2110 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2111 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2114 if(frag_target.flagcarried)
2116 entity tmp_entity = frag_target.flagcarried;
2117 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2118 tmp_entity.ctf_dropper = NULL;
2122 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2124 M_ARGV(2, float) = 0; // frag score
2125 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2128 void ctf_RemovePlayer(entity player)
2130 if(player.flagcarried)
2131 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2133 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2135 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2136 if(flag.pass_target == player) { flag.pass_target = NULL; }
2137 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2141 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2143 entity player = M_ARGV(0, entity);
2145 ctf_RemovePlayer(player);
2148 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2150 entity player = M_ARGV(0, entity);
2152 ctf_RemovePlayer(player);
2155 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2157 entity player = M_ARGV(0, entity);
2159 if(player.flagcarried)
2160 if(!autocvar_g_ctf_portalteleport)
2161 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2164 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2166 if(MUTATOR_RETURNVALUE || gameover) { return; }
2168 entity player = M_ARGV(0, entity);
2170 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2172 // pass the flag to a team mate
2173 if(autocvar_g_ctf_pass)
2175 entity head, closest_target = NULL;
2176 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2178 while(head) // find the closest acceptable target to pass to
2180 if(IS_PLAYER(head) && !IS_DEAD(head))
2181 if(head != player && SAME_TEAM(head, player))
2182 if(!head.speedrunning && !head.vehicle)
2184 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2185 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2186 vector passer_center = CENTER_OR_VIEWOFS(player);
2188 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2190 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2192 if(IS_BOT_CLIENT(head))
2194 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2195 ctf_Handle_Throw(head, player, DROP_PASS);
2199 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2200 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2202 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2205 else if(player.flagcarried)
2209 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2210 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2211 { closest_target = head; }
2213 else { closest_target = head; }
2220 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2223 // throw the flag in front of you
2224 if(autocvar_g_ctf_throw && player.flagcarried)
2226 if(player.throw_count == -1)
2228 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2230 player.throw_prevtime = time;
2231 player.throw_count = 1;
2232 ctf_Handle_Throw(player, NULL, DROP_THROW);
2237 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2243 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2244 else { player.throw_count += 1; }
2245 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2247 player.throw_prevtime = time;
2248 ctf_Handle_Throw(player, NULL, DROP_THROW);
2255 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2257 entity player = M_ARGV(0, entity);
2259 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2261 player.wps_helpme_time = time;
2262 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2264 else // create a normal help me waypointsprite
2266 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2267 WaypointSprite_Ping(player.wps_helpme);
2273 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2275 entity player = M_ARGV(0, entity);
2276 entity veh = M_ARGV(1, entity);
2278 if(player.flagcarried)
2280 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2282 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2286 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2287 setattachment(player.flagcarried, veh, "");
2288 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2289 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2290 //player.flagcarried.angles = '0 0 0';
2296 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2298 entity player = M_ARGV(0, entity);
2300 if(player.flagcarried)
2302 setattachment(player.flagcarried, player, "");
2303 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2304 player.flagcarried.scale = FLAG_SCALE;
2305 player.flagcarried.angles = '0 0 0';
2306 player.flagcarried.nodrawtoclient = NULL;
2311 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2313 entity player = M_ARGV(0, entity);
2315 if(player.flagcarried)
2317 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((player.flagcarried.team) ? APP_TEAM_ENT(player.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
2318 ctf_RespawnFlag(player.flagcarried);
2323 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2325 entity flag; // temporary entity for the search method
2327 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2329 switch(flag.ctf_status)
2334 // lock the flag, game is over
2335 set_movetype(flag, MOVETYPE_NONE);
2336 flag.takedamage = DAMAGE_NO;
2337 flag.solid = SOLID_NOT;
2338 flag.nextthink = false; // stop thinking
2340 //dprint("stopping the ", flag.netname, " from moving.\n");
2348 // do nothing for these flags
2355 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2357 entity bot = M_ARGV(0, entity);
2359 havocbot_ctf_reset_role(bot);
2363 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2365 //M_ARGV(0, float) = ctf_teams;
2366 M_ARGV(1, string) = "ctf_team";
2370 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2372 entity spectatee = M_ARGV(0, entity);
2373 entity client = M_ARGV(1, entity);
2375 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2378 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2380 int record_page = M_ARGV(0, int);
2381 string ret_string = M_ARGV(1, string);
2383 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2385 if (MapInfo_Get_ByID(i))
2387 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2393 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2394 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2398 M_ARGV(1, string) = ret_string;
2401 bool superspec_Spectate(entity this, entity targ); // TODO
2402 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2403 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2405 entity player = M_ARGV(0, entity);
2406 string cmd_name = M_ARGV(1, string);
2407 int cmd_argc = M_ARGV(2, int);
2409 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2411 if(cmd_name == "followfc")
2423 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2424 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2425 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2426 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2430 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2431 if(it.flagcarried && (it.team == _team || _team == 0))
2434 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2435 continue; // already spectating this fc, try another
2436 return superspec_Spectate(player, it);
2441 superspec_msg("", "", player, "No active flag carrier\n", 1);
2446 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2448 entity frag_target = M_ARGV(0, entity);
2450 if(frag_target.flagcarried)
2451 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2459 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2460 CTF flag for team one (Red).
2462 "angle" Angle the flag will point (minus 90 degrees)...
2463 "model" model to use, note this needs red and blue as skins 0 and 1...
2464 "noise" sound played when flag is picked up...
2465 "noise1" sound played when flag is returned by a teammate...
2466 "noise2" sound played when flag is captured...
2467 "noise3" sound played when flag is lost in the field and respawns itself...
2468 "noise4" sound played when flag is dropped by a player...
2469 "noise5" sound played when flag touches the ground... */
2470 spawnfunc(item_flag_team1)
2472 if(!g_ctf) { delete(this); return; }
2474 ctf_FlagSetup(NUM_TEAM_1, this);
2477 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2478 CTF flag for team two (Blue).
2480 "angle" Angle the flag will point (minus 90 degrees)...
2481 "model" model to use, note this needs red and blue as skins 0 and 1...
2482 "noise" sound played when flag is picked up...
2483 "noise1" sound played when flag is returned by a teammate...
2484 "noise2" sound played when flag is captured...
2485 "noise3" sound played when flag is lost in the field and respawns itself...
2486 "noise4" sound played when flag is dropped by a player...
2487 "noise5" sound played when flag touches the ground... */
2488 spawnfunc(item_flag_team2)
2490 if(!g_ctf) { delete(this); return; }
2492 ctf_FlagSetup(NUM_TEAM_2, this);
2495 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2496 CTF flag for team three (Yellow).
2498 "angle" Angle the flag will point (minus 90 degrees)...
2499 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2500 "noise" sound played when flag is picked up...
2501 "noise1" sound played when flag is returned by a teammate...
2502 "noise2" sound played when flag is captured...
2503 "noise3" sound played when flag is lost in the field and respawns itself...
2504 "noise4" sound played when flag is dropped by a player...
2505 "noise5" sound played when flag touches the ground... */
2506 spawnfunc(item_flag_team3)
2508 if(!g_ctf) { delete(this); return; }
2510 ctf_FlagSetup(NUM_TEAM_3, this);
2513 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2514 CTF flag for team four (Pink).
2516 "angle" Angle the flag will point (minus 90 degrees)...
2517 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2518 "noise" sound played when flag is picked up...
2519 "noise1" sound played when flag is returned by a teammate...
2520 "noise2" sound played when flag is captured...
2521 "noise3" sound played when flag is lost in the field and respawns itself...
2522 "noise4" sound played when flag is dropped by a player...
2523 "noise5" sound played when flag touches the ground... */
2524 spawnfunc(item_flag_team4)
2526 if(!g_ctf) { delete(this); return; }
2528 ctf_FlagSetup(NUM_TEAM_4, this);
2531 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2534 "angle" Angle the flag will point (minus 90 degrees)...
2535 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2536 "noise" sound played when flag is picked up...
2537 "noise1" sound played when flag is returned by a teammate...
2538 "noise2" sound played when flag is captured...
2539 "noise3" sound played when flag is lost in the field and respawns itself...
2540 "noise4" sound played when flag is dropped by a player...
2541 "noise5" sound played when flag touches the ground... */
2542 spawnfunc(item_flag_neutral)
2544 if(!g_ctf) { delete(this); return; }
2545 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2547 ctf_FlagSetup(0, this);
2550 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2551 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2552 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.
2554 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2555 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2558 if(!g_ctf) { delete(this); return; }
2560 this.classname = "ctf_team";
2561 this.team = this.cnt + 1;
2564 // compatibility for quake maps
2565 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2566 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2567 spawnfunc(info_player_team1);
2568 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2569 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2570 spawnfunc(info_player_team2);
2571 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2572 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2574 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2575 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2583 void ctf_ScoreRules(int teams)
2585 CheckAllowedTeams(NULL);
2586 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2587 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2588 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2589 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2590 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2591 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2592 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2593 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2594 ScoreRules_basics_end();
2597 // code from here on is just to support maps that don't have flag and team entities
2598 void ctf_SpawnTeam (string teamname, int teamcolor)
2600 entity this = new_pure(ctf_team);
2601 this.netname = teamname;
2602 this.cnt = teamcolor - 1;
2603 this.spawnfunc_checked = true;
2604 this.team = teamcolor;
2607 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2612 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2614 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2615 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2617 switch(tmp_entity.team)
2619 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2620 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2621 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2622 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2624 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2627 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2629 ctf_teams = 0; // so set the default red and blue teams
2630 BITSET_ASSIGN(ctf_teams, BIT(0));
2631 BITSET_ASSIGN(ctf_teams, BIT(1));
2634 //ctf_teams = bound(2, ctf_teams, 4);
2636 // if no teams are found, spawn defaults
2637 if(find(NULL, classname, "ctf_team") == NULL)
2639 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2640 if(ctf_teams & BIT(0))
2641 ctf_SpawnTeam("Red", NUM_TEAM_1);
2642 if(ctf_teams & BIT(1))
2643 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2644 if(ctf_teams & BIT(2))
2645 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2646 if(ctf_teams & BIT(3))
2647 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2650 ctf_ScoreRules(ctf_teams);
2653 void ctf_Initialize()
2655 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2657 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2658 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2659 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2661 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);