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 flag.solid = SOLID_TRIGGER;
1238 flag.takedamage = DAMAGE_NO;
1239 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1240 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1241 flag.health = flag.max_flag_health;
1242 flag.event_damage = ctf_FlagDamage;
1243 flag.pushable = true;
1244 flag.teleportable = TELEPORT_NORMAL;
1245 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1246 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1247 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1248 if(flag.damagedbycontents)
1249 IL_PUSH(g_damagedbycontents, flag);
1250 flag.velocity = '0 0 0';
1251 flag.mangle = flag.angles;
1252 flag.reset = ctf_Reset;
1253 settouch(flag, ctf_FlagTouch);
1254 setthink(flag, ctf_FlagThink);
1255 flag.nextthink = time + FLAG_THINKRATE;
1256 flag.ctf_status = FLAG_BASE;
1258 string teamname = Static_Team_ColorName_Lower(teamnumber);
1260 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1261 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1262 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1263 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1264 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1265 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1269 if(flag.s == "") flag.s = b; \
1270 precache_sound(flag.s);
1272 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1273 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1274 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1275 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1276 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1277 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1278 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1282 precache_model(flag.model);
1285 _setmodel(flag, flag.model); // precision set below
1286 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1287 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1289 if(autocvar_g_ctf_flag_glowtrails)
1293 case NUM_TEAM_1: flag.glow_color = 251; break;
1294 case NUM_TEAM_2: flag.glow_color = 210; break;
1295 case NUM_TEAM_3: flag.glow_color = 110; break;
1296 case NUM_TEAM_4: flag.glow_color = 145; break;
1297 default: flag.glow_color = 254; break;
1299 flag.glow_size = 25;
1300 flag.glow_trail = 1;
1303 flag.effects |= EF_LOWPRECISION;
1304 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1305 if(autocvar_g_ctf_dynamiclights)
1309 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1310 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1311 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1312 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1313 default: flag.effects |= EF_DIMLIGHT; break;
1318 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1320 flag.dropped_origin = flag.origin;
1321 flag.noalign = true;
1322 set_movetype(flag, MOVETYPE_NONE);
1324 else // drop to floor, automatically find a platform and set that as spawn origin
1326 flag.noalign = false;
1328 set_movetype(flag, MOVETYPE_NONE);
1331 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1339 // NOTE: LEGACY CODE, needs to be re-written!
1341 void havocbot_calculate_middlepoint()
1345 vector fo = '0 0 0';
1348 f = ctf_worldflaglist;
1353 f = f.ctf_worldflagnext;
1357 havocbot_ctf_middlepoint = s * (1.0 / n);
1358 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1362 entity havocbot_ctf_find_flag(entity bot)
1365 f = ctf_worldflaglist;
1368 if (CTF_SAMETEAM(bot, f))
1370 f = f.ctf_worldflagnext;
1375 entity havocbot_ctf_find_enemy_flag(entity bot)
1378 f = ctf_worldflaglist;
1383 if(CTF_DIFFTEAM(bot, f))
1390 else if(!bot.flagcarried)
1394 else if (CTF_DIFFTEAM(bot, f))
1396 f = f.ctf_worldflagnext;
1401 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1408 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1409 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1412 if(vdist(it.origin - org, <, tc_radius))
1419 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1422 head = ctf_worldflaglist;
1425 if (CTF_SAMETEAM(this, head))
1427 head = head.ctf_worldflagnext;
1430 navigation_routerating(this, head, ratingscale, 10000);
1433 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1436 head = ctf_worldflaglist;
1439 if (CTF_SAMETEAM(this, head))
1441 head = head.ctf_worldflagnext;
1446 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1449 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1452 head = ctf_worldflaglist;
1457 if(CTF_DIFFTEAM(this, head))
1461 if(this.flagcarried)
1464 else if(!this.flagcarried)
1468 else if(CTF_DIFFTEAM(this, head))
1470 head = head.ctf_worldflagnext;
1473 navigation_routerating(this, head, ratingscale, 10000);
1476 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1478 if (!bot_waypoints_for_items)
1480 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1486 head = havocbot_ctf_find_enemy_flag(this);
1491 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1494 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1498 mf = havocbot_ctf_find_flag(this);
1500 if(mf.ctf_status == FLAG_BASE)
1504 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1507 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1510 head = ctf_worldflaglist;
1513 // flag is out in the field
1514 if(head.ctf_status != FLAG_BASE)
1515 if(head.tag_entity==NULL) // dropped
1519 if(vdist(org - head.origin, <, df_radius))
1520 navigation_routerating(this, head, ratingscale, 10000);
1523 navigation_routerating(this, head, ratingscale, 10000);
1526 head = head.ctf_worldflagnext;
1530 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1532 FOREACH_ENTITY_FLOAT(bot_pickup, true,
1534 // gather health and armor only
1536 if (it.health || it.armorvalue)
1537 if (vdist(it.origin - org, <, sradius))
1539 // get the value of the item
1540 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1542 navigation_routerating(this, it, t * ratingscale, 500);
1547 void havocbot_ctf_reset_role(entity this)
1549 float cdefense, cmiddle, coffense;
1556 if(havocbot_ctf_middlepoint == '0 0 0')
1557 havocbot_calculate_middlepoint();
1560 if (this.flagcarried)
1562 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1566 mf = havocbot_ctf_find_flag(this);
1567 ef = havocbot_ctf_find_enemy_flag(this);
1569 // Retrieve stolen flag
1570 if(mf.ctf_status!=FLAG_BASE)
1572 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1576 // If enemy flag is taken go to the middle to intercept pursuers
1577 if(ef.ctf_status!=FLAG_BASE)
1579 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1583 // if there is only me on the team switch to offense
1585 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1589 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1593 // Evaluate best position to take
1594 // Count mates on middle position
1595 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1597 // Count mates on defense position
1598 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1600 // Count mates on offense position
1601 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1603 if(cdefense<=coffense)
1604 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1605 else if(coffense<=cmiddle)
1606 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1608 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1611 void havocbot_role_ctf_carrier(entity this)
1615 havocbot_ctf_reset_role(this);
1619 if (this.flagcarried == NULL)
1621 havocbot_ctf_reset_role(this);
1625 if (this.bot_strategytime < time)
1627 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1629 navigation_goalrating_start(this);
1631 havocbot_goalrating_ctf_enemybase(this, 50000);
1633 havocbot_goalrating_ctf_ourbase(this, 50000);
1636 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1638 navigation_goalrating_end(this);
1640 if (this.navigation_hasgoals)
1641 this.havocbot_cantfindflag = time + 10;
1642 else if (time > this.havocbot_cantfindflag)
1644 // Can't navigate to my own base, suicide!
1645 // TODO: drop it and wander around
1646 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1652 void havocbot_role_ctf_escort(entity this)
1658 havocbot_ctf_reset_role(this);
1662 if (this.flagcarried)
1664 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1668 // If enemy flag is back on the base switch to previous role
1669 ef = havocbot_ctf_find_enemy_flag(this);
1670 if(ef.ctf_status==FLAG_BASE)
1672 this.havocbot_role = this.havocbot_previous_role;
1673 this.havocbot_role_timeout = 0;
1677 // If the flag carrier reached the base switch to defense
1678 mf = havocbot_ctf_find_flag(this);
1679 if(mf.ctf_status!=FLAG_BASE)
1680 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1682 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1686 // Set the role timeout if necessary
1687 if (!this.havocbot_role_timeout)
1689 this.havocbot_role_timeout = time + random() * 30 + 60;
1692 // If nothing happened just switch to previous role
1693 if (time > this.havocbot_role_timeout)
1695 this.havocbot_role = this.havocbot_previous_role;
1696 this.havocbot_role_timeout = 0;
1700 // Chase the flag carrier
1701 if (this.bot_strategytime < time)
1703 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1704 navigation_goalrating_start(this);
1705 havocbot_goalrating_ctf_enemyflag(this, 30000);
1706 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1707 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1708 navigation_goalrating_end(this);
1712 void havocbot_role_ctf_offense(entity this)
1719 havocbot_ctf_reset_role(this);
1723 if (this.flagcarried)
1725 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1730 mf = havocbot_ctf_find_flag(this);
1731 ef = havocbot_ctf_find_enemy_flag(this);
1734 if(mf.ctf_status!=FLAG_BASE)
1737 pos = mf.tag_entity.origin;
1741 // Try to get it if closer than the enemy base
1742 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1744 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1749 // Escort flag carrier
1750 if(ef.ctf_status!=FLAG_BASE)
1753 pos = ef.tag_entity.origin;
1757 if(vdist(pos - mf.dropped_origin, >, 700))
1759 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1764 // About to fail, switch to middlefield
1767 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1771 // Set the role timeout if necessary
1772 if (!this.havocbot_role_timeout)
1773 this.havocbot_role_timeout = time + 120;
1775 if (time > this.havocbot_role_timeout)
1777 havocbot_ctf_reset_role(this);
1781 if (this.bot_strategytime < time)
1783 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1784 navigation_goalrating_start(this);
1785 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1786 havocbot_goalrating_ctf_enemybase(this, 20000);
1787 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1788 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1789 navigation_goalrating_end(this);
1793 // Retriever (temporary role):
1794 void havocbot_role_ctf_retriever(entity this)
1800 havocbot_ctf_reset_role(this);
1804 if (this.flagcarried)
1806 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1810 // If flag is back on the base switch to previous role
1811 mf = havocbot_ctf_find_flag(this);
1812 if(mf.ctf_status==FLAG_BASE)
1814 havocbot_ctf_reset_role(this);
1818 if (!this.havocbot_role_timeout)
1819 this.havocbot_role_timeout = time + 20;
1821 if (time > this.havocbot_role_timeout)
1823 havocbot_ctf_reset_role(this);
1827 if (this.bot_strategytime < time)
1832 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1833 navigation_goalrating_start(this);
1834 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1835 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1836 havocbot_goalrating_ctf_enemybase(this, 30000);
1837 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1838 navigation_goalrating_end(this);
1842 void havocbot_role_ctf_middle(entity this)
1848 havocbot_ctf_reset_role(this);
1852 if (this.flagcarried)
1854 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1858 mf = havocbot_ctf_find_flag(this);
1859 if(mf.ctf_status!=FLAG_BASE)
1861 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1865 if (!this.havocbot_role_timeout)
1866 this.havocbot_role_timeout = time + 10;
1868 if (time > this.havocbot_role_timeout)
1870 havocbot_ctf_reset_role(this);
1874 if (this.bot_strategytime < time)
1878 org = havocbot_ctf_middlepoint;
1879 org.z = this.origin.z;
1881 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1882 navigation_goalrating_start(this);
1883 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1884 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1885 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1886 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1887 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1888 havocbot_goalrating_ctf_enemybase(this, 2500);
1889 navigation_goalrating_end(this);
1893 void havocbot_role_ctf_defense(entity this)
1899 havocbot_ctf_reset_role(this);
1903 if (this.flagcarried)
1905 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1909 // If own flag was captured
1910 mf = havocbot_ctf_find_flag(this);
1911 if(mf.ctf_status!=FLAG_BASE)
1913 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1917 if (!this.havocbot_role_timeout)
1918 this.havocbot_role_timeout = time + 30;
1920 if (time > this.havocbot_role_timeout)
1922 havocbot_ctf_reset_role(this);
1925 if (this.bot_strategytime < time)
1930 org = mf.dropped_origin;
1931 mp_radius = havocbot_ctf_middlepoint_radius;
1933 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1934 navigation_goalrating_start(this);
1936 // if enemies are closer to our base, go there
1937 entity closestplayer = NULL;
1938 float distance, bestdistance = 10000;
1939 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1940 distance = vlen(org - it.origin);
1941 if(distance<bestdistance)
1944 bestdistance = distance;
1949 if(DIFF_TEAM(closestplayer, this))
1950 if(vdist(org - this.origin, >, 1000))
1951 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1952 havocbot_goalrating_ctf_ourbase(this, 30000);
1954 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1955 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1956 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1957 havocbot_goalrating_items(this, 10000, org, mp_radius);
1958 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1959 navigation_goalrating_end(this);
1963 void havocbot_role_ctf_setrole(entity bot, int role)
1965 string s = "(null)";
1968 case HAVOCBOT_CTF_ROLE_CARRIER:
1970 bot.havocbot_role = havocbot_role_ctf_carrier;
1971 bot.havocbot_role_timeout = 0;
1972 bot.havocbot_cantfindflag = time + 10;
1973 bot.bot_strategytime = 0;
1975 case HAVOCBOT_CTF_ROLE_DEFENSE:
1977 bot.havocbot_role = havocbot_role_ctf_defense;
1978 bot.havocbot_role_timeout = 0;
1980 case HAVOCBOT_CTF_ROLE_MIDDLE:
1982 bot.havocbot_role = havocbot_role_ctf_middle;
1983 bot.havocbot_role_timeout = 0;
1985 case HAVOCBOT_CTF_ROLE_OFFENSE:
1987 bot.havocbot_role = havocbot_role_ctf_offense;
1988 bot.havocbot_role_timeout = 0;
1990 case HAVOCBOT_CTF_ROLE_RETRIEVER:
1992 bot.havocbot_previous_role = bot.havocbot_role;
1993 bot.havocbot_role = havocbot_role_ctf_retriever;
1994 bot.havocbot_role_timeout = time + 10;
1995 bot.bot_strategytime = 0;
1997 case HAVOCBOT_CTF_ROLE_ESCORT:
1999 bot.havocbot_previous_role = bot.havocbot_role;
2000 bot.havocbot_role = havocbot_role_ctf_escort;
2001 bot.havocbot_role_timeout = time + 30;
2002 bot.bot_strategytime = 0;
2005 LOG_TRACE(bot.netname, " switched to ", s);
2013 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2015 entity player = M_ARGV(0, entity);
2017 int t = 0, t2 = 0, t3 = 0;
2019 // initially clear items so they can be set as necessary later.
2020 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2021 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2022 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2023 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2024 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2025 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2027 // scan through all the flags and notify the client about them
2028 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2030 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2031 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2032 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2033 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2034 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; }
2036 switch(flag.ctf_status)
2041 if((flag.owner == player) || (flag.pass_sender == player))
2042 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2044 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2049 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2055 // item for stopping players from capturing the flag too often
2056 if(player.ctf_captureshielded)
2057 player.ctf_flagstatus |= CTF_SHIELDED;
2060 player.ctf_flagstatus |= CTF_STALEMATE;
2062 // update the health of the flag carrier waypointsprite
2063 if(player.wps_flagcarrier)
2064 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2067 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2069 entity frag_attacker = M_ARGV(1, entity);
2070 entity frag_target = M_ARGV(2, entity);
2071 float frag_damage = M_ARGV(4, float);
2072 vector frag_force = M_ARGV(6, vector);
2074 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2076 if(frag_target == frag_attacker) // damage done to yourself
2078 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2079 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2081 else // damage done to everyone else
2083 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2084 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2087 M_ARGV(4, float) = frag_damage;
2088 M_ARGV(6, vector) = frag_force;
2090 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2092 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)))
2093 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2095 frag_target.wps_helpme_time = time;
2096 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2098 // todo: add notification for when flag carrier needs help?
2102 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2104 entity frag_attacker = M_ARGV(1, entity);
2105 entity frag_target = M_ARGV(2, entity);
2107 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2109 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2110 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2113 if(frag_target.flagcarried)
2115 entity tmp_entity = frag_target.flagcarried;
2116 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2117 tmp_entity.ctf_dropper = NULL;
2121 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2123 M_ARGV(2, float) = 0; // frag score
2124 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2127 void ctf_RemovePlayer(entity player)
2129 if(player.flagcarried)
2130 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2132 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2134 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2135 if(flag.pass_target == player) { flag.pass_target = NULL; }
2136 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2140 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2142 entity player = M_ARGV(0, entity);
2144 ctf_RemovePlayer(player);
2147 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2149 entity player = M_ARGV(0, entity);
2151 ctf_RemovePlayer(player);
2154 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2156 entity player = M_ARGV(0, entity);
2158 if(player.flagcarried)
2159 if(!autocvar_g_ctf_portalteleport)
2160 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2163 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2165 if(MUTATOR_RETURNVALUE || gameover) { return; }
2167 entity player = M_ARGV(0, entity);
2169 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2171 // pass the flag to a team mate
2172 if(autocvar_g_ctf_pass)
2174 entity head, closest_target = NULL;
2175 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2177 while(head) // find the closest acceptable target to pass to
2179 if(IS_PLAYER(head) && !IS_DEAD(head))
2180 if(head != player && SAME_TEAM(head, player))
2181 if(!head.speedrunning && !head.vehicle)
2183 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2184 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2185 vector passer_center = CENTER_OR_VIEWOFS(player);
2187 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2189 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2191 if(IS_BOT_CLIENT(head))
2193 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2194 ctf_Handle_Throw(head, player, DROP_PASS);
2198 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2199 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2201 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2204 else if(player.flagcarried)
2208 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2209 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2210 { closest_target = head; }
2212 else { closest_target = head; }
2219 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2222 // throw the flag in front of you
2223 if(autocvar_g_ctf_throw && player.flagcarried)
2225 if(player.throw_count == -1)
2227 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2229 player.throw_prevtime = time;
2230 player.throw_count = 1;
2231 ctf_Handle_Throw(player, NULL, DROP_THROW);
2236 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2242 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2243 else { player.throw_count += 1; }
2244 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2246 player.throw_prevtime = time;
2247 ctf_Handle_Throw(player, NULL, DROP_THROW);
2254 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2256 entity player = M_ARGV(0, entity);
2258 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2260 player.wps_helpme_time = time;
2261 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2263 else // create a normal help me waypointsprite
2265 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2266 WaypointSprite_Ping(player.wps_helpme);
2272 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2274 entity player = M_ARGV(0, entity);
2275 entity veh = M_ARGV(1, entity);
2277 if(player.flagcarried)
2279 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2281 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2285 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2286 setattachment(player.flagcarried, veh, "");
2287 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2288 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2289 //player.flagcarried.angles = '0 0 0';
2295 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2297 entity player = M_ARGV(0, entity);
2299 if(player.flagcarried)
2301 setattachment(player.flagcarried, player, "");
2302 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2303 player.flagcarried.scale = FLAG_SCALE;
2304 player.flagcarried.angles = '0 0 0';
2305 player.flagcarried.nodrawtoclient = NULL;
2310 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2312 entity player = M_ARGV(0, entity);
2314 if(player.flagcarried)
2316 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, ((player.flagcarried.team) ? APP_TEAM_ENT(player.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
2317 ctf_RespawnFlag(player.flagcarried);
2322 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2324 entity flag; // temporary entity for the search method
2326 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2328 switch(flag.ctf_status)
2333 // lock the flag, game is over
2334 set_movetype(flag, MOVETYPE_NONE);
2335 flag.takedamage = DAMAGE_NO;
2336 flag.solid = SOLID_NOT;
2337 flag.nextthink = false; // stop thinking
2339 //dprint("stopping the ", flag.netname, " from moving.\n");
2347 // do nothing for these flags
2354 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2356 entity bot = M_ARGV(0, entity);
2358 havocbot_ctf_reset_role(bot);
2362 MUTATOR_HOOKFUNCTION(ctf, GetTeamCount)
2364 //M_ARGV(0, float) = ctf_teams;
2365 M_ARGV(1, string) = "ctf_team";
2369 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2371 entity spectatee = M_ARGV(0, entity);
2372 entity client = M_ARGV(1, entity);
2374 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2377 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2379 int record_page = M_ARGV(0, int);
2380 string ret_string = M_ARGV(1, string);
2382 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2384 if (MapInfo_Get_ByID(i))
2386 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2392 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2393 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2397 M_ARGV(1, string) = ret_string;
2400 bool superspec_Spectate(entity this, entity targ); // TODO
2401 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2402 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2404 entity player = M_ARGV(0, entity);
2405 string cmd_name = M_ARGV(1, string);
2406 int cmd_argc = M_ARGV(2, int);
2408 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2410 if(cmd_name == "followfc")
2422 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2423 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2424 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2425 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2429 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2430 if(it.flagcarried && (it.team == _team || _team == 0))
2433 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2434 continue; // already spectating this fc, try another
2435 return superspec_Spectate(player, it);
2440 superspec_msg("", "", player, "No active flag carrier\n", 1);
2445 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2447 entity frag_target = M_ARGV(0, entity);
2449 if(frag_target.flagcarried)
2450 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2458 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2459 CTF flag for team one (Red).
2461 "angle" Angle the flag will point (minus 90 degrees)...
2462 "model" model to use, note this needs red and blue as skins 0 and 1...
2463 "noise" sound played when flag is picked up...
2464 "noise1" sound played when flag is returned by a teammate...
2465 "noise2" sound played when flag is captured...
2466 "noise3" sound played when flag is lost in the field and respawns itself...
2467 "noise4" sound played when flag is dropped by a player...
2468 "noise5" sound played when flag touches the ground... */
2469 spawnfunc(item_flag_team1)
2471 if(!g_ctf) { delete(this); return; }
2473 ctf_FlagSetup(NUM_TEAM_1, this);
2476 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2477 CTF flag for team two (Blue).
2479 "angle" Angle the flag will point (minus 90 degrees)...
2480 "model" model to use, note this needs red and blue as skins 0 and 1...
2481 "noise" sound played when flag is picked up...
2482 "noise1" sound played when flag is returned by a teammate...
2483 "noise2" sound played when flag is captured...
2484 "noise3" sound played when flag is lost in the field and respawns itself...
2485 "noise4" sound played when flag is dropped by a player...
2486 "noise5" sound played when flag touches the ground... */
2487 spawnfunc(item_flag_team2)
2489 if(!g_ctf) { delete(this); return; }
2491 ctf_FlagSetup(NUM_TEAM_2, this);
2494 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2495 CTF flag for team three (Yellow).
2497 "angle" Angle the flag will point (minus 90 degrees)...
2498 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2499 "noise" sound played when flag is picked up...
2500 "noise1" sound played when flag is returned by a teammate...
2501 "noise2" sound played when flag is captured...
2502 "noise3" sound played when flag is lost in the field and respawns itself...
2503 "noise4" sound played when flag is dropped by a player...
2504 "noise5" sound played when flag touches the ground... */
2505 spawnfunc(item_flag_team3)
2507 if(!g_ctf) { delete(this); return; }
2509 ctf_FlagSetup(NUM_TEAM_3, this);
2512 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2513 CTF flag for team four (Pink).
2515 "angle" Angle the flag will point (minus 90 degrees)...
2516 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2517 "noise" sound played when flag is picked up...
2518 "noise1" sound played when flag is returned by a teammate...
2519 "noise2" sound played when flag is captured...
2520 "noise3" sound played when flag is lost in the field and respawns itself...
2521 "noise4" sound played when flag is dropped by a player...
2522 "noise5" sound played when flag touches the ground... */
2523 spawnfunc(item_flag_team4)
2525 if(!g_ctf) { delete(this); return; }
2527 ctf_FlagSetup(NUM_TEAM_4, this);
2530 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2533 "angle" Angle the flag will point (minus 90 degrees)...
2534 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2535 "noise" sound played when flag is picked up...
2536 "noise1" sound played when flag is returned by a teammate...
2537 "noise2" sound played when flag is captured...
2538 "noise3" sound played when flag is lost in the field and respawns itself...
2539 "noise4" sound played when flag is dropped by a player...
2540 "noise5" sound played when flag touches the ground... */
2541 spawnfunc(item_flag_neutral)
2543 if(!g_ctf) { delete(this); return; }
2544 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2546 ctf_FlagSetup(0, this);
2549 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2550 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2551 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.
2553 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2554 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2557 if(!g_ctf) { delete(this); return; }
2559 this.classname = "ctf_team";
2560 this.team = this.cnt + 1;
2563 // compatibility for quake maps
2564 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2565 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2566 spawnfunc(info_player_team1);
2567 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2568 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2569 spawnfunc(info_player_team2);
2570 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2571 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2573 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2574 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2582 void ctf_ScoreRules(int teams)
2584 CheckAllowedTeams(NULL);
2585 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2586 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2587 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2588 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2589 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2590 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2591 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2592 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2593 ScoreRules_basics_end();
2596 // code from here on is just to support maps that don't have flag and team entities
2597 void ctf_SpawnTeam (string teamname, int teamcolor)
2599 entity this = new_pure(ctf_team);
2600 this.netname = teamname;
2601 this.cnt = teamcolor - 1;
2602 this.spawnfunc_checked = true;
2603 this.team = teamcolor;
2606 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2611 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2613 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2614 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2616 switch(tmp_entity.team)
2618 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2619 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2620 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2621 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2623 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2626 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2628 ctf_teams = 0; // so set the default red and blue teams
2629 BITSET_ASSIGN(ctf_teams, BIT(0));
2630 BITSET_ASSIGN(ctf_teams, BIT(1));
2633 //ctf_teams = bound(2, ctf_teams, 4);
2635 // if no teams are found, spawn defaults
2636 if(find(NULL, classname, "ctf_team") == NULL)
2638 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2639 if(ctf_teams & BIT(0))
2640 ctf_SpawnTeam("Red", NUM_TEAM_1);
2641 if(ctf_teams & BIT(1))
2642 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2643 if(ctf_teams & BIT(2))
2644 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2645 if(ctf_teams & BIT(3))
2646 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2649 ctf_ScoreRules(ctf_teams);
2652 void ctf_Initialize()
2654 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2656 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2657 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2658 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2660 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);