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);
158 bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
161 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
163 // automatically return if there's only 1 player on the team
164 return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
168 bool ctf_Return_Customize(entity this, entity client)
170 // only to the carrier
171 return boolean(client == this.owner);
174 void ctf_FlagcarrierWaypoints(entity player)
176 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
177 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
178 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
179 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
181 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
183 if(!player.wps_enemyflagcarrier)
185 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
186 wp.colormod = WPCOLOR_ENEMYFC(player.team);
187 setcefc(wp, ctf_Stalemate_Customize);
189 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
190 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
193 if(!player.wps_flagreturn)
195 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
196 owp.colormod = '0 0.8 0.8';
197 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
198 setcefc(owp, ctf_Return_Customize);
203 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
205 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
206 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
207 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
208 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
211 if(current_height) // make sure we can actually do this arcing path
213 targpos = (to + ('0 0 1' * current_height));
214 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
215 if(trace_fraction < 1)
217 //print("normal arc line failed, trying to find new pos...");
218 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
219 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
220 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
221 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
222 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
225 else { targpos = to; }
227 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
229 vector desired_direction = normalize(targpos - from);
230 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
231 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
234 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
236 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
238 // directional tracing only
240 makevectors(passer_angle);
242 // find the closest point on the enemy to the center of the attack
243 float h; // hypotenuse, which is the distance between attacker to head
244 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
246 h = vlen(head_center - passer_center);
247 a = h * (normalize(head_center - passer_center) * v_forward);
249 vector nearest_on_line = (passer_center + a * v_forward);
250 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
252 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
253 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
255 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
260 else { return true; }
264 // =======================
265 // CaptureShield Functions
266 // =======================
268 bool ctf_CaptureShield_CheckStatus(entity p)
270 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
271 int players_worseeq, players_total;
273 if(ctf_captureshield_max_ratio <= 0)
276 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
277 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
278 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
279 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
281 sr = ((s - s2) + (s3 + s4));
283 if(sr >= -ctf_captureshield_min_negscore)
286 players_total = players_worseeq = 0;
287 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
290 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
291 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
292 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
293 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
295 ser = ((se - se2) + (se3 + se4));
302 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
303 // use this rule here
305 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
311 void ctf_CaptureShield_Update(entity player, bool wanted_status)
313 bool updated_status = ctf_CaptureShield_CheckStatus(player);
314 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
316 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
317 player.ctf_captureshielded = updated_status;
321 bool ctf_CaptureShield_Customize(entity this, entity client)
323 if(!client.ctf_captureshielded) { return false; }
324 if(CTF_SAMETEAM(this, client)) { return false; }
329 void ctf_CaptureShield_Touch(entity this, entity toucher)
331 if(!toucher.ctf_captureshielded) { return; }
332 if(CTF_SAMETEAM(this, toucher)) { return; }
334 vector mymid = (this.absmin + this.absmax) * 0.5;
335 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
337 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
338 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
341 void ctf_CaptureShield_Spawn(entity flag)
343 entity shield = new(ctf_captureshield);
346 shield.team = flag.team;
347 settouch(shield, ctf_CaptureShield_Touch);
348 setcefc(shield, ctf_CaptureShield_Customize);
349 shield.effects = EF_ADDITIVE;
350 set_movetype(shield, MOVETYPE_NOCLIP);
351 shield.solid = SOLID_TRIGGER;
352 shield.avelocity = '7 0 11';
355 setorigin(shield, flag.origin);
356 setmodel(shield, MDL_CTF_SHIELD);
357 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
361 // ====================
362 // Drop/Pass/Throw Code
363 // ====================
365 void ctf_Handle_Drop(entity flag, entity player, int droptype)
368 player = (player ? player : flag.pass_sender);
371 set_movetype(flag, MOVETYPE_TOSS);
372 flag.takedamage = DAMAGE_YES;
373 flag.angles = '0 0 0';
374 flag.health = flag.max_flag_health;
375 flag.ctf_droptime = time;
376 flag.ctf_dropper = player;
377 flag.ctf_status = FLAG_DROPPED;
379 // messages and sounds
380 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
381 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
382 ctf_EventLog("dropped", player.team, player);
385 PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
386 PlayerScore_Add(player, SP_CTF_DROPS, 1);
389 if(autocvar_g_ctf_flag_dropped_waypoint) {
390 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);
391 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
394 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
396 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
397 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
400 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
402 if(droptype == DROP_PASS)
404 flag.pass_distance = 0;
405 flag.pass_sender = NULL;
406 flag.pass_target = NULL;
410 void ctf_Handle_Retrieve(entity flag, entity player)
412 entity sender = flag.pass_sender;
414 // transfer flag to player
416 flag.owner.flagcarried = flag;
421 setattachment(flag, player.vehicle, "");
422 setorigin(flag, VEHICLE_FLAG_OFFSET);
423 flag.scale = VEHICLE_FLAG_SCALE;
427 setattachment(flag, player, "");
428 setorigin(flag, FLAG_CARRY_OFFSET);
430 set_movetype(flag, MOVETYPE_NONE);
431 flag.takedamage = DAMAGE_NO;
432 flag.solid = SOLID_NOT;
433 flag.angles = '0 0 0';
434 flag.ctf_status = FLAG_CARRY;
436 // messages and sounds
437 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
438 ctf_EventLog("receive", flag.team, player);
440 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(
442 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
443 else if(it == player)
444 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
445 else if(SAME_TEAM(it, sender))
446 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
449 // create new waypoint
450 ctf_FlagcarrierWaypoints(player);
452 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
453 player.throw_antispam = sender.throw_antispam;
455 flag.pass_distance = 0;
456 flag.pass_sender = NULL;
457 flag.pass_target = NULL;
460 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
462 entity flag = player.flagcarried;
463 vector targ_origin, flag_velocity;
465 if(!flag) { return; }
466 if((droptype == DROP_PASS) && !receiver) { return; }
468 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
471 setattachment(flag, NULL, "");
472 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
473 flag.owner.flagcarried = NULL;
475 flag.solid = SOLID_TRIGGER;
476 flag.ctf_dropper = player;
477 flag.ctf_droptime = time;
479 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
486 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
487 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
488 WarpZone_RefSys_Copy(flag, receiver);
489 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
490 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
492 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
493 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
496 set_movetype(flag, MOVETYPE_FLY);
497 flag.takedamage = DAMAGE_NO;
498 flag.pass_sender = player;
499 flag.pass_target = receiver;
500 flag.ctf_status = FLAG_PASSING;
503 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
504 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
505 ctf_EventLog("pass", flag.team, player);
511 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'));
513 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)));
514 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
515 ctf_Handle_Drop(flag, player, droptype);
521 flag.velocity = '0 0 0'; // do nothing
528 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);
529 ctf_Handle_Drop(flag, player, droptype);
534 // kill old waypointsprite
535 WaypointSprite_Ping(player.wps_flagcarrier);
536 WaypointSprite_Kill(player.wps_flagcarrier);
538 if(player.wps_enemyflagcarrier)
539 WaypointSprite_Kill(player.wps_enemyflagcarrier);
541 if(player.wps_flagreturn)
542 WaypointSprite_Kill(player.wps_flagreturn);
545 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
548 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
550 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
557 void nades_GiveBonus(entity player, float score);
559 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
561 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
562 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
563 entity player_team_flag = NULL, tmp_entity;
564 float old_time, new_time;
566 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
567 if(CTF_DIFFTEAM(player, flag)) { return; }
570 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
571 if(SAME_TEAM(tmp_entity, player))
573 player_team_flag = tmp_entity;
577 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
579 player.throw_prevtime = time;
580 player.throw_count = 0;
582 // messages and sounds
583 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
584 ctf_CaptureRecord(enemy_flag, player);
585 _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);
589 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
590 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
595 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
596 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
598 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
599 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
600 if(!old_time || new_time < old_time)
601 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
604 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
605 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
608 if(capturetype == CAPTURE_NORMAL)
610 WaypointSprite_Kill(player.wps_flagcarrier);
611 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
613 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
614 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
618 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
619 ctf_RespawnFlag(enemy_flag);
622 void ctf_Handle_Return(entity flag, entity player)
624 // messages and sounds
625 if(IS_MONSTER(player))
627 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
631 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
632 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
634 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
635 ctf_EventLog("return", flag.team, player);
638 if(IS_PLAYER(player))
640 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
641 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
643 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
646 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
650 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
651 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
652 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
656 if(player.flagcarried == flag)
657 WaypointSprite_Kill(player.wps_flagcarrier);
660 ctf_RespawnFlag(flag);
663 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
666 float pickup_dropped_score; // used to calculate dropped pickup score
668 // attach the flag to the player
670 player.flagcarried = flag;
673 setattachment(flag, player.vehicle, "");
674 setorigin(flag, VEHICLE_FLAG_OFFSET);
675 flag.scale = VEHICLE_FLAG_SCALE;
679 setattachment(flag, player, "");
680 setorigin(flag, FLAG_CARRY_OFFSET);
684 set_movetype(flag, MOVETYPE_NONE);
685 flag.takedamage = DAMAGE_NO;
686 flag.solid = SOLID_NOT;
687 flag.angles = '0 0 0';
688 flag.ctf_status = FLAG_CARRY;
692 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
693 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
697 // messages and sounds
698 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
700 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
702 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
703 else if(CTF_DIFFTEAM(player, flag))
704 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
706 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team));
708 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
711 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)));
714 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
715 if(CTF_SAMETEAM(flag, it))
716 if(SAME_TEAM(player, it))
717 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
719 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);
722 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
725 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
726 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
731 PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
732 ctf_EventLog("steal", flag.team, player);
738 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);
739 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);
740 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
741 PlayerTeamScore_AddScore(player, pickup_dropped_score);
742 ctf_EventLog("pickup", flag.team, player);
750 if(pickuptype == PICKUP_BASE)
752 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
753 if((player.speedrunning) && (ctf_captimerecord))
754 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
758 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
761 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
762 ctf_FlagcarrierWaypoints(player);
763 WaypointSprite_Ping(player.wps_flagcarrier);
767 // ===================
768 // Main Flag Functions
769 // ===================
771 void ctf_CheckFlagReturn(entity flag, int returntype)
773 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
775 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
777 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
782 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
784 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
785 case RETURN_SPEEDRUN:
786 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), ctf_captimerecord); break;
787 case RETURN_NEEDKILL:
788 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
791 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
793 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
794 ctf_EventLog("returned", flag.team, NULL);
795 ctf_RespawnFlag(flag);
800 bool ctf_Stalemate_Customize(entity this, entity client)
802 // make spectators see what the player would see
803 entity e = WaypointSprite_getviewentity(client);
804 entity wp_owner = this.owner;
807 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
808 if(SAME_TEAM(wp_owner, e)) { return false; }
809 if(!IS_PLAYER(e)) { return false; }
814 void ctf_CheckStalemate()
817 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
820 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
822 // build list of stale flags
823 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
825 if(autocvar_g_ctf_stalemate)
826 if(tmp_entity.ctf_status != FLAG_BASE)
827 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
829 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
830 ctf_staleflaglist = tmp_entity;
832 switch(tmp_entity.team)
834 case NUM_TEAM_1: ++stale_red_flags; break;
835 case NUM_TEAM_2: ++stale_blue_flags; break;
836 case NUM_TEAM_3: ++stale_yellow_flags; break;
837 case NUM_TEAM_4: ++stale_pink_flags; break;
838 default: ++stale_neutral_flags; break;
844 stale_flags = (stale_neutral_flags >= 1);
846 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
848 if(ctf_oneflag && stale_flags == 1)
849 ctf_stalemate = true;
850 else if(stale_flags >= 2)
851 ctf_stalemate = true;
852 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
853 { ctf_stalemate = false; wpforenemy_announced = false; }
854 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
855 { ctf_stalemate = false; wpforenemy_announced = false; }
857 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
860 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
862 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
864 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);
865 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
866 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
870 if (!wpforenemy_announced)
872 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))));
874 wpforenemy_announced = true;
879 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
881 if(ITEM_DAMAGE_NEEDKILL(deathtype))
883 if(autocvar_g_ctf_flag_return_damage_delay)
884 this.ctf_flagdamaged_byworld = true;
888 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
892 if(autocvar_g_ctf_flag_return_damage)
894 // reduce health and check if it should be returned
895 this.health = this.health - damage;
896 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
901 void ctf_FlagThink(entity this)
906 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
909 if(this == ctf_worldflaglist) // only for the first flag
910 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
913 if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
914 LOG_TRACE("wtf the flag got squashed?");
915 tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
916 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
917 setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
921 switch(this.ctf_status)
925 if(autocvar_g_ctf_dropped_capture_radius)
927 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
928 if(tmp_entity.ctf_status == FLAG_DROPPED)
929 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
930 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
931 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
938 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
940 if(autocvar_g_ctf_flag_dropped_floatinwater)
942 vector midpoint = ((this.absmin + this.absmax) * 0.5);
943 if(pointcontents(midpoint) == CONTENT_WATER)
945 this.velocity = this.velocity * 0.5;
947 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
948 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
950 { set_movetype(this, MOVETYPE_FLY); }
952 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
954 if(autocvar_g_ctf_flag_return_dropped)
956 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
959 ctf_CheckFlagReturn(this, RETURN_DROPPED);
963 if(this.ctf_flagdamaged_byworld)
965 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
966 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
969 else if(autocvar_g_ctf_flag_return_time)
971 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
972 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
980 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
983 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
985 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
986 ImpulseCommands(this.owner);
988 if(autocvar_g_ctf_stalemate)
990 if(time >= wpforenemy_nextthink)
992 ctf_CheckStalemate();
993 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
996 if(CTF_SAMETEAM(this, this.owner) && this.team)
998 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
999 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
1000 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1001 ctf_Handle_Return(this, this.owner);
1008 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1009 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1010 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1012 if((this.pass_target == NULL)
1013 || (IS_DEAD(this.pass_target))
1014 || (this.pass_target.flagcarried)
1015 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1016 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1017 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1019 // give up, pass failed
1020 ctf_Handle_Drop(this, NULL, DROP_PASS);
1024 // still a viable target, go for it
1025 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1030 default: // this should never happen
1032 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1038 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1041 if(gameover) { return; }
1042 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1044 bool is_not_monster = (!IS_MONSTER(toucher));
1046 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1047 if(ITEM_TOUCH_NEEDKILL())
1049 if(!autocvar_g_ctf_flag_return_damage_delay)
1052 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1054 if(!flag.ctf_flagdamaged_byworld) { return; }
1057 // special touch behaviors
1058 if(STAT(FROZEN, toucher)) { return; }
1059 else if(IS_VEHICLE(toucher))
1061 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1062 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1064 return; // do nothing
1066 else if(IS_MONSTER(toucher))
1068 if(!autocvar_g_ctf_allow_monster_touch)
1069 return; // do nothing
1071 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1073 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1075 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1076 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1077 flag.wait = time + FLAG_TOUCHRATE;
1081 else if(IS_DEAD(toucher)) { return; }
1083 switch(flag.ctf_status)
1089 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1090 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1091 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1092 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1094 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1095 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1096 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)
1098 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1099 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1101 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1102 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1108 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1109 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1110 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1111 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1117 LOG_TRACE("Someone touched a flag even though it was being carried?");
1123 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1125 if(DIFF_TEAM(toucher, flag.pass_sender))
1127 if(ctf_Immediate_Return_Allowed(flag, toucher))
1128 ctf_Handle_Return(flag, toucher);
1129 else if(is_not_monster && (!toucher.flagcarried))
1130 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1133 ctf_Handle_Retrieve(flag, toucher);
1140 .float last_respawn;
1141 void ctf_RespawnFlag(entity flag)
1143 // check for flag respawn being called twice in a row
1144 if(flag.last_respawn > time - 0.5)
1145 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1147 flag.last_respawn = time;
1149 // reset the player (if there is one)
1150 if((flag.owner) && (flag.owner.flagcarried == flag))
1152 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1153 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1154 WaypointSprite_Kill(flag.wps_flagcarrier);
1156 flag.owner.flagcarried = NULL;
1158 if(flag.speedrunning)
1159 ctf_FakeTimeLimit(flag.owner, -1);
1162 if((flag.owner) && (flag.owner.vehicle))
1163 flag.scale = FLAG_SCALE;
1165 if(flag.ctf_status == FLAG_DROPPED)
1166 { WaypointSprite_Kill(flag.wps_flagdropped); }
1169 setattachment(flag, NULL, "");
1170 setorigin(flag, flag.ctf_spawnorigin);
1172 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1173 flag.takedamage = DAMAGE_NO;
1174 flag.health = flag.max_flag_health;
1175 flag.solid = SOLID_TRIGGER;
1176 flag.velocity = '0 0 0';
1177 flag.angles = flag.mangle;
1178 flag.flags = FL_ITEM | FL_NOTARGET;
1180 flag.ctf_status = FLAG_BASE;
1182 flag.pass_distance = 0;
1183 flag.pass_sender = NULL;
1184 flag.pass_target = NULL;
1185 flag.ctf_dropper = NULL;
1186 flag.ctf_pickuptime = 0;
1187 flag.ctf_droptime = 0;
1188 flag.ctf_flagdamaged_byworld = false;
1190 ctf_CheckStalemate();
1193 void ctf_Reset(entity this)
1195 if(this.owner && IS_PLAYER(this.owner))
1196 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1198 ctf_RespawnFlag(this);
1201 bool ctf_FlagBase_Customize(entity this, entity client)
1203 if(client.flagcarried && CTF_SAMETEAM(client, client.flagcarried))
1208 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1211 waypoint_spawnforitem_force(this, this.origin);
1212 this.nearestwaypointtimeout = 0; // activate waypointing again
1213 this.bot_basewaypoint = this.nearestwaypoint;
1219 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1220 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1221 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1222 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1223 default: basename = WP_FlagBaseNeutral; break;
1226 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1227 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1228 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1229 setcefc(wp, ctf_FlagBase_Customize);
1231 // captureshield setup
1232 ctf_CaptureShield_Spawn(this);
1237 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1240 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1241 ctf_worldflaglist = flag;
1243 setattachment(flag, NULL, "");
1245 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1246 flag.team = teamnumber;
1247 flag.classname = "item_flag_team";
1248 flag.target = "###item###"; // wut?
1249 flag.flags = FL_ITEM | FL_NOTARGET;
1250 IL_PUSH(g_items, flag);
1251 flag.solid = SOLID_TRIGGER;
1252 flag.takedamage = DAMAGE_NO;
1253 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1254 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1255 flag.health = flag.max_flag_health;
1256 flag.event_damage = ctf_FlagDamage;
1257 flag.pushable = true;
1258 flag.teleportable = TELEPORT_NORMAL;
1259 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1260 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1261 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1262 if(flag.damagedbycontents)
1263 IL_PUSH(g_damagedbycontents, flag);
1264 flag.velocity = '0 0 0';
1265 flag.mangle = flag.angles;
1266 flag.reset = ctf_Reset;
1267 settouch(flag, ctf_FlagTouch);
1268 setthink(flag, ctf_FlagThink);
1269 flag.nextthink = time + FLAG_THINKRATE;
1270 flag.ctf_status = FLAG_BASE;
1272 string teamname = Static_Team_ColorName_Lower(teamnumber);
1274 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1275 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1276 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1277 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1278 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1279 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1283 if(flag.s == "") flag.s = b; \
1284 precache_sound(flag.s);
1286 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1287 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1288 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1289 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1290 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1291 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1292 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1296 precache_model(flag.model);
1299 _setmodel(flag, flag.model); // precision set below
1300 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1301 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1303 if(autocvar_g_ctf_flag_glowtrails)
1307 case NUM_TEAM_1: flag.glow_color = 251; break;
1308 case NUM_TEAM_2: flag.glow_color = 210; break;
1309 case NUM_TEAM_3: flag.glow_color = 110; break;
1310 case NUM_TEAM_4: flag.glow_color = 145; break;
1311 default: flag.glow_color = 254; break;
1313 flag.glow_size = 25;
1314 flag.glow_trail = 1;
1317 flag.effects |= EF_LOWPRECISION;
1318 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1319 if(autocvar_g_ctf_dynamiclights)
1323 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1324 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1325 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1326 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1327 default: flag.effects |= EF_DIMLIGHT; break;
1332 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1334 flag.dropped_origin = flag.origin;
1335 flag.noalign = true;
1336 set_movetype(flag, MOVETYPE_NONE);
1338 else // drop to floor, automatically find a platform and set that as spawn origin
1340 flag.noalign = false;
1342 set_movetype(flag, MOVETYPE_NONE);
1345 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1353 // NOTE: LEGACY CODE, needs to be re-written!
1355 void havocbot_calculate_middlepoint()
1359 vector fo = '0 0 0';
1362 f = ctf_worldflaglist;
1367 f = f.ctf_worldflagnext;
1371 havocbot_ctf_middlepoint = s * (1.0 / n);
1372 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1376 entity havocbot_ctf_find_flag(entity bot)
1379 f = ctf_worldflaglist;
1382 if (CTF_SAMETEAM(bot, f))
1384 f = f.ctf_worldflagnext;
1389 entity havocbot_ctf_find_enemy_flag(entity bot)
1392 f = ctf_worldflaglist;
1397 if(CTF_DIFFTEAM(bot, f))
1404 else if(!bot.flagcarried)
1408 else if (CTF_DIFFTEAM(bot, f))
1410 f = f.ctf_worldflagnext;
1415 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1422 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1423 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1426 if(vdist(it.origin - org, <, tc_radius))
1433 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1436 head = ctf_worldflaglist;
1439 if (CTF_SAMETEAM(this, head))
1441 head = head.ctf_worldflagnext;
1444 navigation_routerating(this, head, ratingscale, 10000);
1447 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1450 head = ctf_worldflaglist;
1453 if (CTF_SAMETEAM(this, head))
1455 head = head.ctf_worldflagnext;
1460 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1463 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1466 head = ctf_worldflaglist;
1471 if(CTF_DIFFTEAM(this, head))
1475 if(this.flagcarried)
1478 else if(!this.flagcarried)
1482 else if(CTF_DIFFTEAM(this, head))
1484 head = head.ctf_worldflagnext;
1487 navigation_routerating(this, head, ratingscale, 10000);
1490 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1492 if (!bot_waypoints_for_items)
1494 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1500 head = havocbot_ctf_find_enemy_flag(this);
1505 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1508 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1512 mf = havocbot_ctf_find_flag(this);
1514 if(mf.ctf_status == FLAG_BASE)
1518 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1521 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1524 head = ctf_worldflaglist;
1527 // flag is out in the field
1528 if(head.ctf_status != FLAG_BASE)
1529 if(head.tag_entity==NULL) // dropped
1533 if(vdist(org - head.origin, <, df_radius))
1534 navigation_routerating(this, head, ratingscale, 10000);
1537 navigation_routerating(this, head, ratingscale, 10000);
1540 head = head.ctf_worldflagnext;
1544 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1546 IL_EACH(g_items, it.bot_pickup,
1548 // gather health and armor only
1550 if (it.health || it.armorvalue)
1551 if (vdist(it.origin - org, <, sradius))
1553 // get the value of the item
1554 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1556 navigation_routerating(this, it, t * ratingscale, 500);
1561 void havocbot_ctf_reset_role(entity this)
1563 float cdefense, cmiddle, coffense;
1570 if(havocbot_ctf_middlepoint == '0 0 0')
1571 havocbot_calculate_middlepoint();
1574 if (this.flagcarried)
1576 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1580 mf = havocbot_ctf_find_flag(this);
1581 ef = havocbot_ctf_find_enemy_flag(this);
1583 // Retrieve stolen flag
1584 if(mf.ctf_status!=FLAG_BASE)
1586 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1590 // If enemy flag is taken go to the middle to intercept pursuers
1591 if(ef.ctf_status!=FLAG_BASE)
1593 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1597 // if there is only me on the team switch to offense
1599 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1603 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1607 // Evaluate best position to take
1608 // Count mates on middle position
1609 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1611 // Count mates on defense position
1612 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1614 // Count mates on offense position
1615 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1617 if(cdefense<=coffense)
1618 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1619 else if(coffense<=cmiddle)
1620 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1622 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1625 void havocbot_role_ctf_carrier(entity this)
1629 havocbot_ctf_reset_role(this);
1633 if (this.flagcarried == NULL)
1635 havocbot_ctf_reset_role(this);
1639 if (this.bot_strategytime < time)
1641 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1643 navigation_goalrating_start(this);
1645 havocbot_goalrating_ctf_enemybase(this, 50000);
1647 havocbot_goalrating_ctf_ourbase(this, 50000);
1650 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1652 navigation_goalrating_end(this);
1654 if (this.navigation_hasgoals)
1655 this.havocbot_cantfindflag = time + 10;
1656 else if (time > this.havocbot_cantfindflag)
1658 // Can't navigate to my own base, suicide!
1659 // TODO: drop it and wander around
1660 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1666 void havocbot_role_ctf_escort(entity this)
1672 havocbot_ctf_reset_role(this);
1676 if (this.flagcarried)
1678 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1682 // If enemy flag is back on the base switch to previous role
1683 ef = havocbot_ctf_find_enemy_flag(this);
1684 if(ef.ctf_status==FLAG_BASE)
1686 this.havocbot_role = this.havocbot_previous_role;
1687 this.havocbot_role_timeout = 0;
1691 // If the flag carrier reached the base switch to defense
1692 mf = havocbot_ctf_find_flag(this);
1693 if(mf.ctf_status!=FLAG_BASE)
1694 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1696 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1700 // Set the role timeout if necessary
1701 if (!this.havocbot_role_timeout)
1703 this.havocbot_role_timeout = time + random() * 30 + 60;
1706 // If nothing happened just switch to previous role
1707 if (time > this.havocbot_role_timeout)
1709 this.havocbot_role = this.havocbot_previous_role;
1710 this.havocbot_role_timeout = 0;
1714 // Chase the flag carrier
1715 if (this.bot_strategytime < time)
1717 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1718 navigation_goalrating_start(this);
1719 havocbot_goalrating_ctf_enemyflag(this, 30000);
1720 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1721 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1722 navigation_goalrating_end(this);
1726 void havocbot_role_ctf_offense(entity this)
1733 havocbot_ctf_reset_role(this);
1737 if (this.flagcarried)
1739 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1744 mf = havocbot_ctf_find_flag(this);
1745 ef = havocbot_ctf_find_enemy_flag(this);
1748 if(mf.ctf_status!=FLAG_BASE)
1751 pos = mf.tag_entity.origin;
1755 // Try to get it if closer than the enemy base
1756 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1758 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1763 // Escort flag carrier
1764 if(ef.ctf_status!=FLAG_BASE)
1767 pos = ef.tag_entity.origin;
1771 if(vdist(pos - mf.dropped_origin, >, 700))
1773 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1778 // About to fail, switch to middlefield
1781 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1785 // Set the role timeout if necessary
1786 if (!this.havocbot_role_timeout)
1787 this.havocbot_role_timeout = time + 120;
1789 if (time > this.havocbot_role_timeout)
1791 havocbot_ctf_reset_role(this);
1795 if (this.bot_strategytime < time)
1797 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1798 navigation_goalrating_start(this);
1799 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1800 havocbot_goalrating_ctf_enemybase(this, 20000);
1801 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1802 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1803 navigation_goalrating_end(this);
1807 // Retriever (temporary role):
1808 void havocbot_role_ctf_retriever(entity this)
1814 havocbot_ctf_reset_role(this);
1818 if (this.flagcarried)
1820 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1824 // If flag is back on the base switch to previous role
1825 mf = havocbot_ctf_find_flag(this);
1826 if(mf.ctf_status==FLAG_BASE)
1828 if(this.goalcurrent == mf)
1830 navigation_clearroute(this);
1831 this.bot_strategytime = 0;
1833 havocbot_ctf_reset_role(this);
1837 if (!this.havocbot_role_timeout)
1838 this.havocbot_role_timeout = time + 20;
1840 if (time > this.havocbot_role_timeout)
1842 havocbot_ctf_reset_role(this);
1846 if (this.bot_strategytime < time)
1851 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1852 navigation_goalrating_start(this);
1853 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1854 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1855 havocbot_goalrating_ctf_enemybase(this, 30000);
1856 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1857 navigation_goalrating_end(this);
1861 void havocbot_role_ctf_middle(entity this)
1867 havocbot_ctf_reset_role(this);
1871 if (this.flagcarried)
1873 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1877 mf = havocbot_ctf_find_flag(this);
1878 if(mf.ctf_status!=FLAG_BASE)
1880 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1884 if (!this.havocbot_role_timeout)
1885 this.havocbot_role_timeout = time + 10;
1887 if (time > this.havocbot_role_timeout)
1889 havocbot_ctf_reset_role(this);
1893 if (this.bot_strategytime < time)
1897 org = havocbot_ctf_middlepoint;
1898 org.z = this.origin.z;
1900 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1901 navigation_goalrating_start(this);
1902 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1903 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1904 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1905 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1906 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1907 havocbot_goalrating_ctf_enemybase(this, 2500);
1908 navigation_goalrating_end(this);
1912 void havocbot_role_ctf_defense(entity this)
1918 havocbot_ctf_reset_role(this);
1922 if (this.flagcarried)
1924 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1928 // If own flag was captured
1929 mf = havocbot_ctf_find_flag(this);
1930 if(mf.ctf_status!=FLAG_BASE)
1932 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1936 if (!this.havocbot_role_timeout)
1937 this.havocbot_role_timeout = time + 30;
1939 if (time > this.havocbot_role_timeout)
1941 havocbot_ctf_reset_role(this);
1944 if (this.bot_strategytime < time)
1949 org = mf.dropped_origin;
1950 mp_radius = havocbot_ctf_middlepoint_radius;
1952 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1953 navigation_goalrating_start(this);
1955 // if enemies are closer to our base, go there
1956 entity closestplayer = NULL;
1957 float distance, bestdistance = 10000;
1958 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1959 distance = vlen(org - it.origin);
1960 if(distance<bestdistance)
1963 bestdistance = distance;
1968 if(DIFF_TEAM(closestplayer, this))
1969 if(vdist(org - this.origin, >, 1000))
1970 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1971 havocbot_goalrating_ctf_ourbase(this, 30000);
1973 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1974 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1975 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1976 havocbot_goalrating_items(this, 10000, org, mp_radius);
1977 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1978 navigation_goalrating_end(this);
1982 void havocbot_role_ctf_setrole(entity bot, int role)
1984 string s = "(null)";
1987 case HAVOCBOT_CTF_ROLE_CARRIER:
1989 bot.havocbot_role = havocbot_role_ctf_carrier;
1990 bot.havocbot_role_timeout = 0;
1991 bot.havocbot_cantfindflag = time + 10;
1992 bot.bot_strategytime = 0;
1994 case HAVOCBOT_CTF_ROLE_DEFENSE:
1996 bot.havocbot_role = havocbot_role_ctf_defense;
1997 bot.havocbot_role_timeout = 0;
1999 case HAVOCBOT_CTF_ROLE_MIDDLE:
2001 bot.havocbot_role = havocbot_role_ctf_middle;
2002 bot.havocbot_role_timeout = 0;
2004 case HAVOCBOT_CTF_ROLE_OFFENSE:
2006 bot.havocbot_role = havocbot_role_ctf_offense;
2007 bot.havocbot_role_timeout = 0;
2009 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2011 bot.havocbot_previous_role = bot.havocbot_role;
2012 bot.havocbot_role = havocbot_role_ctf_retriever;
2013 bot.havocbot_role_timeout = time + 10;
2014 bot.bot_strategytime = 0;
2016 case HAVOCBOT_CTF_ROLE_ESCORT:
2018 bot.havocbot_previous_role = bot.havocbot_role;
2019 bot.havocbot_role = havocbot_role_ctf_escort;
2020 bot.havocbot_role_timeout = time + 30;
2021 bot.bot_strategytime = 0;
2024 LOG_TRACE(bot.netname, " switched to ", s);
2032 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2034 entity player = M_ARGV(0, entity);
2036 int t = 0, t2 = 0, t3 = 0;
2038 // initially clear items so they can be set as necessary later.
2039 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2040 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2041 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2042 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2043 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2044 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2046 // scan through all the flags and notify the client about them
2047 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2049 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2050 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2051 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2052 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2053 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; }
2055 switch(flag.ctf_status)
2060 if((flag.owner == player) || (flag.pass_sender == player))
2061 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2063 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2068 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2074 // item for stopping players from capturing the flag too often
2075 if(player.ctf_captureshielded)
2076 player.ctf_flagstatus |= CTF_SHIELDED;
2079 player.ctf_flagstatus |= CTF_STALEMATE;
2081 // update the health of the flag carrier waypointsprite
2082 if(player.wps_flagcarrier)
2083 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2086 MUTATOR_HOOKFUNCTION(ctf, PlayerDamage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2088 entity frag_attacker = M_ARGV(1, entity);
2089 entity frag_target = M_ARGV(2, entity);
2090 float frag_damage = M_ARGV(4, float);
2091 vector frag_force = M_ARGV(6, vector);
2093 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2095 if(frag_target == frag_attacker) // damage done to yourself
2097 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2098 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2100 else // damage done to everyone else
2102 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2103 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2106 M_ARGV(4, float) = frag_damage;
2107 M_ARGV(6, vector) = frag_force;
2109 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2111 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)))
2112 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2114 frag_target.wps_helpme_time = time;
2115 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2117 // todo: add notification for when flag carrier needs help?
2121 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2123 entity frag_attacker = M_ARGV(1, entity);
2124 entity frag_target = M_ARGV(2, entity);
2126 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2128 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2129 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2132 if(frag_target.flagcarried)
2134 entity tmp_entity = frag_target.flagcarried;
2135 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2136 tmp_entity.ctf_dropper = NULL;
2140 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2142 M_ARGV(2, float) = 0; // frag score
2143 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2146 void ctf_RemovePlayer(entity player)
2148 if(player.flagcarried)
2149 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2151 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2153 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2154 if(flag.pass_target == player) { flag.pass_target = NULL; }
2155 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2159 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2161 entity player = M_ARGV(0, entity);
2163 ctf_RemovePlayer(player);
2166 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2168 entity player = M_ARGV(0, entity);
2170 ctf_RemovePlayer(player);
2173 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2175 entity player = M_ARGV(0, entity);
2177 if(player.flagcarried)
2178 if(!autocvar_g_ctf_portalteleport)
2179 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2182 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2184 if(MUTATOR_RETURNVALUE || gameover) { return; }
2186 entity player = M_ARGV(0, entity);
2188 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2190 // pass the flag to a team mate
2191 if(autocvar_g_ctf_pass)
2193 entity head, closest_target = NULL;
2194 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2196 while(head) // find the closest acceptable target to pass to
2198 if(IS_PLAYER(head) && !IS_DEAD(head))
2199 if(head != player && SAME_TEAM(head, player))
2200 if(!head.speedrunning && !head.vehicle)
2202 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2203 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2204 vector passer_center = CENTER_OR_VIEWOFS(player);
2206 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2208 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2210 if(IS_BOT_CLIENT(head))
2212 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2213 ctf_Handle_Throw(head, player, DROP_PASS);
2217 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2218 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2220 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2223 else if(player.flagcarried)
2227 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2228 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2229 { closest_target = head; }
2231 else { closest_target = head; }
2238 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2241 // throw the flag in front of you
2242 if(autocvar_g_ctf_throw && player.flagcarried)
2244 if(player.throw_count == -1)
2246 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2248 player.throw_prevtime = time;
2249 player.throw_count = 1;
2250 ctf_Handle_Throw(player, NULL, DROP_THROW);
2255 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2261 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2262 else { player.throw_count += 1; }
2263 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2265 player.throw_prevtime = time;
2266 ctf_Handle_Throw(player, NULL, DROP_THROW);
2273 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2275 entity player = M_ARGV(0, entity);
2277 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2279 player.wps_helpme_time = time;
2280 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2282 else // create a normal help me waypointsprite
2284 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2285 WaypointSprite_Ping(player.wps_helpme);
2291 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2293 entity player = M_ARGV(0, entity);
2294 entity veh = M_ARGV(1, entity);
2296 if(player.flagcarried)
2298 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2300 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2304 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2305 setattachment(player.flagcarried, veh, "");
2306 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2307 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2308 //player.flagcarried.angles = '0 0 0';
2314 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2316 entity player = M_ARGV(0, entity);
2318 if(player.flagcarried)
2320 setattachment(player.flagcarried, player, "");
2321 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2322 player.flagcarried.scale = FLAG_SCALE;
2323 player.flagcarried.angles = '0 0 0';
2324 player.flagcarried.nodrawtoclient = NULL;
2329 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2331 entity player = M_ARGV(0, entity);
2333 if(player.flagcarried)
2335 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2336 ctf_RespawnFlag(player.flagcarried);
2341 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2343 entity flag; // temporary entity for the search method
2345 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2347 switch(flag.ctf_status)
2352 // lock the flag, game is over
2353 set_movetype(flag, MOVETYPE_NONE);
2354 flag.takedamage = DAMAGE_NO;
2355 flag.solid = SOLID_NOT;
2356 flag.nextthink = false; // stop thinking
2358 //dprint("stopping the ", flag.netname, " from moving.\n");
2366 // do nothing for these flags
2373 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2375 entity bot = M_ARGV(0, entity);
2377 havocbot_ctf_reset_role(bot);
2381 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2383 //M_ARGV(0, float) = ctf_teams;
2384 M_ARGV(1, string) = "ctf_team";
2388 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2390 entity spectatee = M_ARGV(0, entity);
2391 entity client = M_ARGV(1, entity);
2393 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2396 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2398 int record_page = M_ARGV(0, int);
2399 string ret_string = M_ARGV(1, string);
2401 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2403 if (MapInfo_Get_ByID(i))
2405 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2411 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2412 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2416 M_ARGV(1, string) = ret_string;
2419 bool superspec_Spectate(entity this, entity targ); // TODO
2420 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2421 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2423 entity player = M_ARGV(0, entity);
2424 string cmd_name = M_ARGV(1, string);
2425 int cmd_argc = M_ARGV(2, int);
2427 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2429 if(cmd_name == "followfc")
2441 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2442 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2443 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2444 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2448 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2449 if(it.flagcarried && (it.team == _team || _team == 0))
2452 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2453 continue; // already spectating this fc, try another
2454 return superspec_Spectate(player, it);
2459 superspec_msg("", "", player, "No active flag carrier\n", 1);
2464 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2466 entity frag_target = M_ARGV(0, entity);
2468 if(frag_target.flagcarried)
2469 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2477 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2478 CTF flag for team one (Red).
2480 "angle" Angle the flag will point (minus 90 degrees)...
2481 "model" model to use, note this needs red and blue as skins 0 and 1...
2482 "noise" sound played when flag is picked up...
2483 "noise1" sound played when flag is returned by a teammate...
2484 "noise2" sound played when flag is captured...
2485 "noise3" sound played when flag is lost in the field and respawns itself...
2486 "noise4" sound played when flag is dropped by a player...
2487 "noise5" sound played when flag touches the ground... */
2488 spawnfunc(item_flag_team1)
2490 if(!g_ctf) { delete(this); return; }
2492 ctf_FlagSetup(NUM_TEAM_1, this);
2495 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2496 CTF flag for team two (Blue).
2498 "angle" Angle the flag will point (minus 90 degrees)...
2499 "model" model to use, note this needs red and blue as skins 0 and 1...
2500 "noise" sound played when flag is picked up...
2501 "noise1" sound played when flag is returned by a teammate...
2502 "noise2" sound played when flag is captured...
2503 "noise3" sound played when flag is lost in the field and respawns itself...
2504 "noise4" sound played when flag is dropped by a player...
2505 "noise5" sound played when flag touches the ground... */
2506 spawnfunc(item_flag_team2)
2508 if(!g_ctf) { delete(this); return; }
2510 ctf_FlagSetup(NUM_TEAM_2, this);
2513 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2514 CTF flag for team three (Yellow).
2516 "angle" Angle the flag will point (minus 90 degrees)...
2517 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2518 "noise" sound played when flag is picked up...
2519 "noise1" sound played when flag is returned by a teammate...
2520 "noise2" sound played when flag is captured...
2521 "noise3" sound played when flag is lost in the field and respawns itself...
2522 "noise4" sound played when flag is dropped by a player...
2523 "noise5" sound played when flag touches the ground... */
2524 spawnfunc(item_flag_team3)
2526 if(!g_ctf) { delete(this); return; }
2528 ctf_FlagSetup(NUM_TEAM_3, this);
2531 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2532 CTF flag for team four (Pink).
2534 "angle" Angle the flag will point (minus 90 degrees)...
2535 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2536 "noise" sound played when flag is picked up...
2537 "noise1" sound played when flag is returned by a teammate...
2538 "noise2" sound played when flag is captured...
2539 "noise3" sound played when flag is lost in the field and respawns itself...
2540 "noise4" sound played when flag is dropped by a player...
2541 "noise5" sound played when flag touches the ground... */
2542 spawnfunc(item_flag_team4)
2544 if(!g_ctf) { delete(this); return; }
2546 ctf_FlagSetup(NUM_TEAM_4, this);
2549 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2552 "angle" Angle the flag will point (minus 90 degrees)...
2553 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2554 "noise" sound played when flag is picked up...
2555 "noise1" sound played when flag is returned by a teammate...
2556 "noise2" sound played when flag is captured...
2557 "noise3" sound played when flag is lost in the field and respawns itself...
2558 "noise4" sound played when flag is dropped by a player...
2559 "noise5" sound played when flag touches the ground... */
2560 spawnfunc(item_flag_neutral)
2562 if(!g_ctf) { delete(this); return; }
2563 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2565 ctf_FlagSetup(0, this);
2568 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2569 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2570 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.
2572 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2573 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2576 if(!g_ctf) { delete(this); return; }
2578 this.classname = "ctf_team";
2579 this.team = this.cnt + 1;
2582 // compatibility for quake maps
2583 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2584 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2585 spawnfunc(info_player_team1);
2586 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2587 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2588 spawnfunc(info_player_team2);
2589 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2590 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2592 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2593 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2595 // compatibility for wop maps
2596 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2597 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2598 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2599 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2600 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2601 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2609 void ctf_ScoreRules(int teams)
2611 CheckAllowedTeams(NULL);
2612 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2613 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2614 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2615 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2616 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2617 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2618 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2619 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2620 ScoreRules_basics_end();
2623 // code from here on is just to support maps that don't have flag and team entities
2624 void ctf_SpawnTeam (string teamname, int teamcolor)
2626 entity this = new_pure(ctf_team);
2627 this.netname = teamname;
2628 this.cnt = teamcolor - 1;
2629 this.spawnfunc_checked = true;
2630 this.team = teamcolor;
2633 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2638 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2640 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2641 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2643 switch(tmp_entity.team)
2645 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2646 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2647 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2648 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2650 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2653 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2655 ctf_teams = 0; // so set the default red and blue teams
2656 BITSET_ASSIGN(ctf_teams, BIT(0));
2657 BITSET_ASSIGN(ctf_teams, BIT(1));
2660 //ctf_teams = bound(2, ctf_teams, 4);
2662 // if no teams are found, spawn defaults
2663 if(find(NULL, classname, "ctf_team") == NULL)
2665 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2666 if(ctf_teams & BIT(0))
2667 ctf_SpawnTeam("Red", NUM_TEAM_1);
2668 if(ctf_teams & BIT(1))
2669 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2670 if(ctf_teams & BIT(2))
2671 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2672 if(ctf_teams & BIT(3))
2673 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2676 ctf_ScoreRules(ctf_teams);
2679 void ctf_Initialize()
2681 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2683 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2684 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2685 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2687 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);