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"));
139 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
140 else if(!ctf_captimerecord)
141 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, (cap_time * 100));
142 else if(cap_time < cap_record)
143 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));
145 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));
147 // write that shit in the database
148 if(!ctf_oneflag) // but not in 1-flag mode
149 if((!ctf_captimerecord) || (cap_time < cap_record))
151 ctf_captimerecord = cap_time;
152 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
153 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
154 write_recordmarker(player, (time - cap_time), cap_time);
157 if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
158 race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
161 bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
164 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
166 // automatically return if there's only 1 player on the team
167 return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
171 bool ctf_Return_Customize(entity this, entity client)
173 // only to the carrier
174 return boolean(client == this.owner);
177 void ctf_FlagcarrierWaypoints(entity player)
179 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
180 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
181 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
182 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
184 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
186 if(!player.wps_enemyflagcarrier)
188 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
189 wp.colormod = WPCOLOR_ENEMYFC(player.team);
190 setcefc(wp, ctf_Stalemate_Customize);
192 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
193 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
196 if(!player.wps_flagreturn)
198 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
199 owp.colormod = '0 0.8 0.8';
200 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
201 setcefc(owp, ctf_Return_Customize);
206 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
208 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
209 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
210 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
211 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
214 if(current_height) // make sure we can actually do this arcing path
216 targpos = (to + ('0 0 1' * current_height));
217 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
218 if(trace_fraction < 1)
220 //print("normal arc line failed, trying to find new pos...");
221 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
222 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
223 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
224 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
225 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
228 else { targpos = to; }
230 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
232 vector desired_direction = normalize(targpos - from);
233 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
234 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
237 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
239 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
241 // directional tracing only
243 makevectors(passer_angle);
245 // find the closest point on the enemy to the center of the attack
246 float h; // hypotenuse, which is the distance between attacker to head
247 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
249 h = vlen(head_center - passer_center);
250 a = h * (normalize(head_center - passer_center) * v_forward);
252 vector nearest_on_line = (passer_center + a * v_forward);
253 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
255 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
256 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
258 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
263 else { return true; }
267 // =======================
268 // CaptureShield Functions
269 // =======================
271 bool ctf_CaptureShield_CheckStatus(entity p)
273 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
274 int players_worseeq, players_total;
276 if(ctf_captureshield_max_ratio <= 0)
279 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
280 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
281 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
282 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
284 sr = ((s - s2) + (s3 + s4));
286 if(sr >= -ctf_captureshield_min_negscore)
289 players_total = players_worseeq = 0;
290 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
293 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
294 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
295 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
296 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
298 ser = ((se - se2) + (se3 + se4));
305 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
306 // use this rule here
308 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
314 void ctf_CaptureShield_Update(entity player, bool wanted_status)
316 bool updated_status = ctf_CaptureShield_CheckStatus(player);
317 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
319 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
320 player.ctf_captureshielded = updated_status;
324 bool ctf_CaptureShield_Customize(entity this, entity client)
326 if(!client.ctf_captureshielded) { return false; }
327 if(CTF_SAMETEAM(this, client)) { return false; }
332 void ctf_CaptureShield_Touch(entity this, entity toucher)
334 if(!toucher.ctf_captureshielded) { return; }
335 if(CTF_SAMETEAM(this, toucher)) { return; }
337 vector mymid = (this.absmin + this.absmax) * 0.5;
338 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
340 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
341 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
344 void ctf_CaptureShield_Spawn(entity flag)
346 entity shield = new(ctf_captureshield);
349 shield.team = flag.team;
350 settouch(shield, ctf_CaptureShield_Touch);
351 setcefc(shield, ctf_CaptureShield_Customize);
352 shield.effects = EF_ADDITIVE;
353 set_movetype(shield, MOVETYPE_NOCLIP);
354 shield.solid = SOLID_TRIGGER;
355 shield.avelocity = '7 0 11';
358 setorigin(shield, flag.origin);
359 setmodel(shield, MDL_CTF_SHIELD);
360 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
364 // ====================
365 // Drop/Pass/Throw Code
366 // ====================
368 void ctf_Handle_Drop(entity flag, entity player, int droptype)
371 player = (player ? player : flag.pass_sender);
374 set_movetype(flag, MOVETYPE_TOSS);
375 flag.takedamage = DAMAGE_YES;
376 flag.angles = '0 0 0';
377 flag.health = flag.max_flag_health;
378 flag.ctf_droptime = time;
379 flag.ctf_dropper = player;
380 flag.ctf_status = FLAG_DROPPED;
382 // messages and sounds
383 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
384 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
385 ctf_EventLog("dropped", player.team, player);
388 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
389 PlayerScore_Add(player, SP_CTF_DROPS, 1);
392 if(autocvar_g_ctf_flag_dropped_waypoint) {
393 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);
394 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
397 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
399 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
400 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
403 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
405 if(droptype == DROP_PASS)
407 flag.pass_distance = 0;
408 flag.pass_sender = NULL;
409 flag.pass_target = NULL;
413 void ctf_Handle_Retrieve(entity flag, entity player)
415 entity sender = flag.pass_sender;
417 // transfer flag to player
419 flag.owner.flagcarried = flag;
424 setattachment(flag, player.vehicle, "");
425 setorigin(flag, VEHICLE_FLAG_OFFSET);
426 flag.scale = VEHICLE_FLAG_SCALE;
430 setattachment(flag, player, "");
431 setorigin(flag, FLAG_CARRY_OFFSET);
433 set_movetype(flag, MOVETYPE_NONE);
434 flag.takedamage = DAMAGE_NO;
435 flag.solid = SOLID_NOT;
436 flag.angles = '0 0 0';
437 flag.ctf_status = FLAG_CARRY;
439 // messages and sounds
440 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
441 ctf_EventLog("receive", flag.team, player);
443 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(
445 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
446 else if(it == player)
447 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
448 else if(SAME_TEAM(it, sender))
449 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
452 // create new waypoint
453 ctf_FlagcarrierWaypoints(player);
455 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
456 player.throw_antispam = sender.throw_antispam;
458 flag.pass_distance = 0;
459 flag.pass_sender = NULL;
460 flag.pass_target = NULL;
463 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
465 entity flag = player.flagcarried;
466 vector targ_origin, flag_velocity;
468 if(!flag) { return; }
469 if((droptype == DROP_PASS) && !receiver) { return; }
471 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
474 setattachment(flag, NULL, "");
475 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
476 flag.owner.flagcarried = NULL;
478 flag.solid = SOLID_TRIGGER;
479 flag.ctf_dropper = player;
480 flag.ctf_droptime = time;
482 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
489 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
490 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
491 WarpZone_RefSys_Copy(flag, receiver);
492 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
493 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
495 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
496 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
499 set_movetype(flag, MOVETYPE_FLY);
500 flag.takedamage = DAMAGE_NO;
501 flag.pass_sender = player;
502 flag.pass_target = receiver;
503 flag.ctf_status = FLAG_PASSING;
506 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
507 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
508 ctf_EventLog("pass", flag.team, player);
514 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'));
516 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)));
517 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
518 ctf_Handle_Drop(flag, player, droptype);
524 flag.velocity = '0 0 0'; // do nothing
531 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);
532 ctf_Handle_Drop(flag, player, droptype);
537 // kill old waypointsprite
538 WaypointSprite_Ping(player.wps_flagcarrier);
539 WaypointSprite_Kill(player.wps_flagcarrier);
541 if(player.wps_enemyflagcarrier)
542 WaypointSprite_Kill(player.wps_enemyflagcarrier);
544 if(player.wps_flagreturn)
545 WaypointSprite_Kill(player.wps_flagreturn);
548 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
551 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
553 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
560 void nades_GiveBonus(entity player, float score);
562 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
564 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
565 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
566 entity player_team_flag = NULL, tmp_entity;
567 float old_time, new_time;
569 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
570 if(CTF_DIFFTEAM(player, flag)) { return; }
573 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
574 if(SAME_TEAM(tmp_entity, player))
576 player_team_flag = tmp_entity;
580 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
582 player.throw_prevtime = time;
583 player.throw_count = 0;
585 // messages and sounds
586 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
587 ctf_CaptureRecord(enemy_flag, player);
588 _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);
592 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
593 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
598 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
599 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
601 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
602 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
603 if(!old_time || new_time < old_time)
604 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
607 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
608 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
611 if(capturetype == CAPTURE_NORMAL)
613 WaypointSprite_Kill(player.wps_flagcarrier);
614 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
616 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
617 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
621 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
622 ctf_RespawnFlag(enemy_flag);
625 void ctf_Handle_Return(entity flag, entity player)
627 // messages and sounds
628 if(IS_MONSTER(player))
630 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
634 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
635 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
637 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
638 ctf_EventLog("return", flag.team, player);
641 if(IS_PLAYER(player))
643 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
644 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
646 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
649 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
653 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
654 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
655 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
659 if(player.flagcarried == flag)
660 WaypointSprite_Kill(player.wps_flagcarrier);
663 ctf_RespawnFlag(flag);
666 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
669 float pickup_dropped_score; // used to calculate dropped pickup score
671 // attach the flag to the player
673 player.flagcarried = flag;
676 setattachment(flag, player.vehicle, "");
677 setorigin(flag, VEHICLE_FLAG_OFFSET);
678 flag.scale = VEHICLE_FLAG_SCALE;
682 setattachment(flag, player, "");
683 setorigin(flag, FLAG_CARRY_OFFSET);
687 set_movetype(flag, MOVETYPE_NONE);
688 flag.takedamage = DAMAGE_NO;
689 flag.solid = SOLID_NOT;
690 flag.angles = '0 0 0';
691 flag.ctf_status = FLAG_CARRY;
695 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
696 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
700 // messages and sounds
701 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
703 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
705 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
706 else if(CTF_DIFFTEAM(player, flag))
707 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
709 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
711 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
714 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)));
717 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
718 if(CTF_SAMETEAM(flag, it))
719 if(SAME_TEAM(player, it))
720 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
722 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);
725 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
728 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
729 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
734 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
735 ctf_EventLog("steal", flag.team, player);
741 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);
742 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);
743 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
744 PlayerTeamScore_AddScore(player, pickup_dropped_score);
745 ctf_EventLog("pickup", flag.team, player);
753 if(pickuptype == PICKUP_BASE)
755 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
756 if((player.speedrunning) && (ctf_captimerecord))
757 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
761 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
764 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
765 ctf_FlagcarrierWaypoints(player);
766 WaypointSprite_Ping(player.wps_flagcarrier);
770 // ===================
771 // Main Flag Functions
772 // ===================
774 void ctf_CheckFlagReturn(entity flag, int returntype)
776 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
778 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
780 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
785 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
787 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
788 case RETURN_SPEEDRUN:
789 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), ctf_captimerecord); break;
790 case RETURN_NEEDKILL:
791 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
794 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
796 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
797 ctf_EventLog("returned", flag.team, NULL);
798 ctf_RespawnFlag(flag);
803 bool ctf_Stalemate_Customize(entity this, entity client)
805 // make spectators see what the player would see
806 entity e = WaypointSprite_getviewentity(client);
807 entity wp_owner = this.owner;
810 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
811 if(SAME_TEAM(wp_owner, e)) { return false; }
812 if(!IS_PLAYER(e)) { return false; }
817 void ctf_CheckStalemate()
820 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
823 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
825 // build list of stale flags
826 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
828 if(autocvar_g_ctf_stalemate)
829 if(tmp_entity.ctf_status != FLAG_BASE)
830 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
832 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
833 ctf_staleflaglist = tmp_entity;
835 switch(tmp_entity.team)
837 case NUM_TEAM_1: ++stale_red_flags; break;
838 case NUM_TEAM_2: ++stale_blue_flags; break;
839 case NUM_TEAM_3: ++stale_yellow_flags; break;
840 case NUM_TEAM_4: ++stale_pink_flags; break;
841 default: ++stale_neutral_flags; break;
847 stale_flags = (stale_neutral_flags >= 1);
849 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
851 if(ctf_oneflag && stale_flags == 1)
852 ctf_stalemate = true;
853 else if(stale_flags >= 2)
854 ctf_stalemate = true;
855 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
856 { ctf_stalemate = false; wpforenemy_announced = false; }
857 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
858 { ctf_stalemate = false; wpforenemy_announced = false; }
860 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
863 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
865 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
867 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);
868 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
869 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
873 if (!wpforenemy_announced)
875 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))));
877 wpforenemy_announced = true;
882 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
884 if(ITEM_DAMAGE_NEEDKILL(deathtype))
886 if(autocvar_g_ctf_flag_return_damage_delay)
887 this.ctf_flagdamaged_byworld = true;
891 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
895 if(autocvar_g_ctf_flag_return_damage)
897 // reduce health and check if it should be returned
898 this.health = this.health - damage;
899 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
904 void ctf_FlagThink(entity this)
909 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
912 if(this == ctf_worldflaglist) // only for the first flag
913 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
916 if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
917 LOG_TRACE("wtf the flag got squashed?");
918 tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
919 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
920 setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
924 switch(this.ctf_status)
928 if(autocvar_g_ctf_dropped_capture_radius)
930 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
931 if(tmp_entity.ctf_status == FLAG_DROPPED)
932 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
933 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
934 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
941 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
943 if(autocvar_g_ctf_flag_dropped_floatinwater)
945 vector midpoint = ((this.absmin + this.absmax) * 0.5);
946 if(pointcontents(midpoint) == CONTENT_WATER)
948 this.velocity = this.velocity * 0.5;
950 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
951 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
953 { set_movetype(this, MOVETYPE_FLY); }
955 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
957 if(autocvar_g_ctf_flag_return_dropped)
959 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
962 ctf_CheckFlagReturn(this, RETURN_DROPPED);
966 if(this.ctf_flagdamaged_byworld)
968 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
969 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
972 else if(autocvar_g_ctf_flag_return_time)
974 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
975 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
983 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
986 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
988 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
989 ImpulseCommands(this.owner);
991 if(autocvar_g_ctf_stalemate)
993 if(time >= wpforenemy_nextthink)
995 ctf_CheckStalemate();
996 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
999 if(CTF_SAMETEAM(this, this.owner) && this.team)
1001 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1002 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
1003 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1004 ctf_Handle_Return(this, this.owner);
1011 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1012 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1013 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1015 if((this.pass_target == NULL)
1016 || (IS_DEAD(this.pass_target))
1017 || (this.pass_target.flagcarried)
1018 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1019 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1020 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1022 // give up, pass failed
1023 ctf_Handle_Drop(this, NULL, DROP_PASS);
1027 // still a viable target, go for it
1028 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1033 default: // this should never happen
1035 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1041 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1044 if(game_stopped) return;
1045 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1047 bool is_not_monster = (!IS_MONSTER(toucher));
1049 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1050 if(ITEM_TOUCH_NEEDKILL())
1052 if(!autocvar_g_ctf_flag_return_damage_delay)
1055 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1057 if(!flag.ctf_flagdamaged_byworld) { return; }
1060 // special touch behaviors
1061 if(STAT(FROZEN, toucher)) { return; }
1062 else if(IS_VEHICLE(toucher))
1064 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1065 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1067 return; // do nothing
1069 else if(IS_MONSTER(toucher))
1071 if(!autocvar_g_ctf_allow_monster_touch)
1072 return; // do nothing
1074 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1076 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1078 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1079 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1080 flag.wait = time + FLAG_TOUCHRATE;
1084 else if(IS_DEAD(toucher)) { return; }
1086 switch(flag.ctf_status)
1092 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1093 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1094 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1095 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1097 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1098 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1099 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)
1101 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1102 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1104 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1105 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1111 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1112 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1113 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1114 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1120 LOG_TRACE("Someone touched a flag even though it was being carried?");
1126 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1128 if(DIFF_TEAM(toucher, flag.pass_sender))
1130 if(ctf_Immediate_Return_Allowed(flag, toucher))
1131 ctf_Handle_Return(flag, toucher);
1132 else if(is_not_monster && (!toucher.flagcarried))
1133 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1136 ctf_Handle_Retrieve(flag, toucher);
1143 .float last_respawn;
1144 void ctf_RespawnFlag(entity flag)
1146 // check for flag respawn being called twice in a row
1147 if(flag.last_respawn > time - 0.5)
1148 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1150 flag.last_respawn = time;
1152 // reset the player (if there is one)
1153 if((flag.owner) && (flag.owner.flagcarried == flag))
1155 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1156 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1157 WaypointSprite_Kill(flag.wps_flagcarrier);
1159 flag.owner.flagcarried = NULL;
1161 if(flag.speedrunning)
1162 ctf_FakeTimeLimit(flag.owner, -1);
1165 if((flag.owner) && (flag.owner.vehicle))
1166 flag.scale = FLAG_SCALE;
1168 if(flag.ctf_status == FLAG_DROPPED)
1169 { WaypointSprite_Kill(flag.wps_flagdropped); }
1172 setattachment(flag, NULL, "");
1173 setorigin(flag, flag.ctf_spawnorigin);
1175 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1176 flag.takedamage = DAMAGE_NO;
1177 flag.health = flag.max_flag_health;
1178 flag.solid = SOLID_TRIGGER;
1179 flag.velocity = '0 0 0';
1180 flag.angles = flag.mangle;
1181 flag.flags = FL_ITEM | FL_NOTARGET;
1183 flag.ctf_status = FLAG_BASE;
1185 flag.pass_distance = 0;
1186 flag.pass_sender = NULL;
1187 flag.pass_target = NULL;
1188 flag.ctf_dropper = NULL;
1189 flag.ctf_pickuptime = 0;
1190 flag.ctf_droptime = 0;
1191 flag.ctf_flagdamaged_byworld = false;
1193 ctf_CheckStalemate();
1196 void ctf_Reset(entity this)
1198 if(this.owner && IS_PLAYER(this.owner))
1199 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1201 ctf_RespawnFlag(this);
1204 bool ctf_FlagBase_Customize(entity this, entity client)
1206 if(client.flagcarried && CTF_SAMETEAM(client, client.flagcarried))
1211 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1214 waypoint_spawnforitem_force(this, this.origin);
1215 this.nearestwaypointtimeout = 0; // activate waypointing again
1216 this.bot_basewaypoint = this.nearestwaypoint;
1222 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1223 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1224 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1225 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1226 default: basename = WP_FlagBaseNeutral; break;
1229 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1230 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1231 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1232 setcefc(wp, ctf_FlagBase_Customize);
1234 // captureshield setup
1235 ctf_CaptureShield_Spawn(this);
1240 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1243 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1244 ctf_worldflaglist = flag;
1246 setattachment(flag, NULL, "");
1248 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1249 flag.team = teamnumber;
1250 flag.classname = "item_flag_team";
1251 flag.target = "###item###"; // wut?
1252 flag.flags = FL_ITEM | FL_NOTARGET;
1253 IL_PUSH(g_items, flag);
1254 flag.solid = SOLID_TRIGGER;
1255 flag.takedamage = DAMAGE_NO;
1256 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1257 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1258 flag.health = flag.max_flag_health;
1259 flag.event_damage = ctf_FlagDamage;
1260 flag.pushable = true;
1261 flag.teleportable = TELEPORT_NORMAL;
1262 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1263 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1264 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1265 if(flag.damagedbycontents)
1266 IL_PUSH(g_damagedbycontents, flag);
1267 flag.velocity = '0 0 0';
1268 flag.mangle = flag.angles;
1269 flag.reset = ctf_Reset;
1270 settouch(flag, ctf_FlagTouch);
1271 setthink(flag, ctf_FlagThink);
1272 flag.nextthink = time + FLAG_THINKRATE;
1273 flag.ctf_status = FLAG_BASE;
1275 string teamname = Static_Team_ColorName_Lower(teamnumber);
1277 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1278 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1279 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1280 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1281 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1282 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1286 if(flag.s == "") flag.s = b; \
1287 precache_sound(flag.s);
1289 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1290 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1291 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1292 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1293 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1294 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1295 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1299 precache_model(flag.model);
1302 _setmodel(flag, flag.model); // precision set below
1303 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1304 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1306 if(autocvar_g_ctf_flag_glowtrails)
1310 case NUM_TEAM_1: flag.glow_color = 251; break;
1311 case NUM_TEAM_2: flag.glow_color = 210; break;
1312 case NUM_TEAM_3: flag.glow_color = 110; break;
1313 case NUM_TEAM_4: flag.glow_color = 145; break;
1314 default: flag.glow_color = 254; break;
1316 flag.glow_size = 25;
1317 flag.glow_trail = 1;
1320 flag.effects |= EF_LOWPRECISION;
1321 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1322 if(autocvar_g_ctf_dynamiclights)
1326 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1327 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1328 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1329 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1330 default: flag.effects |= EF_DIMLIGHT; break;
1335 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1337 flag.dropped_origin = flag.origin;
1338 flag.noalign = true;
1339 set_movetype(flag, MOVETYPE_NONE);
1341 else // drop to floor, automatically find a platform and set that as spawn origin
1343 flag.noalign = false;
1345 set_movetype(flag, MOVETYPE_NONE);
1348 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1356 // NOTE: LEGACY CODE, needs to be re-written!
1358 void havocbot_calculate_middlepoint()
1362 vector fo = '0 0 0';
1365 f = ctf_worldflaglist;
1370 f = f.ctf_worldflagnext;
1374 havocbot_ctf_middlepoint = s * (1.0 / n);
1375 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1379 entity havocbot_ctf_find_flag(entity bot)
1382 f = ctf_worldflaglist;
1385 if (CTF_SAMETEAM(bot, f))
1387 f = f.ctf_worldflagnext;
1392 entity havocbot_ctf_find_enemy_flag(entity bot)
1395 f = ctf_worldflaglist;
1400 if(CTF_DIFFTEAM(bot, f))
1407 else if(!bot.flagcarried)
1411 else if (CTF_DIFFTEAM(bot, f))
1413 f = f.ctf_worldflagnext;
1418 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1425 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1426 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1429 if(vdist(it.origin - org, <, tc_radius))
1436 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1439 head = ctf_worldflaglist;
1442 if (CTF_SAMETEAM(this, head))
1444 head = head.ctf_worldflagnext;
1447 navigation_routerating(this, head, ratingscale, 10000);
1450 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1453 head = ctf_worldflaglist;
1456 if (CTF_SAMETEAM(this, head))
1458 head = head.ctf_worldflagnext;
1463 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1466 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1469 head = ctf_worldflaglist;
1474 if(CTF_DIFFTEAM(this, head))
1478 if(this.flagcarried)
1481 else if(!this.flagcarried)
1485 else if(CTF_DIFFTEAM(this, head))
1487 head = head.ctf_worldflagnext;
1490 navigation_routerating(this, head, ratingscale, 10000);
1493 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1495 if (!bot_waypoints_for_items)
1497 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1503 head = havocbot_ctf_find_enemy_flag(this);
1508 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1511 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1515 mf = havocbot_ctf_find_flag(this);
1517 if(mf.ctf_status == FLAG_BASE)
1521 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1524 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1527 head = ctf_worldflaglist;
1530 // flag is out in the field
1531 if(head.ctf_status != FLAG_BASE)
1532 if(head.tag_entity==NULL) // dropped
1536 if(vdist(org - head.origin, <, df_radius))
1537 navigation_routerating(this, head, ratingscale, 10000);
1540 navigation_routerating(this, head, ratingscale, 10000);
1543 head = head.ctf_worldflagnext;
1547 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1549 IL_EACH(g_items, it.bot_pickup,
1551 // gather health and armor only
1553 if (it.health || it.armorvalue)
1554 if (vdist(it.origin - org, <, sradius))
1556 // get the value of the item
1557 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1559 navigation_routerating(this, it, t * ratingscale, 500);
1564 void havocbot_ctf_reset_role(entity this)
1566 float cdefense, cmiddle, coffense;
1573 if(havocbot_ctf_middlepoint == '0 0 0')
1574 havocbot_calculate_middlepoint();
1577 if (this.flagcarried)
1579 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1583 mf = havocbot_ctf_find_flag(this);
1584 ef = havocbot_ctf_find_enemy_flag(this);
1586 // Retrieve stolen flag
1587 if(mf.ctf_status!=FLAG_BASE)
1589 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1593 // If enemy flag is taken go to the middle to intercept pursuers
1594 if(ef.ctf_status!=FLAG_BASE)
1596 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1600 // if there is only me on the team switch to offense
1602 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1606 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1610 // Evaluate best position to take
1611 // Count mates on middle position
1612 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1614 // Count mates on defense position
1615 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1617 // Count mates on offense position
1618 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1620 if(cdefense<=coffense)
1621 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1622 else if(coffense<=cmiddle)
1623 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1625 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1628 void havocbot_role_ctf_carrier(entity this)
1632 havocbot_ctf_reset_role(this);
1636 if (this.flagcarried == NULL)
1638 havocbot_ctf_reset_role(this);
1642 if (this.bot_strategytime < time)
1644 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1646 navigation_goalrating_start(this);
1648 havocbot_goalrating_ctf_enemybase(this, 50000);
1650 havocbot_goalrating_ctf_ourbase(this, 50000);
1653 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1655 navigation_goalrating_end(this);
1657 if (this.navigation_hasgoals)
1658 this.havocbot_cantfindflag = time + 10;
1659 else if (time > this.havocbot_cantfindflag)
1661 // Can't navigate to my own base, suicide!
1662 // TODO: drop it and wander around
1663 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1669 void havocbot_role_ctf_escort(entity this)
1675 havocbot_ctf_reset_role(this);
1679 if (this.flagcarried)
1681 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1685 // If enemy flag is back on the base switch to previous role
1686 ef = havocbot_ctf_find_enemy_flag(this);
1687 if(ef.ctf_status==FLAG_BASE)
1689 this.havocbot_role = this.havocbot_previous_role;
1690 this.havocbot_role_timeout = 0;
1694 // If the flag carrier reached the base switch to defense
1695 mf = havocbot_ctf_find_flag(this);
1696 if(mf.ctf_status!=FLAG_BASE)
1697 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1699 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1703 // Set the role timeout if necessary
1704 if (!this.havocbot_role_timeout)
1706 this.havocbot_role_timeout = time + random() * 30 + 60;
1709 // If nothing happened just switch to previous role
1710 if (time > this.havocbot_role_timeout)
1712 this.havocbot_role = this.havocbot_previous_role;
1713 this.havocbot_role_timeout = 0;
1717 // Chase the flag carrier
1718 if (this.bot_strategytime < time)
1720 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1721 navigation_goalrating_start(this);
1722 havocbot_goalrating_ctf_enemyflag(this, 30000);
1723 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1724 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1725 navigation_goalrating_end(this);
1729 void havocbot_role_ctf_offense(entity this)
1736 havocbot_ctf_reset_role(this);
1740 if (this.flagcarried)
1742 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1747 mf = havocbot_ctf_find_flag(this);
1748 ef = havocbot_ctf_find_enemy_flag(this);
1751 if(mf.ctf_status!=FLAG_BASE)
1754 pos = mf.tag_entity.origin;
1758 // Try to get it if closer than the enemy base
1759 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1761 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1766 // Escort flag carrier
1767 if(ef.ctf_status!=FLAG_BASE)
1770 pos = ef.tag_entity.origin;
1774 if(vdist(pos - mf.dropped_origin, >, 700))
1776 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1781 // About to fail, switch to middlefield
1784 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1788 // Set the role timeout if necessary
1789 if (!this.havocbot_role_timeout)
1790 this.havocbot_role_timeout = time + 120;
1792 if (time > this.havocbot_role_timeout)
1794 havocbot_ctf_reset_role(this);
1798 if (this.bot_strategytime < time)
1800 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1801 navigation_goalrating_start(this);
1802 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1803 havocbot_goalrating_ctf_enemybase(this, 20000);
1804 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1805 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1806 navigation_goalrating_end(this);
1810 // Retriever (temporary role):
1811 void havocbot_role_ctf_retriever(entity this)
1817 havocbot_ctf_reset_role(this);
1821 if (this.flagcarried)
1823 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1827 // If flag is back on the base switch to previous role
1828 mf = havocbot_ctf_find_flag(this);
1829 if(mf.ctf_status==FLAG_BASE)
1831 if(this.goalcurrent == mf)
1833 navigation_clearroute(this);
1834 this.bot_strategytime = 0;
1836 havocbot_ctf_reset_role(this);
1840 if (!this.havocbot_role_timeout)
1841 this.havocbot_role_timeout = time + 20;
1843 if (time > this.havocbot_role_timeout)
1845 havocbot_ctf_reset_role(this);
1849 if (this.bot_strategytime < time)
1854 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1855 navigation_goalrating_start(this);
1856 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1857 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1858 havocbot_goalrating_ctf_enemybase(this, 30000);
1859 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1860 navigation_goalrating_end(this);
1864 void havocbot_role_ctf_middle(entity this)
1870 havocbot_ctf_reset_role(this);
1874 if (this.flagcarried)
1876 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1880 mf = havocbot_ctf_find_flag(this);
1881 if(mf.ctf_status!=FLAG_BASE)
1883 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1887 if (!this.havocbot_role_timeout)
1888 this.havocbot_role_timeout = time + 10;
1890 if (time > this.havocbot_role_timeout)
1892 havocbot_ctf_reset_role(this);
1896 if (this.bot_strategytime < time)
1900 org = havocbot_ctf_middlepoint;
1901 org.z = this.origin.z;
1903 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1904 navigation_goalrating_start(this);
1905 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1906 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1907 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1908 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1909 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1910 havocbot_goalrating_ctf_enemybase(this, 2500);
1911 navigation_goalrating_end(this);
1915 void havocbot_role_ctf_defense(entity this)
1921 havocbot_ctf_reset_role(this);
1925 if (this.flagcarried)
1927 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1931 // If own flag was captured
1932 mf = havocbot_ctf_find_flag(this);
1933 if(mf.ctf_status!=FLAG_BASE)
1935 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1939 if (!this.havocbot_role_timeout)
1940 this.havocbot_role_timeout = time + 30;
1942 if (time > this.havocbot_role_timeout)
1944 havocbot_ctf_reset_role(this);
1947 if (this.bot_strategytime < time)
1952 org = mf.dropped_origin;
1953 mp_radius = havocbot_ctf_middlepoint_radius;
1955 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1956 navigation_goalrating_start(this);
1958 // if enemies are closer to our base, go there
1959 entity closestplayer = NULL;
1960 float distance, bestdistance = 10000;
1961 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1962 distance = vlen(org - it.origin);
1963 if(distance<bestdistance)
1966 bestdistance = distance;
1971 if(DIFF_TEAM(closestplayer, this))
1972 if(vdist(org - this.origin, >, 1000))
1973 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1974 havocbot_goalrating_ctf_ourbase(this, 30000);
1976 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1977 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1978 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1979 havocbot_goalrating_items(this, 10000, org, mp_radius);
1980 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1981 navigation_goalrating_end(this);
1985 void havocbot_role_ctf_setrole(entity bot, int role)
1987 string s = "(null)";
1990 case HAVOCBOT_CTF_ROLE_CARRIER:
1992 bot.havocbot_role = havocbot_role_ctf_carrier;
1993 bot.havocbot_role_timeout = 0;
1994 bot.havocbot_cantfindflag = time + 10;
1995 bot.bot_strategytime = 0;
1997 case HAVOCBOT_CTF_ROLE_DEFENSE:
1999 bot.havocbot_role = havocbot_role_ctf_defense;
2000 bot.havocbot_role_timeout = 0;
2002 case HAVOCBOT_CTF_ROLE_MIDDLE:
2004 bot.havocbot_role = havocbot_role_ctf_middle;
2005 bot.havocbot_role_timeout = 0;
2007 case HAVOCBOT_CTF_ROLE_OFFENSE:
2009 bot.havocbot_role = havocbot_role_ctf_offense;
2010 bot.havocbot_role_timeout = 0;
2012 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2014 bot.havocbot_previous_role = bot.havocbot_role;
2015 bot.havocbot_role = havocbot_role_ctf_retriever;
2016 bot.havocbot_role_timeout = time + 10;
2017 bot.bot_strategytime = 0;
2019 case HAVOCBOT_CTF_ROLE_ESCORT:
2021 bot.havocbot_previous_role = bot.havocbot_role;
2022 bot.havocbot_role = havocbot_role_ctf_escort;
2023 bot.havocbot_role_timeout = time + 30;
2024 bot.bot_strategytime = 0;
2027 LOG_TRACE(bot.netname, " switched to ", s);
2035 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2037 entity player = M_ARGV(0, entity);
2039 int t = 0, t2 = 0, t3 = 0;
2041 // initially clear items so they can be set as necessary later.
2042 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2043 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2044 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2045 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2046 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2047 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2049 // scan through all the flags and notify the client about them
2050 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2052 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2053 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2054 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2055 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2056 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; }
2058 switch(flag.ctf_status)
2063 if((flag.owner == player) || (flag.pass_sender == player))
2064 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2066 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2071 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2077 // item for stopping players from capturing the flag too often
2078 if(player.ctf_captureshielded)
2079 player.ctf_flagstatus |= CTF_SHIELDED;
2082 player.ctf_flagstatus |= CTF_STALEMATE;
2084 // update the health of the flag carrier waypointsprite
2085 if(player.wps_flagcarrier)
2086 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2089 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2091 entity frag_attacker = M_ARGV(1, entity);
2092 entity frag_target = M_ARGV(2, entity);
2093 float frag_damage = M_ARGV(4, float);
2094 vector frag_force = M_ARGV(6, vector);
2096 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2098 if(frag_target == frag_attacker) // damage done to yourself
2100 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2101 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2103 else // damage done to everyone else
2105 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2106 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2109 M_ARGV(4, float) = frag_damage;
2110 M_ARGV(6, vector) = frag_force;
2112 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2114 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)))
2115 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2117 frag_target.wps_helpme_time = time;
2118 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2120 // todo: add notification for when flag carrier needs help?
2124 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2126 entity frag_attacker = M_ARGV(1, entity);
2127 entity frag_target = M_ARGV(2, entity);
2129 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2131 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2132 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2135 if(frag_target.flagcarried)
2137 entity tmp_entity = frag_target.flagcarried;
2138 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2139 tmp_entity.ctf_dropper = NULL;
2143 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2145 M_ARGV(2, float) = 0; // frag score
2146 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2149 void ctf_RemovePlayer(entity player)
2151 if(player.flagcarried)
2152 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2154 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2156 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2157 if(flag.pass_target == player) { flag.pass_target = NULL; }
2158 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2162 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2164 entity player = M_ARGV(0, entity);
2166 ctf_RemovePlayer(player);
2169 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2171 entity player = M_ARGV(0, entity);
2173 ctf_RemovePlayer(player);
2176 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2178 if(!autocvar_g_ctf_leaderboard)
2181 entity player = M_ARGV(0, entity);
2183 if(IS_REAL_CLIENT(player))
2185 for(int i = 1; i <= RANKINGS_CNT; ++i)
2187 race_SendRankings(i, 0, 0, MSG_ONE);
2192 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2194 entity player = M_ARGV(0, entity);
2196 if(player.flagcarried)
2197 if(!autocvar_g_ctf_portalteleport)
2198 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2201 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2203 if(MUTATOR_RETURNVALUE || game_stopped) return;
2205 entity player = M_ARGV(0, entity);
2207 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2209 // pass the flag to a team mate
2210 if(autocvar_g_ctf_pass)
2212 entity head, closest_target = NULL;
2213 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2215 while(head) // find the closest acceptable target to pass to
2217 if(IS_PLAYER(head) && !IS_DEAD(head))
2218 if(head != player && SAME_TEAM(head, player))
2219 if(!head.speedrunning && !head.vehicle)
2221 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2222 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2223 vector passer_center = CENTER_OR_VIEWOFS(player);
2225 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2227 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2229 if(IS_BOT_CLIENT(head))
2231 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2232 ctf_Handle_Throw(head, player, DROP_PASS);
2236 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2237 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2239 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2242 else if(player.flagcarried)
2246 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2247 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2248 { closest_target = head; }
2250 else { closest_target = head; }
2257 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2260 // throw the flag in front of you
2261 if(autocvar_g_ctf_throw && player.flagcarried)
2263 if(player.throw_count == -1)
2265 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2267 player.throw_prevtime = time;
2268 player.throw_count = 1;
2269 ctf_Handle_Throw(player, NULL, DROP_THROW);
2274 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2280 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2281 else { player.throw_count += 1; }
2282 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2284 player.throw_prevtime = time;
2285 ctf_Handle_Throw(player, NULL, DROP_THROW);
2292 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2294 entity player = M_ARGV(0, entity);
2296 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2298 player.wps_helpme_time = time;
2299 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2301 else // create a normal help me waypointsprite
2303 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2304 WaypointSprite_Ping(player.wps_helpme);
2310 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2312 entity player = M_ARGV(0, entity);
2313 entity veh = M_ARGV(1, entity);
2315 if(player.flagcarried)
2317 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2319 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2323 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2324 setattachment(player.flagcarried, veh, "");
2325 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2326 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2327 //player.flagcarried.angles = '0 0 0';
2333 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2335 entity player = M_ARGV(0, entity);
2337 if(player.flagcarried)
2339 setattachment(player.flagcarried, player, "");
2340 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2341 player.flagcarried.scale = FLAG_SCALE;
2342 player.flagcarried.angles = '0 0 0';
2343 player.flagcarried.nodrawtoclient = NULL;
2348 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2350 entity player = M_ARGV(0, entity);
2352 if(player.flagcarried)
2354 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2355 ctf_RespawnFlag(player.flagcarried);
2360 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2362 entity flag; // temporary entity for the search method
2364 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2366 switch(flag.ctf_status)
2371 // lock the flag, game is over
2372 set_movetype(flag, MOVETYPE_NONE);
2373 flag.takedamage = DAMAGE_NO;
2374 flag.solid = SOLID_NOT;
2375 flag.nextthink = false; // stop thinking
2377 //dprint("stopping the ", flag.netname, " from moving.\n");
2385 // do nothing for these flags
2392 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2394 entity bot = M_ARGV(0, entity);
2396 havocbot_ctf_reset_role(bot);
2400 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2402 //M_ARGV(0, float) = ctf_teams;
2403 M_ARGV(1, string) = "ctf_team";
2407 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2409 entity spectatee = M_ARGV(0, entity);
2410 entity client = M_ARGV(1, entity);
2412 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2415 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2417 int record_page = M_ARGV(0, int);
2418 string ret_string = M_ARGV(1, string);
2420 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2422 if (MapInfo_Get_ByID(i))
2424 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2430 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2431 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2435 M_ARGV(1, string) = ret_string;
2438 bool superspec_Spectate(entity this, entity targ); // TODO
2439 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2440 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2442 entity player = M_ARGV(0, entity);
2443 string cmd_name = M_ARGV(1, string);
2444 int cmd_argc = M_ARGV(2, int);
2446 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2448 if(cmd_name == "followfc")
2460 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2461 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2462 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2463 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2467 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2468 if(it.flagcarried && (it.team == _team || _team == 0))
2471 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2472 continue; // already spectating this fc, try another
2473 return superspec_Spectate(player, it);
2478 superspec_msg("", "", player, "No active flag carrier\n", 1);
2483 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2485 entity frag_target = M_ARGV(0, entity);
2487 if(frag_target.flagcarried)
2488 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2496 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2497 CTF flag for team one (Red).
2499 "angle" Angle the flag will point (minus 90 degrees)...
2500 "model" model to use, note this needs red and blue as skins 0 and 1...
2501 "noise" sound played when flag is picked up...
2502 "noise1" sound played when flag is returned by a teammate...
2503 "noise2" sound played when flag is captured...
2504 "noise3" sound played when flag is lost in the field and respawns itself...
2505 "noise4" sound played when flag is dropped by a player...
2506 "noise5" sound played when flag touches the ground... */
2507 spawnfunc(item_flag_team1)
2509 if(!g_ctf) { delete(this); return; }
2511 ctf_FlagSetup(NUM_TEAM_1, this);
2514 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2515 CTF flag for team two (Blue).
2517 "angle" Angle the flag will point (minus 90 degrees)...
2518 "model" model to use, note this needs red and blue as skins 0 and 1...
2519 "noise" sound played when flag is picked up...
2520 "noise1" sound played when flag is returned by a teammate...
2521 "noise2" sound played when flag is captured...
2522 "noise3" sound played when flag is lost in the field and respawns itself...
2523 "noise4" sound played when flag is dropped by a player...
2524 "noise5" sound played when flag touches the ground... */
2525 spawnfunc(item_flag_team2)
2527 if(!g_ctf) { delete(this); return; }
2529 ctf_FlagSetup(NUM_TEAM_2, this);
2532 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2533 CTF flag for team three (Yellow).
2535 "angle" Angle the flag will point (minus 90 degrees)...
2536 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2537 "noise" sound played when flag is picked up...
2538 "noise1" sound played when flag is returned by a teammate...
2539 "noise2" sound played when flag is captured...
2540 "noise3" sound played when flag is lost in the field and respawns itself...
2541 "noise4" sound played when flag is dropped by a player...
2542 "noise5" sound played when flag touches the ground... */
2543 spawnfunc(item_flag_team3)
2545 if(!g_ctf) { delete(this); return; }
2547 ctf_FlagSetup(NUM_TEAM_3, this);
2550 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2551 CTF flag for team four (Pink).
2553 "angle" Angle the flag will point (minus 90 degrees)...
2554 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2555 "noise" sound played when flag is picked up...
2556 "noise1" sound played when flag is returned by a teammate...
2557 "noise2" sound played when flag is captured...
2558 "noise3" sound played when flag is lost in the field and respawns itself...
2559 "noise4" sound played when flag is dropped by a player...
2560 "noise5" sound played when flag touches the ground... */
2561 spawnfunc(item_flag_team4)
2563 if(!g_ctf) { delete(this); return; }
2565 ctf_FlagSetup(NUM_TEAM_4, this);
2568 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2571 "angle" Angle the flag will point (minus 90 degrees)...
2572 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2573 "noise" sound played when flag is picked up...
2574 "noise1" sound played when flag is returned by a teammate...
2575 "noise2" sound played when flag is captured...
2576 "noise3" sound played when flag is lost in the field and respawns itself...
2577 "noise4" sound played when flag is dropped by a player...
2578 "noise5" sound played when flag touches the ground... */
2579 spawnfunc(item_flag_neutral)
2581 if(!g_ctf) { delete(this); return; }
2582 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2584 ctf_FlagSetup(0, this);
2587 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2588 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2589 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.
2591 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2592 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2595 if(!g_ctf) { delete(this); return; }
2597 this.classname = "ctf_team";
2598 this.team = this.cnt + 1;
2601 // compatibility for quake maps
2602 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2603 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2604 spawnfunc(info_player_team1);
2605 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2606 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2607 spawnfunc(info_player_team2);
2608 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2609 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2611 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2612 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2614 // compatibility for wop maps
2615 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2616 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2617 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2618 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2619 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2620 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2628 void ctf_ScoreRules(int teams)
2630 CheckAllowedTeams(NULL);
2631 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2632 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2633 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2634 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2635 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2636 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2637 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2638 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2639 ScoreRules_basics_end();
2642 // code from here on is just to support maps that don't have flag and team entities
2643 void ctf_SpawnTeam (string teamname, int teamcolor)
2645 entity this = new_pure(ctf_team);
2646 this.netname = teamname;
2647 this.cnt = teamcolor - 1;
2648 this.spawnfunc_checked = true;
2649 this.team = teamcolor;
2652 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2657 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2659 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2660 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2662 switch(tmp_entity.team)
2664 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2665 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2666 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2667 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2669 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2672 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2674 ctf_teams = 0; // so set the default red and blue teams
2675 BITSET_ASSIGN(ctf_teams, BIT(0));
2676 BITSET_ASSIGN(ctf_teams, BIT(1));
2679 //ctf_teams = bound(2, ctf_teams, 4);
2681 // if no teams are found, spawn defaults
2682 if(find(NULL, classname, "ctf_team") == NULL)
2684 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2685 if(ctf_teams & BIT(0))
2686 ctf_SpawnTeam("Red", NUM_TEAM_1);
2687 if(ctf_teams & BIT(1))
2688 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2689 if(ctf_teams & BIT(2))
2690 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2691 if(ctf_teams & BIT(3))
2692 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2695 ctf_ScoreRules(ctf_teams);
2698 void ctf_Initialize()
2700 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2702 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2703 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2704 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2706 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);