1 #include "gamemode_ctf.qh"
3 #include <common/effects/all.qh>
4 #include <common/vehicles/all.qh>
5 #include <server/teamplay.qh>
7 #include <lib/warpzone/common.qh>
9 bool autocvar_g_ctf_allow_vehicle_carry;
10 bool autocvar_g_ctf_allow_vehicle_touch;
11 bool autocvar_g_ctf_allow_monster_touch;
12 bool autocvar_g_ctf_throw;
13 float autocvar_g_ctf_throw_angle_max;
14 float autocvar_g_ctf_throw_angle_min;
15 int autocvar_g_ctf_throw_punish_count;
16 float autocvar_g_ctf_throw_punish_delay;
17 float autocvar_g_ctf_throw_punish_time;
18 float autocvar_g_ctf_throw_strengthmultiplier;
19 float autocvar_g_ctf_throw_velocity_forward;
20 float autocvar_g_ctf_throw_velocity_up;
21 float autocvar_g_ctf_drop_velocity_up;
22 float autocvar_g_ctf_drop_velocity_side;
23 bool autocvar_g_ctf_oneflag_reverse;
24 bool autocvar_g_ctf_portalteleport;
25 bool autocvar_g_ctf_pass;
26 float autocvar_g_ctf_pass_arc;
27 float autocvar_g_ctf_pass_arc_max;
28 float autocvar_g_ctf_pass_directional_max;
29 float autocvar_g_ctf_pass_directional_min;
30 float autocvar_g_ctf_pass_radius;
31 float autocvar_g_ctf_pass_wait;
32 bool autocvar_g_ctf_pass_request;
33 float autocvar_g_ctf_pass_turnrate;
34 float autocvar_g_ctf_pass_timelimit;
35 float autocvar_g_ctf_pass_velocity;
36 bool autocvar_g_ctf_dynamiclights;
37 float autocvar_g_ctf_flag_collect_delay;
38 float autocvar_g_ctf_flag_damageforcescale;
39 bool autocvar_g_ctf_flag_dropped_waypoint;
40 bool autocvar_g_ctf_flag_dropped_floatinwater;
41 bool autocvar_g_ctf_flag_glowtrails;
42 int autocvar_g_ctf_flag_health;
43 bool autocvar_g_ctf_flag_return;
44 bool autocvar_g_ctf_flag_return_carrying;
45 float autocvar_g_ctf_flag_return_carried_radius;
46 float autocvar_g_ctf_flag_return_time;
47 bool autocvar_g_ctf_flag_return_when_unreachable;
48 float autocvar_g_ctf_flag_return_damage;
49 float autocvar_g_ctf_flag_return_damage_delay;
50 float autocvar_g_ctf_flag_return_dropped;
51 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
52 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
53 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
54 float autocvar_g_ctf_flagcarrier_selfforcefactor;
55 float autocvar_g_ctf_flagcarrier_damagefactor;
56 float autocvar_g_ctf_flagcarrier_forcefactor;
57 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
58 bool autocvar_g_ctf_fullbrightflags;
59 bool autocvar_g_ctf_ignore_frags;
60 bool autocvar_g_ctf_score_ignore_fields;
61 int autocvar_g_ctf_score_capture;
62 int autocvar_g_ctf_score_capture_assist;
63 int autocvar_g_ctf_score_kill;
64 int autocvar_g_ctf_score_penalty_drop;
65 int autocvar_g_ctf_score_penalty_returned;
66 int autocvar_g_ctf_score_pickup_base;
67 int autocvar_g_ctf_score_pickup_dropped_early;
68 int autocvar_g_ctf_score_pickup_dropped_late;
69 int autocvar_g_ctf_score_return;
70 float autocvar_g_ctf_shield_force;
71 float autocvar_g_ctf_shield_max_ratio;
72 int autocvar_g_ctf_shield_min_negscore;
73 bool autocvar_g_ctf_stalemate;
74 int autocvar_g_ctf_stalemate_endcondition;
75 float autocvar_g_ctf_stalemate_time;
76 bool autocvar_g_ctf_reverse;
77 float autocvar_g_ctf_dropped_capture_delay;
78 float autocvar_g_ctf_dropped_capture_radius;
80 void ctf_FakeTimeLimit(entity e, float t)
83 WriteByte(MSG_ONE, 3); // svc_updatestat
84 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
86 WriteCoord(MSG_ONE, autocvar_timelimit);
88 WriteCoord(MSG_ONE, (t + 1) / 60);
91 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
93 if(autocvar_sv_eventlog)
94 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
95 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
98 void ctf_CaptureRecord(entity flag, entity player)
100 float cap_record = ctf_captimerecord;
101 float cap_time = (time - flag.ctf_pickuptime);
102 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
106 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
107 else if(!ctf_captimerecord)
108 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
109 else if(cap_time < cap_record)
110 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_BROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
112 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_UNBROKEN), player.netname, refername, TIME_ENCODE(cap_time), TIME_ENCODE(cap_record));
114 // write that shit in the database
115 if(!ctf_oneflag) // but not in 1-flag mode
116 if((!ctf_captimerecord) || (cap_time < cap_record))
118 ctf_captimerecord = cap_time;
119 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
120 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
121 write_recordmarker(player, flag.ctf_pickuptime, cap_time);
124 if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
125 race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
128 bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
131 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
133 // automatically return if there's only 1 player on the team
134 return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
138 bool ctf_Return_Customize(entity this, entity client)
140 // only to the carrier
141 return boolean(client == this.owner);
144 void ctf_FlagcarrierWaypoints(entity player)
146 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
147 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
148 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
149 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
151 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
153 if(!player.wps_enemyflagcarrier)
155 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
156 wp.colormod = WPCOLOR_ENEMYFC(player.team);
157 setcefc(wp, ctf_Stalemate_Customize);
159 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
160 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
163 if(!player.wps_flagreturn)
165 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
166 owp.colormod = '0 0.8 0.8';
167 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
168 setcefc(owp, ctf_Return_Customize);
173 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
175 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
176 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
177 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
178 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
181 if(current_height) // make sure we can actually do this arcing path
183 targpos = (to + ('0 0 1' * current_height));
184 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
185 if(trace_fraction < 1)
187 //print("normal arc line failed, trying to find new pos...");
188 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
189 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
190 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
191 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
192 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
195 else { targpos = to; }
197 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
199 vector desired_direction = normalize(targpos - from);
200 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
201 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
204 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
206 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
208 // directional tracing only
210 makevectors(passer_angle);
212 // find the closest point on the enemy to the center of the attack
213 float h; // hypotenuse, which is the distance between attacker to head
214 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
216 h = vlen(head_center - passer_center);
217 a = h * (normalize(head_center - passer_center) * v_forward);
219 vector nearest_on_line = (passer_center + a * v_forward);
220 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
222 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
223 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
225 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
230 else { return true; }
234 // =======================
235 // CaptureShield Functions
236 // =======================
238 bool ctf_CaptureShield_CheckStatus(entity p)
240 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
241 int players_worseeq, players_total;
243 if(ctf_captureshield_max_ratio <= 0)
246 s = GameRules_scoring_add(p, CTF_CAPS, 0);
247 s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
248 s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
249 s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
251 sr = ((s - s2) + (s3 + s4));
253 if(sr >= -ctf_captureshield_min_negscore)
256 players_total = players_worseeq = 0;
257 FOREACH_CLIENT(IS_PLAYER(it), {
260 se = GameRules_scoring_add(it, CTF_CAPS, 0);
261 se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
262 se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
263 se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0);
265 ser = ((se - se2) + (se3 + se4));
272 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
273 // use this rule here
275 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
281 void ctf_CaptureShield_Update(entity player, bool wanted_status)
283 bool updated_status = ctf_CaptureShield_CheckStatus(player);
284 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
286 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
287 player.ctf_captureshielded = updated_status;
291 bool ctf_CaptureShield_Customize(entity this, entity client)
293 if(!client.ctf_captureshielded) { return false; }
294 if(CTF_SAMETEAM(this, client)) { return false; }
299 void ctf_CaptureShield_Touch(entity this, entity toucher)
301 if(!toucher.ctf_captureshielded) { return; }
302 if(CTF_SAMETEAM(this, toucher)) { return; }
304 vector mymid = (this.absmin + this.absmax) * 0.5;
305 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
307 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
308 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
311 void ctf_CaptureShield_Spawn(entity flag)
313 entity shield = new(ctf_captureshield);
316 shield.team = flag.team;
317 settouch(shield, ctf_CaptureShield_Touch);
318 setcefc(shield, ctf_CaptureShield_Customize);
319 shield.effects = EF_ADDITIVE;
320 set_movetype(shield, MOVETYPE_NOCLIP);
321 shield.solid = SOLID_TRIGGER;
322 shield.avelocity = '7 0 11';
325 setorigin(shield, flag.origin);
326 setmodel(shield, MDL_CTF_SHIELD);
327 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
331 // ====================
332 // Drop/Pass/Throw Code
333 // ====================
335 void ctf_Handle_Drop(entity flag, entity player, int droptype)
338 player = (player ? player : flag.pass_sender);
341 set_movetype(flag, MOVETYPE_TOSS);
342 flag.takedamage = DAMAGE_YES;
343 flag.angles = '0 0 0';
344 flag.health = flag.max_flag_health;
345 flag.ctf_droptime = time;
346 flag.ctf_dropper = player;
347 flag.ctf_status = FLAG_DROPPED;
349 // messages and sounds
350 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
351 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
352 ctf_EventLog("dropped", player.team, player);
355 GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
356 GameRules_scoring_add(player, CTF_DROPS, 1);
359 if(autocvar_g_ctf_flag_dropped_waypoint) {
360 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);
361 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
364 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
366 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
367 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
370 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
372 if(droptype == DROP_PASS)
374 flag.pass_distance = 0;
375 flag.pass_sender = NULL;
376 flag.pass_target = NULL;
380 void ctf_Handle_Retrieve(entity flag, entity player)
382 entity sender = flag.pass_sender;
384 // transfer flag to player
386 flag.owner.flagcarried = flag;
387 GameRules_scoring_vip(player, true);
392 setattachment(flag, player.vehicle, "");
393 setorigin(flag, VEHICLE_FLAG_OFFSET);
394 flag.scale = VEHICLE_FLAG_SCALE;
398 setattachment(flag, player, "");
399 setorigin(flag, FLAG_CARRY_OFFSET);
401 set_movetype(flag, MOVETYPE_NONE);
402 flag.takedamage = DAMAGE_NO;
403 flag.solid = SOLID_NOT;
404 flag.angles = '0 0 0';
405 flag.ctf_status = FLAG_CARRY;
407 // messages and sounds
408 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
409 ctf_EventLog("receive", flag.team, player);
411 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
413 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
414 else if(it == player)
415 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
416 else if(SAME_TEAM(it, sender))
417 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
420 // create new waypoint
421 ctf_FlagcarrierWaypoints(player);
423 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
424 player.throw_antispam = sender.throw_antispam;
426 flag.pass_distance = 0;
427 flag.pass_sender = NULL;
428 flag.pass_target = NULL;
431 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
433 entity flag = player.flagcarried;
434 vector targ_origin, flag_velocity;
436 if(!flag) { return; }
437 if((droptype == DROP_PASS) && !receiver) { return; }
439 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
442 setattachment(flag, NULL, "");
443 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
444 flag.owner.flagcarried = NULL;
445 GameRules_scoring_vip(flag.owner, false);
447 flag.solid = SOLID_TRIGGER;
448 flag.ctf_dropper = player;
449 flag.ctf_droptime = time;
450 navigation_dynamicgoal_set(flag);
452 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
459 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
460 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
461 WarpZone_RefSys_Copy(flag, receiver);
462 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
463 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
465 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
466 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
469 set_movetype(flag, MOVETYPE_FLY);
470 flag.takedamage = DAMAGE_NO;
471 flag.pass_sender = player;
472 flag.pass_target = receiver;
473 flag.ctf_status = FLAG_PASSING;
476 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
477 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
478 ctf_EventLog("pass", flag.team, player);
484 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'));
486 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)));
487 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
488 ctf_Handle_Drop(flag, player, droptype);
494 flag.velocity = '0 0 0'; // do nothing
501 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);
502 ctf_Handle_Drop(flag, player, droptype);
507 // kill old waypointsprite
508 WaypointSprite_Ping(player.wps_flagcarrier);
509 WaypointSprite_Kill(player.wps_flagcarrier);
511 if(player.wps_enemyflagcarrier)
512 WaypointSprite_Kill(player.wps_enemyflagcarrier);
514 if(player.wps_flagreturn)
515 WaypointSprite_Kill(player.wps_flagreturn);
518 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
521 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
523 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
530 void nades_GiveBonus(entity player, float score);
532 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
534 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
535 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
536 entity player_team_flag = NULL, tmp_entity;
537 float old_time, new_time;
539 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
540 if(CTF_DIFFTEAM(player, flag)) { return; }
541 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)
544 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
545 if(SAME_TEAM(tmp_entity, player))
547 player_team_flag = tmp_entity;
551 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
553 player.throw_prevtime = time;
554 player.throw_count = 0;
556 // messages and sounds
557 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
558 ctf_CaptureRecord(enemy_flag, player);
559 _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);
563 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
564 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
570 if(enemy_flag.score_capture || flag.score_capture)
571 pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
572 GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
574 if(enemy_flag.score_team_capture || flag.score_team_capture)
575 capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
576 GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
578 old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
579 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
580 if(!old_time || new_time < old_time)
581 GameRules_scoring_add(player, CTF_CAPTIME, new_time - old_time);
584 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
585 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
588 if(capturetype == CAPTURE_NORMAL)
590 WaypointSprite_Kill(player.wps_flagcarrier);
591 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
593 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
594 { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
598 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
599 ctf_RespawnFlag(enemy_flag);
602 void ctf_Handle_Return(entity flag, entity player)
604 // messages and sounds
605 if(IS_MONSTER(player))
607 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
611 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
612 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
614 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
615 ctf_EventLog("return", flag.team, player);
618 if(IS_PLAYER(player))
620 GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
621 GameRules_scoring_add(player, CTF_RETURNS, 1); // add to count of returns
623 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
626 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
630 GameRules_scoring_add(flag.ctf_dropper, SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
631 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
632 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
636 if(player.flagcarried == flag)
637 WaypointSprite_Kill(player.wps_flagcarrier);
640 ctf_RespawnFlag(flag);
643 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
646 float pickup_dropped_score; // used to calculate dropped pickup score
648 // attach the flag to the player
650 player.flagcarried = flag;
651 GameRules_scoring_vip(player, true);
654 setattachment(flag, player.vehicle, "");
655 setorigin(flag, VEHICLE_FLAG_OFFSET);
656 flag.scale = VEHICLE_FLAG_SCALE;
660 setattachment(flag, player, "");
661 setorigin(flag, FLAG_CARRY_OFFSET);
665 set_movetype(flag, MOVETYPE_NONE);
666 flag.takedamage = DAMAGE_NO;
667 flag.solid = SOLID_NOT;
668 flag.angles = '0 0 0';
669 flag.ctf_status = FLAG_CARRY;
673 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
674 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
678 // messages and sounds
679 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
681 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
683 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
684 else if(CTF_DIFFTEAM(player, flag))
685 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
687 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
689 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
692 FOREACH_CLIENT(IS_PLAYER(it) && it != player && DIFF_TEAM(it, player), { Send_Notification(NOTIF_ONE, it, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname); });
695 FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
696 if(CTF_SAMETEAM(flag, it))
697 if(SAME_TEAM(player, it))
698 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
700 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);
703 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
706 GameRules_scoring_add(player, CTF_PICKUPS, 1);
707 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
712 GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
713 ctf_EventLog("steal", flag.team, player);
719 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);
720 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);
721 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
722 GameRules_scoring_add_team(player, SCORE, pickup_dropped_score);
723 ctf_EventLog("pickup", flag.team, player);
731 if(pickuptype == PICKUP_BASE)
733 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
734 if((player.speedrunning) && (ctf_captimerecord))
735 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
739 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
742 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
743 ctf_FlagcarrierWaypoints(player);
744 WaypointSprite_Ping(player.wps_flagcarrier);
748 // ===================
749 // Main Flag Functions
750 // ===================
752 void ctf_CheckFlagReturn(entity flag, int returntype)
754 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
756 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
758 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
763 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
765 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
766 case RETURN_SPEEDRUN:
767 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
768 case RETURN_NEEDKILL:
769 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
772 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
774 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
775 ctf_EventLog("returned", flag.team, NULL);
776 ctf_RespawnFlag(flag);
781 bool ctf_Stalemate_Customize(entity this, entity client)
783 // make spectators see what the player would see
784 entity e = WaypointSprite_getviewentity(client);
785 entity wp_owner = this.owner;
788 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
789 if(SAME_TEAM(wp_owner, e)) { return false; }
790 if(!IS_PLAYER(e)) { return false; }
795 void ctf_CheckStalemate()
798 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
801 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
803 // build list of stale flags
804 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
806 if(autocvar_g_ctf_stalemate)
807 if(tmp_entity.ctf_status != FLAG_BASE)
808 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
810 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
811 ctf_staleflaglist = tmp_entity;
813 switch(tmp_entity.team)
815 case NUM_TEAM_1: ++stale_red_flags; break;
816 case NUM_TEAM_2: ++stale_blue_flags; break;
817 case NUM_TEAM_3: ++stale_yellow_flags; break;
818 case NUM_TEAM_4: ++stale_pink_flags; break;
819 default: ++stale_neutral_flags; break;
825 stale_flags = (stale_neutral_flags >= 1);
827 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
829 if(ctf_oneflag && stale_flags == 1)
830 ctf_stalemate = true;
831 else if(stale_flags >= 2)
832 ctf_stalemate = true;
833 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
834 { ctf_stalemate = false; wpforenemy_announced = false; }
835 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
836 { ctf_stalemate = false; wpforenemy_announced = false; }
838 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
841 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
843 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
845 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);
846 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
847 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
851 if (!wpforenemy_announced)
853 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), { Send_Notification(NOTIF_ONE, it, MSG_CENTER, ((it.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER)); });
855 wpforenemy_announced = true;
860 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
862 if(ITEM_DAMAGE_NEEDKILL(deathtype))
864 if(autocvar_g_ctf_flag_return_damage_delay)
865 this.ctf_flagdamaged_byworld = true;
869 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
873 if(autocvar_g_ctf_flag_return_damage)
875 // reduce health and check if it should be returned
876 this.health = this.health - damage;
877 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
882 void ctf_FlagThink(entity this)
887 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
890 if(this == ctf_worldflaglist) // only for the first flag
891 FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
894 if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
895 LOG_TRACE("wtf the flag got squashed?");
896 tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
897 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
898 setsize(this, this.m_mins, this.m_maxs);
902 switch(this.ctf_status)
906 if(autocvar_g_ctf_dropped_capture_radius)
908 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
909 if(tmp_entity.ctf_status == FLAG_DROPPED)
910 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
911 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
912 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
919 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
921 if(autocvar_g_ctf_flag_dropped_floatinwater)
923 vector midpoint = ((this.absmin + this.absmax) * 0.5);
924 if(pointcontents(midpoint) == CONTENT_WATER)
926 this.velocity = this.velocity * 0.5;
928 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
929 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
931 { set_movetype(this, MOVETYPE_FLY); }
933 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
935 if(autocvar_g_ctf_flag_return_dropped)
937 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
940 ctf_CheckFlagReturn(this, RETURN_DROPPED);
944 if(this.ctf_flagdamaged_byworld)
946 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
947 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
950 else if(autocvar_g_ctf_flag_return_time)
952 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
953 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
961 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
964 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
966 CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
967 ImpulseCommands(this.owner);
969 if(autocvar_g_ctf_stalemate)
971 if(time >= wpforenemy_nextthink)
973 ctf_CheckStalemate();
974 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
977 if(CTF_SAMETEAM(this, this.owner) && this.team)
979 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
980 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
981 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
982 ctf_Handle_Return(this, this.owner);
989 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
990 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
991 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
993 if((this.pass_target == NULL)
994 || (IS_DEAD(this.pass_target))
995 || (this.pass_target.flagcarried)
996 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
997 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
998 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1000 // give up, pass failed
1001 ctf_Handle_Drop(this, NULL, DROP_PASS);
1005 // still a viable target, go for it
1006 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1011 default: // this should never happen
1013 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1019 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1022 if(game_stopped) return;
1023 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1025 bool is_not_monster = (!IS_MONSTER(toucher));
1027 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1028 if(ITEM_TOUCH_NEEDKILL())
1030 if(!autocvar_g_ctf_flag_return_damage_delay)
1033 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1035 if(!flag.ctf_flagdamaged_byworld) { return; }
1038 // special touch behaviors
1039 if(STAT(FROZEN, toucher)) { return; }
1040 else if(IS_VEHICLE(toucher))
1042 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1043 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1045 return; // do nothing
1047 else if(IS_MONSTER(toucher))
1049 if(!autocvar_g_ctf_allow_monster_touch)
1050 return; // do nothing
1052 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1054 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1056 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1057 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1058 flag.wait = time + FLAG_TOUCHRATE;
1062 else if(IS_DEAD(toucher)) { return; }
1064 switch(flag.ctf_status)
1070 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1071 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1072 else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1073 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1075 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1076 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1077 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)
1079 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1080 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1082 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1083 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1089 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1090 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1091 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1092 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1098 LOG_TRACE("Someone touched a flag even though it was being carried?");
1104 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1106 if(DIFF_TEAM(toucher, flag.pass_sender))
1108 if(ctf_Immediate_Return_Allowed(flag, toucher))
1109 ctf_Handle_Return(flag, toucher);
1110 else if(is_not_monster && (!toucher.flagcarried))
1111 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1113 else if(!toucher.flagcarried)
1114 ctf_Handle_Retrieve(flag, toucher);
1121 .float last_respawn;
1122 void ctf_RespawnFlag(entity flag)
1124 // check for flag respawn being called twice in a row
1125 if(flag.last_respawn > time - 0.5)
1126 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1128 flag.last_respawn = time;
1130 // reset the player (if there is one)
1131 if((flag.owner) && (flag.owner.flagcarried == flag))
1133 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1134 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1135 WaypointSprite_Kill(flag.wps_flagcarrier);
1137 flag.owner.flagcarried = NULL;
1138 GameRules_scoring_vip(flag.owner, false);
1140 if(flag.speedrunning)
1141 ctf_FakeTimeLimit(flag.owner, -1);
1144 if((flag.owner) && (flag.owner.vehicle))
1145 flag.scale = FLAG_SCALE;
1147 if(flag.ctf_status == FLAG_DROPPED)
1148 { WaypointSprite_Kill(flag.wps_flagdropped); }
1151 setattachment(flag, NULL, "");
1152 setorigin(flag, flag.ctf_spawnorigin);
1154 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1155 flag.takedamage = DAMAGE_NO;
1156 flag.health = flag.max_flag_health;
1157 flag.solid = SOLID_TRIGGER;
1158 flag.velocity = '0 0 0';
1159 flag.angles = flag.mangle;
1160 flag.flags = FL_ITEM | FL_NOTARGET;
1162 flag.ctf_status = FLAG_BASE;
1164 flag.pass_distance = 0;
1165 flag.pass_sender = NULL;
1166 flag.pass_target = NULL;
1167 flag.ctf_dropper = NULL;
1168 flag.ctf_pickuptime = 0;
1169 flag.ctf_droptime = 0;
1170 flag.ctf_flagdamaged_byworld = false;
1171 navigation_dynamicgoal_unset(flag);
1173 ctf_CheckStalemate();
1176 void ctf_Reset(entity this)
1178 if(this.owner && IS_PLAYER(this.owner))
1179 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1181 ctf_RespawnFlag(this);
1184 bool ctf_FlagBase_Customize(entity this, entity client)
1186 entity e = WaypointSprite_getviewentity(client);
1187 entity wp_owner = this.owner;
1188 entity flag = e.flagcarried;
1189 if(flag && CTF_SAMETEAM(e, flag))
1191 if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
1196 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1199 waypoint_spawnforitem_force(this, this.origin);
1200 navigation_dynamicgoal_init(this, true);
1206 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1207 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1208 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1209 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1210 default: basename = WP_FlagBaseNeutral; break;
1213 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1214 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1215 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1216 setcefc(wp, ctf_FlagBase_Customize);
1218 // captureshield setup
1219 ctf_CaptureShield_Spawn(this);
1224 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1227 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1228 ctf_worldflaglist = flag;
1230 setattachment(flag, NULL, "");
1232 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1233 flag.team = teamnumber;
1234 flag.classname = "item_flag_team";
1235 flag.target = "###item###"; // wut?
1236 flag.flags = FL_ITEM | FL_NOTARGET;
1237 IL_PUSH(g_items, flag);
1238 flag.solid = SOLID_TRIGGER;
1239 flag.takedamage = DAMAGE_NO;
1240 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1241 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1242 flag.health = flag.max_flag_health;
1243 flag.event_damage = ctf_FlagDamage;
1244 flag.pushable = true;
1245 flag.teleportable = TELEPORT_NORMAL;
1246 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1247 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1248 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1249 if(flag.damagedbycontents)
1250 IL_PUSH(g_damagedbycontents, flag);
1251 flag.velocity = '0 0 0';
1252 flag.mangle = flag.angles;
1253 flag.reset = ctf_Reset;
1254 settouch(flag, ctf_FlagTouch);
1255 setthink(flag, ctf_FlagThink);
1256 flag.nextthink = time + FLAG_THINKRATE;
1257 flag.ctf_status = FLAG_BASE;
1259 // crudely force them all to 0
1260 if(autocvar_g_ctf_score_ignore_fields)
1261 flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1263 string teamname = Static_Team_ColorName_Lower(teamnumber);
1265 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1266 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1267 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1268 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1269 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1270 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1274 if(flag.s == "") flag.s = b; \
1275 precache_sound(flag.s);
1277 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1278 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1279 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1280 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1281 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1282 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1283 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1287 precache_model(flag.model);
1290 _setmodel(flag, flag.model); // precision set below
1291 setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
1292 flag.m_mins = flag.mins; // store these for squash checks
1293 flag.m_maxs = flag.maxs;
1294 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1296 if(autocvar_g_ctf_flag_glowtrails)
1300 case NUM_TEAM_1: flag.glow_color = 251; break;
1301 case NUM_TEAM_2: flag.glow_color = 210; break;
1302 case NUM_TEAM_3: flag.glow_color = 110; break;
1303 case NUM_TEAM_4: flag.glow_color = 145; break;
1304 default: flag.glow_color = 254; break;
1306 flag.glow_size = 25;
1307 flag.glow_trail = 1;
1310 flag.effects |= EF_LOWPRECISION;
1311 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1312 if(autocvar_g_ctf_dynamiclights)
1316 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1317 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1318 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1319 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1320 default: flag.effects |= EF_DIMLIGHT; break;
1325 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1327 flag.dropped_origin = flag.origin;
1328 flag.noalign = true;
1329 set_movetype(flag, MOVETYPE_NONE);
1331 else // drop to floor, automatically find a platform and set that as spawn origin
1333 flag.noalign = false;
1335 set_movetype(flag, MOVETYPE_NONE);
1338 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1346 // NOTE: LEGACY CODE, needs to be re-written!
1348 void havocbot_ctf_calculate_middlepoint()
1352 vector fo = '0 0 0';
1355 f = ctf_worldflaglist;
1360 f = f.ctf_worldflagnext;
1366 havocbot_middlepoint = s / n;
1367 havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
1369 havocbot_symmetryaxis_equation = '0 0 0';
1372 // for symmetrical editing of waypoints
1373 entity f1 = ctf_worldflaglist;
1374 entity f2 = f1.ctf_worldflagnext;
1375 float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
1376 float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
1377 havocbot_symmetryaxis_equation.x = m;
1378 havocbot_symmetryaxis_equation.y = q;
1380 // store number of flags in this otherwise unused vector component
1381 havocbot_symmetryaxis_equation.z = n;
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), {
1432 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1435 if(vdist(it.origin - org, <, tc_radius))
1444 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1447 head = ctf_worldflaglist;
1450 if (CTF_SAMETEAM(this, head))
1452 head = head.ctf_worldflagnext;
1455 navigation_routerating(this, head, ratingscale, 10000);
1459 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1462 head = ctf_worldflaglist;
1465 if (CTF_SAMETEAM(this, head))
1467 if (this.flagcarried)
1468 if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
1470 head = head.ctf_worldflagnext; // skip base if it has a different group
1475 head = head.ctf_worldflagnext;
1480 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1483 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1486 head = ctf_worldflaglist;
1491 if(CTF_DIFFTEAM(this, head))
1495 if(this.flagcarried)
1498 else if(!this.flagcarried)
1502 else if(CTF_DIFFTEAM(this, head))
1504 head = head.ctf_worldflagnext;
1507 navigation_routerating(this, head, ratingscale, 10000);
1510 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1512 if (!bot_waypoints_for_items)
1514 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1520 head = havocbot_ctf_find_enemy_flag(this);
1525 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1528 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1532 mf = havocbot_ctf_find_flag(this);
1534 if(mf.ctf_status == FLAG_BASE)
1538 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1541 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1544 head = ctf_worldflaglist;
1547 // flag is out in the field
1548 if(head.ctf_status != FLAG_BASE)
1549 if(head.tag_entity==NULL) // dropped
1553 if(vdist(org - head.origin, <, df_radius))
1554 navigation_routerating(this, head, ratingscale, 10000);
1557 navigation_routerating(this, head, ratingscale, 10000);
1560 head = head.ctf_worldflagnext;
1564 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1566 IL_EACH(g_items, it.bot_pickup,
1568 // gather health and armor only
1570 if (it.health || it.armorvalue)
1571 if (vdist(it.origin - org, <, sradius))
1573 // get the value of the item
1574 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1576 navigation_routerating(this, it, t * ratingscale, 500);
1581 void havocbot_ctf_reset_role(entity this)
1583 float cdefense, cmiddle, coffense;
1591 if (this.flagcarried)
1593 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1597 mf = havocbot_ctf_find_flag(this);
1598 ef = havocbot_ctf_find_enemy_flag(this);
1600 // Retrieve stolen flag
1601 if(mf.ctf_status!=FLAG_BASE)
1603 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1607 // If enemy flag is taken go to the middle to intercept pursuers
1608 if(ef.ctf_status!=FLAG_BASE)
1610 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1614 // if there is only me on the team switch to offense
1616 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
1620 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1624 // Evaluate best position to take
1625 // Count mates on middle position
1626 cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
1628 // Count mates on defense position
1629 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
1631 // Count mates on offense position
1632 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
1634 if(cdefense<=coffense)
1635 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1636 else if(coffense<=cmiddle)
1637 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1639 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1642 void havocbot_role_ctf_carrier(entity this)
1646 havocbot_ctf_reset_role(this);
1650 if (this.flagcarried == NULL)
1652 havocbot_ctf_reset_role(this);
1656 if (this.bot_strategytime < time)
1658 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1660 navigation_goalrating_start(this);
1662 havocbot_goalrating_ctf_enemybase(this, 50000);
1664 havocbot_goalrating_ctf_ourbase(this, 50000);
1667 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1669 navigation_goalrating_end(this);
1671 if (this.goalentity)
1672 this.havocbot_cantfindflag = time + 10;
1673 else if (time > this.havocbot_cantfindflag)
1675 // Can't navigate to my own base, suicide!
1676 // TODO: drop it and wander around
1677 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1683 void havocbot_role_ctf_escort(entity this)
1689 havocbot_ctf_reset_role(this);
1693 if (this.flagcarried)
1695 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1699 // If enemy flag is back on the base switch to previous role
1700 ef = havocbot_ctf_find_enemy_flag(this);
1701 if(ef.ctf_status==FLAG_BASE)
1703 this.havocbot_role = this.havocbot_previous_role;
1704 this.havocbot_role_timeout = 0;
1708 // If the flag carrier reached the base switch to defense
1709 mf = havocbot_ctf_find_flag(this);
1710 if(mf.ctf_status!=FLAG_BASE)
1711 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1713 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1717 // Set the role timeout if necessary
1718 if (!this.havocbot_role_timeout)
1720 this.havocbot_role_timeout = time + random() * 30 + 60;
1723 // If nothing happened just switch to previous role
1724 if (time > this.havocbot_role_timeout)
1726 this.havocbot_role = this.havocbot_previous_role;
1727 this.havocbot_role_timeout = 0;
1731 // Chase the flag carrier
1732 if (this.bot_strategytime < time)
1734 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1735 navigation_goalrating_start(this);
1736 havocbot_goalrating_ctf_enemyflag(this, 30000);
1737 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1738 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1739 navigation_goalrating_end(this);
1743 void havocbot_role_ctf_offense(entity this)
1750 havocbot_ctf_reset_role(this);
1754 if (this.flagcarried)
1756 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1761 mf = havocbot_ctf_find_flag(this);
1762 ef = havocbot_ctf_find_enemy_flag(this);
1765 if(mf.ctf_status!=FLAG_BASE)
1768 pos = mf.tag_entity.origin;
1772 // Try to get it if closer than the enemy base
1773 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1775 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1780 // Escort flag carrier
1781 if(ef.ctf_status!=FLAG_BASE)
1784 pos = ef.tag_entity.origin;
1788 if(vdist(pos - mf.dropped_origin, >, 700))
1790 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1795 // About to fail, switch to middlefield
1798 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1802 // Set the role timeout if necessary
1803 if (!this.havocbot_role_timeout)
1804 this.havocbot_role_timeout = time + 120;
1806 if (time > this.havocbot_role_timeout)
1808 havocbot_ctf_reset_role(this);
1812 if (this.bot_strategytime < time)
1814 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1815 navigation_goalrating_start(this);
1816 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1817 havocbot_goalrating_ctf_enemybase(this, 20000);
1818 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1819 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1820 navigation_goalrating_end(this);
1824 // Retriever (temporary role):
1825 void havocbot_role_ctf_retriever(entity this)
1831 havocbot_ctf_reset_role(this);
1835 if (this.flagcarried)
1837 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1841 // If flag is back on the base switch to previous role
1842 mf = havocbot_ctf_find_flag(this);
1843 if(mf.ctf_status==FLAG_BASE)
1845 if(this.goalcurrent == mf)
1847 navigation_clearroute(this);
1848 this.bot_strategytime = 0;
1850 havocbot_ctf_reset_role(this);
1854 if (!this.havocbot_role_timeout)
1855 this.havocbot_role_timeout = time + 20;
1857 if (time > this.havocbot_role_timeout)
1859 havocbot_ctf_reset_role(this);
1863 if (this.bot_strategytime < time)
1868 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1869 navigation_goalrating_start(this);
1870 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1871 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1872 havocbot_goalrating_ctf_enemybase(this, 30000);
1873 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1874 navigation_goalrating_end(this);
1878 void havocbot_role_ctf_middle(entity this)
1884 havocbot_ctf_reset_role(this);
1888 if (this.flagcarried)
1890 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1894 mf = havocbot_ctf_find_flag(this);
1895 if(mf.ctf_status!=FLAG_BASE)
1897 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1901 if (!this.havocbot_role_timeout)
1902 this.havocbot_role_timeout = time + 10;
1904 if (time > this.havocbot_role_timeout)
1906 havocbot_ctf_reset_role(this);
1910 if (this.bot_strategytime < time)
1914 org = havocbot_middlepoint;
1915 org.z = this.origin.z;
1917 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1918 navigation_goalrating_start(this);
1919 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1920 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1921 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
1922 havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
1923 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1924 havocbot_goalrating_ctf_enemybase(this, 2500);
1925 navigation_goalrating_end(this);
1929 void havocbot_role_ctf_defense(entity this)
1935 havocbot_ctf_reset_role(this);
1939 if (this.flagcarried)
1941 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1945 // If own flag was captured
1946 mf = havocbot_ctf_find_flag(this);
1947 if(mf.ctf_status!=FLAG_BASE)
1949 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1953 if (!this.havocbot_role_timeout)
1954 this.havocbot_role_timeout = time + 30;
1956 if (time > this.havocbot_role_timeout)
1958 havocbot_ctf_reset_role(this);
1961 if (this.bot_strategytime < time)
1963 vector org = mf.dropped_origin;
1965 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
1966 navigation_goalrating_start(this);
1968 // if enemies are closer to our base, go there
1969 entity closestplayer = NULL;
1970 float distance, bestdistance = 10000;
1971 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
1972 distance = vlen(org - it.origin);
1973 if(distance<bestdistance)
1976 bestdistance = distance;
1981 if(DIFF_TEAM(closestplayer, this))
1982 if(vdist(org - this.origin, >, 1000))
1983 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1984 havocbot_goalrating_ctf_ourbase(this, 30000);
1986 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1987 havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
1988 havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
1989 havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
1990 havocbot_goalrating_items(this, 5000, this.origin, 10000);
1991 navigation_goalrating_end(this);
1995 void havocbot_role_ctf_setrole(entity bot, int role)
1997 string s = "(null)";
2000 case HAVOCBOT_CTF_ROLE_CARRIER:
2002 bot.havocbot_role = havocbot_role_ctf_carrier;
2003 bot.havocbot_role_timeout = 0;
2004 bot.havocbot_cantfindflag = time + 10;
2005 bot.bot_strategytime = 0;
2007 case HAVOCBOT_CTF_ROLE_DEFENSE:
2009 bot.havocbot_role = havocbot_role_ctf_defense;
2010 bot.havocbot_role_timeout = 0;
2012 case HAVOCBOT_CTF_ROLE_MIDDLE:
2014 bot.havocbot_role = havocbot_role_ctf_middle;
2015 bot.havocbot_role_timeout = 0;
2017 case HAVOCBOT_CTF_ROLE_OFFENSE:
2019 bot.havocbot_role = havocbot_role_ctf_offense;
2020 bot.havocbot_role_timeout = 0;
2022 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2024 bot.havocbot_previous_role = bot.havocbot_role;
2025 bot.havocbot_role = havocbot_role_ctf_retriever;
2026 bot.havocbot_role_timeout = time + 10;
2027 bot.bot_strategytime = 0;
2029 case HAVOCBOT_CTF_ROLE_ESCORT:
2031 bot.havocbot_previous_role = bot.havocbot_role;
2032 bot.havocbot_role = havocbot_role_ctf_escort;
2033 bot.havocbot_role_timeout = time + 30;
2034 bot.bot_strategytime = 0;
2037 LOG_TRACE(bot.netname, " switched to ", s);
2045 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2047 entity player = M_ARGV(0, entity);
2049 int t = 0, t2 = 0, t3 = 0;
2050 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)
2052 // initially clear items so they can be set as necessary later.
2053 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2054 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2055 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2056 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2057 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2058 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2060 // scan through all the flags and notify the client about them
2061 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2063 if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2064 if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2065 if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2066 if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2067 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; }
2069 switch(flag.ctf_status)
2074 if((flag.owner == player) || (flag.pass_sender == player))
2075 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2077 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2082 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2088 // item for stopping players from capturing the flag too often
2089 if(player.ctf_captureshielded)
2090 player.ctf_flagstatus |= CTF_SHIELDED;
2093 player.ctf_flagstatus |= CTF_STALEMATE;
2095 // update the health of the flag carrier waypointsprite
2096 if(player.wps_flagcarrier)
2097 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2100 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2102 entity frag_attacker = M_ARGV(1, entity);
2103 entity frag_target = M_ARGV(2, entity);
2104 float frag_damage = M_ARGV(4, float);
2105 vector frag_force = M_ARGV(6, vector);
2107 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2109 if(frag_target == frag_attacker) // damage done to yourself
2111 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2112 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2114 else // damage done to everyone else
2116 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2117 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2120 M_ARGV(4, float) = frag_damage;
2121 M_ARGV(6, vector) = frag_force;
2123 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2125 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)))
2126 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2128 frag_target.wps_helpme_time = time;
2129 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2131 // todo: add notification for when flag carrier needs help?
2135 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2137 entity frag_attacker = M_ARGV(1, entity);
2138 entity frag_target = M_ARGV(2, entity);
2140 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2142 GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2143 GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
2146 if(frag_target.flagcarried)
2148 entity tmp_entity = frag_target.flagcarried;
2149 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2150 tmp_entity.ctf_dropper = NULL;
2154 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2156 M_ARGV(2, float) = 0; // frag score
2157 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2160 void ctf_RemovePlayer(entity player)
2162 if(player.flagcarried)
2163 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2165 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2167 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2168 if(flag.pass_target == player) { flag.pass_target = NULL; }
2169 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2173 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2175 entity player = M_ARGV(0, entity);
2177 ctf_RemovePlayer(player);
2180 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2182 entity player = M_ARGV(0, entity);
2184 ctf_RemovePlayer(player);
2187 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2189 if(!autocvar_g_ctf_leaderboard)
2192 entity player = M_ARGV(0, entity);
2194 if(IS_REAL_CLIENT(player))
2196 for(int i = 1; i <= RANKINGS_CNT; ++i)
2198 race_SendRankings(i, 0, 0, MSG_ONE);
2203 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2205 if(!autocvar_g_ctf_leaderboard)
2208 entity player = M_ARGV(0, entity);
2210 if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
2212 if (!player.stored_netname)
2213 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2214 if(player.stored_netname != player.netname)
2216 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2217 strunzone(player.stored_netname);
2218 player.stored_netname = strzone(player.netname);
2223 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2225 entity player = M_ARGV(0, entity);
2227 if(player.flagcarried)
2228 if(!autocvar_g_ctf_portalteleport)
2229 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2232 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2234 if(MUTATOR_RETURNVALUE || game_stopped) return;
2236 entity player = M_ARGV(0, entity);
2238 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2240 // pass the flag to a team mate
2241 if(autocvar_g_ctf_pass)
2243 entity head, closest_target = NULL;
2244 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2246 while(head) // find the closest acceptable target to pass to
2248 if(IS_PLAYER(head) && !IS_DEAD(head))
2249 if(head != player && SAME_TEAM(head, player))
2250 if(!head.speedrunning && !head.vehicle)
2252 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2253 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2254 vector passer_center = CENTER_OR_VIEWOFS(player);
2256 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2258 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2260 if(IS_BOT_CLIENT(head))
2262 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2263 ctf_Handle_Throw(head, player, DROP_PASS);
2267 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2268 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2270 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2273 else if(player.flagcarried && !head.flagcarried)
2277 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2278 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2279 { closest_target = head; }
2281 else { closest_target = head; }
2288 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2291 // throw the flag in front of you
2292 if(autocvar_g_ctf_throw && player.flagcarried)
2294 if(player.throw_count == -1)
2296 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2298 player.throw_prevtime = time;
2299 player.throw_count = 1;
2300 ctf_Handle_Throw(player, NULL, DROP_THROW);
2305 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2311 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2312 else { player.throw_count += 1; }
2313 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2315 player.throw_prevtime = time;
2316 ctf_Handle_Throw(player, NULL, DROP_THROW);
2323 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2325 entity player = M_ARGV(0, entity);
2327 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2329 player.wps_helpme_time = time;
2330 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2332 else // create a normal help me waypointsprite
2334 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2335 WaypointSprite_Ping(player.wps_helpme);
2341 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2343 entity player = M_ARGV(0, entity);
2344 entity veh = M_ARGV(1, entity);
2346 if(player.flagcarried)
2348 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2350 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2354 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2355 setattachment(player.flagcarried, veh, "");
2356 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2357 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2358 //player.flagcarried.angles = '0 0 0';
2364 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2366 entity player = M_ARGV(0, entity);
2368 if(player.flagcarried)
2370 setattachment(player.flagcarried, player, "");
2371 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2372 player.flagcarried.scale = FLAG_SCALE;
2373 player.flagcarried.angles = '0 0 0';
2374 player.flagcarried.nodrawtoclient = NULL;
2379 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2381 entity player = M_ARGV(0, entity);
2383 if(player.flagcarried)
2385 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2386 ctf_RespawnFlag(player.flagcarried);
2391 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2393 entity flag; // temporary entity for the search method
2395 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2397 switch(flag.ctf_status)
2402 // lock the flag, game is over
2403 set_movetype(flag, MOVETYPE_NONE);
2404 flag.takedamage = DAMAGE_NO;
2405 flag.solid = SOLID_NOT;
2406 flag.nextthink = false; // stop thinking
2408 //dprint("stopping the ", flag.netname, " from moving.\n");
2416 // do nothing for these flags
2423 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2425 entity bot = M_ARGV(0, entity);
2427 havocbot_ctf_reset_role(bot);
2431 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2433 //M_ARGV(0, float) = ctf_teams;
2434 M_ARGV(1, string) = "ctf_team";
2438 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2440 entity spectatee = M_ARGV(0, entity);
2441 entity client = M_ARGV(1, entity);
2443 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2446 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2448 int record_page = M_ARGV(0, int);
2449 string ret_string = M_ARGV(1, string);
2451 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2453 if (MapInfo_Get_ByID(i))
2455 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2461 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2462 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2466 M_ARGV(1, string) = ret_string;
2469 bool superspec_Spectate(entity this, entity targ); // TODO
2470 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2471 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2473 entity player = M_ARGV(0, entity);
2474 string cmd_name = M_ARGV(1, string);
2475 int cmd_argc = M_ARGV(2, int);
2477 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2479 if(cmd_name == "followfc")
2491 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2492 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2493 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2494 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2498 FOREACH_CLIENT(IS_PLAYER(it), {
2499 if(it.flagcarried && (it.team == _team || _team == 0))
2502 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2503 continue; // already spectating this fc, try another
2504 return superspec_Spectate(player, it);
2509 superspec_msg("", "", player, "No active flag carrier\n", 1);
2514 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2516 entity frag_target = M_ARGV(0, entity);
2518 if(frag_target.flagcarried)
2519 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2527 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2528 CTF flag for team one (Red).
2530 "angle" Angle the flag will point (minus 90 degrees)...
2531 "model" model to use, note this needs red and blue as skins 0 and 1...
2532 "noise" sound played when flag is picked up...
2533 "noise1" sound played when flag is returned by a teammate...
2534 "noise2" sound played when flag is captured...
2535 "noise3" sound played when flag is lost in the field and respawns itself...
2536 "noise4" sound played when flag is dropped by a player...
2537 "noise5" sound played when flag touches the ground... */
2538 spawnfunc(item_flag_team1)
2540 if(!g_ctf) { delete(this); return; }
2542 ctf_FlagSetup(NUM_TEAM_1, this);
2545 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2546 CTF flag for team two (Blue).
2548 "angle" Angle the flag will point (minus 90 degrees)...
2549 "model" model to use, note this needs red and blue as skins 0 and 1...
2550 "noise" sound played when flag is picked up...
2551 "noise1" sound played when flag is returned by a teammate...
2552 "noise2" sound played when flag is captured...
2553 "noise3" sound played when flag is lost in the field and respawns itself...
2554 "noise4" sound played when flag is dropped by a player...
2555 "noise5" sound played when flag touches the ground... */
2556 spawnfunc(item_flag_team2)
2558 if(!g_ctf) { delete(this); return; }
2560 ctf_FlagSetup(NUM_TEAM_2, this);
2563 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2564 CTF flag for team three (Yellow).
2566 "angle" Angle the flag will point (minus 90 degrees)...
2567 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2568 "noise" sound played when flag is picked up...
2569 "noise1" sound played when flag is returned by a teammate...
2570 "noise2" sound played when flag is captured...
2571 "noise3" sound played when flag is lost in the field and respawns itself...
2572 "noise4" sound played when flag is dropped by a player...
2573 "noise5" sound played when flag touches the ground... */
2574 spawnfunc(item_flag_team3)
2576 if(!g_ctf) { delete(this); return; }
2578 ctf_FlagSetup(NUM_TEAM_3, this);
2581 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2582 CTF flag for team four (Pink).
2584 "angle" Angle the flag will point (minus 90 degrees)...
2585 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2586 "noise" sound played when flag is picked up...
2587 "noise1" sound played when flag is returned by a teammate...
2588 "noise2" sound played when flag is captured...
2589 "noise3" sound played when flag is lost in the field and respawns itself...
2590 "noise4" sound played when flag is dropped by a player...
2591 "noise5" sound played when flag touches the ground... */
2592 spawnfunc(item_flag_team4)
2594 if(!g_ctf) { delete(this); return; }
2596 ctf_FlagSetup(NUM_TEAM_4, this);
2599 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2602 "angle" Angle the flag will point (minus 90 degrees)...
2603 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2604 "noise" sound played when flag is picked up...
2605 "noise1" sound played when flag is returned by a teammate...
2606 "noise2" sound played when flag is captured...
2607 "noise3" sound played when flag is lost in the field and respawns itself...
2608 "noise4" sound played when flag is dropped by a player...
2609 "noise5" sound played when flag touches the ground... */
2610 spawnfunc(item_flag_neutral)
2612 if(!g_ctf) { delete(this); return; }
2613 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2615 ctf_FlagSetup(0, this);
2618 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2619 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2620 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.
2622 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2623 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2626 if(!g_ctf) { delete(this); return; }
2628 this.classname = "ctf_team";
2629 this.team = this.cnt + 1;
2632 // compatibility for quake maps
2633 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2634 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2635 spawnfunc(info_player_team1);
2636 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2637 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2638 spawnfunc(info_player_team2);
2639 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2640 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2642 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2643 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2645 // compatibility for wop maps
2646 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2647 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2648 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2649 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2650 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2651 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2659 void ctf_ScoreRules(int teams)
2661 CheckAllowedTeams(NULL);
2662 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
2663 field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2664 field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2665 field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2666 field(SP_CTF_PICKUPS, "pickups", 0);
2667 field(SP_CTF_FCKILLS, "fckills", 0);
2668 field(SP_CTF_RETURNS, "returns", 0);
2669 field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2673 // code from here on is just to support maps that don't have flag and team entities
2674 void ctf_SpawnTeam (string teamname, int teamcolor)
2676 entity this = new_pure(ctf_team);
2677 this.netname = teamname;
2678 this.cnt = teamcolor - 1;
2679 this.spawnfunc_checked = true;
2680 this.team = teamcolor;
2683 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2688 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2690 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2691 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2693 switch(tmp_entity.team)
2695 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2696 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2697 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2698 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2700 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2703 havocbot_ctf_calculate_middlepoint();
2705 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2707 ctf_teams = 0; // so set the default red and blue teams
2708 BITSET_ASSIGN(ctf_teams, BIT(0));
2709 BITSET_ASSIGN(ctf_teams, BIT(1));
2712 //ctf_teams = bound(2, ctf_teams, 4);
2714 // if no teams are found, spawn defaults
2715 if(find(NULL, classname, "ctf_team") == NULL)
2717 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2718 if(ctf_teams & BIT(0))
2719 ctf_SpawnTeam("Red", NUM_TEAM_1);
2720 if(ctf_teams & BIT(1))
2721 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2722 if(ctf_teams & BIT(2))
2723 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2724 if(ctf_teams & BIT(3))
2725 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2728 ctf_ScoreRules(ctf_teams);
2731 void ctf_Initialize()
2733 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2735 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2736 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2737 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2739 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);