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);
1135 else if(!toucher.flagcarried)
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;
1375 havocbot_ctf_middlepoint = s / n;
1376 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1380 entity havocbot_ctf_find_flag(entity bot)
1383 f = ctf_worldflaglist;
1386 if (CTF_SAMETEAM(bot, f))
1388 f = f.ctf_worldflagnext;
1393 entity havocbot_ctf_find_enemy_flag(entity bot)
1396 f = ctf_worldflaglist;
1401 if(CTF_DIFFTEAM(bot, f))
1408 else if(!bot.flagcarried)
1412 else if (CTF_DIFFTEAM(bot, f))
1414 f = f.ctf_worldflagnext;
1419 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1426 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1427 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1430 if(vdist(it.origin - org, <, tc_radius))
1437 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1440 head = ctf_worldflaglist;
1443 if (CTF_SAMETEAM(this, head))
1445 head = head.ctf_worldflagnext;
1448 navigation_routerating(this, head, ratingscale, 10000);
1451 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1454 head = ctf_worldflaglist;
1457 if (CTF_SAMETEAM(this, head))
1459 head = head.ctf_worldflagnext;
1464 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1467 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1470 head = ctf_worldflaglist;
1475 if(CTF_DIFFTEAM(this, head))
1479 if(this.flagcarried)
1482 else if(!this.flagcarried)
1486 else if(CTF_DIFFTEAM(this, head))
1488 head = head.ctf_worldflagnext;
1491 navigation_routerating(this, head, ratingscale, 10000);
1494 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1496 if (!bot_waypoints_for_items)
1498 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1504 head = havocbot_ctf_find_enemy_flag(this);
1509 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1512 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1516 mf = havocbot_ctf_find_flag(this);
1518 if(mf.ctf_status == FLAG_BASE)
1522 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1525 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1528 head = ctf_worldflaglist;
1531 // flag is out in the field
1532 if(head.ctf_status != FLAG_BASE)
1533 if(head.tag_entity==NULL) // dropped
1537 if(vdist(org - head.origin, <, df_radius))
1538 navigation_routerating(this, head, ratingscale, 10000);
1541 navigation_routerating(this, head, ratingscale, 10000);
1544 head = head.ctf_worldflagnext;
1548 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1550 IL_EACH(g_items, it.bot_pickup,
1552 // gather health and armor only
1554 if (it.health || it.armorvalue)
1555 if (vdist(it.origin - org, <, sradius))
1557 // get the value of the item
1558 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1560 navigation_routerating(this, it, t * ratingscale, 500);
1565 void havocbot_ctf_reset_role(entity this)
1567 float cdefense, cmiddle, coffense;
1574 if(havocbot_ctf_middlepoint == '0 0 0')
1575 havocbot_calculate_middlepoint();
1578 if (this.flagcarried)
1580 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1584 mf = havocbot_ctf_find_flag(this);
1585 ef = havocbot_ctf_find_enemy_flag(this);
1587 // Retrieve stolen flag
1588 if(mf.ctf_status!=FLAG_BASE)
1590 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1594 // If enemy flag is taken go to the middle to intercept pursuers
1595 if(ef.ctf_status!=FLAG_BASE)
1597 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1601 // if there is only me on the team switch to offense
1603 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1607 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1611 // Evaluate best position to take
1612 // Count mates on middle position
1613 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1615 // Count mates on defense position
1616 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1618 // Count mates on offense position
1619 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1621 if(cdefense<=coffense)
1622 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1623 else if(coffense<=cmiddle)
1624 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1626 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1629 void havocbot_role_ctf_carrier(entity this)
1633 havocbot_ctf_reset_role(this);
1637 if (this.flagcarried == NULL)
1639 havocbot_ctf_reset_role(this);
1643 if (this.bot_strategytime < time)
1645 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1647 navigation_goalrating_start(this);
1649 havocbot_goalrating_ctf_enemybase(this, 50000);
1651 havocbot_goalrating_ctf_ourbase(this, 50000);
1654 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1656 navigation_goalrating_end(this);
1658 if (this.navigation_hasgoals)
1659 this.havocbot_cantfindflag = time + 10;
1660 else if (time > this.havocbot_cantfindflag)
1662 // Can't navigate to my own base, suicide!
1663 // TODO: drop it and wander around
1664 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1670 void havocbot_role_ctf_escort(entity this)
1676 havocbot_ctf_reset_role(this);
1680 if (this.flagcarried)
1682 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1686 // If enemy flag is back on the base switch to previous role
1687 ef = havocbot_ctf_find_enemy_flag(this);
1688 if(ef.ctf_status==FLAG_BASE)
1690 this.havocbot_role = this.havocbot_previous_role;
1691 this.havocbot_role_timeout = 0;
1695 // If the flag carrier reached the base switch to defense
1696 mf = havocbot_ctf_find_flag(this);
1697 if(mf.ctf_status!=FLAG_BASE)
1698 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1700 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1704 // Set the role timeout if necessary
1705 if (!this.havocbot_role_timeout)
1707 this.havocbot_role_timeout = time + random() * 30 + 60;
1710 // If nothing happened just switch to previous role
1711 if (time > this.havocbot_role_timeout)
1713 this.havocbot_role = this.havocbot_previous_role;
1714 this.havocbot_role_timeout = 0;
1718 // Chase the flag carrier
1719 if (this.bot_strategytime < time)
1721 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1722 navigation_goalrating_start(this);
1723 havocbot_goalrating_ctf_enemyflag(this, 30000);
1724 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1725 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1726 navigation_goalrating_end(this);
1730 void havocbot_role_ctf_offense(entity this)
1737 havocbot_ctf_reset_role(this);
1741 if (this.flagcarried)
1743 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1748 mf = havocbot_ctf_find_flag(this);
1749 ef = havocbot_ctf_find_enemy_flag(this);
1752 if(mf.ctf_status!=FLAG_BASE)
1755 pos = mf.tag_entity.origin;
1759 // Try to get it if closer than the enemy base
1760 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1762 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1767 // Escort flag carrier
1768 if(ef.ctf_status!=FLAG_BASE)
1771 pos = ef.tag_entity.origin;
1775 if(vdist(pos - mf.dropped_origin, >, 700))
1777 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1782 // About to fail, switch to middlefield
1785 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1789 // Set the role timeout if necessary
1790 if (!this.havocbot_role_timeout)
1791 this.havocbot_role_timeout = time + 120;
1793 if (time > this.havocbot_role_timeout)
1795 havocbot_ctf_reset_role(this);
1799 if (this.bot_strategytime < time)
1801 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1802 navigation_goalrating_start(this);
1803 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1804 havocbot_goalrating_ctf_enemybase(this, 20000);
1805 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1806 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1807 navigation_goalrating_end(this);
1811 // Retriever (temporary role):
1812 void havocbot_role_ctf_retriever(entity this)
1818 havocbot_ctf_reset_role(this);
1822 if (this.flagcarried)
1824 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1828 // If flag is back on the base switch to previous role
1829 mf = havocbot_ctf_find_flag(this);
1830 if(mf.ctf_status==FLAG_BASE)
1832 if(this.goalcurrent == mf)
1834 navigation_clearroute(this);
1835 this.bot_strategytime = 0;
1837 havocbot_ctf_reset_role(this);
1841 if (!this.havocbot_role_timeout)
1842 this.havocbot_role_timeout = time + 20;
1844 if (time > this.havocbot_role_timeout)
1846 havocbot_ctf_reset_role(this);
1850 if (this.bot_strategytime < time)
1855 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1856 navigation_goalrating_start(this);
1857 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1858 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1859 havocbot_goalrating_ctf_enemybase(this, 30000);
1860 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1861 navigation_goalrating_end(this);
1865 void havocbot_role_ctf_middle(entity this)
1871 havocbot_ctf_reset_role(this);
1875 if (this.flagcarried)
1877 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1881 mf = havocbot_ctf_find_flag(this);
1882 if(mf.ctf_status!=FLAG_BASE)
1884 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1888 if (!this.havocbot_role_timeout)
1889 this.havocbot_role_timeout = time + 10;
1891 if (time > this.havocbot_role_timeout)
1893 havocbot_ctf_reset_role(this);
1897 if (this.bot_strategytime < time)
1901 org = havocbot_ctf_middlepoint;
1902 org.z = this.origin.z;
1904 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1905 navigation_goalrating_start(this);
1906 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1907 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1908 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1909 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1910 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1911 havocbot_goalrating_ctf_enemybase(this, 2500);
1912 navigation_goalrating_end(this);
1916 void havocbot_role_ctf_defense(entity this)
1922 havocbot_ctf_reset_role(this);
1926 if (this.flagcarried)
1928 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1932 // If own flag was captured
1933 mf = havocbot_ctf_find_flag(this);
1934 if(mf.ctf_status!=FLAG_BASE)
1936 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1940 if (!this.havocbot_role_timeout)
1941 this.havocbot_role_timeout = time + 30;
1943 if (time > this.havocbot_role_timeout)
1945 havocbot_ctf_reset_role(this);
1948 if (this.bot_strategytime < time)
1953 org = mf.dropped_origin;
1954 mp_radius = havocbot_ctf_middlepoint_radius;
1956 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1957 navigation_goalrating_start(this);
1959 // if enemies are closer to our base, go there
1960 entity closestplayer = NULL;
1961 float distance, bestdistance = 10000;
1962 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1963 distance = vlen(org - it.origin);
1964 if(distance<bestdistance)
1967 bestdistance = distance;
1972 if(DIFF_TEAM(closestplayer, this))
1973 if(vdist(org - this.origin, >, 1000))
1974 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1975 havocbot_goalrating_ctf_ourbase(this, 30000);
1977 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1978 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1979 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1980 havocbot_goalrating_items(this, 10000, org, mp_radius);
1981 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1982 navigation_goalrating_end(this);
1986 void havocbot_role_ctf_setrole(entity bot, int role)
1988 string s = "(null)";
1991 case HAVOCBOT_CTF_ROLE_CARRIER:
1993 bot.havocbot_role = havocbot_role_ctf_carrier;
1994 bot.havocbot_role_timeout = 0;
1995 bot.havocbot_cantfindflag = time + 10;
1996 bot.bot_strategytime = 0;
1998 case HAVOCBOT_CTF_ROLE_DEFENSE:
2000 bot.havocbot_role = havocbot_role_ctf_defense;
2001 bot.havocbot_role_timeout = 0;
2003 case HAVOCBOT_CTF_ROLE_MIDDLE:
2005 bot.havocbot_role = havocbot_role_ctf_middle;
2006 bot.havocbot_role_timeout = 0;
2008 case HAVOCBOT_CTF_ROLE_OFFENSE:
2010 bot.havocbot_role = havocbot_role_ctf_offense;
2011 bot.havocbot_role_timeout = 0;
2013 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2015 bot.havocbot_previous_role = bot.havocbot_role;
2016 bot.havocbot_role = havocbot_role_ctf_retriever;
2017 bot.havocbot_role_timeout = time + 10;
2018 bot.bot_strategytime = 0;
2020 case HAVOCBOT_CTF_ROLE_ESCORT:
2022 bot.havocbot_previous_role = bot.havocbot_role;
2023 bot.havocbot_role = havocbot_role_ctf_escort;
2024 bot.havocbot_role_timeout = time + 30;
2025 bot.bot_strategytime = 0;
2028 LOG_TRACE(bot.netname, " switched to ", s);
2036 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2038 entity player = M_ARGV(0, entity);
2040 int t = 0, t2 = 0, t3 = 0;
2042 // initially clear items so they can be set as necessary later.
2043 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2044 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2045 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2046 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2047 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2048 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2050 // scan through all the flags and notify the client about them
2051 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2053 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2054 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2055 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2056 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2057 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; }
2059 switch(flag.ctf_status)
2064 if((flag.owner == player) || (flag.pass_sender == player))
2065 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2067 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2072 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2078 // item for stopping players from capturing the flag too often
2079 if(player.ctf_captureshielded)
2080 player.ctf_flagstatus |= CTF_SHIELDED;
2083 player.ctf_flagstatus |= CTF_STALEMATE;
2085 // update the health of the flag carrier waypointsprite
2086 if(player.wps_flagcarrier)
2087 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2090 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2092 entity frag_attacker = M_ARGV(1, entity);
2093 entity frag_target = M_ARGV(2, entity);
2094 float frag_damage = M_ARGV(4, float);
2095 vector frag_force = M_ARGV(6, vector);
2097 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2099 if(frag_target == frag_attacker) // damage done to yourself
2101 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2102 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2104 else // damage done to everyone else
2106 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2107 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2110 M_ARGV(4, float) = frag_damage;
2111 M_ARGV(6, vector) = frag_force;
2113 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2115 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)))
2116 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2118 frag_target.wps_helpme_time = time;
2119 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2121 // todo: add notification for when flag carrier needs help?
2125 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2127 entity frag_attacker = M_ARGV(1, entity);
2128 entity frag_target = M_ARGV(2, entity);
2130 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2132 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2133 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2136 if(frag_target.flagcarried)
2138 entity tmp_entity = frag_target.flagcarried;
2139 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2140 tmp_entity.ctf_dropper = NULL;
2144 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2146 M_ARGV(2, float) = 0; // frag score
2147 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2150 void ctf_RemovePlayer(entity player)
2152 if(player.flagcarried)
2153 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2155 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2157 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2158 if(flag.pass_target == player) { flag.pass_target = NULL; }
2159 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2163 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2165 entity player = M_ARGV(0, entity);
2167 ctf_RemovePlayer(player);
2170 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2172 entity player = M_ARGV(0, entity);
2174 ctf_RemovePlayer(player);
2177 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2179 if(!autocvar_g_ctf_leaderboard)
2182 entity player = M_ARGV(0, entity);
2184 if(IS_REAL_CLIENT(player))
2186 for(int i = 1; i <= RANKINGS_CNT; ++i)
2188 race_SendRankings(i, 0, 0, MSG_ONE);
2193 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2195 if(!autocvar_g_ctf_leaderboard)
2198 entity player = M_ARGV(0, entity);
2200 if(player.cvar_cl_allow_uidtracking == 1 && player.cvar_cl_allow_uid2name == 1)
2202 if (!player.stored_netname)
2203 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2204 if(player.stored_netname != player.netname)
2206 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2207 strunzone(player.stored_netname);
2208 player.stored_netname = strzone(player.netname);
2213 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2215 entity player = M_ARGV(0, entity);
2217 if(player.flagcarried)
2218 if(!autocvar_g_ctf_portalteleport)
2219 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2222 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2224 if(MUTATOR_RETURNVALUE || game_stopped) return;
2226 entity player = M_ARGV(0, entity);
2228 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2230 // pass the flag to a team mate
2231 if(autocvar_g_ctf_pass)
2233 entity head, closest_target = NULL;
2234 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2236 while(head) // find the closest acceptable target to pass to
2238 if(IS_PLAYER(head) && !IS_DEAD(head))
2239 if(head != player && SAME_TEAM(head, player))
2240 if(!head.speedrunning && !head.vehicle)
2242 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2243 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2244 vector passer_center = CENTER_OR_VIEWOFS(player);
2246 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2248 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2250 if(IS_BOT_CLIENT(head))
2252 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2253 ctf_Handle_Throw(head, player, DROP_PASS);
2257 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2258 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2260 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2263 else if(player.flagcarried && !head.flagcarried)
2267 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2268 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2269 { closest_target = head; }
2271 else { closest_target = head; }
2278 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2281 // throw the flag in front of you
2282 if(autocvar_g_ctf_throw && player.flagcarried)
2284 if(player.throw_count == -1)
2286 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2288 player.throw_prevtime = time;
2289 player.throw_count = 1;
2290 ctf_Handle_Throw(player, NULL, DROP_THROW);
2295 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2301 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2302 else { player.throw_count += 1; }
2303 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2305 player.throw_prevtime = time;
2306 ctf_Handle_Throw(player, NULL, DROP_THROW);
2313 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2315 entity player = M_ARGV(0, entity);
2317 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2319 player.wps_helpme_time = time;
2320 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2322 else // create a normal help me waypointsprite
2324 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2325 WaypointSprite_Ping(player.wps_helpme);
2331 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2333 entity player = M_ARGV(0, entity);
2334 entity veh = M_ARGV(1, entity);
2336 if(player.flagcarried)
2338 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2340 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2344 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2345 setattachment(player.flagcarried, veh, "");
2346 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2347 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2348 //player.flagcarried.angles = '0 0 0';
2354 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2356 entity player = M_ARGV(0, entity);
2358 if(player.flagcarried)
2360 setattachment(player.flagcarried, player, "");
2361 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2362 player.flagcarried.scale = FLAG_SCALE;
2363 player.flagcarried.angles = '0 0 0';
2364 player.flagcarried.nodrawtoclient = NULL;
2369 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2371 entity player = M_ARGV(0, entity);
2373 if(player.flagcarried)
2375 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2376 ctf_RespawnFlag(player.flagcarried);
2381 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2383 entity flag; // temporary entity for the search method
2385 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2387 switch(flag.ctf_status)
2392 // lock the flag, game is over
2393 set_movetype(flag, MOVETYPE_NONE);
2394 flag.takedamage = DAMAGE_NO;
2395 flag.solid = SOLID_NOT;
2396 flag.nextthink = false; // stop thinking
2398 //dprint("stopping the ", flag.netname, " from moving.\n");
2406 // do nothing for these flags
2413 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2415 entity bot = M_ARGV(0, entity);
2417 havocbot_ctf_reset_role(bot);
2421 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2423 //M_ARGV(0, float) = ctf_teams;
2424 M_ARGV(1, string) = "ctf_team";
2428 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2430 entity spectatee = M_ARGV(0, entity);
2431 entity client = M_ARGV(1, entity);
2433 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2436 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2438 int record_page = M_ARGV(0, int);
2439 string ret_string = M_ARGV(1, string);
2441 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2443 if (MapInfo_Get_ByID(i))
2445 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2451 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2452 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2456 M_ARGV(1, string) = ret_string;
2459 bool superspec_Spectate(entity this, entity targ); // TODO
2460 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2461 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2463 entity player = M_ARGV(0, entity);
2464 string cmd_name = M_ARGV(1, string);
2465 int cmd_argc = M_ARGV(2, int);
2467 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2469 if(cmd_name == "followfc")
2481 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2482 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2483 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2484 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2488 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2489 if(it.flagcarried && (it.team == _team || _team == 0))
2492 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2493 continue; // already spectating this fc, try another
2494 return superspec_Spectate(player, it);
2499 superspec_msg("", "", player, "No active flag carrier\n", 1);
2504 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2506 entity frag_target = M_ARGV(0, entity);
2508 if(frag_target.flagcarried)
2509 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2517 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2518 CTF flag for team one (Red).
2520 "angle" Angle the flag will point (minus 90 degrees)...
2521 "model" model to use, note this needs red and blue as skins 0 and 1...
2522 "noise" sound played when flag is picked up...
2523 "noise1" sound played when flag is returned by a teammate...
2524 "noise2" sound played when flag is captured...
2525 "noise3" sound played when flag is lost in the field and respawns itself...
2526 "noise4" sound played when flag is dropped by a player...
2527 "noise5" sound played when flag touches the ground... */
2528 spawnfunc(item_flag_team1)
2530 if(!g_ctf) { delete(this); return; }
2532 ctf_FlagSetup(NUM_TEAM_1, this);
2535 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2536 CTF flag for team two (Blue).
2538 "angle" Angle the flag will point (minus 90 degrees)...
2539 "model" model to use, note this needs red and blue as skins 0 and 1...
2540 "noise" sound played when flag is picked up...
2541 "noise1" sound played when flag is returned by a teammate...
2542 "noise2" sound played when flag is captured...
2543 "noise3" sound played when flag is lost in the field and respawns itself...
2544 "noise4" sound played when flag is dropped by a player...
2545 "noise5" sound played when flag touches the ground... */
2546 spawnfunc(item_flag_team2)
2548 if(!g_ctf) { delete(this); return; }
2550 ctf_FlagSetup(NUM_TEAM_2, this);
2553 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2554 CTF flag for team three (Yellow).
2556 "angle" Angle the flag will point (minus 90 degrees)...
2557 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2558 "noise" sound played when flag is picked up...
2559 "noise1" sound played when flag is returned by a teammate...
2560 "noise2" sound played when flag is captured...
2561 "noise3" sound played when flag is lost in the field and respawns itself...
2562 "noise4" sound played when flag is dropped by a player...
2563 "noise5" sound played when flag touches the ground... */
2564 spawnfunc(item_flag_team3)
2566 if(!g_ctf) { delete(this); return; }
2568 ctf_FlagSetup(NUM_TEAM_3, this);
2571 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2572 CTF flag for team four (Pink).
2574 "angle" Angle the flag will point (minus 90 degrees)...
2575 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2576 "noise" sound played when flag is picked up...
2577 "noise1" sound played when flag is returned by a teammate...
2578 "noise2" sound played when flag is captured...
2579 "noise3" sound played when flag is lost in the field and respawns itself...
2580 "noise4" sound played when flag is dropped by a player...
2581 "noise5" sound played when flag touches the ground... */
2582 spawnfunc(item_flag_team4)
2584 if(!g_ctf) { delete(this); return; }
2586 ctf_FlagSetup(NUM_TEAM_4, this);
2589 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2592 "angle" Angle the flag will point (minus 90 degrees)...
2593 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2594 "noise" sound played when flag is picked up...
2595 "noise1" sound played when flag is returned by a teammate...
2596 "noise2" sound played when flag is captured...
2597 "noise3" sound played when flag is lost in the field and respawns itself...
2598 "noise4" sound played when flag is dropped by a player...
2599 "noise5" sound played when flag touches the ground... */
2600 spawnfunc(item_flag_neutral)
2602 if(!g_ctf) { delete(this); return; }
2603 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2605 ctf_FlagSetup(0, this);
2608 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2609 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2610 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.
2612 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2613 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2616 if(!g_ctf) { delete(this); return; }
2618 this.classname = "ctf_team";
2619 this.team = this.cnt + 1;
2622 // compatibility for quake maps
2623 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2624 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2625 spawnfunc(info_player_team1);
2626 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2627 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2628 spawnfunc(info_player_team2);
2629 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2630 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2632 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2633 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2635 // compatibility for wop maps
2636 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2637 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2638 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2639 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2640 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2641 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2649 void ctf_ScoreRules(int teams)
2651 CheckAllowedTeams(NULL);
2652 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2653 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2654 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2655 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2656 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2657 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2658 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2659 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2660 ScoreRules_basics_end();
2663 // code from here on is just to support maps that don't have flag and team entities
2664 void ctf_SpawnTeam (string teamname, int teamcolor)
2666 entity this = new_pure(ctf_team);
2667 this.netname = teamname;
2668 this.cnt = teamcolor - 1;
2669 this.spawnfunc_checked = true;
2670 this.team = teamcolor;
2673 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2678 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2680 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2681 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2683 switch(tmp_entity.team)
2685 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2686 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2687 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2688 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2690 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2693 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2695 ctf_teams = 0; // so set the default red and blue teams
2696 BITSET_ASSIGN(ctf_teams, BIT(0));
2697 BITSET_ASSIGN(ctf_teams, BIT(1));
2700 //ctf_teams = bound(2, ctf_teams, 4);
2702 // if no teams are found, spawn defaults
2703 if(find(NULL, classname, "ctf_team") == NULL)
2705 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2706 if(ctf_teams & BIT(0))
2707 ctf_SpawnTeam("Red", NUM_TEAM_1);
2708 if(ctf_teams & BIT(1))
2709 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2710 if(ctf_teams & BIT(2))
2711 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2712 if(ctf_teams & BIT(3))
2713 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2716 ctf_ScoreRules(ctf_teams);
2719 void ctf_Initialize()
2721 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2723 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2724 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2725 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2727 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);