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 bool autocvar_g_ctf_score_ignore_fields;
95 int autocvar_g_ctf_score_capture;
96 int autocvar_g_ctf_score_capture_assist;
97 int autocvar_g_ctf_score_kill;
98 int autocvar_g_ctf_score_penalty_drop;
99 int autocvar_g_ctf_score_penalty_returned;
100 int autocvar_g_ctf_score_pickup_base;
101 int autocvar_g_ctf_score_pickup_dropped_early;
102 int autocvar_g_ctf_score_pickup_dropped_late;
103 int autocvar_g_ctf_score_return;
104 float autocvar_g_ctf_shield_force;
105 float autocvar_g_ctf_shield_max_ratio;
106 int autocvar_g_ctf_shield_min_negscore;
107 bool autocvar_g_ctf_stalemate;
108 int autocvar_g_ctf_stalemate_endcondition;
109 float autocvar_g_ctf_stalemate_time;
110 bool autocvar_g_ctf_reverse;
111 float autocvar_g_ctf_dropped_capture_delay;
112 float autocvar_g_ctf_dropped_capture_radius;
114 void ctf_FakeTimeLimit(entity e, float t)
117 WriteByte(MSG_ONE, 3); // svc_updatestat
118 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
120 WriteCoord(MSG_ONE, autocvar_timelimit);
122 WriteCoord(MSG_ONE, (t + 1) / 60);
125 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
127 if(autocvar_sv_eventlog)
128 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
129 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
132 void ctf_CaptureRecord(entity flag, entity player)
134 float cap_record = ctf_captimerecord;
135 float cap_time = (time - flag.ctf_pickuptime);
136 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
140 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
141 else if(!ctf_captimerecord)
142 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, (cap_time * 100));
143 else if(cap_time < cap_record)
144 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100));
146 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100));
148 // write that shit in the database
149 if(!ctf_oneflag) // but not in 1-flag mode
150 if((!ctf_captimerecord) || (cap_time < cap_record))
152 ctf_captimerecord = cap_time;
153 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
154 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
155 write_recordmarker(player, (time - cap_time), cap_time);
158 if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
159 race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
162 bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
165 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
167 // automatically return if there's only 1 player on the team
168 return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
172 bool ctf_Return_Customize(entity this, entity client)
174 // only to the carrier
175 return boolean(client == this.owner);
178 void ctf_FlagcarrierWaypoints(entity player)
180 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
181 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
182 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
183 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
185 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
187 if(!player.wps_enemyflagcarrier)
189 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
190 wp.colormod = WPCOLOR_ENEMYFC(player.team);
191 setcefc(wp, ctf_Stalemate_Customize);
193 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
194 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
197 if(!player.wps_flagreturn)
199 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
200 owp.colormod = '0 0.8 0.8';
201 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
202 setcefc(owp, ctf_Return_Customize);
207 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
209 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
210 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
211 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
212 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
215 if(current_height) // make sure we can actually do this arcing path
217 targpos = (to + ('0 0 1' * current_height));
218 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
219 if(trace_fraction < 1)
221 //print("normal arc line failed, trying to find new pos...");
222 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
223 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
224 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
225 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
226 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
229 else { targpos = to; }
231 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
233 vector desired_direction = normalize(targpos - from);
234 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
235 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
238 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
240 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
242 // directional tracing only
244 makevectors(passer_angle);
246 // find the closest point on the enemy to the center of the attack
247 float h; // hypotenuse, which is the distance between attacker to head
248 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
250 h = vlen(head_center - passer_center);
251 a = h * (normalize(head_center - passer_center) * v_forward);
253 vector nearest_on_line = (passer_center + a * v_forward);
254 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
256 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
257 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
259 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
264 else { return true; }
268 // =======================
269 // CaptureShield Functions
270 // =======================
272 bool ctf_CaptureShield_CheckStatus(entity p)
274 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
275 int players_worseeq, players_total;
277 if(ctf_captureshield_max_ratio <= 0)
280 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
281 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
282 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
283 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
285 sr = ((s - s2) + (s3 + s4));
287 if(sr >= -ctf_captureshield_min_negscore)
290 players_total = players_worseeq = 0;
291 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
294 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
295 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
296 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
297 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
299 ser = ((se - se2) + (se3 + se4));
306 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
307 // use this rule here
309 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
315 void ctf_CaptureShield_Update(entity player, bool wanted_status)
317 bool updated_status = ctf_CaptureShield_CheckStatus(player);
318 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
320 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
321 player.ctf_captureshielded = updated_status;
325 bool ctf_CaptureShield_Customize(entity this, entity client)
327 if(!client.ctf_captureshielded) { return false; }
328 if(CTF_SAMETEAM(this, client)) { return false; }
333 void ctf_CaptureShield_Touch(entity this, entity toucher)
335 if(!toucher.ctf_captureshielded) { return; }
336 if(CTF_SAMETEAM(this, toucher)) { return; }
338 vector mymid = (this.absmin + this.absmax) * 0.5;
339 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
341 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
342 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
345 void ctf_CaptureShield_Spawn(entity flag)
347 entity shield = new(ctf_captureshield);
350 shield.team = flag.team;
351 settouch(shield, ctf_CaptureShield_Touch);
352 setcefc(shield, ctf_CaptureShield_Customize);
353 shield.effects = EF_ADDITIVE;
354 set_movetype(shield, MOVETYPE_NOCLIP);
355 shield.solid = SOLID_TRIGGER;
356 shield.avelocity = '7 0 11';
359 setorigin(shield, flag.origin);
360 setmodel(shield, MDL_CTF_SHIELD);
361 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
365 // ====================
366 // Drop/Pass/Throw Code
367 // ====================
369 void ctf_Handle_Drop(entity flag, entity player, int droptype)
372 player = (player ? player : flag.pass_sender);
375 set_movetype(flag, MOVETYPE_TOSS);
376 flag.takedamage = DAMAGE_YES;
377 flag.angles = '0 0 0';
378 flag.health = flag.max_flag_health;
379 flag.ctf_droptime = time;
380 flag.ctf_dropper = player;
381 flag.ctf_status = FLAG_DROPPED;
383 // messages and sounds
384 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
385 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
386 ctf_EventLog("dropped", player.team, player);
389 PlayerTeamScore_AddScore(player, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
390 PlayerScore_Add(player, SP_CTF_DROPS, 1);
393 if(autocvar_g_ctf_flag_dropped_waypoint) {
394 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);
395 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
398 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
400 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
401 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
404 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
406 if(droptype == DROP_PASS)
408 flag.pass_distance = 0;
409 flag.pass_sender = NULL;
410 flag.pass_target = NULL;
414 void ctf_Handle_Retrieve(entity flag, entity player)
416 entity sender = flag.pass_sender;
418 // transfer flag to player
420 flag.owner.flagcarried = flag;
425 setattachment(flag, player.vehicle, "");
426 setorigin(flag, VEHICLE_FLAG_OFFSET);
427 flag.scale = VEHICLE_FLAG_SCALE;
431 setattachment(flag, player, "");
432 setorigin(flag, FLAG_CARRY_OFFSET);
434 set_movetype(flag, MOVETYPE_NONE);
435 flag.takedamage = DAMAGE_NO;
436 flag.solid = SOLID_NOT;
437 flag.angles = '0 0 0';
438 flag.ctf_status = FLAG_CARRY;
440 // messages and sounds
441 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
442 ctf_EventLog("receive", flag.team, player);
444 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(
446 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
447 else if(it == player)
448 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
449 else if(SAME_TEAM(it, sender))
450 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
453 // create new waypoint
454 ctf_FlagcarrierWaypoints(player);
456 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
457 player.throw_antispam = sender.throw_antispam;
459 flag.pass_distance = 0;
460 flag.pass_sender = NULL;
461 flag.pass_target = NULL;
464 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
466 entity flag = player.flagcarried;
467 vector targ_origin, flag_velocity;
469 if(!flag) { return; }
470 if((droptype == DROP_PASS) && !receiver) { return; }
472 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
475 setattachment(flag, NULL, "");
476 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
477 flag.owner.flagcarried = NULL;
479 flag.solid = SOLID_TRIGGER;
480 flag.ctf_dropper = player;
481 flag.ctf_droptime = time;
483 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
490 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
491 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
492 WarpZone_RefSys_Copy(flag, receiver);
493 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
494 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
496 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
497 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
500 set_movetype(flag, MOVETYPE_FLY);
501 flag.takedamage = DAMAGE_NO;
502 flag.pass_sender = player;
503 flag.pass_target = receiver;
504 flag.ctf_status = FLAG_PASSING;
507 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
508 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
509 ctf_EventLog("pass", flag.team, player);
515 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'));
517 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)));
518 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
519 ctf_Handle_Drop(flag, player, droptype);
525 flag.velocity = '0 0 0'; // do nothing
532 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);
533 ctf_Handle_Drop(flag, player, droptype);
538 // kill old waypointsprite
539 WaypointSprite_Ping(player.wps_flagcarrier);
540 WaypointSprite_Kill(player.wps_flagcarrier);
542 if(player.wps_enemyflagcarrier)
543 WaypointSprite_Kill(player.wps_enemyflagcarrier);
545 if(player.wps_flagreturn)
546 WaypointSprite_Kill(player.wps_flagreturn);
549 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
552 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
554 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
561 void nades_GiveBonus(entity player, float score);
563 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
565 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
566 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
567 entity player_team_flag = NULL, tmp_entity;
568 float old_time, new_time;
570 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
571 if(CTF_DIFFTEAM(player, flag)) { return; }
572 if((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
575 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
576 if(SAME_TEAM(tmp_entity, player))
578 player_team_flag = tmp_entity;
582 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
584 player.throw_prevtime = time;
585 player.throw_count = 0;
587 // messages and sounds
588 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
589 ctf_CaptureRecord(enemy_flag, player);
590 _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);
594 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
595 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
600 PlayerTeamScore_AddScore(player, ((enemy_flag.score_capture) ? enemy_flag.score_capture : autocvar_g_ctf_score_capture));
601 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, ((enemy_flag.score_team_capture) ? enemy_flag.score_team_capture : 1));
603 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
604 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
605 if(!old_time || new_time < old_time)
606 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
609 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
610 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
613 if(capturetype == CAPTURE_NORMAL)
615 WaypointSprite_Kill(player.wps_flagcarrier);
616 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
618 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
619 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
623 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
624 ctf_RespawnFlag(enemy_flag);
627 void ctf_Handle_Return(entity flag, entity player)
629 // messages and sounds
630 if(IS_MONSTER(player))
632 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
636 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
637 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
639 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
640 ctf_EventLog("return", flag.team, player);
643 if(IS_PLAYER(player))
645 PlayerTeamScore_AddScore(player, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
646 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
648 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
651 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
655 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
656 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
657 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
661 if(player.flagcarried == flag)
662 WaypointSprite_Kill(player.wps_flagcarrier);
665 ctf_RespawnFlag(flag);
668 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
671 float pickup_dropped_score; // used to calculate dropped pickup score
673 // attach the flag to the player
675 player.flagcarried = flag;
678 setattachment(flag, player.vehicle, "");
679 setorigin(flag, VEHICLE_FLAG_OFFSET);
680 flag.scale = VEHICLE_FLAG_SCALE;
684 setattachment(flag, player, "");
685 setorigin(flag, FLAG_CARRY_OFFSET);
689 set_movetype(flag, MOVETYPE_NONE);
690 flag.takedamage = DAMAGE_NO;
691 flag.solid = SOLID_NOT;
692 flag.angles = '0 0 0';
693 flag.ctf_status = FLAG_CARRY;
697 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
698 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
702 // messages and sounds
703 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
705 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
707 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
708 else if(CTF_DIFFTEAM(player, flag))
709 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
711 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
713 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
716 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)));
719 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
720 if(CTF_SAMETEAM(flag, it))
721 if(SAME_TEAM(player, it))
722 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
724 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);
727 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
730 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
731 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
736 PlayerTeamScore_AddScore(player, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
737 ctf_EventLog("steal", flag.team, player);
743 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);
744 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);
745 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
746 PlayerTeamScore_AddScore(player, pickup_dropped_score);
747 ctf_EventLog("pickup", flag.team, player);
755 if(pickuptype == PICKUP_BASE)
757 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
758 if((player.speedrunning) && (ctf_captimerecord))
759 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
763 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
766 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
767 ctf_FlagcarrierWaypoints(player);
768 WaypointSprite_Ping(player.wps_flagcarrier);
772 // ===================
773 // Main Flag Functions
774 // ===================
776 void ctf_CheckFlagReturn(entity flag, int returntype)
778 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
780 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
782 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
787 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
789 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
790 case RETURN_SPEEDRUN:
791 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), ctf_captimerecord); break;
792 case RETURN_NEEDKILL:
793 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
796 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
798 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
799 ctf_EventLog("returned", flag.team, NULL);
800 ctf_RespawnFlag(flag);
805 bool ctf_Stalemate_Customize(entity this, entity client)
807 // make spectators see what the player would see
808 entity e = WaypointSprite_getviewentity(client);
809 entity wp_owner = this.owner;
812 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
813 if(SAME_TEAM(wp_owner, e)) { return false; }
814 if(!IS_PLAYER(e)) { return false; }
819 void ctf_CheckStalemate()
822 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
825 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
827 // build list of stale flags
828 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
830 if(autocvar_g_ctf_stalemate)
831 if(tmp_entity.ctf_status != FLAG_BASE)
832 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
834 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
835 ctf_staleflaglist = tmp_entity;
837 switch(tmp_entity.team)
839 case NUM_TEAM_1: ++stale_red_flags; break;
840 case NUM_TEAM_2: ++stale_blue_flags; break;
841 case NUM_TEAM_3: ++stale_yellow_flags; break;
842 case NUM_TEAM_4: ++stale_pink_flags; break;
843 default: ++stale_neutral_flags; break;
849 stale_flags = (stale_neutral_flags >= 1);
851 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
853 if(ctf_oneflag && stale_flags == 1)
854 ctf_stalemate = true;
855 else if(stale_flags >= 2)
856 ctf_stalemate = true;
857 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
858 { ctf_stalemate = false; wpforenemy_announced = false; }
859 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
860 { ctf_stalemate = false; wpforenemy_announced = false; }
862 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
865 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
867 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
869 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);
870 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
871 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
875 if (!wpforenemy_announced)
877 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))));
879 wpforenemy_announced = true;
884 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
886 if(ITEM_DAMAGE_NEEDKILL(deathtype))
888 if(autocvar_g_ctf_flag_return_damage_delay)
889 this.ctf_flagdamaged_byworld = true;
893 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
897 if(autocvar_g_ctf_flag_return_damage)
899 // reduce health and check if it should be returned
900 this.health = this.health - damage;
901 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
906 void ctf_FlagThink(entity this)
911 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
914 if(this == ctf_worldflaglist) // only for the first flag
915 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
918 if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
919 LOG_TRACE("wtf the flag got squashed?");
920 tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
921 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
922 setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
926 switch(this.ctf_status)
930 if(autocvar_g_ctf_dropped_capture_radius)
932 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
933 if(tmp_entity.ctf_status == FLAG_DROPPED)
934 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
935 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
936 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
943 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
945 if(autocvar_g_ctf_flag_dropped_floatinwater)
947 vector midpoint = ((this.absmin + this.absmax) * 0.5);
948 if(pointcontents(midpoint) == CONTENT_WATER)
950 this.velocity = this.velocity * 0.5;
952 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
953 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
955 { set_movetype(this, MOVETYPE_FLY); }
957 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
959 if(autocvar_g_ctf_flag_return_dropped)
961 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
964 ctf_CheckFlagReturn(this, RETURN_DROPPED);
968 if(this.ctf_flagdamaged_byworld)
970 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
971 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
974 else if(autocvar_g_ctf_flag_return_time)
976 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
977 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
985 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
988 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
990 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
991 ImpulseCommands(this.owner);
993 if(autocvar_g_ctf_stalemate)
995 if(time >= wpforenemy_nextthink)
997 ctf_CheckStalemate();
998 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1001 if(CTF_SAMETEAM(this, this.owner) && this.team)
1003 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1004 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
1005 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1006 ctf_Handle_Return(this, this.owner);
1013 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1014 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1015 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1017 if((this.pass_target == NULL)
1018 || (IS_DEAD(this.pass_target))
1019 || (this.pass_target.flagcarried)
1020 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1021 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1022 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1024 // give up, pass failed
1025 ctf_Handle_Drop(this, NULL, DROP_PASS);
1029 // still a viable target, go for it
1030 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1035 default: // this should never happen
1037 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1043 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1046 if(game_stopped) return;
1047 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1049 bool is_not_monster = (!IS_MONSTER(toucher));
1051 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1052 if(ITEM_TOUCH_NEEDKILL())
1054 if(!autocvar_g_ctf_flag_return_damage_delay)
1057 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1059 if(!flag.ctf_flagdamaged_byworld) { return; }
1062 // special touch behaviors
1063 if(STAT(FROZEN, toucher)) { return; }
1064 else if(IS_VEHICLE(toucher))
1066 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1067 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1069 return; // do nothing
1071 else if(IS_MONSTER(toucher))
1073 if(!autocvar_g_ctf_allow_monster_touch)
1074 return; // do nothing
1076 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1078 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1080 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1081 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1082 flag.wait = time + FLAG_TOUCHRATE;
1086 else if(IS_DEAD(toucher)) { return; }
1088 switch(flag.ctf_status)
1094 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1095 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1096 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1097 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1099 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1100 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1101 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)
1103 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1104 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1106 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1107 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1113 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1114 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1115 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1116 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1122 LOG_TRACE("Someone touched a flag even though it was being carried?");
1128 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1130 if(DIFF_TEAM(toucher, flag.pass_sender))
1132 if(ctf_Immediate_Return_Allowed(flag, toucher))
1133 ctf_Handle_Return(flag, toucher);
1134 else if(is_not_monster && (!toucher.flagcarried))
1135 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1137 else if(!toucher.flagcarried)
1138 ctf_Handle_Retrieve(flag, toucher);
1145 .float last_respawn;
1146 void ctf_RespawnFlag(entity flag)
1148 // check for flag respawn being called twice in a row
1149 if(flag.last_respawn > time - 0.5)
1150 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1152 flag.last_respawn = time;
1154 // reset the player (if there is one)
1155 if((flag.owner) && (flag.owner.flagcarried == flag))
1157 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1158 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1159 WaypointSprite_Kill(flag.wps_flagcarrier);
1161 flag.owner.flagcarried = NULL;
1163 if(flag.speedrunning)
1164 ctf_FakeTimeLimit(flag.owner, -1);
1167 if((flag.owner) && (flag.owner.vehicle))
1168 flag.scale = FLAG_SCALE;
1170 if(flag.ctf_status == FLAG_DROPPED)
1171 { WaypointSprite_Kill(flag.wps_flagdropped); }
1174 setattachment(flag, NULL, "");
1175 setorigin(flag, flag.ctf_spawnorigin);
1177 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1178 flag.takedamage = DAMAGE_NO;
1179 flag.health = flag.max_flag_health;
1180 flag.solid = SOLID_TRIGGER;
1181 flag.velocity = '0 0 0';
1182 flag.angles = flag.mangle;
1183 flag.flags = FL_ITEM | FL_NOTARGET;
1185 flag.ctf_status = FLAG_BASE;
1187 flag.pass_distance = 0;
1188 flag.pass_sender = NULL;
1189 flag.pass_target = NULL;
1190 flag.ctf_dropper = NULL;
1191 flag.ctf_pickuptime = 0;
1192 flag.ctf_droptime = 0;
1193 flag.ctf_flagdamaged_byworld = false;
1195 ctf_CheckStalemate();
1198 void ctf_Reset(entity this)
1200 if(this.owner && IS_PLAYER(this.owner))
1201 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1203 ctf_RespawnFlag(this);
1206 bool ctf_FlagBase_Customize(entity this, entity client)
1208 entity e = WaypointSprite_getviewentity(client);
1209 entity wp_owner = this.owner;
1210 entity flag = e.flagcarried;
1211 if(flag && CTF_SAMETEAM(e, flag))
1213 if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
1218 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1221 waypoint_spawnforitem_force(this, this.origin);
1222 this.nearestwaypointtimeout = 0; // activate waypointing again
1223 this.bot_basewaypoint = this.nearestwaypoint;
1229 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1230 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1231 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1232 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1233 default: basename = WP_FlagBaseNeutral; break;
1236 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1237 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1238 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1239 setcefc(wp, ctf_FlagBase_Customize);
1241 // captureshield setup
1242 ctf_CaptureShield_Spawn(this);
1247 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1250 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1251 ctf_worldflaglist = flag;
1253 setattachment(flag, NULL, "");
1255 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1256 flag.team = teamnumber;
1257 flag.classname = "item_flag_team";
1258 flag.target = "###item###"; // wut?
1259 flag.flags = FL_ITEM | FL_NOTARGET;
1260 IL_PUSH(g_items, flag);
1261 flag.solid = SOLID_TRIGGER;
1262 flag.takedamage = DAMAGE_NO;
1263 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1264 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1265 flag.health = flag.max_flag_health;
1266 flag.event_damage = ctf_FlagDamage;
1267 flag.pushable = true;
1268 flag.teleportable = TELEPORT_NORMAL;
1269 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1270 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1271 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1272 if(flag.damagedbycontents)
1273 IL_PUSH(g_damagedbycontents, flag);
1274 flag.velocity = '0 0 0';
1275 flag.mangle = flag.angles;
1276 flag.reset = ctf_Reset;
1277 settouch(flag, ctf_FlagTouch);
1278 setthink(flag, ctf_FlagThink);
1279 flag.nextthink = time + FLAG_THINKRATE;
1280 flag.ctf_status = FLAG_BASE;
1282 // crudely force them all to 0
1283 if(autocvar_g_ctf_score_ignore_fields)
1284 flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1286 string teamname = Static_Team_ColorName_Lower(teamnumber);
1288 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1289 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1290 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1291 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1292 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1293 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1297 if(flag.s == "") flag.s = b; \
1298 precache_sound(flag.s);
1300 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1301 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1302 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1303 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1304 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1305 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1306 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1310 precache_model(flag.model);
1313 _setmodel(flag, flag.model); // precision set below
1314 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1315 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1317 if(autocvar_g_ctf_flag_glowtrails)
1321 case NUM_TEAM_1: flag.glow_color = 251; break;
1322 case NUM_TEAM_2: flag.glow_color = 210; break;
1323 case NUM_TEAM_3: flag.glow_color = 110; break;
1324 case NUM_TEAM_4: flag.glow_color = 145; break;
1325 default: flag.glow_color = 254; break;
1327 flag.glow_size = 25;
1328 flag.glow_trail = 1;
1331 flag.effects |= EF_LOWPRECISION;
1332 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1333 if(autocvar_g_ctf_dynamiclights)
1337 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1338 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1339 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1340 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1341 default: flag.effects |= EF_DIMLIGHT; break;
1346 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1348 flag.dropped_origin = flag.origin;
1349 flag.noalign = true;
1350 set_movetype(flag, MOVETYPE_NONE);
1352 else // drop to floor, automatically find a platform and set that as spawn origin
1354 flag.noalign = false;
1356 set_movetype(flag, MOVETYPE_NONE);
1359 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1367 // NOTE: LEGACY CODE, needs to be re-written!
1369 void havocbot_calculate_middlepoint()
1373 vector fo = '0 0 0';
1376 f = ctf_worldflaglist;
1381 f = f.ctf_worldflagnext;
1386 havocbot_ctf_middlepoint = s / n;
1387 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1391 entity havocbot_ctf_find_flag(entity bot)
1394 f = ctf_worldflaglist;
1397 if (CTF_SAMETEAM(bot, f))
1399 f = f.ctf_worldflagnext;
1404 entity havocbot_ctf_find_enemy_flag(entity bot)
1407 f = ctf_worldflaglist;
1412 if(CTF_DIFFTEAM(bot, f))
1419 else if(!bot.flagcarried)
1423 else if (CTF_DIFFTEAM(bot, f))
1425 f = f.ctf_worldflagnext;
1430 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1437 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1438 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1441 if(vdist(it.origin - org, <, tc_radius))
1448 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1451 head = ctf_worldflaglist;
1454 if (CTF_SAMETEAM(this, head))
1456 head = head.ctf_worldflagnext;
1459 navigation_routerating(this, head, ratingscale, 10000);
1462 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1465 head = ctf_worldflaglist;
1468 if (CTF_SAMETEAM(this, head))
1470 head = head.ctf_worldflagnext;
1475 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1478 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1481 head = ctf_worldflaglist;
1486 if(CTF_DIFFTEAM(this, head))
1490 if(this.flagcarried)
1493 else if(!this.flagcarried)
1497 else if(CTF_DIFFTEAM(this, head))
1499 head = head.ctf_worldflagnext;
1502 navigation_routerating(this, head, ratingscale, 10000);
1505 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1507 if (!bot_waypoints_for_items)
1509 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1515 head = havocbot_ctf_find_enemy_flag(this);
1520 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1523 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1527 mf = havocbot_ctf_find_flag(this);
1529 if(mf.ctf_status == FLAG_BASE)
1533 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1536 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1539 head = ctf_worldflaglist;
1542 // flag is out in the field
1543 if(head.ctf_status != FLAG_BASE)
1544 if(head.tag_entity==NULL) // dropped
1548 if(vdist(org - head.origin, <, df_radius))
1549 navigation_routerating(this, head, ratingscale, 10000);
1552 navigation_routerating(this, head, ratingscale, 10000);
1555 head = head.ctf_worldflagnext;
1559 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1561 IL_EACH(g_items, it.bot_pickup,
1563 // gather health and armor only
1565 if (it.health || it.armorvalue)
1566 if (vdist(it.origin - org, <, sradius))
1568 // get the value of the item
1569 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1571 navigation_routerating(this, it, t * ratingscale, 500);
1576 void havocbot_ctf_reset_role(entity this)
1578 float cdefense, cmiddle, coffense;
1585 if(havocbot_ctf_middlepoint == '0 0 0')
1586 havocbot_calculate_middlepoint();
1589 if (this.flagcarried)
1591 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1595 mf = havocbot_ctf_find_flag(this);
1596 ef = havocbot_ctf_find_enemy_flag(this);
1598 // Retrieve stolen flag
1599 if(mf.ctf_status!=FLAG_BASE)
1601 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1605 // If enemy flag is taken go to the middle to intercept pursuers
1606 if(ef.ctf_status!=FLAG_BASE)
1608 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1612 // if there is only me on the team switch to offense
1614 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1618 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1622 // Evaluate best position to take
1623 // Count mates on middle position
1624 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1626 // Count mates on defense position
1627 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1629 // Count mates on offense position
1630 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1632 if(cdefense<=coffense)
1633 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1634 else if(coffense<=cmiddle)
1635 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1637 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1640 void havocbot_role_ctf_carrier(entity this)
1644 havocbot_ctf_reset_role(this);
1648 if (this.flagcarried == NULL)
1650 havocbot_ctf_reset_role(this);
1654 if (this.bot_strategytime < time)
1656 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1658 navigation_goalrating_start(this);
1660 havocbot_goalrating_ctf_enemybase(this, 50000);
1662 havocbot_goalrating_ctf_ourbase(this, 50000);
1665 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1667 navigation_goalrating_end(this);
1669 if (this.navigation_hasgoals)
1670 this.havocbot_cantfindflag = time + 10;
1671 else if (time > this.havocbot_cantfindflag)
1673 // Can't navigate to my own base, suicide!
1674 // TODO: drop it and wander around
1675 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1681 void havocbot_role_ctf_escort(entity this)
1687 havocbot_ctf_reset_role(this);
1691 if (this.flagcarried)
1693 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1697 // If enemy flag is back on the base switch to previous role
1698 ef = havocbot_ctf_find_enemy_flag(this);
1699 if(ef.ctf_status==FLAG_BASE)
1701 this.havocbot_role = this.havocbot_previous_role;
1702 this.havocbot_role_timeout = 0;
1706 // If the flag carrier reached the base switch to defense
1707 mf = havocbot_ctf_find_flag(this);
1708 if(mf.ctf_status!=FLAG_BASE)
1709 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1711 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1715 // Set the role timeout if necessary
1716 if (!this.havocbot_role_timeout)
1718 this.havocbot_role_timeout = time + random() * 30 + 60;
1721 // If nothing happened just switch to previous role
1722 if (time > this.havocbot_role_timeout)
1724 this.havocbot_role = this.havocbot_previous_role;
1725 this.havocbot_role_timeout = 0;
1729 // Chase the flag carrier
1730 if (this.bot_strategytime < time)
1732 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1733 navigation_goalrating_start(this);
1734 havocbot_goalrating_ctf_enemyflag(this, 30000);
1735 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1736 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1737 navigation_goalrating_end(this);
1741 void havocbot_role_ctf_offense(entity this)
1748 havocbot_ctf_reset_role(this);
1752 if (this.flagcarried)
1754 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1759 mf = havocbot_ctf_find_flag(this);
1760 ef = havocbot_ctf_find_enemy_flag(this);
1763 if(mf.ctf_status!=FLAG_BASE)
1766 pos = mf.tag_entity.origin;
1770 // Try to get it if closer than the enemy base
1771 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1773 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1778 // Escort flag carrier
1779 if(ef.ctf_status!=FLAG_BASE)
1782 pos = ef.tag_entity.origin;
1786 if(vdist(pos - mf.dropped_origin, >, 700))
1788 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1793 // About to fail, switch to middlefield
1796 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1800 // Set the role timeout if necessary
1801 if (!this.havocbot_role_timeout)
1802 this.havocbot_role_timeout = time + 120;
1804 if (time > this.havocbot_role_timeout)
1806 havocbot_ctf_reset_role(this);
1810 if (this.bot_strategytime < time)
1812 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1813 navigation_goalrating_start(this);
1814 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1815 havocbot_goalrating_ctf_enemybase(this, 20000);
1816 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1817 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1818 navigation_goalrating_end(this);
1822 // Retriever (temporary role):
1823 void havocbot_role_ctf_retriever(entity this)
1829 havocbot_ctf_reset_role(this);
1833 if (this.flagcarried)
1835 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1839 // If flag is back on the base switch to previous role
1840 mf = havocbot_ctf_find_flag(this);
1841 if(mf.ctf_status==FLAG_BASE)
1843 if(this.goalcurrent == mf)
1845 navigation_clearroute(this);
1846 this.bot_strategytime = 0;
1848 havocbot_ctf_reset_role(this);
1852 if (!this.havocbot_role_timeout)
1853 this.havocbot_role_timeout = time + 20;
1855 if (time > this.havocbot_role_timeout)
1857 havocbot_ctf_reset_role(this);
1861 if (this.bot_strategytime < time)
1866 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1867 navigation_goalrating_start(this);
1868 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1869 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1870 havocbot_goalrating_ctf_enemybase(this, 30000);
1871 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1872 navigation_goalrating_end(this);
1876 void havocbot_role_ctf_middle(entity this)
1882 havocbot_ctf_reset_role(this);
1886 if (this.flagcarried)
1888 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1892 mf = havocbot_ctf_find_flag(this);
1893 if(mf.ctf_status!=FLAG_BASE)
1895 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1899 if (!this.havocbot_role_timeout)
1900 this.havocbot_role_timeout = time + 10;
1902 if (time > this.havocbot_role_timeout)
1904 havocbot_ctf_reset_role(this);
1908 if (this.bot_strategytime < time)
1912 org = havocbot_ctf_middlepoint;
1913 org.z = this.origin.z;
1915 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1916 navigation_goalrating_start(this);
1917 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1918 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1919 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1920 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1921 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1922 havocbot_goalrating_ctf_enemybase(this, 2500);
1923 navigation_goalrating_end(this);
1927 void havocbot_role_ctf_defense(entity this)
1933 havocbot_ctf_reset_role(this);
1937 if (this.flagcarried)
1939 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1943 // If own flag was captured
1944 mf = havocbot_ctf_find_flag(this);
1945 if(mf.ctf_status!=FLAG_BASE)
1947 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1951 if (!this.havocbot_role_timeout)
1952 this.havocbot_role_timeout = time + 30;
1954 if (time > this.havocbot_role_timeout)
1956 havocbot_ctf_reset_role(this);
1959 if (this.bot_strategytime < time)
1964 org = mf.dropped_origin;
1965 mp_radius = havocbot_ctf_middlepoint_radius;
1967 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1968 navigation_goalrating_start(this);
1970 // if enemies are closer to our base, go there
1971 entity closestplayer = NULL;
1972 float distance, bestdistance = 10000;
1973 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1974 distance = vlen(org - it.origin);
1975 if(distance<bestdistance)
1978 bestdistance = distance;
1983 if(DIFF_TEAM(closestplayer, this))
1984 if(vdist(org - this.origin, >, 1000))
1985 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1986 havocbot_goalrating_ctf_ourbase(this, 30000);
1988 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1989 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1990 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1991 havocbot_goalrating_items(this, 10000, org, mp_radius);
1992 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1993 navigation_goalrating_end(this);
1997 void havocbot_role_ctf_setrole(entity bot, int role)
1999 string s = "(null)";
2002 case HAVOCBOT_CTF_ROLE_CARRIER:
2004 bot.havocbot_role = havocbot_role_ctf_carrier;
2005 bot.havocbot_role_timeout = 0;
2006 bot.havocbot_cantfindflag = time + 10;
2007 bot.bot_strategytime = 0;
2009 case HAVOCBOT_CTF_ROLE_DEFENSE:
2011 bot.havocbot_role = havocbot_role_ctf_defense;
2012 bot.havocbot_role_timeout = 0;
2014 case HAVOCBOT_CTF_ROLE_MIDDLE:
2016 bot.havocbot_role = havocbot_role_ctf_middle;
2017 bot.havocbot_role_timeout = 0;
2019 case HAVOCBOT_CTF_ROLE_OFFENSE:
2021 bot.havocbot_role = havocbot_role_ctf_offense;
2022 bot.havocbot_role_timeout = 0;
2024 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2026 bot.havocbot_previous_role = bot.havocbot_role;
2027 bot.havocbot_role = havocbot_role_ctf_retriever;
2028 bot.havocbot_role_timeout = time + 10;
2029 bot.bot_strategytime = 0;
2031 case HAVOCBOT_CTF_ROLE_ESCORT:
2033 bot.havocbot_previous_role = bot.havocbot_role;
2034 bot.havocbot_role = havocbot_role_ctf_escort;
2035 bot.havocbot_role_timeout = time + 30;
2036 bot.bot_strategytime = 0;
2039 LOG_TRACE(bot.netname, " switched to ", s);
2047 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2049 entity player = M_ARGV(0, entity);
2051 int t = 0, t2 = 0, t3 = 0;
2053 // initially clear items so they can be set as necessary later.
2054 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2055 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2056 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2057 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2058 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2059 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2061 // scan through all the flags and notify the client about them
2062 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2064 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2065 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2066 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2067 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2068 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; }
2070 switch(flag.ctf_status)
2075 if((flag.owner == player) || (flag.pass_sender == player))
2076 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2078 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2083 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2089 // item for stopping players from capturing the flag too often
2090 if(player.ctf_captureshielded)
2091 player.ctf_flagstatus |= CTF_SHIELDED;
2094 player.ctf_flagstatus |= CTF_STALEMATE;
2096 // update the health of the flag carrier waypointsprite
2097 if(player.wps_flagcarrier)
2098 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2101 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2103 entity frag_attacker = M_ARGV(1, entity);
2104 entity frag_target = M_ARGV(2, entity);
2105 float frag_damage = M_ARGV(4, float);
2106 vector frag_force = M_ARGV(6, vector);
2108 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2110 if(frag_target == frag_attacker) // damage done to yourself
2112 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2113 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2115 else // damage done to everyone else
2117 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2118 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2121 M_ARGV(4, float) = frag_damage;
2122 M_ARGV(6, vector) = frag_force;
2124 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2126 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)))
2127 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2129 frag_target.wps_helpme_time = time;
2130 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2132 // todo: add notification for when flag carrier needs help?
2136 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2138 entity frag_attacker = M_ARGV(1, entity);
2139 entity frag_target = M_ARGV(2, entity);
2141 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2143 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2144 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2147 if(frag_target.flagcarried)
2149 entity tmp_entity = frag_target.flagcarried;
2150 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2151 tmp_entity.ctf_dropper = NULL;
2155 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2157 M_ARGV(2, float) = 0; // frag score
2158 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2161 void ctf_RemovePlayer(entity player)
2163 if(player.flagcarried)
2164 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2166 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2168 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2169 if(flag.pass_target == player) { flag.pass_target = NULL; }
2170 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2174 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2176 entity player = M_ARGV(0, entity);
2178 ctf_RemovePlayer(player);
2181 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2183 entity player = M_ARGV(0, entity);
2185 ctf_RemovePlayer(player);
2188 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2190 if(!autocvar_g_ctf_leaderboard)
2193 entity player = M_ARGV(0, entity);
2195 if(IS_REAL_CLIENT(player))
2197 for(int i = 1; i <= RANKINGS_CNT; ++i)
2199 race_SendRankings(i, 0, 0, MSG_ONE);
2204 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2206 if(!autocvar_g_ctf_leaderboard)
2209 entity player = M_ARGV(0, entity);
2211 if(player.cvar_cl_allow_uidtracking == 1 && player.cvar_cl_allow_uid2name == 1)
2213 if (!player.stored_netname)
2214 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2215 if(player.stored_netname != player.netname)
2217 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2218 strunzone(player.stored_netname);
2219 player.stored_netname = strzone(player.netname);
2224 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2226 entity player = M_ARGV(0, entity);
2228 if(player.flagcarried)
2229 if(!autocvar_g_ctf_portalteleport)
2230 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2233 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2235 if(MUTATOR_RETURNVALUE || game_stopped) return;
2237 entity player = M_ARGV(0, entity);
2239 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2241 // pass the flag to a team mate
2242 if(autocvar_g_ctf_pass)
2244 entity head, closest_target = NULL;
2245 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2247 while(head) // find the closest acceptable target to pass to
2249 if(IS_PLAYER(head) && !IS_DEAD(head))
2250 if(head != player && SAME_TEAM(head, player))
2251 if(!head.speedrunning && !head.vehicle)
2253 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2254 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2255 vector passer_center = CENTER_OR_VIEWOFS(player);
2257 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2259 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2261 if(IS_BOT_CLIENT(head))
2263 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2264 ctf_Handle_Throw(head, player, DROP_PASS);
2268 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2269 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2271 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2274 else if(player.flagcarried && !head.flagcarried)
2278 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2279 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2280 { closest_target = head; }
2282 else { closest_target = head; }
2289 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2292 // throw the flag in front of you
2293 if(autocvar_g_ctf_throw && player.flagcarried)
2295 if(player.throw_count == -1)
2297 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2299 player.throw_prevtime = time;
2300 player.throw_count = 1;
2301 ctf_Handle_Throw(player, NULL, DROP_THROW);
2306 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2312 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2313 else { player.throw_count += 1; }
2314 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2316 player.throw_prevtime = time;
2317 ctf_Handle_Throw(player, NULL, DROP_THROW);
2324 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2326 entity player = M_ARGV(0, entity);
2328 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2330 player.wps_helpme_time = time;
2331 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2333 else // create a normal help me waypointsprite
2335 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2336 WaypointSprite_Ping(player.wps_helpme);
2342 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2344 entity player = M_ARGV(0, entity);
2345 entity veh = M_ARGV(1, entity);
2347 if(player.flagcarried)
2349 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2351 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2355 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2356 setattachment(player.flagcarried, veh, "");
2357 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2358 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2359 //player.flagcarried.angles = '0 0 0';
2365 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2367 entity player = M_ARGV(0, entity);
2369 if(player.flagcarried)
2371 setattachment(player.flagcarried, player, "");
2372 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2373 player.flagcarried.scale = FLAG_SCALE;
2374 player.flagcarried.angles = '0 0 0';
2375 player.flagcarried.nodrawtoclient = NULL;
2380 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2382 entity player = M_ARGV(0, entity);
2384 if(player.flagcarried)
2386 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2387 ctf_RespawnFlag(player.flagcarried);
2392 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2394 entity flag; // temporary entity for the search method
2396 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2398 switch(flag.ctf_status)
2403 // lock the flag, game is over
2404 set_movetype(flag, MOVETYPE_NONE);
2405 flag.takedamage = DAMAGE_NO;
2406 flag.solid = SOLID_NOT;
2407 flag.nextthink = false; // stop thinking
2409 //dprint("stopping the ", flag.netname, " from moving.\n");
2417 // do nothing for these flags
2424 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2426 entity bot = M_ARGV(0, entity);
2428 havocbot_ctf_reset_role(bot);
2432 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2434 //M_ARGV(0, float) = ctf_teams;
2435 M_ARGV(1, string) = "ctf_team";
2439 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2441 entity spectatee = M_ARGV(0, entity);
2442 entity client = M_ARGV(1, entity);
2444 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2447 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2449 int record_page = M_ARGV(0, int);
2450 string ret_string = M_ARGV(1, string);
2452 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2454 if (MapInfo_Get_ByID(i))
2456 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2462 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2463 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2467 M_ARGV(1, string) = ret_string;
2470 bool superspec_Spectate(entity this, entity targ); // TODO
2471 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2472 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2474 entity player = M_ARGV(0, entity);
2475 string cmd_name = M_ARGV(1, string);
2476 int cmd_argc = M_ARGV(2, int);
2478 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2480 if(cmd_name == "followfc")
2492 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2493 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2494 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2495 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2499 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2500 if(it.flagcarried && (it.team == _team || _team == 0))
2503 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2504 continue; // already spectating this fc, try another
2505 return superspec_Spectate(player, it);
2510 superspec_msg("", "", player, "No active flag carrier\n", 1);
2515 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2517 entity frag_target = M_ARGV(0, entity);
2519 if(frag_target.flagcarried)
2520 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2528 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2529 CTF flag for team one (Red).
2531 "angle" Angle the flag will point (minus 90 degrees)...
2532 "model" model to use, note this needs red and blue as skins 0 and 1...
2533 "noise" sound played when flag is picked up...
2534 "noise1" sound played when flag is returned by a teammate...
2535 "noise2" sound played when flag is captured...
2536 "noise3" sound played when flag is lost in the field and respawns itself...
2537 "noise4" sound played when flag is dropped by a player...
2538 "noise5" sound played when flag touches the ground... */
2539 spawnfunc(item_flag_team1)
2541 if(!g_ctf) { delete(this); return; }
2543 ctf_FlagSetup(NUM_TEAM_1, this);
2546 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2547 CTF flag for team two (Blue).
2549 "angle" Angle the flag will point (minus 90 degrees)...
2550 "model" model to use, note this needs red and blue as skins 0 and 1...
2551 "noise" sound played when flag is picked up...
2552 "noise1" sound played when flag is returned by a teammate...
2553 "noise2" sound played when flag is captured...
2554 "noise3" sound played when flag is lost in the field and respawns itself...
2555 "noise4" sound played when flag is dropped by a player...
2556 "noise5" sound played when flag touches the ground... */
2557 spawnfunc(item_flag_team2)
2559 if(!g_ctf) { delete(this); return; }
2561 ctf_FlagSetup(NUM_TEAM_2, this);
2564 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2565 CTF flag for team three (Yellow).
2567 "angle" Angle the flag will point (minus 90 degrees)...
2568 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2569 "noise" sound played when flag is picked up...
2570 "noise1" sound played when flag is returned by a teammate...
2571 "noise2" sound played when flag is captured...
2572 "noise3" sound played when flag is lost in the field and respawns itself...
2573 "noise4" sound played when flag is dropped by a player...
2574 "noise5" sound played when flag touches the ground... */
2575 spawnfunc(item_flag_team3)
2577 if(!g_ctf) { delete(this); return; }
2579 ctf_FlagSetup(NUM_TEAM_3, this);
2582 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2583 CTF flag for team four (Pink).
2585 "angle" Angle the flag will point (minus 90 degrees)...
2586 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2587 "noise" sound played when flag is picked up...
2588 "noise1" sound played when flag is returned by a teammate...
2589 "noise2" sound played when flag is captured...
2590 "noise3" sound played when flag is lost in the field and respawns itself...
2591 "noise4" sound played when flag is dropped by a player...
2592 "noise5" sound played when flag touches the ground... */
2593 spawnfunc(item_flag_team4)
2595 if(!g_ctf) { delete(this); return; }
2597 ctf_FlagSetup(NUM_TEAM_4, this);
2600 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2603 "angle" Angle the flag will point (minus 90 degrees)...
2604 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2605 "noise" sound played when flag is picked up...
2606 "noise1" sound played when flag is returned by a teammate...
2607 "noise2" sound played when flag is captured...
2608 "noise3" sound played when flag is lost in the field and respawns itself...
2609 "noise4" sound played when flag is dropped by a player...
2610 "noise5" sound played when flag touches the ground... */
2611 spawnfunc(item_flag_neutral)
2613 if(!g_ctf) { delete(this); return; }
2614 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2616 ctf_FlagSetup(0, this);
2619 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2620 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2621 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.
2623 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2624 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2627 if(!g_ctf) { delete(this); return; }
2629 this.classname = "ctf_team";
2630 this.team = this.cnt + 1;
2633 // compatibility for quake maps
2634 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2635 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2636 spawnfunc(info_player_team1);
2637 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2638 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2639 spawnfunc(info_player_team2);
2640 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2641 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2643 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2644 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2646 // compatibility for wop maps
2647 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2648 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2649 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2650 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2651 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2652 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2660 void ctf_ScoreRules(int teams)
2662 CheckAllowedTeams(NULL);
2663 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2664 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2665 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2666 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2667 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2668 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2669 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2670 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2671 ScoreRules_basics_end();
2674 // code from here on is just to support maps that don't have flag and team entities
2675 void ctf_SpawnTeam (string teamname, int teamcolor)
2677 entity this = new_pure(ctf_team);
2678 this.netname = teamname;
2679 this.cnt = teamcolor - 1;
2680 this.spawnfunc_checked = true;
2681 this.team = teamcolor;
2684 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2689 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2691 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2692 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2694 switch(tmp_entity.team)
2696 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2697 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2698 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2699 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2701 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2704 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2706 ctf_teams = 0; // so set the default red and blue teams
2707 BITSET_ASSIGN(ctf_teams, BIT(0));
2708 BITSET_ASSIGN(ctf_teams, BIT(1));
2711 //ctf_teams = bound(2, ctf_teams, 4);
2713 // if no teams are found, spawn defaults
2714 if(find(NULL, classname, "ctf_team") == NULL)
2716 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2717 if(ctf_teams & BIT(0))
2718 ctf_SpawnTeam("Red", NUM_TEAM_1);
2719 if(ctf_teams & BIT(1))
2720 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2721 if(ctf_teams & BIT(2))
2722 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2723 if(ctf_teams & BIT(3))
2724 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2727 ctf_ScoreRules(ctf_teams);
2730 void ctf_Initialize()
2732 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2734 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2735 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2736 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2738 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);