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_RETURN : CENTER_CTF_PICKUP_RETURN_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(game_stopped) 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;
1372 havocbot_ctf_middlepoint = s / n;
1373 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1377 entity havocbot_ctf_find_flag(entity bot)
1380 f = ctf_worldflaglist;
1383 if (CTF_SAMETEAM(bot, f))
1385 f = f.ctf_worldflagnext;
1390 entity havocbot_ctf_find_enemy_flag(entity bot)
1393 f = ctf_worldflaglist;
1398 if(CTF_DIFFTEAM(bot, f))
1405 else if(!bot.flagcarried)
1409 else if (CTF_DIFFTEAM(bot, f))
1411 f = f.ctf_worldflagnext;
1416 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1423 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1424 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1427 if(vdist(it.origin - org, <, tc_radius))
1434 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1437 head = ctf_worldflaglist;
1440 if (CTF_SAMETEAM(this, head))
1442 head = head.ctf_worldflagnext;
1445 navigation_routerating(this, head, ratingscale, 10000);
1448 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1451 head = ctf_worldflaglist;
1454 if (CTF_SAMETEAM(this, head))
1456 head = head.ctf_worldflagnext;
1461 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1464 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1467 head = ctf_worldflaglist;
1472 if(CTF_DIFFTEAM(this, head))
1476 if(this.flagcarried)
1479 else if(!this.flagcarried)
1483 else if(CTF_DIFFTEAM(this, head))
1485 head = head.ctf_worldflagnext;
1488 navigation_routerating(this, head, ratingscale, 10000);
1491 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1493 if (!bot_waypoints_for_items)
1495 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1501 head = havocbot_ctf_find_enemy_flag(this);
1506 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1509 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1513 mf = havocbot_ctf_find_flag(this);
1515 if(mf.ctf_status == FLAG_BASE)
1519 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1522 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1525 head = ctf_worldflaglist;
1528 // flag is out in the field
1529 if(head.ctf_status != FLAG_BASE)
1530 if(head.tag_entity==NULL) // dropped
1534 if(vdist(org - head.origin, <, df_radius))
1535 navigation_routerating(this, head, ratingscale, 10000);
1538 navigation_routerating(this, head, ratingscale, 10000);
1541 head = head.ctf_worldflagnext;
1545 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1547 IL_EACH(g_items, it.bot_pickup,
1549 // gather health and armor only
1551 if (it.health || it.armorvalue)
1552 if (vdist(it.origin - org, <, sradius))
1554 // get the value of the item
1555 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1557 navigation_routerating(this, it, t * ratingscale, 500);
1562 void havocbot_ctf_reset_role(entity this)
1564 float cdefense, cmiddle, coffense;
1571 if(havocbot_ctf_middlepoint == '0 0 0')
1572 havocbot_calculate_middlepoint();
1575 if (this.flagcarried)
1577 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1581 mf = havocbot_ctf_find_flag(this);
1582 ef = havocbot_ctf_find_enemy_flag(this);
1584 // Retrieve stolen flag
1585 if(mf.ctf_status!=FLAG_BASE)
1587 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1591 // If enemy flag is taken go to the middle to intercept pursuers
1592 if(ef.ctf_status!=FLAG_BASE)
1594 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1598 // if there is only me on the team switch to offense
1600 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1604 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1608 // Evaluate best position to take
1609 // Count mates on middle position
1610 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1612 // Count mates on defense position
1613 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1615 // Count mates on offense position
1616 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1618 if(cdefense<=coffense)
1619 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1620 else if(coffense<=cmiddle)
1621 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1623 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1626 void havocbot_role_ctf_carrier(entity this)
1630 havocbot_ctf_reset_role(this);
1634 if (this.flagcarried == NULL)
1636 havocbot_ctf_reset_role(this);
1640 if (this.bot_strategytime < time)
1642 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1644 navigation_goalrating_start(this);
1646 havocbot_goalrating_ctf_enemybase(this, 50000);
1648 havocbot_goalrating_ctf_ourbase(this, 50000);
1651 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1653 navigation_goalrating_end(this);
1655 if (this.navigation_hasgoals)
1656 this.havocbot_cantfindflag = time + 10;
1657 else if (time > this.havocbot_cantfindflag)
1659 // Can't navigate to my own base, suicide!
1660 // TODO: drop it and wander around
1661 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1667 void havocbot_role_ctf_escort(entity this)
1673 havocbot_ctf_reset_role(this);
1677 if (this.flagcarried)
1679 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1683 // If enemy flag is back on the base switch to previous role
1684 ef = havocbot_ctf_find_enemy_flag(this);
1685 if(ef.ctf_status==FLAG_BASE)
1687 this.havocbot_role = this.havocbot_previous_role;
1688 this.havocbot_role_timeout = 0;
1692 // If the flag carrier reached the base switch to defense
1693 mf = havocbot_ctf_find_flag(this);
1694 if(mf.ctf_status!=FLAG_BASE)
1695 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1697 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1701 // Set the role timeout if necessary
1702 if (!this.havocbot_role_timeout)
1704 this.havocbot_role_timeout = time + random() * 30 + 60;
1707 // If nothing happened just switch to previous role
1708 if (time > this.havocbot_role_timeout)
1710 this.havocbot_role = this.havocbot_previous_role;
1711 this.havocbot_role_timeout = 0;
1715 // Chase the flag carrier
1716 if (this.bot_strategytime < time)
1718 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1719 navigation_goalrating_start(this);
1720 havocbot_goalrating_ctf_enemyflag(this, 30000);
1721 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1722 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1723 navigation_goalrating_end(this);
1727 void havocbot_role_ctf_offense(entity this)
1734 havocbot_ctf_reset_role(this);
1738 if (this.flagcarried)
1740 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1745 mf = havocbot_ctf_find_flag(this);
1746 ef = havocbot_ctf_find_enemy_flag(this);
1749 if(mf.ctf_status!=FLAG_BASE)
1752 pos = mf.tag_entity.origin;
1756 // Try to get it if closer than the enemy base
1757 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1759 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1764 // Escort flag carrier
1765 if(ef.ctf_status!=FLAG_BASE)
1768 pos = ef.tag_entity.origin;
1772 if(vdist(pos - mf.dropped_origin, >, 700))
1774 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1779 // About to fail, switch to middlefield
1782 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1786 // Set the role timeout if necessary
1787 if (!this.havocbot_role_timeout)
1788 this.havocbot_role_timeout = time + 120;
1790 if (time > this.havocbot_role_timeout)
1792 havocbot_ctf_reset_role(this);
1796 if (this.bot_strategytime < time)
1798 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1799 navigation_goalrating_start(this);
1800 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1801 havocbot_goalrating_ctf_enemybase(this, 20000);
1802 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1803 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1804 navigation_goalrating_end(this);
1808 // Retriever (temporary role):
1809 void havocbot_role_ctf_retriever(entity this)
1815 havocbot_ctf_reset_role(this);
1819 if (this.flagcarried)
1821 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1825 // If flag is back on the base switch to previous role
1826 mf = havocbot_ctf_find_flag(this);
1827 if(mf.ctf_status==FLAG_BASE)
1829 if(this.goalcurrent == mf)
1831 navigation_clearroute(this);
1832 this.bot_strategytime = 0;
1834 havocbot_ctf_reset_role(this);
1838 if (!this.havocbot_role_timeout)
1839 this.havocbot_role_timeout = time + 20;
1841 if (time > this.havocbot_role_timeout)
1843 havocbot_ctf_reset_role(this);
1847 if (this.bot_strategytime < time)
1852 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1853 navigation_goalrating_start(this);
1854 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1855 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1856 havocbot_goalrating_ctf_enemybase(this, 30000);
1857 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1858 navigation_goalrating_end(this);
1862 void havocbot_role_ctf_middle(entity this)
1868 havocbot_ctf_reset_role(this);
1872 if (this.flagcarried)
1874 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1878 mf = havocbot_ctf_find_flag(this);
1879 if(mf.ctf_status!=FLAG_BASE)
1881 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1885 if (!this.havocbot_role_timeout)
1886 this.havocbot_role_timeout = time + 10;
1888 if (time > this.havocbot_role_timeout)
1890 havocbot_ctf_reset_role(this);
1894 if (this.bot_strategytime < time)
1898 org = havocbot_ctf_middlepoint;
1899 org.z = this.origin.z;
1901 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1902 navigation_goalrating_start(this);
1903 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1904 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1905 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1906 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1907 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1908 havocbot_goalrating_ctf_enemybase(this, 2500);
1909 navigation_goalrating_end(this);
1913 void havocbot_role_ctf_defense(entity this)
1919 havocbot_ctf_reset_role(this);
1923 if (this.flagcarried)
1925 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1929 // If own flag was captured
1930 mf = havocbot_ctf_find_flag(this);
1931 if(mf.ctf_status!=FLAG_BASE)
1933 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1937 if (!this.havocbot_role_timeout)
1938 this.havocbot_role_timeout = time + 30;
1940 if (time > this.havocbot_role_timeout)
1942 havocbot_ctf_reset_role(this);
1945 if (this.bot_strategytime < time)
1950 org = mf.dropped_origin;
1951 mp_radius = havocbot_ctf_middlepoint_radius;
1953 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1954 navigation_goalrating_start(this);
1956 // if enemies are closer to our base, go there
1957 entity closestplayer = NULL;
1958 float distance, bestdistance = 10000;
1959 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1960 distance = vlen(org - it.origin);
1961 if(distance<bestdistance)
1964 bestdistance = distance;
1969 if(DIFF_TEAM(closestplayer, this))
1970 if(vdist(org - this.origin, >, 1000))
1971 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1972 havocbot_goalrating_ctf_ourbase(this, 30000);
1974 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1975 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1976 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1977 havocbot_goalrating_items(this, 10000, org, mp_radius);
1978 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1979 navigation_goalrating_end(this);
1983 void havocbot_role_ctf_setrole(entity bot, int role)
1985 string s = "(null)";
1988 case HAVOCBOT_CTF_ROLE_CARRIER:
1990 bot.havocbot_role = havocbot_role_ctf_carrier;
1991 bot.havocbot_role_timeout = 0;
1992 bot.havocbot_cantfindflag = time + 10;
1993 bot.bot_strategytime = 0;
1995 case HAVOCBOT_CTF_ROLE_DEFENSE:
1997 bot.havocbot_role = havocbot_role_ctf_defense;
1998 bot.havocbot_role_timeout = 0;
2000 case HAVOCBOT_CTF_ROLE_MIDDLE:
2002 bot.havocbot_role = havocbot_role_ctf_middle;
2003 bot.havocbot_role_timeout = 0;
2005 case HAVOCBOT_CTF_ROLE_OFFENSE:
2007 bot.havocbot_role = havocbot_role_ctf_offense;
2008 bot.havocbot_role_timeout = 0;
2010 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2012 bot.havocbot_previous_role = bot.havocbot_role;
2013 bot.havocbot_role = havocbot_role_ctf_retriever;
2014 bot.havocbot_role_timeout = time + 10;
2015 bot.bot_strategytime = 0;
2017 case HAVOCBOT_CTF_ROLE_ESCORT:
2019 bot.havocbot_previous_role = bot.havocbot_role;
2020 bot.havocbot_role = havocbot_role_ctf_escort;
2021 bot.havocbot_role_timeout = time + 30;
2022 bot.bot_strategytime = 0;
2025 LOG_TRACE(bot.netname, " switched to ", s);
2033 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2035 entity player = M_ARGV(0, entity);
2037 int t = 0, t2 = 0, t3 = 0;
2039 // initially clear items so they can be set as necessary later.
2040 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2041 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2042 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2043 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2044 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2045 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2047 // scan through all the flags and notify the client about them
2048 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2050 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2051 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2052 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2053 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2054 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; }
2056 switch(flag.ctf_status)
2061 if((flag.owner == player) || (flag.pass_sender == player))
2062 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2064 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2069 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2075 // item for stopping players from capturing the flag too often
2076 if(player.ctf_captureshielded)
2077 player.ctf_flagstatus |= CTF_SHIELDED;
2080 player.ctf_flagstatus |= CTF_STALEMATE;
2082 // update the health of the flag carrier waypointsprite
2083 if(player.wps_flagcarrier)
2084 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2087 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2089 entity frag_attacker = M_ARGV(1, entity);
2090 entity frag_target = M_ARGV(2, entity);
2091 float frag_damage = M_ARGV(4, float);
2092 vector frag_force = M_ARGV(6, vector);
2094 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2096 if(frag_target == frag_attacker) // damage done to yourself
2098 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2099 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2101 else // damage done to everyone else
2103 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2104 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2107 M_ARGV(4, float) = frag_damage;
2108 M_ARGV(6, vector) = frag_force;
2110 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2112 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)))
2113 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2115 frag_target.wps_helpme_time = time;
2116 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2118 // todo: add notification for when flag carrier needs help?
2122 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2124 entity frag_attacker = M_ARGV(1, entity);
2125 entity frag_target = M_ARGV(2, entity);
2127 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2129 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2130 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2133 if(frag_target.flagcarried)
2135 entity tmp_entity = frag_target.flagcarried;
2136 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2137 tmp_entity.ctf_dropper = NULL;
2141 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2143 M_ARGV(2, float) = 0; // frag score
2144 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2147 void ctf_RemovePlayer(entity player)
2149 if(player.flagcarried)
2150 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2152 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2154 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2155 if(flag.pass_target == player) { flag.pass_target = NULL; }
2156 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2160 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2162 entity player = M_ARGV(0, entity);
2164 ctf_RemovePlayer(player);
2167 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2169 entity player = M_ARGV(0, entity);
2171 ctf_RemovePlayer(player);
2174 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2176 entity player = M_ARGV(0, entity);
2178 if(player.flagcarried)
2179 if(!autocvar_g_ctf_portalteleport)
2180 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2183 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2185 if(MUTATOR_RETURNVALUE || game_stopped) return;
2187 entity player = M_ARGV(0, entity);
2189 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2191 // pass the flag to a team mate
2192 if(autocvar_g_ctf_pass)
2194 entity head, closest_target = NULL;
2195 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2197 while(head) // find the closest acceptable target to pass to
2199 if(IS_PLAYER(head) && !IS_DEAD(head))
2200 if(head != player && SAME_TEAM(head, player))
2201 if(!head.speedrunning && !head.vehicle)
2203 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2204 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2205 vector passer_center = CENTER_OR_VIEWOFS(player);
2207 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2209 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2211 if(IS_BOT_CLIENT(head))
2213 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2214 ctf_Handle_Throw(head, player, DROP_PASS);
2218 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2219 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2221 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2224 else if(player.flagcarried)
2228 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2229 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2230 { closest_target = head; }
2232 else { closest_target = head; }
2239 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2242 // throw the flag in front of you
2243 if(autocvar_g_ctf_throw && player.flagcarried)
2245 if(player.throw_count == -1)
2247 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2249 player.throw_prevtime = time;
2250 player.throw_count = 1;
2251 ctf_Handle_Throw(player, NULL, DROP_THROW);
2256 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2262 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2263 else { player.throw_count += 1; }
2264 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2266 player.throw_prevtime = time;
2267 ctf_Handle_Throw(player, NULL, DROP_THROW);
2274 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2276 entity player = M_ARGV(0, entity);
2278 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2280 player.wps_helpme_time = time;
2281 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2283 else // create a normal help me waypointsprite
2285 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2286 WaypointSprite_Ping(player.wps_helpme);
2292 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2294 entity player = M_ARGV(0, entity);
2295 entity veh = M_ARGV(1, entity);
2297 if(player.flagcarried)
2299 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2301 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2305 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2306 setattachment(player.flagcarried, veh, "");
2307 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2308 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2309 //player.flagcarried.angles = '0 0 0';
2315 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2317 entity player = M_ARGV(0, entity);
2319 if(player.flagcarried)
2321 setattachment(player.flagcarried, player, "");
2322 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2323 player.flagcarried.scale = FLAG_SCALE;
2324 player.flagcarried.angles = '0 0 0';
2325 player.flagcarried.nodrawtoclient = NULL;
2330 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2332 entity player = M_ARGV(0, entity);
2334 if(player.flagcarried)
2336 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2337 ctf_RespawnFlag(player.flagcarried);
2342 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2344 entity flag; // temporary entity for the search method
2346 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2348 switch(flag.ctf_status)
2353 // lock the flag, game is over
2354 set_movetype(flag, MOVETYPE_NONE);
2355 flag.takedamage = DAMAGE_NO;
2356 flag.solid = SOLID_NOT;
2357 flag.nextthink = false; // stop thinking
2359 //dprint("stopping the ", flag.netname, " from moving.\n");
2367 // do nothing for these flags
2374 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2376 entity bot = M_ARGV(0, entity);
2378 havocbot_ctf_reset_role(bot);
2382 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2384 //M_ARGV(0, float) = ctf_teams;
2385 M_ARGV(1, string) = "ctf_team";
2389 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2391 entity spectatee = M_ARGV(0, entity);
2392 entity client = M_ARGV(1, entity);
2394 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2397 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2399 int record_page = M_ARGV(0, int);
2400 string ret_string = M_ARGV(1, string);
2402 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2404 if (MapInfo_Get_ByID(i))
2406 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2412 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2413 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2417 M_ARGV(1, string) = ret_string;
2420 bool superspec_Spectate(entity this, entity targ); // TODO
2421 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2422 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2424 entity player = M_ARGV(0, entity);
2425 string cmd_name = M_ARGV(1, string);
2426 int cmd_argc = M_ARGV(2, int);
2428 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2430 if(cmd_name == "followfc")
2442 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2443 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2444 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2445 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2449 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2450 if(it.flagcarried && (it.team == _team || _team == 0))
2453 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2454 continue; // already spectating this fc, try another
2455 return superspec_Spectate(player, it);
2460 superspec_msg("", "", player, "No active flag carrier\n", 1);
2465 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2467 entity frag_target = M_ARGV(0, entity);
2469 if(frag_target.flagcarried)
2470 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2478 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2479 CTF flag for team one (Red).
2481 "angle" Angle the flag will point (minus 90 degrees)...
2482 "model" model to use, note this needs red and blue as skins 0 and 1...
2483 "noise" sound played when flag is picked up...
2484 "noise1" sound played when flag is returned by a teammate...
2485 "noise2" sound played when flag is captured...
2486 "noise3" sound played when flag is lost in the field and respawns itself...
2487 "noise4" sound played when flag is dropped by a player...
2488 "noise5" sound played when flag touches the ground... */
2489 spawnfunc(item_flag_team1)
2491 if(!g_ctf) { delete(this); return; }
2493 ctf_FlagSetup(NUM_TEAM_1, this);
2496 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2497 CTF flag for team two (Blue).
2499 "angle" Angle the flag will point (minus 90 degrees)...
2500 "model" model to use, note this needs red and blue as skins 0 and 1...
2501 "noise" sound played when flag is picked up...
2502 "noise1" sound played when flag is returned by a teammate...
2503 "noise2" sound played when flag is captured...
2504 "noise3" sound played when flag is lost in the field and respawns itself...
2505 "noise4" sound played when flag is dropped by a player...
2506 "noise5" sound played when flag touches the ground... */
2507 spawnfunc(item_flag_team2)
2509 if(!g_ctf) { delete(this); return; }
2511 ctf_FlagSetup(NUM_TEAM_2, this);
2514 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2515 CTF flag for team three (Yellow).
2517 "angle" Angle the flag will point (minus 90 degrees)...
2518 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2519 "noise" sound played when flag is picked up...
2520 "noise1" sound played when flag is returned by a teammate...
2521 "noise2" sound played when flag is captured...
2522 "noise3" sound played when flag is lost in the field and respawns itself...
2523 "noise4" sound played when flag is dropped by a player...
2524 "noise5" sound played when flag touches the ground... */
2525 spawnfunc(item_flag_team3)
2527 if(!g_ctf) { delete(this); return; }
2529 ctf_FlagSetup(NUM_TEAM_3, this);
2532 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2533 CTF flag for team four (Pink).
2535 "angle" Angle the flag will point (minus 90 degrees)...
2536 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2537 "noise" sound played when flag is picked up...
2538 "noise1" sound played when flag is returned by a teammate...
2539 "noise2" sound played when flag is captured...
2540 "noise3" sound played when flag is lost in the field and respawns itself...
2541 "noise4" sound played when flag is dropped by a player...
2542 "noise5" sound played when flag touches the ground... */
2543 spawnfunc(item_flag_team4)
2545 if(!g_ctf) { delete(this); return; }
2547 ctf_FlagSetup(NUM_TEAM_4, this);
2550 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2553 "angle" Angle the flag will point (minus 90 degrees)...
2554 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2555 "noise" sound played when flag is picked up...
2556 "noise1" sound played when flag is returned by a teammate...
2557 "noise2" sound played when flag is captured...
2558 "noise3" sound played when flag is lost in the field and respawns itself...
2559 "noise4" sound played when flag is dropped by a player...
2560 "noise5" sound played when flag touches the ground... */
2561 spawnfunc(item_flag_neutral)
2563 if(!g_ctf) { delete(this); return; }
2564 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2566 ctf_FlagSetup(0, this);
2569 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2570 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2571 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.
2573 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2574 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2577 if(!g_ctf) { delete(this); return; }
2579 this.classname = "ctf_team";
2580 this.team = this.cnt + 1;
2583 // compatibility for quake maps
2584 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2585 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2586 spawnfunc(info_player_team1);
2587 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2588 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2589 spawnfunc(info_player_team2);
2590 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2591 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2593 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2594 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2596 // compatibility for wop maps
2597 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2598 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2599 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2600 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2601 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2602 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2610 void ctf_ScoreRules(int teams)
2612 CheckAllowedTeams(NULL);
2613 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2614 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2615 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2616 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2617 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2618 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2619 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2620 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2621 ScoreRules_basics_end();
2624 // code from here on is just to support maps that don't have flag and team entities
2625 void ctf_SpawnTeam (string teamname, int teamcolor)
2627 entity this = new_pure(ctf_team);
2628 this.netname = teamname;
2629 this.cnt = teamcolor - 1;
2630 this.spawnfunc_checked = true;
2631 this.team = teamcolor;
2634 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2639 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2641 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2642 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2644 switch(tmp_entity.team)
2646 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2647 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2648 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2649 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2651 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2654 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2656 ctf_teams = 0; // so set the default red and blue teams
2657 BITSET_ASSIGN(ctf_teams, BIT(0));
2658 BITSET_ASSIGN(ctf_teams, BIT(1));
2661 //ctf_teams = bound(2, ctf_teams, 4);
2663 // if no teams are found, spawn defaults
2664 if(find(NULL, classname, "ctf_team") == NULL)
2666 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2667 if(ctf_teams & BIT(0))
2668 ctf_SpawnTeam("Red", NUM_TEAM_1);
2669 if(ctf_teams & BIT(1))
2670 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2671 if(ctf_teams & BIT(2))
2672 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2673 if(ctf_teams & BIT(3))
2674 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2677 ctf_ScoreRules(ctf_teams);
2680 void ctf_Initialize()
2682 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2684 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2685 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2686 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2688 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);