1 #include "gamemode_ctf.qh"
6 REGISTER_MUTATOR(ctf, false)
10 if (time > 1) // game loads at time 1
11 error("This is a game type and it cannot be added at runtime.");
15 SetLimits(autocvar_capturelimit_override, autocvar_captureleadlimit_override, autocvar_timelimit_override, -1);
16 have_team_spawns = -1; // request team spawns
19 MUTATOR_ONROLLBACK_OR_REMOVE
21 // we actually cannot roll back ctf_Initialize here
22 // BUT: we don't need to! If this gets called, adding always
28 LOG_INFO("This is a game type and it cannot be removed at runtime.");
37 #include <common/vehicles/all.qh>
38 #include <server/teamplay.qh>
41 #include <lib/warpzone/common.qh>
43 bool autocvar_g_ctf_allow_vehicle_carry;
44 bool autocvar_g_ctf_allow_vehicle_touch;
45 bool autocvar_g_ctf_allow_monster_touch;
46 bool autocvar_g_ctf_throw;
47 float autocvar_g_ctf_throw_angle_max;
48 float autocvar_g_ctf_throw_angle_min;
49 int autocvar_g_ctf_throw_punish_count;
50 float autocvar_g_ctf_throw_punish_delay;
51 float autocvar_g_ctf_throw_punish_time;
52 float autocvar_g_ctf_throw_strengthmultiplier;
53 float autocvar_g_ctf_throw_velocity_forward;
54 float autocvar_g_ctf_throw_velocity_up;
55 float autocvar_g_ctf_drop_velocity_up;
56 float autocvar_g_ctf_drop_velocity_side;
57 bool autocvar_g_ctf_oneflag_reverse;
58 bool autocvar_g_ctf_portalteleport;
59 bool autocvar_g_ctf_pass;
60 float autocvar_g_ctf_pass_arc;
61 float autocvar_g_ctf_pass_arc_max;
62 float autocvar_g_ctf_pass_directional_max;
63 float autocvar_g_ctf_pass_directional_min;
64 float autocvar_g_ctf_pass_radius;
65 float autocvar_g_ctf_pass_wait;
66 bool autocvar_g_ctf_pass_request;
67 float autocvar_g_ctf_pass_turnrate;
68 float autocvar_g_ctf_pass_timelimit;
69 float autocvar_g_ctf_pass_velocity;
70 bool autocvar_g_ctf_dynamiclights;
71 float autocvar_g_ctf_flag_collect_delay;
72 float autocvar_g_ctf_flag_damageforcescale;
73 bool autocvar_g_ctf_flag_dropped_waypoint;
74 bool autocvar_g_ctf_flag_dropped_floatinwater;
75 bool autocvar_g_ctf_flag_glowtrails;
76 int autocvar_g_ctf_flag_health;
77 bool autocvar_g_ctf_flag_return;
78 bool autocvar_g_ctf_flag_return_carrying;
79 float autocvar_g_ctf_flag_return_carried_radius;
80 float autocvar_g_ctf_flag_return_time;
81 bool autocvar_g_ctf_flag_return_when_unreachable;
82 float autocvar_g_ctf_flag_return_damage;
83 float autocvar_g_ctf_flag_return_damage_delay;
84 float autocvar_g_ctf_flag_return_dropped;
85 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
86 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
87 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
88 float autocvar_g_ctf_flagcarrier_selfforcefactor;
89 float autocvar_g_ctf_flagcarrier_damagefactor;
90 float autocvar_g_ctf_flagcarrier_forcefactor;
91 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
92 bool autocvar_g_ctf_fullbrightflags;
93 bool autocvar_g_ctf_ignore_frags;
94 bool autocvar_g_ctf_score_ignore_fields;
95 int autocvar_g_ctf_score_capture;
96 int autocvar_g_ctf_score_capture_assist;
97 int autocvar_g_ctf_score_kill;
98 int autocvar_g_ctf_score_penalty_drop;
99 int autocvar_g_ctf_score_penalty_returned;
100 int autocvar_g_ctf_score_pickup_base;
101 int autocvar_g_ctf_score_pickup_dropped_early;
102 int autocvar_g_ctf_score_pickup_dropped_late;
103 int autocvar_g_ctf_score_return;
104 float autocvar_g_ctf_shield_force;
105 float autocvar_g_ctf_shield_max_ratio;
106 int autocvar_g_ctf_shield_min_negscore;
107 bool autocvar_g_ctf_stalemate;
108 int autocvar_g_ctf_stalemate_endcondition;
109 float autocvar_g_ctf_stalemate_time;
110 bool autocvar_g_ctf_reverse;
111 float autocvar_g_ctf_dropped_capture_delay;
112 float autocvar_g_ctf_dropped_capture_radius;
114 void ctf_FakeTimeLimit(entity e, float t)
117 WriteByte(MSG_ONE, 3); // svc_updatestat
118 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
120 WriteCoord(MSG_ONE, autocvar_timelimit);
122 WriteCoord(MSG_ONE, (t + 1) / 60);
125 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
127 if(autocvar_sv_eventlog)
128 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
129 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
132 void ctf_CaptureRecord(entity flag, entity player)
134 float cap_record = ctf_captimerecord;
135 float cap_time = (time - flag.ctf_pickuptime);
136 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
140 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
141 else if(!ctf_captimerecord)
142 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, (cap_time * 100));
143 else if(cap_time < cap_record)
144 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100));
146 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, (cap_time * 100), (cap_record * 100));
148 // write that shit in the database
149 if(!ctf_oneflag) // but not in 1-flag mode
150 if((!ctf_captimerecord) || (cap_time < cap_record))
152 ctf_captimerecord = cap_time;
153 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
154 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
155 write_recordmarker(player, (time - cap_time), cap_time);
158 if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
159 race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
162 bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
165 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
167 // automatically return if there's only 1 player on the team
168 return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
172 bool ctf_Return_Customize(entity this, entity client)
174 // only to the carrier
175 return boolean(client == this.owner);
178 void ctf_FlagcarrierWaypoints(entity player)
180 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
181 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
182 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
183 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
185 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
187 if(!player.wps_enemyflagcarrier)
189 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
190 wp.colormod = WPCOLOR_ENEMYFC(player.team);
191 setcefc(wp, ctf_Stalemate_Customize);
193 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
194 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
197 if(!player.wps_flagreturn)
199 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
200 owp.colormod = '0 0.8 0.8';
201 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
202 setcefc(owp, ctf_Return_Customize);
207 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
209 float current_distance = vlen((('1 0 0' * to.x) + ('0 1 0' * to.y)) - (('1 0 0' * from.x) + ('0 1 0' * from.y))); // for the sake of this check, exclude Z axis
210 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
211 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
212 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
215 if(current_height) // make sure we can actually do this arcing path
217 targpos = (to + ('0 0 1' * current_height));
218 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
219 if(trace_fraction < 1)
221 //print("normal arc line failed, trying to find new pos...");
222 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
223 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
224 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
225 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
226 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
229 else { targpos = to; }
231 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
233 vector desired_direction = normalize(targpos - from);
234 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
235 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
238 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
240 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
242 // directional tracing only
244 makevectors(passer_angle);
246 // find the closest point on the enemy to the center of the attack
247 float h; // hypotenuse, which is the distance between attacker to head
248 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
250 h = vlen(head_center - passer_center);
251 a = h * (normalize(head_center - passer_center) * v_forward);
253 vector nearest_on_line = (passer_center + a * v_forward);
254 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
256 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
257 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
259 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
264 else { return true; }
268 // =======================
269 // CaptureShield Functions
270 // =======================
272 bool ctf_CaptureShield_CheckStatus(entity p)
274 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
275 int players_worseeq, players_total;
277 if(ctf_captureshield_max_ratio <= 0)
280 s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
281 s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
282 s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
283 s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
285 sr = ((s - s2) + (s3 + s4));
287 if(sr >= -ctf_captureshield_min_negscore)
290 players_total = players_worseeq = 0;
291 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
294 se = PlayerScore_Add(it, SP_CTF_CAPS, 0);
295 se2 = PlayerScore_Add(it, SP_CTF_PICKUPS, 0);
296 se3 = PlayerScore_Add(it, SP_CTF_RETURNS, 0);
297 se4 = PlayerScore_Add(it, SP_CTF_FCKILLS, 0);
299 ser = ((se - se2) + (se3 + se4));
306 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
307 // use this rule here
309 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
315 void ctf_CaptureShield_Update(entity player, bool wanted_status)
317 bool updated_status = ctf_CaptureShield_CheckStatus(player);
318 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
320 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
321 player.ctf_captureshielded = updated_status;
325 bool ctf_CaptureShield_Customize(entity this, entity client)
327 if(!client.ctf_captureshielded) { return false; }
328 if(CTF_SAMETEAM(this, client)) { return false; }
333 void ctf_CaptureShield_Touch(entity this, entity toucher)
335 if(!toucher.ctf_captureshielded) { return; }
336 if(CTF_SAMETEAM(this, toucher)) { return; }
338 vector mymid = (this.absmin + this.absmax) * 0.5;
339 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
341 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
342 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
345 void ctf_CaptureShield_Spawn(entity flag)
347 entity shield = new(ctf_captureshield);
350 shield.team = flag.team;
351 settouch(shield, ctf_CaptureShield_Touch);
352 setcefc(shield, ctf_CaptureShield_Customize);
353 shield.effects = EF_ADDITIVE;
354 set_movetype(shield, MOVETYPE_NOCLIP);
355 shield.solid = SOLID_TRIGGER;
356 shield.avelocity = '7 0 11';
359 setorigin(shield, flag.origin);
360 setmodel(shield, MDL_CTF_SHIELD);
361 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
365 // ====================
366 // Drop/Pass/Throw Code
367 // ====================
369 void ctf_Handle_Drop(entity flag, entity player, int droptype)
372 player = (player ? player : flag.pass_sender);
375 set_movetype(flag, MOVETYPE_TOSS);
376 flag.takedamage = DAMAGE_YES;
377 flag.angles = '0 0 0';
378 flag.health = flag.max_flag_health;
379 flag.ctf_droptime = time;
380 flag.ctf_dropper = player;
381 flag.ctf_status = FLAG_DROPPED;
383 // messages and sounds
384 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
385 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
386 ctf_EventLog("dropped", player.team, player);
389 PlayerTeamScore_AddScore(player, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
390 PlayerScore_Add(player, SP_CTF_DROPS, 1);
393 if(autocvar_g_ctf_flag_dropped_waypoint) {
394 entity wp = WaypointSprite_Spawn(WP_FlagDropped, 0, 0, flag, FLAG_WAYPOINT_OFFSET, NULL, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, true, RADARICON_FLAG);
395 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
398 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
400 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
401 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
404 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
406 if(droptype == DROP_PASS)
408 flag.pass_distance = 0;
409 flag.pass_sender = NULL;
410 flag.pass_target = NULL;
414 void ctf_Handle_Retrieve(entity flag, entity player)
416 entity sender = flag.pass_sender;
418 // transfer flag to player
420 flag.owner.flagcarried = flag;
425 setattachment(flag, player.vehicle, "");
426 setorigin(flag, VEHICLE_FLAG_OFFSET);
427 flag.scale = VEHICLE_FLAG_SCALE;
431 setattachment(flag, player, "");
432 setorigin(flag, FLAG_CARRY_OFFSET);
434 set_movetype(flag, MOVETYPE_NONE);
435 flag.takedamage = DAMAGE_NO;
436 flag.solid = SOLID_NOT;
437 flag.angles = '0 0 0';
438 flag.ctf_status = FLAG_CARRY;
440 // messages and sounds
441 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
442 ctf_EventLog("receive", flag.team, player);
444 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(
446 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
447 else if(it == player)
448 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
449 else if(SAME_TEAM(it, sender))
450 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
453 // create new waypoint
454 ctf_FlagcarrierWaypoints(player);
456 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
457 player.throw_antispam = sender.throw_antispam;
459 flag.pass_distance = 0;
460 flag.pass_sender = NULL;
461 flag.pass_target = NULL;
464 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
466 entity flag = player.flagcarried;
467 vector targ_origin, flag_velocity;
469 if(!flag) { return; }
470 if((droptype == DROP_PASS) && !receiver) { return; }
472 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
475 setattachment(flag, NULL, "");
476 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
477 flag.owner.flagcarried = NULL;
479 flag.solid = SOLID_TRIGGER;
480 flag.ctf_dropper = player;
481 flag.ctf_droptime = time;
483 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
490 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
491 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
492 WarpZone_RefSys_Copy(flag, receiver);
493 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
494 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
496 flag.pass_distance = vlen((('1 0 0' * targ_origin.x) + ('0 1 0' * targ_origin.y)) - (('1 0 0' * player.origin.x) + ('0 1 0' * player.origin.y))); // for the sake of this check, exclude Z axis
497 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
500 set_movetype(flag, MOVETYPE_FLY);
501 flag.takedamage = DAMAGE_NO;
502 flag.pass_sender = player;
503 flag.pass_target = receiver;
504 flag.ctf_status = FLAG_PASSING;
507 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
508 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
509 ctf_EventLog("pass", flag.team, player);
515 makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
517 flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
518 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
519 ctf_Handle_Drop(flag, player, droptype);
525 flag.velocity = '0 0 0'; // do nothing
532 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
533 ctf_Handle_Drop(flag, player, droptype);
538 // kill old waypointsprite
539 WaypointSprite_Ping(player.wps_flagcarrier);
540 WaypointSprite_Kill(player.wps_flagcarrier);
542 if(player.wps_enemyflagcarrier)
543 WaypointSprite_Kill(player.wps_enemyflagcarrier);
545 if(player.wps_flagreturn)
546 WaypointSprite_Kill(player.wps_flagreturn);
549 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
552 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
554 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
561 void nades_GiveBonus(entity player, float score);
563 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
565 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
566 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
567 entity player_team_flag = NULL, tmp_entity;
568 float old_time, new_time;
570 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
571 if(CTF_DIFFTEAM(player, flag)) { return; }
574 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
575 if(SAME_TEAM(tmp_entity, player))
577 player_team_flag = tmp_entity;
581 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
583 player.throw_prevtime = time;
584 player.throw_count = 0;
586 // messages and sounds
587 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
588 ctf_CaptureRecord(enemy_flag, player);
589 _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);
593 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
594 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
599 PlayerTeamScore_AddScore(player, ((enemy_flag.score_capture) ? enemy_flag.score_capture : autocvar_g_ctf_score_capture));
600 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
602 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
603 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
604 if(!old_time || new_time < old_time)
605 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
608 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
609 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
612 if(capturetype == CAPTURE_NORMAL)
614 WaypointSprite_Kill(player.wps_flagcarrier);
615 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
617 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
618 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
622 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
623 ctf_RespawnFlag(enemy_flag);
626 void ctf_Handle_Return(entity flag, entity player)
628 // messages and sounds
629 if(IS_MONSTER(player))
631 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
635 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
636 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
638 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
639 ctf_EventLog("return", flag.team, player);
642 if(IS_PLAYER(player))
644 PlayerTeamScore_AddScore(player, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
645 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
647 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
650 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
654 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
655 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
656 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
660 if(player.flagcarried == flag)
661 WaypointSprite_Kill(player.wps_flagcarrier);
664 ctf_RespawnFlag(flag);
667 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
670 float pickup_dropped_score; // used to calculate dropped pickup score
672 // attach the flag to the player
674 player.flagcarried = flag;
677 setattachment(flag, player.vehicle, "");
678 setorigin(flag, VEHICLE_FLAG_OFFSET);
679 flag.scale = VEHICLE_FLAG_SCALE;
683 setattachment(flag, player, "");
684 setorigin(flag, FLAG_CARRY_OFFSET);
688 set_movetype(flag, MOVETYPE_NONE);
689 flag.takedamage = DAMAGE_NO;
690 flag.solid = SOLID_NOT;
691 flag.angles = '0 0 0';
692 flag.ctf_status = FLAG_CARRY;
696 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
697 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
701 // messages and sounds
702 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
704 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
706 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
707 else if(CTF_DIFFTEAM(player, flag))
708 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
710 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
712 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
715 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)));
718 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
719 if(CTF_SAMETEAM(flag, it))
720 if(SAME_TEAM(player, it))
721 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
723 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);
726 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
729 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
730 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
735 PlayerTeamScore_AddScore(player, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
736 ctf_EventLog("steal", flag.team, player);
742 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);
743 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);
744 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
745 PlayerTeamScore_AddScore(player, pickup_dropped_score);
746 ctf_EventLog("pickup", flag.team, player);
754 if(pickuptype == PICKUP_BASE)
756 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
757 if((player.speedrunning) && (ctf_captimerecord))
758 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
762 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
765 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
766 ctf_FlagcarrierWaypoints(player);
767 WaypointSprite_Ping(player.wps_flagcarrier);
771 // ===================
772 // Main Flag Functions
773 // ===================
775 void ctf_CheckFlagReturn(entity flag, int returntype)
777 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
779 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
781 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
786 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
788 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
789 case RETURN_SPEEDRUN:
790 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), ctf_captimerecord); break;
791 case RETURN_NEEDKILL:
792 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
795 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
797 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
798 ctf_EventLog("returned", flag.team, NULL);
799 ctf_RespawnFlag(flag);
804 bool ctf_Stalemate_Customize(entity this, entity client)
806 // make spectators see what the player would see
807 entity e = WaypointSprite_getviewentity(client);
808 entity wp_owner = this.owner;
811 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
812 if(SAME_TEAM(wp_owner, e)) { return false; }
813 if(!IS_PLAYER(e)) { return false; }
818 void ctf_CheckStalemate()
821 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
824 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
826 // build list of stale flags
827 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
829 if(autocvar_g_ctf_stalemate)
830 if(tmp_entity.ctf_status != FLAG_BASE)
831 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
833 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
834 ctf_staleflaglist = tmp_entity;
836 switch(tmp_entity.team)
838 case NUM_TEAM_1: ++stale_red_flags; break;
839 case NUM_TEAM_2: ++stale_blue_flags; break;
840 case NUM_TEAM_3: ++stale_yellow_flags; break;
841 case NUM_TEAM_4: ++stale_pink_flags; break;
842 default: ++stale_neutral_flags; break;
848 stale_flags = (stale_neutral_flags >= 1);
850 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
852 if(ctf_oneflag && stale_flags == 1)
853 ctf_stalemate = true;
854 else if(stale_flags >= 2)
855 ctf_stalemate = true;
856 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
857 { ctf_stalemate = false; wpforenemy_announced = false; }
858 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
859 { ctf_stalemate = false; wpforenemy_announced = false; }
861 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
864 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
866 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
868 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);
869 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
870 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
874 if (!wpforenemy_announced)
876 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))));
878 wpforenemy_announced = true;
883 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
885 if(ITEM_DAMAGE_NEEDKILL(deathtype))
887 if(autocvar_g_ctf_flag_return_damage_delay)
888 this.ctf_flagdamaged_byworld = true;
892 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
896 if(autocvar_g_ctf_flag_return_damage)
898 // reduce health and check if it should be returned
899 this.health = this.health - damage;
900 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
905 void ctf_FlagThink(entity this)
910 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
913 if(this == ctf_worldflaglist) // only for the first flag
914 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
917 if(this.mins != CTF_FLAG.m_mins || this.maxs != CTF_FLAG.m_maxs) { // reset the flag boundaries in case it got squished
918 LOG_TRACE("wtf the flag got squashed?");
919 tracebox(this.origin, CTF_FLAG.m_mins, CTF_FLAG.m_maxs, this.origin, MOVE_NOMONSTERS, this);
920 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
921 setsize(this, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
925 switch(this.ctf_status)
929 if(autocvar_g_ctf_dropped_capture_radius)
931 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
932 if(tmp_entity.ctf_status == FLAG_DROPPED)
933 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
934 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
935 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
942 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
944 if(autocvar_g_ctf_flag_dropped_floatinwater)
946 vector midpoint = ((this.absmin + this.absmax) * 0.5);
947 if(pointcontents(midpoint) == CONTENT_WATER)
949 this.velocity = this.velocity * 0.5;
951 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
952 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
954 { set_movetype(this, MOVETYPE_FLY); }
956 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
958 if(autocvar_g_ctf_flag_return_dropped)
960 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
963 ctf_CheckFlagReturn(this, RETURN_DROPPED);
967 if(this.ctf_flagdamaged_byworld)
969 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
970 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
973 else if(autocvar_g_ctf_flag_return_time)
975 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
976 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
984 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
987 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
989 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
990 ImpulseCommands(this.owner);
992 if(autocvar_g_ctf_stalemate)
994 if(time >= wpforenemy_nextthink)
996 ctf_CheckStalemate();
997 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1000 if(CTF_SAMETEAM(this, this.owner) && this.team)
1002 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1003 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
1004 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1005 ctf_Handle_Return(this, this.owner);
1012 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1013 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1014 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1016 if((this.pass_target == NULL)
1017 || (IS_DEAD(this.pass_target))
1018 || (this.pass_target.flagcarried)
1019 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1020 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1021 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1023 // give up, pass failed
1024 ctf_Handle_Drop(this, NULL, DROP_PASS);
1028 // still a viable target, go for it
1029 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1034 default: // this should never happen
1036 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1042 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1045 if(game_stopped) return;
1046 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1048 bool is_not_monster = (!IS_MONSTER(toucher));
1050 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1051 if(ITEM_TOUCH_NEEDKILL())
1053 if(!autocvar_g_ctf_flag_return_damage_delay)
1056 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1058 if(!flag.ctf_flagdamaged_byworld) { return; }
1061 // special touch behaviors
1062 if(STAT(FROZEN, toucher)) { return; }
1063 else if(IS_VEHICLE(toucher))
1065 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1066 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1068 return; // do nothing
1070 else if(IS_MONSTER(toucher))
1072 if(!autocvar_g_ctf_allow_monster_touch)
1073 return; // do nothing
1075 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1077 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1079 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1080 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1081 flag.wait = time + FLAG_TOUCHRATE;
1085 else if(IS_DEAD(toucher)) { return; }
1087 switch(flag.ctf_status)
1093 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1094 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1095 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1096 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1098 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1099 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1100 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)
1102 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1103 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1105 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1106 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1112 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1113 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1114 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1115 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1121 LOG_TRACE("Someone touched a flag even though it was being carried?");
1127 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1129 if(DIFF_TEAM(toucher, flag.pass_sender))
1131 if(ctf_Immediate_Return_Allowed(flag, toucher))
1132 ctf_Handle_Return(flag, toucher);
1133 else if(is_not_monster && (!toucher.flagcarried))
1134 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1136 else if(!toucher.flagcarried)
1137 ctf_Handle_Retrieve(flag, toucher);
1144 .float last_respawn;
1145 void ctf_RespawnFlag(entity flag)
1147 // check for flag respawn being called twice in a row
1148 if(flag.last_respawn > time - 0.5)
1149 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1151 flag.last_respawn = time;
1153 // reset the player (if there is one)
1154 if((flag.owner) && (flag.owner.flagcarried == flag))
1156 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1157 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1158 WaypointSprite_Kill(flag.wps_flagcarrier);
1160 flag.owner.flagcarried = NULL;
1162 if(flag.speedrunning)
1163 ctf_FakeTimeLimit(flag.owner, -1);
1166 if((flag.owner) && (flag.owner.vehicle))
1167 flag.scale = FLAG_SCALE;
1169 if(flag.ctf_status == FLAG_DROPPED)
1170 { WaypointSprite_Kill(flag.wps_flagdropped); }
1173 setattachment(flag, NULL, "");
1174 setorigin(flag, flag.ctf_spawnorigin);
1176 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1177 flag.takedamage = DAMAGE_NO;
1178 flag.health = flag.max_flag_health;
1179 flag.solid = SOLID_TRIGGER;
1180 flag.velocity = '0 0 0';
1181 flag.angles = flag.mangle;
1182 flag.flags = FL_ITEM | FL_NOTARGET;
1184 flag.ctf_status = FLAG_BASE;
1186 flag.pass_distance = 0;
1187 flag.pass_sender = NULL;
1188 flag.pass_target = NULL;
1189 flag.ctf_dropper = NULL;
1190 flag.ctf_pickuptime = 0;
1191 flag.ctf_droptime = 0;
1192 flag.ctf_flagdamaged_byworld = false;
1194 ctf_CheckStalemate();
1197 void ctf_Reset(entity this)
1199 if(this.owner && IS_PLAYER(this.owner))
1200 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1202 ctf_RespawnFlag(this);
1205 bool ctf_FlagBase_Customize(entity this, entity client)
1207 if(client.flagcarried && CTF_SAMETEAM(client, client.flagcarried))
1212 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1215 waypoint_spawnforitem_force(this, this.origin);
1216 this.nearestwaypointtimeout = 0; // activate waypointing again
1217 this.bot_basewaypoint = this.nearestwaypoint;
1223 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1224 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1225 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1226 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1227 default: basename = WP_FlagBaseNeutral; break;
1230 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1231 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1232 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1233 setcefc(wp, ctf_FlagBase_Customize);
1235 // captureshield setup
1236 ctf_CaptureShield_Spawn(this);
1241 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1244 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1245 ctf_worldflaglist = flag;
1247 setattachment(flag, NULL, "");
1249 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1250 flag.team = teamnumber;
1251 flag.classname = "item_flag_team";
1252 flag.target = "###item###"; // wut?
1253 flag.flags = FL_ITEM | FL_NOTARGET;
1254 IL_PUSH(g_items, flag);
1255 flag.solid = SOLID_TRIGGER;
1256 flag.takedamage = DAMAGE_NO;
1257 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1258 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1259 flag.health = flag.max_flag_health;
1260 flag.event_damage = ctf_FlagDamage;
1261 flag.pushable = true;
1262 flag.teleportable = TELEPORT_NORMAL;
1263 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1264 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1265 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1266 if(flag.damagedbycontents)
1267 IL_PUSH(g_damagedbycontents, flag);
1268 flag.velocity = '0 0 0';
1269 flag.mangle = flag.angles;
1270 flag.reset = ctf_Reset;
1271 settouch(flag, ctf_FlagTouch);
1272 setthink(flag, ctf_FlagThink);
1273 flag.nextthink = time + FLAG_THINKRATE;
1274 flag.ctf_status = FLAG_BASE;
1276 // crudely force them all to 0
1277 if(autocvar_g_ctf_score_ignore_fields)
1278 flag.score_assist = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1280 string teamname = Static_Team_ColorName_Lower(teamnumber);
1282 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1283 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1284 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1285 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1286 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1287 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1291 if(flag.s == "") flag.s = b; \
1292 precache_sound(flag.s);
1294 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1295 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1296 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1297 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1298 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1299 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1300 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1304 precache_model(flag.model);
1307 _setmodel(flag, flag.model); // precision set below
1308 setsize(flag, CTF_FLAG.m_mins, CTF_FLAG.m_maxs);
1309 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1311 if(autocvar_g_ctf_flag_glowtrails)
1315 case NUM_TEAM_1: flag.glow_color = 251; break;
1316 case NUM_TEAM_2: flag.glow_color = 210; break;
1317 case NUM_TEAM_3: flag.glow_color = 110; break;
1318 case NUM_TEAM_4: flag.glow_color = 145; break;
1319 default: flag.glow_color = 254; break;
1321 flag.glow_size = 25;
1322 flag.glow_trail = 1;
1325 flag.effects |= EF_LOWPRECISION;
1326 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1327 if(autocvar_g_ctf_dynamiclights)
1331 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1332 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1333 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1334 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1335 default: flag.effects |= EF_DIMLIGHT; break;
1340 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1342 flag.dropped_origin = flag.origin;
1343 flag.noalign = true;
1344 set_movetype(flag, MOVETYPE_NONE);
1346 else // drop to floor, automatically find a platform and set that as spawn origin
1348 flag.noalign = false;
1350 set_movetype(flag, MOVETYPE_NONE);
1353 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1361 // NOTE: LEGACY CODE, needs to be re-written!
1363 void havocbot_calculate_middlepoint()
1367 vector fo = '0 0 0';
1370 f = ctf_worldflaglist;
1375 f = f.ctf_worldflagnext;
1380 havocbot_ctf_middlepoint = s / n;
1381 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1385 entity havocbot_ctf_find_flag(entity bot)
1388 f = ctf_worldflaglist;
1391 if (CTF_SAMETEAM(bot, f))
1393 f = f.ctf_worldflagnext;
1398 entity havocbot_ctf_find_enemy_flag(entity bot)
1401 f = ctf_worldflaglist;
1406 if(CTF_DIFFTEAM(bot, f))
1413 else if(!bot.flagcarried)
1417 else if (CTF_DIFFTEAM(bot, f))
1419 f = f.ctf_worldflagnext;
1424 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1431 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1432 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1435 if(vdist(it.origin - org, <, tc_radius))
1442 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1445 head = ctf_worldflaglist;
1448 if (CTF_SAMETEAM(this, head))
1450 head = head.ctf_worldflagnext;
1453 navigation_routerating(this, head, ratingscale, 10000);
1456 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1459 head = ctf_worldflaglist;
1462 if (CTF_SAMETEAM(this, head))
1464 head = head.ctf_worldflagnext;
1469 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1472 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1475 head = ctf_worldflaglist;
1480 if(CTF_DIFFTEAM(this, head))
1484 if(this.flagcarried)
1487 else if(!this.flagcarried)
1491 else if(CTF_DIFFTEAM(this, head))
1493 head = head.ctf_worldflagnext;
1496 navigation_routerating(this, head, ratingscale, 10000);
1499 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1501 if (!bot_waypoints_for_items)
1503 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1509 head = havocbot_ctf_find_enemy_flag(this);
1514 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1517 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1521 mf = havocbot_ctf_find_flag(this);
1523 if(mf.ctf_status == FLAG_BASE)
1527 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1530 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1533 head = ctf_worldflaglist;
1536 // flag is out in the field
1537 if(head.ctf_status != FLAG_BASE)
1538 if(head.tag_entity==NULL) // dropped
1542 if(vdist(org - head.origin, <, df_radius))
1543 navigation_routerating(this, head, ratingscale, 10000);
1546 navigation_routerating(this, head, ratingscale, 10000);
1549 head = head.ctf_worldflagnext;
1553 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1555 IL_EACH(g_items, it.bot_pickup,
1557 // gather health and armor only
1559 if (it.health || it.armorvalue)
1560 if (vdist(it.origin - org, <, sradius))
1562 // get the value of the item
1563 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1565 navigation_routerating(this, it, t * ratingscale, 500);
1570 void havocbot_ctf_reset_role(entity this)
1572 float cdefense, cmiddle, coffense;
1579 if(havocbot_ctf_middlepoint == '0 0 0')
1580 havocbot_calculate_middlepoint();
1583 if (this.flagcarried)
1585 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1589 mf = havocbot_ctf_find_flag(this);
1590 ef = havocbot_ctf_find_enemy_flag(this);
1592 // Retrieve stolen flag
1593 if(mf.ctf_status!=FLAG_BASE)
1595 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1599 // If enemy flag is taken go to the middle to intercept pursuers
1600 if(ef.ctf_status!=FLAG_BASE)
1602 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1606 // if there is only me on the team switch to offense
1608 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1612 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1616 // Evaluate best position to take
1617 // Count mates on middle position
1618 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1620 // Count mates on defense position
1621 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1623 // Count mates on offense position
1624 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1626 if(cdefense<=coffense)
1627 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1628 else if(coffense<=cmiddle)
1629 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1631 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1634 void havocbot_role_ctf_carrier(entity this)
1638 havocbot_ctf_reset_role(this);
1642 if (this.flagcarried == NULL)
1644 havocbot_ctf_reset_role(this);
1648 if (this.bot_strategytime < time)
1650 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1652 navigation_goalrating_start(this);
1654 havocbot_goalrating_ctf_enemybase(this, 50000);
1656 havocbot_goalrating_ctf_ourbase(this, 50000);
1659 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1661 navigation_goalrating_end(this);
1663 if (this.navigation_hasgoals)
1664 this.havocbot_cantfindflag = time + 10;
1665 else if (time > this.havocbot_cantfindflag)
1667 // Can't navigate to my own base, suicide!
1668 // TODO: drop it and wander around
1669 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1675 void havocbot_role_ctf_escort(entity this)
1681 havocbot_ctf_reset_role(this);
1685 if (this.flagcarried)
1687 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1691 // If enemy flag is back on the base switch to previous role
1692 ef = havocbot_ctf_find_enemy_flag(this);
1693 if(ef.ctf_status==FLAG_BASE)
1695 this.havocbot_role = this.havocbot_previous_role;
1696 this.havocbot_role_timeout = 0;
1700 // If the flag carrier reached the base switch to defense
1701 mf = havocbot_ctf_find_flag(this);
1702 if(mf.ctf_status!=FLAG_BASE)
1703 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1705 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1709 // Set the role timeout if necessary
1710 if (!this.havocbot_role_timeout)
1712 this.havocbot_role_timeout = time + random() * 30 + 60;
1715 // If nothing happened just switch to previous role
1716 if (time > this.havocbot_role_timeout)
1718 this.havocbot_role = this.havocbot_previous_role;
1719 this.havocbot_role_timeout = 0;
1723 // Chase the flag carrier
1724 if (this.bot_strategytime < time)
1726 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1727 navigation_goalrating_start(this);
1728 havocbot_goalrating_ctf_enemyflag(this, 30000);
1729 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1730 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1731 navigation_goalrating_end(this);
1735 void havocbot_role_ctf_offense(entity this)
1742 havocbot_ctf_reset_role(this);
1746 if (this.flagcarried)
1748 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1753 mf = havocbot_ctf_find_flag(this);
1754 ef = havocbot_ctf_find_enemy_flag(this);
1757 if(mf.ctf_status!=FLAG_BASE)
1760 pos = mf.tag_entity.origin;
1764 // Try to get it if closer than the enemy base
1765 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1767 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1772 // Escort flag carrier
1773 if(ef.ctf_status!=FLAG_BASE)
1776 pos = ef.tag_entity.origin;
1780 if(vdist(pos - mf.dropped_origin, >, 700))
1782 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1787 // About to fail, switch to middlefield
1790 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1794 // Set the role timeout if necessary
1795 if (!this.havocbot_role_timeout)
1796 this.havocbot_role_timeout = time + 120;
1798 if (time > this.havocbot_role_timeout)
1800 havocbot_ctf_reset_role(this);
1804 if (this.bot_strategytime < time)
1806 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1807 navigation_goalrating_start(this);
1808 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1809 havocbot_goalrating_ctf_enemybase(this, 20000);
1810 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1811 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1812 navigation_goalrating_end(this);
1816 // Retriever (temporary role):
1817 void havocbot_role_ctf_retriever(entity this)
1823 havocbot_ctf_reset_role(this);
1827 if (this.flagcarried)
1829 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1833 // If flag is back on the base switch to previous role
1834 mf = havocbot_ctf_find_flag(this);
1835 if(mf.ctf_status==FLAG_BASE)
1837 if(this.goalcurrent == mf)
1839 navigation_clearroute(this);
1840 this.bot_strategytime = 0;
1842 havocbot_ctf_reset_role(this);
1846 if (!this.havocbot_role_timeout)
1847 this.havocbot_role_timeout = time + 20;
1849 if (time > this.havocbot_role_timeout)
1851 havocbot_ctf_reset_role(this);
1855 if (this.bot_strategytime < time)
1860 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1861 navigation_goalrating_start(this);
1862 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1863 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1864 havocbot_goalrating_ctf_enemybase(this, 30000);
1865 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1866 navigation_goalrating_end(this);
1870 void havocbot_role_ctf_middle(entity this)
1876 havocbot_ctf_reset_role(this);
1880 if (this.flagcarried)
1882 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1886 mf = havocbot_ctf_find_flag(this);
1887 if(mf.ctf_status!=FLAG_BASE)
1889 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1893 if (!this.havocbot_role_timeout)
1894 this.havocbot_role_timeout = time + 10;
1896 if (time > this.havocbot_role_timeout)
1898 havocbot_ctf_reset_role(this);
1902 if (this.bot_strategytime < time)
1906 org = havocbot_ctf_middlepoint;
1907 org.z = this.origin.z;
1909 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1910 navigation_goalrating_start(this);
1911 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1912 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1913 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1914 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1915 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1916 havocbot_goalrating_ctf_enemybase(this, 2500);
1917 navigation_goalrating_end(this);
1921 void havocbot_role_ctf_defense(entity this)
1927 havocbot_ctf_reset_role(this);
1931 if (this.flagcarried)
1933 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1937 // If own flag was captured
1938 mf = havocbot_ctf_find_flag(this);
1939 if(mf.ctf_status!=FLAG_BASE)
1941 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1945 if (!this.havocbot_role_timeout)
1946 this.havocbot_role_timeout = time + 30;
1948 if (time > this.havocbot_role_timeout)
1950 havocbot_ctf_reset_role(this);
1953 if (this.bot_strategytime < time)
1958 org = mf.dropped_origin;
1959 mp_radius = havocbot_ctf_middlepoint_radius;
1961 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1962 navigation_goalrating_start(this);
1964 // if enemies are closer to our base, go there
1965 entity closestplayer = NULL;
1966 float distance, bestdistance = 10000;
1967 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1968 distance = vlen(org - it.origin);
1969 if(distance<bestdistance)
1972 bestdistance = distance;
1977 if(DIFF_TEAM(closestplayer, this))
1978 if(vdist(org - this.origin, >, 1000))
1979 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1980 havocbot_goalrating_ctf_ourbase(this, 30000);
1982 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1983 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
1984 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
1985 havocbot_goalrating_items(this, 10000, org, mp_radius);
1986 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1987 navigation_goalrating_end(this);
1991 void havocbot_role_ctf_setrole(entity bot, int role)
1993 string s = "(null)";
1996 case HAVOCBOT_CTF_ROLE_CARRIER:
1998 bot.havocbot_role = havocbot_role_ctf_carrier;
1999 bot.havocbot_role_timeout = 0;
2000 bot.havocbot_cantfindflag = time + 10;
2001 bot.bot_strategytime = 0;
2003 case HAVOCBOT_CTF_ROLE_DEFENSE:
2005 bot.havocbot_role = havocbot_role_ctf_defense;
2006 bot.havocbot_role_timeout = 0;
2008 case HAVOCBOT_CTF_ROLE_MIDDLE:
2010 bot.havocbot_role = havocbot_role_ctf_middle;
2011 bot.havocbot_role_timeout = 0;
2013 case HAVOCBOT_CTF_ROLE_OFFENSE:
2015 bot.havocbot_role = havocbot_role_ctf_offense;
2016 bot.havocbot_role_timeout = 0;
2018 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2020 bot.havocbot_previous_role = bot.havocbot_role;
2021 bot.havocbot_role = havocbot_role_ctf_retriever;
2022 bot.havocbot_role_timeout = time + 10;
2023 bot.bot_strategytime = 0;
2025 case HAVOCBOT_CTF_ROLE_ESCORT:
2027 bot.havocbot_previous_role = bot.havocbot_role;
2028 bot.havocbot_role = havocbot_role_ctf_escort;
2029 bot.havocbot_role_timeout = time + 30;
2030 bot.bot_strategytime = 0;
2033 LOG_TRACE(bot.netname, " switched to ", s);
2041 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2043 entity player = M_ARGV(0, entity);
2045 int t = 0, t2 = 0, t3 = 0;
2047 // initially clear items so they can be set as necessary later.
2048 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2049 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2050 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2051 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2052 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2053 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2055 // scan through all the flags and notify the client about them
2056 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2058 if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2059 if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2060 if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2061 if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2062 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; }
2064 switch(flag.ctf_status)
2069 if((flag.owner == player) || (flag.pass_sender == player))
2070 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2072 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2077 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2083 // item for stopping players from capturing the flag too often
2084 if(player.ctf_captureshielded)
2085 player.ctf_flagstatus |= CTF_SHIELDED;
2088 player.ctf_flagstatus |= CTF_STALEMATE;
2090 // update the health of the flag carrier waypointsprite
2091 if(player.wps_flagcarrier)
2092 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2095 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2097 entity frag_attacker = M_ARGV(1, entity);
2098 entity frag_target = M_ARGV(2, entity);
2099 float frag_damage = M_ARGV(4, float);
2100 vector frag_force = M_ARGV(6, vector);
2102 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2104 if(frag_target == frag_attacker) // damage done to yourself
2106 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2107 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2109 else // damage done to everyone else
2111 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2112 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2115 M_ARGV(4, float) = frag_damage;
2116 M_ARGV(6, vector) = frag_force;
2118 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2120 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)))
2121 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2123 frag_target.wps_helpme_time = time;
2124 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2126 // todo: add notification for when flag carrier needs help?
2130 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2132 entity frag_attacker = M_ARGV(1, entity);
2133 entity frag_target = M_ARGV(2, entity);
2135 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2137 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2138 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2141 if(frag_target.flagcarried)
2143 entity tmp_entity = frag_target.flagcarried;
2144 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2145 tmp_entity.ctf_dropper = NULL;
2149 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2151 M_ARGV(2, float) = 0; // frag score
2152 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2155 void ctf_RemovePlayer(entity player)
2157 if(player.flagcarried)
2158 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2160 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2162 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2163 if(flag.pass_target == player) { flag.pass_target = NULL; }
2164 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2168 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2170 entity player = M_ARGV(0, entity);
2172 ctf_RemovePlayer(player);
2175 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2177 entity player = M_ARGV(0, entity);
2179 ctf_RemovePlayer(player);
2182 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2184 if(!autocvar_g_ctf_leaderboard)
2187 entity player = M_ARGV(0, entity);
2189 if(IS_REAL_CLIENT(player))
2191 for(int i = 1; i <= RANKINGS_CNT; ++i)
2193 race_SendRankings(i, 0, 0, MSG_ONE);
2198 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2200 if(!autocvar_g_ctf_leaderboard)
2203 entity player = M_ARGV(0, entity);
2205 if(player.cvar_cl_allow_uidtracking == 1 && player.cvar_cl_allow_uid2name == 1)
2207 if (!player.stored_netname)
2208 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2209 if(player.stored_netname != player.netname)
2211 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2212 strunzone(player.stored_netname);
2213 player.stored_netname = strzone(player.netname);
2218 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2220 entity player = M_ARGV(0, entity);
2222 if(player.flagcarried)
2223 if(!autocvar_g_ctf_portalteleport)
2224 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2227 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2229 if(MUTATOR_RETURNVALUE || game_stopped) return;
2231 entity player = M_ARGV(0, entity);
2233 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2235 // pass the flag to a team mate
2236 if(autocvar_g_ctf_pass)
2238 entity head, closest_target = NULL;
2239 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2241 while(head) // find the closest acceptable target to pass to
2243 if(IS_PLAYER(head) && !IS_DEAD(head))
2244 if(head != player && SAME_TEAM(head, player))
2245 if(!head.speedrunning && !head.vehicle)
2247 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2248 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2249 vector passer_center = CENTER_OR_VIEWOFS(player);
2251 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2253 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2255 if(IS_BOT_CLIENT(head))
2257 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2258 ctf_Handle_Throw(head, player, DROP_PASS);
2262 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2263 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2265 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2268 else if(player.flagcarried && !head.flagcarried)
2272 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2273 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2274 { closest_target = head; }
2276 else { closest_target = head; }
2283 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2286 // throw the flag in front of you
2287 if(autocvar_g_ctf_throw && player.flagcarried)
2289 if(player.throw_count == -1)
2291 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2293 player.throw_prevtime = time;
2294 player.throw_count = 1;
2295 ctf_Handle_Throw(player, NULL, DROP_THROW);
2300 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2306 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2307 else { player.throw_count += 1; }
2308 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2310 player.throw_prevtime = time;
2311 ctf_Handle_Throw(player, NULL, DROP_THROW);
2318 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2320 entity player = M_ARGV(0, entity);
2322 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2324 player.wps_helpme_time = time;
2325 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2327 else // create a normal help me waypointsprite
2329 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2330 WaypointSprite_Ping(player.wps_helpme);
2336 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2338 entity player = M_ARGV(0, entity);
2339 entity veh = M_ARGV(1, entity);
2341 if(player.flagcarried)
2343 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2345 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2349 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2350 setattachment(player.flagcarried, veh, "");
2351 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2352 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2353 //player.flagcarried.angles = '0 0 0';
2359 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2361 entity player = M_ARGV(0, entity);
2363 if(player.flagcarried)
2365 setattachment(player.flagcarried, player, "");
2366 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2367 player.flagcarried.scale = FLAG_SCALE;
2368 player.flagcarried.angles = '0 0 0';
2369 player.flagcarried.nodrawtoclient = NULL;
2374 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2376 entity player = M_ARGV(0, entity);
2378 if(player.flagcarried)
2380 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2381 ctf_RespawnFlag(player.flagcarried);
2386 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2388 entity flag; // temporary entity for the search method
2390 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2392 switch(flag.ctf_status)
2397 // lock the flag, game is over
2398 set_movetype(flag, MOVETYPE_NONE);
2399 flag.takedamage = DAMAGE_NO;
2400 flag.solid = SOLID_NOT;
2401 flag.nextthink = false; // stop thinking
2403 //dprint("stopping the ", flag.netname, " from moving.\n");
2411 // do nothing for these flags
2418 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2420 entity bot = M_ARGV(0, entity);
2422 havocbot_ctf_reset_role(bot);
2426 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2428 //M_ARGV(0, float) = ctf_teams;
2429 M_ARGV(1, string) = "ctf_team";
2433 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2435 entity spectatee = M_ARGV(0, entity);
2436 entity client = M_ARGV(1, entity);
2438 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2441 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2443 int record_page = M_ARGV(0, int);
2444 string ret_string = M_ARGV(1, string);
2446 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2448 if (MapInfo_Get_ByID(i))
2450 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2456 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2457 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2461 M_ARGV(1, string) = ret_string;
2464 bool superspec_Spectate(entity this, entity targ); // TODO
2465 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2466 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2468 entity player = M_ARGV(0, entity);
2469 string cmd_name = M_ARGV(1, string);
2470 int cmd_argc = M_ARGV(2, int);
2472 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2474 if(cmd_name == "followfc")
2486 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2487 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2488 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2489 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2493 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2494 if(it.flagcarried && (it.team == _team || _team == 0))
2497 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2498 continue; // already spectating this fc, try another
2499 return superspec_Spectate(player, it);
2504 superspec_msg("", "", player, "No active flag carrier\n", 1);
2509 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2511 entity frag_target = M_ARGV(0, entity);
2513 if(frag_target.flagcarried)
2514 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2522 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2523 CTF flag for team one (Red).
2525 "angle" Angle the flag will point (minus 90 degrees)...
2526 "model" model to use, note this needs red and blue as skins 0 and 1...
2527 "noise" sound played when flag is picked up...
2528 "noise1" sound played when flag is returned by a teammate...
2529 "noise2" sound played when flag is captured...
2530 "noise3" sound played when flag is lost in the field and respawns itself...
2531 "noise4" sound played when flag is dropped by a player...
2532 "noise5" sound played when flag touches the ground... */
2533 spawnfunc(item_flag_team1)
2535 if(!g_ctf) { delete(this); return; }
2537 ctf_FlagSetup(NUM_TEAM_1, this);
2540 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2541 CTF flag for team two (Blue).
2543 "angle" Angle the flag will point (minus 90 degrees)...
2544 "model" model to use, note this needs red and blue as skins 0 and 1...
2545 "noise" sound played when flag is picked up...
2546 "noise1" sound played when flag is returned by a teammate...
2547 "noise2" sound played when flag is captured...
2548 "noise3" sound played when flag is lost in the field and respawns itself...
2549 "noise4" sound played when flag is dropped by a player...
2550 "noise5" sound played when flag touches the ground... */
2551 spawnfunc(item_flag_team2)
2553 if(!g_ctf) { delete(this); return; }
2555 ctf_FlagSetup(NUM_TEAM_2, this);
2558 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2559 CTF flag for team three (Yellow).
2561 "angle" Angle the flag will point (minus 90 degrees)...
2562 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2563 "noise" sound played when flag is picked up...
2564 "noise1" sound played when flag is returned by a teammate...
2565 "noise2" sound played when flag is captured...
2566 "noise3" sound played when flag is lost in the field and respawns itself...
2567 "noise4" sound played when flag is dropped by a player...
2568 "noise5" sound played when flag touches the ground... */
2569 spawnfunc(item_flag_team3)
2571 if(!g_ctf) { delete(this); return; }
2573 ctf_FlagSetup(NUM_TEAM_3, this);
2576 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2577 CTF flag for team four (Pink).
2579 "angle" Angle the flag will point (minus 90 degrees)...
2580 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2581 "noise" sound played when flag is picked up...
2582 "noise1" sound played when flag is returned by a teammate...
2583 "noise2" sound played when flag is captured...
2584 "noise3" sound played when flag is lost in the field and respawns itself...
2585 "noise4" sound played when flag is dropped by a player...
2586 "noise5" sound played when flag touches the ground... */
2587 spawnfunc(item_flag_team4)
2589 if(!g_ctf) { delete(this); return; }
2591 ctf_FlagSetup(NUM_TEAM_4, this);
2594 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2597 "angle" Angle the flag will point (minus 90 degrees)...
2598 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2599 "noise" sound played when flag is picked up...
2600 "noise1" sound played when flag is returned by a teammate...
2601 "noise2" sound played when flag is captured...
2602 "noise3" sound played when flag is lost in the field and respawns itself...
2603 "noise4" sound played when flag is dropped by a player...
2604 "noise5" sound played when flag touches the ground... */
2605 spawnfunc(item_flag_neutral)
2607 if(!g_ctf) { delete(this); return; }
2608 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2610 ctf_FlagSetup(0, this);
2613 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2614 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2615 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.
2617 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2618 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2621 if(!g_ctf) { delete(this); return; }
2623 this.classname = "ctf_team";
2624 this.team = this.cnt + 1;
2627 // compatibility for quake maps
2628 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2629 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2630 spawnfunc(info_player_team1);
2631 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2632 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2633 spawnfunc(info_player_team2);
2634 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2635 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2637 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2638 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2640 // compatibility for wop maps
2641 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2642 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2643 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2644 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2645 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2646 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2654 void ctf_ScoreRules(int teams)
2656 CheckAllowedTeams(NULL);
2657 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2658 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2659 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2660 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2661 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2662 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2663 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2664 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2665 ScoreRules_basics_end();
2668 // code from here on is just to support maps that don't have flag and team entities
2669 void ctf_SpawnTeam (string teamname, int teamcolor)
2671 entity this = new_pure(ctf_team);
2672 this.netname = teamname;
2673 this.cnt = teamcolor - 1;
2674 this.spawnfunc_checked = true;
2675 this.team = teamcolor;
2678 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2683 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2685 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2686 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2688 switch(tmp_entity.team)
2690 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2691 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2692 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2693 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2695 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2698 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2700 ctf_teams = 0; // so set the default red and blue teams
2701 BITSET_ASSIGN(ctf_teams, BIT(0));
2702 BITSET_ASSIGN(ctf_teams, BIT(1));
2705 //ctf_teams = bound(2, ctf_teams, 4);
2707 // if no teams are found, spawn defaults
2708 if(find(NULL, classname, "ctf_team") == NULL)
2710 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2711 if(ctf_teams & BIT(0))
2712 ctf_SpawnTeam("Red", NUM_TEAM_1);
2713 if(ctf_teams & BIT(1))
2714 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2715 if(ctf_teams & BIT(2))
2716 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2717 if(ctf_teams & BIT(3))
2718 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2721 ctf_ScoreRules(ctf_teams);
2724 void ctf_Initialize()
2726 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2728 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2729 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2730 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2732 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);