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; }
572 if((flag.cnt || enemy_flag.cnt) && flag.cnt != enemy_flag.cnt) { return; } // this should catch some edge cases (capturing grouped flag at ungrouped flag disallowed etc)
575 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
576 if(SAME_TEAM(tmp_entity, player))
578 player_team_flag = tmp_entity;
582 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
584 player.throw_prevtime = time;
585 player.throw_count = 0;
587 // messages and sounds
588 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
589 ctf_CaptureRecord(enemy_flag, player);
590 _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);
594 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
595 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
601 if(enemy_flag.score_capture || flag.score_capture)
602 pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
603 PlayerTeamScore_AddScore(player, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
605 if(enemy_flag.score_team_capture || flag.score_team_capture)
606 capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
607 PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, ((capscore) ? capscore : 1));
609 old_time = PlayerScore_Add(player, SP_CTF_CAPTIME, 0);
610 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
611 if(!old_time || new_time < old_time)
612 PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
615 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
616 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
619 if(capturetype == CAPTURE_NORMAL)
621 WaypointSprite_Kill(player.wps_flagcarrier);
622 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
624 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
625 { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
629 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
630 ctf_RespawnFlag(enemy_flag);
633 void ctf_Handle_Return(entity flag, entity player)
635 // messages and sounds
636 if(IS_MONSTER(player))
638 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
642 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
643 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
645 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
646 ctf_EventLog("return", flag.team, player);
649 if(IS_PLAYER(player))
651 PlayerTeamScore_AddScore(player, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
652 PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
654 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
657 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
661 PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
662 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
663 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
667 if(player.flagcarried == flag)
668 WaypointSprite_Kill(player.wps_flagcarrier);
671 ctf_RespawnFlag(flag);
674 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
677 float pickup_dropped_score; // used to calculate dropped pickup score
679 // attach the flag to the player
681 player.flagcarried = flag;
684 setattachment(flag, player.vehicle, "");
685 setorigin(flag, VEHICLE_FLAG_OFFSET);
686 flag.scale = VEHICLE_FLAG_SCALE;
690 setattachment(flag, player, "");
691 setorigin(flag, FLAG_CARRY_OFFSET);
695 set_movetype(flag, MOVETYPE_NONE);
696 flag.takedamage = DAMAGE_NO;
697 flag.solid = SOLID_NOT;
698 flag.angles = '0 0 0';
699 flag.ctf_status = FLAG_CARRY;
703 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
704 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
708 // messages and sounds
709 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
711 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
713 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
714 else if(CTF_DIFFTEAM(player, flag))
715 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
717 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
719 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
722 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)));
725 FOREACH_CLIENT(IS_PLAYER(it) && it != player, LAMBDA(
726 if(CTF_SAMETEAM(flag, it))
727 if(SAME_TEAM(player, it))
728 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
730 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);
733 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
736 PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
737 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
742 PlayerTeamScore_AddScore(player, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
743 ctf_EventLog("steal", flag.team, player);
749 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);
750 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);
751 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
752 PlayerTeamScore_AddScore(player, pickup_dropped_score);
753 ctf_EventLog("pickup", flag.team, player);
761 if(pickuptype == PICKUP_BASE)
763 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
764 if((player.speedrunning) && (ctf_captimerecord))
765 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
769 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
772 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
773 ctf_FlagcarrierWaypoints(player);
774 WaypointSprite_Ping(player.wps_flagcarrier);
778 // ===================
779 // Main Flag Functions
780 // ===================
782 void ctf_CheckFlagReturn(entity flag, int returntype)
784 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
786 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
788 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
793 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
795 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
796 case RETURN_SPEEDRUN:
797 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), ctf_captimerecord); break;
798 case RETURN_NEEDKILL:
799 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
802 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
804 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
805 ctf_EventLog("returned", flag.team, NULL);
806 ctf_RespawnFlag(flag);
811 bool ctf_Stalemate_Customize(entity this, entity client)
813 // make spectators see what the player would see
814 entity e = WaypointSprite_getviewentity(client);
815 entity wp_owner = this.owner;
818 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
819 if(SAME_TEAM(wp_owner, e)) { return false; }
820 if(!IS_PLAYER(e)) { return false; }
825 void ctf_CheckStalemate()
828 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
831 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
833 // build list of stale flags
834 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
836 if(autocvar_g_ctf_stalemate)
837 if(tmp_entity.ctf_status != FLAG_BASE)
838 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
840 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
841 ctf_staleflaglist = tmp_entity;
843 switch(tmp_entity.team)
845 case NUM_TEAM_1: ++stale_red_flags; break;
846 case NUM_TEAM_2: ++stale_blue_flags; break;
847 case NUM_TEAM_3: ++stale_yellow_flags; break;
848 case NUM_TEAM_4: ++stale_pink_flags; break;
849 default: ++stale_neutral_flags; break;
855 stale_flags = (stale_neutral_flags >= 1);
857 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
859 if(ctf_oneflag && stale_flags == 1)
860 ctf_stalemate = true;
861 else if(stale_flags >= 2)
862 ctf_stalemate = true;
863 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
864 { ctf_stalemate = false; wpforenemy_announced = false; }
865 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
866 { ctf_stalemate = false; wpforenemy_announced = false; }
868 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
871 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
873 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
875 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);
876 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
877 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
881 if (!wpforenemy_announced)
883 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))));
885 wpforenemy_announced = true;
890 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
892 if(ITEM_DAMAGE_NEEDKILL(deathtype))
894 if(autocvar_g_ctf_flag_return_damage_delay)
895 this.ctf_flagdamaged_byworld = true;
899 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
903 if(autocvar_g_ctf_flag_return_damage)
905 // reduce health and check if it should be returned
906 this.health = this.health - damage;
907 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
912 void ctf_FlagThink(entity this)
917 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
920 if(this == ctf_worldflaglist) // only for the first flag
921 FOREACH_CLIENT(true, LAMBDA(ctf_CaptureShield_Update(it, 1))); // release shield only
924 if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
925 LOG_TRACE("wtf the flag got squashed?");
926 tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
927 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
928 setsize(this, this.m_mins, this.m_maxs);
932 switch(this.ctf_status)
936 if(autocvar_g_ctf_dropped_capture_radius)
938 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
939 if(tmp_entity.ctf_status == FLAG_DROPPED)
940 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
941 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
942 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
949 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
951 if(autocvar_g_ctf_flag_dropped_floatinwater)
953 vector midpoint = ((this.absmin + this.absmax) * 0.5);
954 if(pointcontents(midpoint) == CONTENT_WATER)
956 this.velocity = this.velocity * 0.5;
958 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
959 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
961 { set_movetype(this, MOVETYPE_FLY); }
963 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
965 if(autocvar_g_ctf_flag_return_dropped)
967 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
970 ctf_CheckFlagReturn(this, RETURN_DROPPED);
974 if(this.ctf_flagdamaged_byworld)
976 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
977 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
980 else if(autocvar_g_ctf_flag_return_time)
982 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
983 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
991 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
994 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
996 this.owner.impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
997 ImpulseCommands(this.owner);
999 if(autocvar_g_ctf_stalemate)
1001 if(time >= wpforenemy_nextthink)
1003 ctf_CheckStalemate();
1004 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1007 if(CTF_SAMETEAM(this, this.owner) && this.team)
1009 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1010 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
1011 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1012 ctf_Handle_Return(this, this.owner);
1019 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1020 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1021 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1023 if((this.pass_target == NULL)
1024 || (IS_DEAD(this.pass_target))
1025 || (this.pass_target.flagcarried)
1026 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1027 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1028 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1030 // give up, pass failed
1031 ctf_Handle_Drop(this, NULL, DROP_PASS);
1035 // still a viable target, go for it
1036 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1041 default: // this should never happen
1043 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1049 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1052 if(game_stopped) return;
1053 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1055 bool is_not_monster = (!IS_MONSTER(toucher));
1057 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1058 if(ITEM_TOUCH_NEEDKILL())
1060 if(!autocvar_g_ctf_flag_return_damage_delay)
1063 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1065 if(!flag.ctf_flagdamaged_byworld) { return; }
1068 // special touch behaviors
1069 if(STAT(FROZEN, toucher)) { return; }
1070 else if(IS_VEHICLE(toucher))
1072 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1073 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1075 return; // do nothing
1077 else if(IS_MONSTER(toucher))
1079 if(!autocvar_g_ctf_allow_monster_touch)
1080 return; // do nothing
1082 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1084 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1086 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1087 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1088 flag.wait = time + FLAG_TOUCHRATE;
1092 else if(IS_DEAD(toucher)) { return; }
1094 switch(flag.ctf_status)
1100 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1101 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1102 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1103 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1105 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1106 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1107 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)
1109 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1110 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1112 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1113 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1119 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1120 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1121 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1122 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1128 LOG_TRACE("Someone touched a flag even though it was being carried?");
1134 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1136 if(DIFF_TEAM(toucher, flag.pass_sender))
1138 if(ctf_Immediate_Return_Allowed(flag, toucher))
1139 ctf_Handle_Return(flag, toucher);
1140 else if(is_not_monster && (!toucher.flagcarried))
1141 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1143 else if(!toucher.flagcarried)
1144 ctf_Handle_Retrieve(flag, toucher);
1151 .float last_respawn;
1152 void ctf_RespawnFlag(entity flag)
1154 // check for flag respawn being called twice in a row
1155 if(flag.last_respawn > time - 0.5)
1156 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1158 flag.last_respawn = time;
1160 // reset the player (if there is one)
1161 if((flag.owner) && (flag.owner.flagcarried == flag))
1163 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1164 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1165 WaypointSprite_Kill(flag.wps_flagcarrier);
1167 flag.owner.flagcarried = NULL;
1169 if(flag.speedrunning)
1170 ctf_FakeTimeLimit(flag.owner, -1);
1173 if((flag.owner) && (flag.owner.vehicle))
1174 flag.scale = FLAG_SCALE;
1176 if(flag.ctf_status == FLAG_DROPPED)
1177 { WaypointSprite_Kill(flag.wps_flagdropped); }
1180 setattachment(flag, NULL, "");
1181 setorigin(flag, flag.ctf_spawnorigin);
1183 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1184 flag.takedamage = DAMAGE_NO;
1185 flag.health = flag.max_flag_health;
1186 flag.solid = SOLID_TRIGGER;
1187 flag.velocity = '0 0 0';
1188 flag.angles = flag.mangle;
1189 flag.flags = FL_ITEM | FL_NOTARGET;
1191 flag.ctf_status = FLAG_BASE;
1193 flag.pass_distance = 0;
1194 flag.pass_sender = NULL;
1195 flag.pass_target = NULL;
1196 flag.ctf_dropper = NULL;
1197 flag.ctf_pickuptime = 0;
1198 flag.ctf_droptime = 0;
1199 flag.ctf_flagdamaged_byworld = false;
1201 ctf_CheckStalemate();
1204 void ctf_Reset(entity this)
1206 if(this.owner && IS_PLAYER(this.owner))
1207 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1209 ctf_RespawnFlag(this);
1212 bool ctf_FlagBase_Customize(entity this, entity client)
1214 entity e = WaypointSprite_getviewentity(client);
1215 entity wp_owner = this.owner;
1216 entity flag = e.flagcarried;
1217 if(flag && CTF_SAMETEAM(e, flag))
1219 if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
1224 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1227 waypoint_spawnforitem_force(this, this.origin);
1228 this.nearestwaypointtimeout = 0; // activate waypointing again
1229 this.bot_basewaypoint = this.nearestwaypoint;
1235 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1236 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1237 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1238 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1239 default: basename = WP_FlagBaseNeutral; break;
1242 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1243 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1244 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1245 setcefc(wp, ctf_FlagBase_Customize);
1247 // captureshield setup
1248 ctf_CaptureShield_Spawn(this);
1253 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1256 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1257 ctf_worldflaglist = flag;
1259 setattachment(flag, NULL, "");
1261 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1262 flag.team = teamnumber;
1263 flag.classname = "item_flag_team";
1264 flag.target = "###item###"; // wut?
1265 flag.flags = FL_ITEM | FL_NOTARGET;
1266 IL_PUSH(g_items, flag);
1267 flag.solid = SOLID_TRIGGER;
1268 flag.takedamage = DAMAGE_NO;
1269 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1270 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1271 flag.health = flag.max_flag_health;
1272 flag.event_damage = ctf_FlagDamage;
1273 flag.pushable = true;
1274 flag.teleportable = TELEPORT_NORMAL;
1275 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1276 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1277 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1278 if(flag.damagedbycontents)
1279 IL_PUSH(g_damagedbycontents, flag);
1280 flag.velocity = '0 0 0';
1281 flag.mangle = flag.angles;
1282 flag.reset = ctf_Reset;
1283 settouch(flag, ctf_FlagTouch);
1284 setthink(flag, ctf_FlagThink);
1285 flag.nextthink = time + FLAG_THINKRATE;
1286 flag.ctf_status = FLAG_BASE;
1288 // crudely force them all to 0
1289 if(autocvar_g_ctf_score_ignore_fields)
1290 flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1292 string teamname = Static_Team_ColorName_Lower(teamnumber);
1294 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1295 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1296 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1297 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1298 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1299 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1303 if(flag.s == "") flag.s = b; \
1304 precache_sound(flag.s);
1306 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1307 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1308 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1309 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1310 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1311 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1312 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1316 precache_model(flag.model);
1319 _setmodel(flag, flag.model); // precision set below
1320 setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
1321 flag.m_mins = flag.mins; // store these for squash checks
1322 flag.m_maxs = flag.maxs;
1323 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1325 if(autocvar_g_ctf_flag_glowtrails)
1329 case NUM_TEAM_1: flag.glow_color = 251; break;
1330 case NUM_TEAM_2: flag.glow_color = 210; break;
1331 case NUM_TEAM_3: flag.glow_color = 110; break;
1332 case NUM_TEAM_4: flag.glow_color = 145; break;
1333 default: flag.glow_color = 254; break;
1335 flag.glow_size = 25;
1336 flag.glow_trail = 1;
1339 flag.effects |= EF_LOWPRECISION;
1340 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1341 if(autocvar_g_ctf_dynamiclights)
1345 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1346 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1347 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1348 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1349 default: flag.effects |= EF_DIMLIGHT; break;
1354 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1356 flag.dropped_origin = flag.origin;
1357 flag.noalign = true;
1358 set_movetype(flag, MOVETYPE_NONE);
1360 else // drop to floor, automatically find a platform and set that as spawn origin
1362 flag.noalign = false;
1364 set_movetype(flag, MOVETYPE_NONE);
1367 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1375 // NOTE: LEGACY CODE, needs to be re-written!
1377 void havocbot_calculate_middlepoint()
1381 vector fo = '0 0 0';
1384 f = ctf_worldflaglist;
1389 f = f.ctf_worldflagnext;
1394 havocbot_ctf_middlepoint = s / n;
1395 havocbot_ctf_middlepoint_radius = vlen(fo - havocbot_ctf_middlepoint);
1399 entity havocbot_ctf_find_flag(entity bot)
1402 f = ctf_worldflaglist;
1405 if (CTF_SAMETEAM(bot, f))
1407 f = f.ctf_worldflagnext;
1412 entity havocbot_ctf_find_enemy_flag(entity bot)
1415 f = ctf_worldflaglist;
1420 if(CTF_DIFFTEAM(bot, f))
1427 else if(!bot.flagcarried)
1431 else if (CTF_DIFFTEAM(bot, f))
1433 f = f.ctf_worldflagnext;
1438 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1445 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
1446 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1449 if(vdist(it.origin - org, <, tc_radius))
1458 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1461 head = ctf_worldflaglist;
1464 if (CTF_SAMETEAM(this, head))
1466 head = head.ctf_worldflagnext;
1469 navigation_routerating(this, head, ratingscale, 10000);
1473 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1476 head = ctf_worldflaglist;
1479 if (CTF_SAMETEAM(this, head))
1481 if (this.flagcarried)
1482 if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
1484 head = head.ctf_worldflagnext; // skip base if it has a different group
1489 head = head.ctf_worldflagnext;
1494 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1497 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1500 head = ctf_worldflaglist;
1505 if(CTF_DIFFTEAM(this, head))
1509 if(this.flagcarried)
1512 else if(!this.flagcarried)
1516 else if(CTF_DIFFTEAM(this, head))
1518 head = head.ctf_worldflagnext;
1521 navigation_routerating(this, head, ratingscale, 10000);
1524 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1526 if (!bot_waypoints_for_items)
1528 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1534 head = havocbot_ctf_find_enemy_flag(this);
1539 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1542 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1546 mf = havocbot_ctf_find_flag(this);
1548 if(mf.ctf_status == FLAG_BASE)
1552 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1555 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1558 head = ctf_worldflaglist;
1561 // flag is out in the field
1562 if(head.ctf_status != FLAG_BASE)
1563 if(head.tag_entity==NULL) // dropped
1567 if(vdist(org - head.origin, <, df_radius))
1568 navigation_routerating(this, head, ratingscale, 10000);
1571 navigation_routerating(this, head, ratingscale, 10000);
1574 head = head.ctf_worldflagnext;
1578 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1580 IL_EACH(g_items, it.bot_pickup,
1582 // gather health and armor only
1584 if (it.health || it.armorvalue)
1585 if (vdist(it.origin - org, <, sradius))
1587 // get the value of the item
1588 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1590 navigation_routerating(this, it, t * ratingscale, 500);
1595 void havocbot_ctf_reset_role(entity this)
1597 float cdefense, cmiddle, coffense;
1604 if(havocbot_ctf_middlepoint == '0 0 0')
1605 havocbot_calculate_middlepoint();
1608 if (this.flagcarried)
1610 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1614 mf = havocbot_ctf_find_flag(this);
1615 ef = havocbot_ctf_find_enemy_flag(this);
1617 // Retrieve stolen flag
1618 if(mf.ctf_status!=FLAG_BASE)
1620 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1624 // If enemy flag is taken go to the middle to intercept pursuers
1625 if(ef.ctf_status!=FLAG_BASE)
1627 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1631 // if there is only me on the team switch to offense
1633 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), LAMBDA(++c));
1637 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1641 // Evaluate best position to take
1642 // Count mates on middle position
1643 cmiddle = havocbot_ctf_teamcount(this, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
1645 // Count mates on defense position
1646 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);
1648 // Count mates on offense position
1649 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_ctf_middlepoint_radius);
1651 if(cdefense<=coffense)
1652 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1653 else if(coffense<=cmiddle)
1654 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1656 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1659 void havocbot_role_ctf_carrier(entity this)
1663 havocbot_ctf_reset_role(this);
1667 if (this.flagcarried == NULL)
1669 havocbot_ctf_reset_role(this);
1673 if (this.bot_strategytime < time)
1675 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1677 navigation_goalrating_start(this);
1679 havocbot_goalrating_ctf_enemybase(this, 50000);
1681 havocbot_goalrating_ctf_ourbase(this, 50000);
1684 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1686 navigation_goalrating_end(this);
1688 if (this.navigation_hasgoals)
1689 this.havocbot_cantfindflag = time + 10;
1690 else if (time > this.havocbot_cantfindflag)
1692 // Can't navigate to my own base, suicide!
1693 // TODO: drop it and wander around
1694 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1700 void havocbot_role_ctf_escort(entity this)
1706 havocbot_ctf_reset_role(this);
1710 if (this.flagcarried)
1712 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1716 // If enemy flag is back on the base switch to previous role
1717 ef = havocbot_ctf_find_enemy_flag(this);
1718 if(ef.ctf_status==FLAG_BASE)
1720 this.havocbot_role = this.havocbot_previous_role;
1721 this.havocbot_role_timeout = 0;
1725 // If the flag carrier reached the base switch to defense
1726 mf = havocbot_ctf_find_flag(this);
1727 if(mf.ctf_status!=FLAG_BASE)
1728 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1730 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1734 // Set the role timeout if necessary
1735 if (!this.havocbot_role_timeout)
1737 this.havocbot_role_timeout = time + random() * 30 + 60;
1740 // If nothing happened just switch to previous role
1741 if (time > this.havocbot_role_timeout)
1743 this.havocbot_role = this.havocbot_previous_role;
1744 this.havocbot_role_timeout = 0;
1748 // Chase the flag carrier
1749 if (this.bot_strategytime < time)
1751 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1752 navigation_goalrating_start(this);
1753 havocbot_goalrating_ctf_enemyflag(this, 30000);
1754 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1755 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1756 navigation_goalrating_end(this);
1760 void havocbot_role_ctf_offense(entity this)
1767 havocbot_ctf_reset_role(this);
1771 if (this.flagcarried)
1773 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1778 mf = havocbot_ctf_find_flag(this);
1779 ef = havocbot_ctf_find_enemy_flag(this);
1782 if(mf.ctf_status!=FLAG_BASE)
1785 pos = mf.tag_entity.origin;
1789 // Try to get it if closer than the enemy base
1790 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1792 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1797 // Escort flag carrier
1798 if(ef.ctf_status!=FLAG_BASE)
1801 pos = ef.tag_entity.origin;
1805 if(vdist(pos - mf.dropped_origin, >, 700))
1807 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1812 // About to fail, switch to middlefield
1815 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1819 // Set the role timeout if necessary
1820 if (!this.havocbot_role_timeout)
1821 this.havocbot_role_timeout = time + 120;
1823 if (time > this.havocbot_role_timeout)
1825 havocbot_ctf_reset_role(this);
1829 if (this.bot_strategytime < time)
1831 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1832 navigation_goalrating_start(this);
1833 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1834 havocbot_goalrating_ctf_enemybase(this, 20000);
1835 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1836 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1837 navigation_goalrating_end(this);
1841 // Retriever (temporary role):
1842 void havocbot_role_ctf_retriever(entity this)
1848 havocbot_ctf_reset_role(this);
1852 if (this.flagcarried)
1854 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1858 // If flag is back on the base switch to previous role
1859 mf = havocbot_ctf_find_flag(this);
1860 if(mf.ctf_status==FLAG_BASE)
1862 if(this.goalcurrent == mf)
1864 navigation_clearroute(this);
1865 this.bot_strategytime = 0;
1867 havocbot_ctf_reset_role(this);
1871 if (!this.havocbot_role_timeout)
1872 this.havocbot_role_timeout = time + 20;
1874 if (time > this.havocbot_role_timeout)
1876 havocbot_ctf_reset_role(this);
1880 if (this.bot_strategytime < time)
1885 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1886 navigation_goalrating_start(this);
1887 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1888 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1889 havocbot_goalrating_ctf_enemybase(this, 30000);
1890 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1891 navigation_goalrating_end(this);
1895 void havocbot_role_ctf_middle(entity this)
1901 havocbot_ctf_reset_role(this);
1905 if (this.flagcarried)
1907 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1911 mf = havocbot_ctf_find_flag(this);
1912 if(mf.ctf_status!=FLAG_BASE)
1914 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1918 if (!this.havocbot_role_timeout)
1919 this.havocbot_role_timeout = time + 10;
1921 if (time > this.havocbot_role_timeout)
1923 havocbot_ctf_reset_role(this);
1927 if (this.bot_strategytime < time)
1931 org = havocbot_ctf_middlepoint;
1932 org.z = this.origin.z;
1934 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1935 navigation_goalrating_start(this);
1936 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1937 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1938 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_ctf_middlepoint_radius * 0.5);
1939 havocbot_goalrating_items(this, 5000, org, havocbot_ctf_middlepoint_radius * 0.5);
1940 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1941 havocbot_goalrating_ctf_enemybase(this, 2500);
1942 navigation_goalrating_end(this);
1946 void havocbot_role_ctf_defense(entity this)
1952 havocbot_ctf_reset_role(this);
1956 if (this.flagcarried)
1958 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1962 // If own flag was captured
1963 mf = havocbot_ctf_find_flag(this);
1964 if(mf.ctf_status!=FLAG_BASE)
1966 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1970 if (!this.havocbot_role_timeout)
1971 this.havocbot_role_timeout = time + 30;
1973 if (time > this.havocbot_role_timeout)
1975 havocbot_ctf_reset_role(this);
1978 if (this.bot_strategytime < time)
1983 org = mf.dropped_origin;
1984 mp_radius = havocbot_ctf_middlepoint_radius;
1986 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1987 navigation_goalrating_start(this);
1989 // if enemies are closer to our base, go there
1990 entity closestplayer = NULL;
1991 float distance, bestdistance = 10000;
1992 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), LAMBDA(
1993 distance = vlen(org - it.origin);
1994 if(distance<bestdistance)
1997 bestdistance = distance;
2002 if(DIFF_TEAM(closestplayer, this))
2003 if(vdist(org - this.origin, >, 1000))
2004 if(checkpvs(this.origin,closestplayer)||random()<0.5)
2005 havocbot_goalrating_ctf_ourbase(this, 30000);
2007 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
2008 havocbot_goalrating_ctf_droppedflags(this, 20000, org, mp_radius);
2009 havocbot_goalrating_enemyplayers(this, 15000, org, mp_radius);
2010 havocbot_goalrating_items(this, 10000, org, mp_radius);
2011 havocbot_goalrating_items(this, 5000, this.origin, 10000);
2012 navigation_goalrating_end(this);
2016 void havocbot_role_ctf_setrole(entity bot, int role)
2018 string s = "(null)";
2021 case HAVOCBOT_CTF_ROLE_CARRIER:
2023 bot.havocbot_role = havocbot_role_ctf_carrier;
2024 bot.havocbot_role_timeout = 0;
2025 bot.havocbot_cantfindflag = time + 10;
2026 bot.bot_strategytime = 0;
2028 case HAVOCBOT_CTF_ROLE_DEFENSE:
2030 bot.havocbot_role = havocbot_role_ctf_defense;
2031 bot.havocbot_role_timeout = 0;
2033 case HAVOCBOT_CTF_ROLE_MIDDLE:
2035 bot.havocbot_role = havocbot_role_ctf_middle;
2036 bot.havocbot_role_timeout = 0;
2038 case HAVOCBOT_CTF_ROLE_OFFENSE:
2040 bot.havocbot_role = havocbot_role_ctf_offense;
2041 bot.havocbot_role_timeout = 0;
2043 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2045 bot.havocbot_previous_role = bot.havocbot_role;
2046 bot.havocbot_role = havocbot_role_ctf_retriever;
2047 bot.havocbot_role_timeout = time + 10;
2048 bot.bot_strategytime = 0;
2050 case HAVOCBOT_CTF_ROLE_ESCORT:
2052 bot.havocbot_previous_role = bot.havocbot_role;
2053 bot.havocbot_role = havocbot_role_ctf_escort;
2054 bot.havocbot_role_timeout = time + 30;
2055 bot.bot_strategytime = 0;
2058 LOG_TRACE(bot.netname, " switched to ", s);
2066 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2068 entity player = M_ARGV(0, entity);
2070 int t = 0, t2 = 0, t3 = 0;
2071 bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
2073 // initially clear items so they can be set as necessary later.
2074 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2075 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2076 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2077 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2078 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2079 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2081 // scan through all the flags and notify the client about them
2082 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2084 if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2085 if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2086 if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2087 if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2088 if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; player.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
2090 switch(flag.ctf_status)
2095 if((flag.owner == player) || (flag.pass_sender == player))
2096 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2098 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2103 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2109 // item for stopping players from capturing the flag too often
2110 if(player.ctf_captureshielded)
2111 player.ctf_flagstatus |= CTF_SHIELDED;
2114 player.ctf_flagstatus |= CTF_STALEMATE;
2116 // update the health of the flag carrier waypointsprite
2117 if(player.wps_flagcarrier)
2118 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2121 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2123 entity frag_attacker = M_ARGV(1, entity);
2124 entity frag_target = M_ARGV(2, entity);
2125 float frag_damage = M_ARGV(4, float);
2126 vector frag_force = M_ARGV(6, vector);
2128 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2130 if(frag_target == frag_attacker) // damage done to yourself
2132 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2133 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2135 else // damage done to everyone else
2137 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2138 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2141 M_ARGV(4, float) = frag_damage;
2142 M_ARGV(6, vector) = frag_force;
2144 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2146 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)))
2147 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2149 frag_target.wps_helpme_time = time;
2150 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2152 // todo: add notification for when flag carrier needs help?
2156 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2158 entity frag_attacker = M_ARGV(1, entity);
2159 entity frag_target = M_ARGV(2, entity);
2161 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2163 PlayerTeamScore_AddScore(frag_attacker, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2164 PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
2167 if(frag_target.flagcarried)
2169 entity tmp_entity = frag_target.flagcarried;
2170 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2171 tmp_entity.ctf_dropper = NULL;
2175 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2177 M_ARGV(2, float) = 0; // frag score
2178 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2181 void ctf_RemovePlayer(entity player)
2183 if(player.flagcarried)
2184 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2186 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2188 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2189 if(flag.pass_target == player) { flag.pass_target = NULL; }
2190 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2194 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2196 entity player = M_ARGV(0, entity);
2198 ctf_RemovePlayer(player);
2201 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2203 entity player = M_ARGV(0, entity);
2205 ctf_RemovePlayer(player);
2208 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2210 if(!autocvar_g_ctf_leaderboard)
2213 entity player = M_ARGV(0, entity);
2215 if(IS_REAL_CLIENT(player))
2217 for(int i = 1; i <= RANKINGS_CNT; ++i)
2219 race_SendRankings(i, 0, 0, MSG_ONE);
2224 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2226 if(!autocvar_g_ctf_leaderboard)
2229 entity player = M_ARGV(0, entity);
2231 if(player.cvar_cl_allow_uidtracking == 1 && player.cvar_cl_allow_uid2name == 1)
2233 if (!player.stored_netname)
2234 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2235 if(player.stored_netname != player.netname)
2237 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2238 strunzone(player.stored_netname);
2239 player.stored_netname = strzone(player.netname);
2244 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2246 entity player = M_ARGV(0, entity);
2248 if(player.flagcarried)
2249 if(!autocvar_g_ctf_portalteleport)
2250 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2253 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2255 if(MUTATOR_RETURNVALUE || game_stopped) return;
2257 entity player = M_ARGV(0, entity);
2259 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2261 // pass the flag to a team mate
2262 if(autocvar_g_ctf_pass)
2264 entity head, closest_target = NULL;
2265 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2267 while(head) // find the closest acceptable target to pass to
2269 if(IS_PLAYER(head) && !IS_DEAD(head))
2270 if(head != player && SAME_TEAM(head, player))
2271 if(!head.speedrunning && !head.vehicle)
2273 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2274 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2275 vector passer_center = CENTER_OR_VIEWOFS(player);
2277 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2279 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2281 if(IS_BOT_CLIENT(head))
2283 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2284 ctf_Handle_Throw(head, player, DROP_PASS);
2288 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2289 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2291 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2294 else if(player.flagcarried && !head.flagcarried)
2298 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2299 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2300 { closest_target = head; }
2302 else { closest_target = head; }
2309 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2312 // throw the flag in front of you
2313 if(autocvar_g_ctf_throw && player.flagcarried)
2315 if(player.throw_count == -1)
2317 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2319 player.throw_prevtime = time;
2320 player.throw_count = 1;
2321 ctf_Handle_Throw(player, NULL, DROP_THROW);
2326 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2332 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2333 else { player.throw_count += 1; }
2334 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2336 player.throw_prevtime = time;
2337 ctf_Handle_Throw(player, NULL, DROP_THROW);
2344 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2346 entity player = M_ARGV(0, entity);
2348 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2350 player.wps_helpme_time = time;
2351 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2353 else // create a normal help me waypointsprite
2355 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2356 WaypointSprite_Ping(player.wps_helpme);
2362 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2364 entity player = M_ARGV(0, entity);
2365 entity veh = M_ARGV(1, entity);
2367 if(player.flagcarried)
2369 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2371 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2375 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2376 setattachment(player.flagcarried, veh, "");
2377 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2378 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2379 //player.flagcarried.angles = '0 0 0';
2385 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2387 entity player = M_ARGV(0, entity);
2389 if(player.flagcarried)
2391 setattachment(player.flagcarried, player, "");
2392 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2393 player.flagcarried.scale = FLAG_SCALE;
2394 player.flagcarried.angles = '0 0 0';
2395 player.flagcarried.nodrawtoclient = NULL;
2400 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2402 entity player = M_ARGV(0, entity);
2404 if(player.flagcarried)
2406 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2407 ctf_RespawnFlag(player.flagcarried);
2412 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2414 entity flag; // temporary entity for the search method
2416 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2418 switch(flag.ctf_status)
2423 // lock the flag, game is over
2424 set_movetype(flag, MOVETYPE_NONE);
2425 flag.takedamage = DAMAGE_NO;
2426 flag.solid = SOLID_NOT;
2427 flag.nextthink = false; // stop thinking
2429 //dprint("stopping the ", flag.netname, " from moving.\n");
2437 // do nothing for these flags
2444 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2446 entity bot = M_ARGV(0, entity);
2448 havocbot_ctf_reset_role(bot);
2452 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2454 //M_ARGV(0, float) = ctf_teams;
2455 M_ARGV(1, string) = "ctf_team";
2459 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2461 entity spectatee = M_ARGV(0, entity);
2462 entity client = M_ARGV(1, entity);
2464 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2467 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2469 int record_page = M_ARGV(0, int);
2470 string ret_string = M_ARGV(1, string);
2472 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2474 if (MapInfo_Get_ByID(i))
2476 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2482 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2483 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2487 M_ARGV(1, string) = ret_string;
2490 bool superspec_Spectate(entity this, entity targ); // TODO
2491 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2492 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2494 entity player = M_ARGV(0, entity);
2495 string cmd_name = M_ARGV(1, string);
2496 int cmd_argc = M_ARGV(2, int);
2498 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2500 if(cmd_name == "followfc")
2512 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2513 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2514 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2515 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2519 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA(
2520 if(it.flagcarried && (it.team == _team || _team == 0))
2523 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2524 continue; // already spectating this fc, try another
2525 return superspec_Spectate(player, it);
2530 superspec_msg("", "", player, "No active flag carrier\n", 1);
2535 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2537 entity frag_target = M_ARGV(0, entity);
2539 if(frag_target.flagcarried)
2540 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2548 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2549 CTF flag for team one (Red).
2551 "angle" Angle the flag will point (minus 90 degrees)...
2552 "model" model to use, note this needs red and blue as skins 0 and 1...
2553 "noise" sound played when flag is picked up...
2554 "noise1" sound played when flag is returned by a teammate...
2555 "noise2" sound played when flag is captured...
2556 "noise3" sound played when flag is lost in the field and respawns itself...
2557 "noise4" sound played when flag is dropped by a player...
2558 "noise5" sound played when flag touches the ground... */
2559 spawnfunc(item_flag_team1)
2561 if(!g_ctf) { delete(this); return; }
2563 ctf_FlagSetup(NUM_TEAM_1, this);
2566 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2567 CTF flag for team two (Blue).
2569 "angle" Angle the flag will point (minus 90 degrees)...
2570 "model" model to use, note this needs red and blue as skins 0 and 1...
2571 "noise" sound played when flag is picked up...
2572 "noise1" sound played when flag is returned by a teammate...
2573 "noise2" sound played when flag is captured...
2574 "noise3" sound played when flag is lost in the field and respawns itself...
2575 "noise4" sound played when flag is dropped by a player...
2576 "noise5" sound played when flag touches the ground... */
2577 spawnfunc(item_flag_team2)
2579 if(!g_ctf) { delete(this); return; }
2581 ctf_FlagSetup(NUM_TEAM_2, this);
2584 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2585 CTF flag for team three (Yellow).
2587 "angle" Angle the flag will point (minus 90 degrees)...
2588 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2589 "noise" sound played when flag is picked up...
2590 "noise1" sound played when flag is returned by a teammate...
2591 "noise2" sound played when flag is captured...
2592 "noise3" sound played when flag is lost in the field and respawns itself...
2593 "noise4" sound played when flag is dropped by a player...
2594 "noise5" sound played when flag touches the ground... */
2595 spawnfunc(item_flag_team3)
2597 if(!g_ctf) { delete(this); return; }
2599 ctf_FlagSetup(NUM_TEAM_3, this);
2602 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2603 CTF flag for team four (Pink).
2605 "angle" Angle the flag will point (minus 90 degrees)...
2606 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2607 "noise" sound played when flag is picked up...
2608 "noise1" sound played when flag is returned by a teammate...
2609 "noise2" sound played when flag is captured...
2610 "noise3" sound played when flag is lost in the field and respawns itself...
2611 "noise4" sound played when flag is dropped by a player...
2612 "noise5" sound played when flag touches the ground... */
2613 spawnfunc(item_flag_team4)
2615 if(!g_ctf) { delete(this); return; }
2617 ctf_FlagSetup(NUM_TEAM_4, this);
2620 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2623 "angle" Angle the flag will point (minus 90 degrees)...
2624 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2625 "noise" sound played when flag is picked up...
2626 "noise1" sound played when flag is returned by a teammate...
2627 "noise2" sound played when flag is captured...
2628 "noise3" sound played when flag is lost in the field and respawns itself...
2629 "noise4" sound played when flag is dropped by a player...
2630 "noise5" sound played when flag touches the ground... */
2631 spawnfunc(item_flag_neutral)
2633 if(!g_ctf) { delete(this); return; }
2634 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2636 ctf_FlagSetup(0, this);
2639 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2640 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2641 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.
2643 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2644 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2647 if(!g_ctf) { delete(this); return; }
2649 this.classname = "ctf_team";
2650 this.team = this.cnt + 1;
2653 // compatibility for quake maps
2654 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2655 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2656 spawnfunc(info_player_team1);
2657 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2658 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2659 spawnfunc(info_player_team2);
2660 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2661 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2663 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2664 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2666 // compatibility for wop maps
2667 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2668 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2669 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2670 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2671 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2672 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2680 void ctf_ScoreRules(int teams)
2682 CheckAllowedTeams(NULL);
2683 ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, true);
2684 ScoreInfo_SetLabel_TeamScore (ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2685 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2686 ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2687 ScoreInfo_SetLabel_PlayerScore(SP_CTF_PICKUPS, "pickups", 0);
2688 ScoreInfo_SetLabel_PlayerScore(SP_CTF_FCKILLS, "fckills", 0);
2689 ScoreInfo_SetLabel_PlayerScore(SP_CTF_RETURNS, "returns", 0);
2690 ScoreInfo_SetLabel_PlayerScore(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2691 ScoreRules_basics_end();
2694 // code from here on is just to support maps that don't have flag and team entities
2695 void ctf_SpawnTeam (string teamname, int teamcolor)
2697 entity this = new_pure(ctf_team);
2698 this.netname = teamname;
2699 this.cnt = teamcolor - 1;
2700 this.spawnfunc_checked = true;
2701 this.team = teamcolor;
2704 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2709 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2711 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2712 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2714 switch(tmp_entity.team)
2716 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2717 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2718 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2719 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2721 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2724 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2726 ctf_teams = 0; // so set the default red and blue teams
2727 BITSET_ASSIGN(ctf_teams, BIT(0));
2728 BITSET_ASSIGN(ctf_teams, BIT(1));
2731 //ctf_teams = bound(2, ctf_teams, 4);
2733 // if no teams are found, spawn defaults
2734 if(find(NULL, classname, "ctf_team") == NULL)
2736 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2737 if(ctf_teams & BIT(0))
2738 ctf_SpawnTeam("Red", NUM_TEAM_1);
2739 if(ctf_teams & BIT(1))
2740 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2741 if(ctf_teams & BIT(2))
2742 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2743 if(ctf_teams & BIT(3))
2744 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2747 ctf_ScoreRules(ctf_teams);
2750 void ctf_Initialize()
2752 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2754 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2755 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2756 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2758 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);