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 (navigation_goalrating_timeout(this))
1658 navigation_goalrating_start(this);
1661 havocbot_goalrating_ctf_enemybase(this, 50000);
1663 havocbot_goalrating_ctf_ourbase(this, 50000);
1666 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1668 navigation_goalrating_end(this);
1670 navigation_goalrating_timeout_set(this);
1672 if (this.goalentity)
1673 this.havocbot_cantfindflag = time + 10;
1674 else if (time > this.havocbot_cantfindflag)
1676 // Can't navigate to my own base, suicide!
1677 // TODO: drop it and wander around
1678 Damage(this, this, this, 100000, DEATH_KILL.m_id, this.origin, '0 0 0');
1684 void havocbot_role_ctf_escort(entity this)
1690 havocbot_ctf_reset_role(this);
1694 if (this.flagcarried)
1696 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1700 // If enemy flag is back on the base switch to previous role
1701 ef = havocbot_ctf_find_enemy_flag(this);
1702 if(ef.ctf_status==FLAG_BASE)
1704 this.havocbot_role = this.havocbot_previous_role;
1705 this.havocbot_role_timeout = 0;
1709 // If the flag carrier reached the base switch to defense
1710 mf = havocbot_ctf_find_flag(this);
1711 if(mf.ctf_status!=FLAG_BASE)
1712 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1714 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1718 // Set the role timeout if necessary
1719 if (!this.havocbot_role_timeout)
1721 this.havocbot_role_timeout = time + random() * 30 + 60;
1724 // If nothing happened just switch to previous role
1725 if (time > this.havocbot_role_timeout)
1727 this.havocbot_role = this.havocbot_previous_role;
1728 this.havocbot_role_timeout = 0;
1732 // Chase the flag carrier
1733 if (navigation_goalrating_timeout(this))
1735 navigation_goalrating_start(this);
1737 havocbot_goalrating_ctf_enemyflag(this, 30000);
1738 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1739 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1741 navigation_goalrating_end(this);
1743 navigation_goalrating_timeout_set(this);
1747 void havocbot_role_ctf_offense(entity this)
1754 havocbot_ctf_reset_role(this);
1758 if (this.flagcarried)
1760 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1765 mf = havocbot_ctf_find_flag(this);
1766 ef = havocbot_ctf_find_enemy_flag(this);
1769 if(mf.ctf_status!=FLAG_BASE)
1772 pos = mf.tag_entity.origin;
1776 // Try to get it if closer than the enemy base
1777 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1779 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1784 // Escort flag carrier
1785 if(ef.ctf_status!=FLAG_BASE)
1788 pos = ef.tag_entity.origin;
1792 if(vdist(pos - mf.dropped_origin, >, 700))
1794 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1799 // About to fail, switch to middlefield
1802 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1806 // Set the role timeout if necessary
1807 if (!this.havocbot_role_timeout)
1808 this.havocbot_role_timeout = time + 120;
1810 if (time > this.havocbot_role_timeout)
1812 havocbot_ctf_reset_role(this);
1816 if (navigation_goalrating_timeout(this))
1818 navigation_goalrating_start(this);
1820 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1821 havocbot_goalrating_ctf_enemybase(this, 20000);
1822 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1823 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1825 navigation_goalrating_end(this);
1827 navigation_goalrating_timeout_set(this);
1831 // Retriever (temporary role):
1832 void havocbot_role_ctf_retriever(entity this)
1838 havocbot_ctf_reset_role(this);
1842 if (this.flagcarried)
1844 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1848 // If flag is back on the base switch to previous role
1849 mf = havocbot_ctf_find_flag(this);
1850 if(mf.ctf_status==FLAG_BASE)
1852 if(this.goalcurrent == mf)
1854 navigation_clearroute(this);
1855 navigation_goalrating_timeout_force(this);
1857 havocbot_ctf_reset_role(this);
1861 if (!this.havocbot_role_timeout)
1862 this.havocbot_role_timeout = time + 20;
1864 if (time > this.havocbot_role_timeout)
1866 havocbot_ctf_reset_role(this);
1870 if (navigation_goalrating_timeout(this))
1875 navigation_goalrating_start(this);
1877 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1878 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1879 havocbot_goalrating_ctf_enemybase(this, 30000);
1880 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1882 navigation_goalrating_end(this);
1884 navigation_goalrating_timeout_set(this);
1888 void havocbot_role_ctf_middle(entity this)
1894 havocbot_ctf_reset_role(this);
1898 if (this.flagcarried)
1900 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1904 mf = havocbot_ctf_find_flag(this);
1905 if(mf.ctf_status!=FLAG_BASE)
1907 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1911 if (!this.havocbot_role_timeout)
1912 this.havocbot_role_timeout = time + 10;
1914 if (time > this.havocbot_role_timeout)
1916 havocbot_ctf_reset_role(this);
1920 if (navigation_goalrating_timeout(this))
1924 org = havocbot_middlepoint;
1925 org.z = this.origin.z;
1927 navigation_goalrating_start(this);
1929 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1930 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1931 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
1932 havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
1933 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1934 havocbot_goalrating_ctf_enemybase(this, 2500);
1936 navigation_goalrating_end(this);
1938 navigation_goalrating_timeout_set(this);
1942 void havocbot_role_ctf_defense(entity this)
1948 havocbot_ctf_reset_role(this);
1952 if (this.flagcarried)
1954 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1958 // If own flag was captured
1959 mf = havocbot_ctf_find_flag(this);
1960 if(mf.ctf_status!=FLAG_BASE)
1962 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1966 if (!this.havocbot_role_timeout)
1967 this.havocbot_role_timeout = time + 30;
1969 if (time > this.havocbot_role_timeout)
1971 havocbot_ctf_reset_role(this);
1974 if (navigation_goalrating_timeout(this))
1976 vector org = mf.dropped_origin;
1978 navigation_goalrating_start(this);
1980 // if enemies are closer to our base, go there
1981 entity closestplayer = NULL;
1982 float distance, bestdistance = 10000;
1983 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
1984 distance = vlen(org - it.origin);
1985 if(distance<bestdistance)
1988 bestdistance = distance;
1993 if(DIFF_TEAM(closestplayer, this))
1994 if(vdist(org - this.origin, >, 1000))
1995 if(checkpvs(this.origin,closestplayer)||random()<0.5)
1996 havocbot_goalrating_ctf_ourbase(this, 30000);
1998 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
1999 havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
2000 havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
2001 havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
2002 havocbot_goalrating_items(this, 5000, this.origin, 10000);
2004 navigation_goalrating_end(this);
2006 navigation_goalrating_timeout_set(this);
2010 void havocbot_role_ctf_setrole(entity bot, int role)
2012 string s = "(null)";
2015 case HAVOCBOT_CTF_ROLE_CARRIER:
2017 bot.havocbot_role = havocbot_role_ctf_carrier;
2018 bot.havocbot_role_timeout = 0;
2019 bot.havocbot_cantfindflag = time + 10;
2020 navigation_goalrating_timeout_force(bot);
2022 case HAVOCBOT_CTF_ROLE_DEFENSE:
2024 bot.havocbot_role = havocbot_role_ctf_defense;
2025 bot.havocbot_role_timeout = 0;
2027 case HAVOCBOT_CTF_ROLE_MIDDLE:
2029 bot.havocbot_role = havocbot_role_ctf_middle;
2030 bot.havocbot_role_timeout = 0;
2032 case HAVOCBOT_CTF_ROLE_OFFENSE:
2034 bot.havocbot_role = havocbot_role_ctf_offense;
2035 bot.havocbot_role_timeout = 0;
2037 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2039 bot.havocbot_previous_role = bot.havocbot_role;
2040 bot.havocbot_role = havocbot_role_ctf_retriever;
2041 bot.havocbot_role_timeout = time + 10;
2042 navigation_goalrating_timeout_force(bot);
2044 case HAVOCBOT_CTF_ROLE_ESCORT:
2046 bot.havocbot_previous_role = bot.havocbot_role;
2047 bot.havocbot_role = havocbot_role_ctf_escort;
2048 bot.havocbot_role_timeout = time + 30;
2049 navigation_goalrating_timeout_force(bot);
2052 LOG_TRACE(bot.netname, " switched to ", s);
2060 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2062 entity player = M_ARGV(0, entity);
2064 int t = 0, t2 = 0, t3 = 0;
2065 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)
2067 // initially clear items so they can be set as necessary later.
2068 player.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2069 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2070 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2071 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2072 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2073 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2075 // scan through all the flags and notify the client about them
2076 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2078 if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2079 if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2080 if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2081 if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2082 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; }
2084 switch(flag.ctf_status)
2089 if((flag.owner == player) || (flag.pass_sender == player))
2090 player.ctf_flagstatus |= t; // carrying: player is currently carrying the flag
2092 player.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
2097 player.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
2103 // item for stopping players from capturing the flag too often
2104 if(player.ctf_captureshielded)
2105 player.ctf_flagstatus |= CTF_SHIELDED;
2108 player.ctf_flagstatus |= CTF_STALEMATE;
2110 // update the health of the flag carrier waypointsprite
2111 if(player.wps_flagcarrier)
2112 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2115 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2117 entity frag_attacker = M_ARGV(1, entity);
2118 entity frag_target = M_ARGV(2, entity);
2119 float frag_damage = M_ARGV(4, float);
2120 vector frag_force = M_ARGV(6, vector);
2122 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2124 if(frag_target == frag_attacker) // damage done to yourself
2126 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2127 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2129 else // damage done to everyone else
2131 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2132 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2135 M_ARGV(4, float) = frag_damage;
2136 M_ARGV(6, vector) = frag_force;
2138 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2140 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)))
2141 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2143 frag_target.wps_helpme_time = time;
2144 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2146 // todo: add notification for when flag carrier needs help?
2150 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2152 entity frag_attacker = M_ARGV(1, entity);
2153 entity frag_target = M_ARGV(2, entity);
2155 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2157 GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2158 GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
2161 if(frag_target.flagcarried)
2163 entity tmp_entity = frag_target.flagcarried;
2164 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2165 tmp_entity.ctf_dropper = NULL;
2169 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2171 M_ARGV(2, float) = 0; // frag score
2172 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2175 void ctf_RemovePlayer(entity player)
2177 if(player.flagcarried)
2178 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2180 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2182 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2183 if(flag.pass_target == player) { flag.pass_target = NULL; }
2184 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2188 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2190 entity player = M_ARGV(0, entity);
2192 ctf_RemovePlayer(player);
2195 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2197 entity player = M_ARGV(0, entity);
2199 ctf_RemovePlayer(player);
2202 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2204 if(!autocvar_g_ctf_leaderboard)
2207 entity player = M_ARGV(0, entity);
2209 if(IS_REAL_CLIENT(player))
2211 for(int i = 1; i <= RANKINGS_CNT; ++i)
2213 race_SendRankings(i, 0, 0, MSG_ONE);
2218 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2220 if(!autocvar_g_ctf_leaderboard)
2223 entity player = M_ARGV(0, entity);
2225 if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
2227 if (!player.stored_netname)
2228 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2229 if(player.stored_netname != player.netname)
2231 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2232 strunzone(player.stored_netname);
2233 player.stored_netname = strzone(player.netname);
2238 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2240 entity player = M_ARGV(0, entity);
2242 if(player.flagcarried)
2243 if(!autocvar_g_ctf_portalteleport)
2244 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2247 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2249 if(MUTATOR_RETURNVALUE || game_stopped) return;
2251 entity player = M_ARGV(0, entity);
2253 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2255 // pass the flag to a team mate
2256 if(autocvar_g_ctf_pass)
2258 entity head, closest_target = NULL;
2259 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2261 while(head) // find the closest acceptable target to pass to
2263 if(IS_PLAYER(head) && !IS_DEAD(head))
2264 if(head != player && SAME_TEAM(head, player))
2265 if(!head.speedrunning && !head.vehicle)
2267 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2268 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2269 vector passer_center = CENTER_OR_VIEWOFS(player);
2271 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2273 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2275 if(IS_BOT_CLIENT(head))
2277 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2278 ctf_Handle_Throw(head, player, DROP_PASS);
2282 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2283 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2285 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2288 else if(player.flagcarried && !head.flagcarried)
2292 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2293 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2294 { closest_target = head; }
2296 else { closest_target = head; }
2303 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2306 // throw the flag in front of you
2307 if(autocvar_g_ctf_throw && player.flagcarried)
2309 if(player.throw_count == -1)
2311 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2313 player.throw_prevtime = time;
2314 player.throw_count = 1;
2315 ctf_Handle_Throw(player, NULL, DROP_THROW);
2320 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2326 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2327 else { player.throw_count += 1; }
2328 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2330 player.throw_prevtime = time;
2331 ctf_Handle_Throw(player, NULL, DROP_THROW);
2338 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2340 entity player = M_ARGV(0, entity);
2342 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2344 player.wps_helpme_time = time;
2345 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2347 else // create a normal help me waypointsprite
2349 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2350 WaypointSprite_Ping(player.wps_helpme);
2356 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2358 entity player = M_ARGV(0, entity);
2359 entity veh = M_ARGV(1, entity);
2361 if(player.flagcarried)
2363 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2365 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2369 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2370 setattachment(player.flagcarried, veh, "");
2371 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2372 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2373 //player.flagcarried.angles = '0 0 0';
2379 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2381 entity player = M_ARGV(0, entity);
2383 if(player.flagcarried)
2385 setattachment(player.flagcarried, player, "");
2386 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2387 player.flagcarried.scale = FLAG_SCALE;
2388 player.flagcarried.angles = '0 0 0';
2389 player.flagcarried.nodrawtoclient = NULL;
2394 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2396 entity player = M_ARGV(0, entity);
2398 if(player.flagcarried)
2400 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2401 ctf_RespawnFlag(player.flagcarried);
2406 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2408 entity flag; // temporary entity for the search method
2410 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2412 switch(flag.ctf_status)
2417 // lock the flag, game is over
2418 set_movetype(flag, MOVETYPE_NONE);
2419 flag.takedamage = DAMAGE_NO;
2420 flag.solid = SOLID_NOT;
2421 flag.nextthink = false; // stop thinking
2423 //dprint("stopping the ", flag.netname, " from moving.\n");
2431 // do nothing for these flags
2438 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2440 entity bot = M_ARGV(0, entity);
2442 havocbot_ctf_reset_role(bot);
2446 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2448 //M_ARGV(0, float) = ctf_teams;
2449 M_ARGV(1, string) = "ctf_team";
2453 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2455 entity spectatee = M_ARGV(0, entity);
2456 entity client = M_ARGV(1, entity);
2458 client.ctf_flagstatus = spectatee.ctf_flagstatus;
2461 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2463 int record_page = M_ARGV(0, int);
2464 string ret_string = M_ARGV(1, string);
2466 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2468 if (MapInfo_Get_ByID(i))
2470 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2476 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2477 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2481 M_ARGV(1, string) = ret_string;
2484 bool superspec_Spectate(entity this, entity targ); // TODO
2485 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2486 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2488 entity player = M_ARGV(0, entity);
2489 string cmd_name = M_ARGV(1, string);
2490 int cmd_argc = M_ARGV(2, int);
2492 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2494 if(cmd_name == "followfc")
2506 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2507 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2508 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2509 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2513 FOREACH_CLIENT(IS_PLAYER(it), {
2514 if(it.flagcarried && (it.team == _team || _team == 0))
2517 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2518 continue; // already spectating this fc, try another
2519 return superspec_Spectate(player, it);
2524 superspec_msg("", "", player, "No active flag carrier\n", 1);
2529 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2531 entity frag_target = M_ARGV(0, entity);
2533 if(frag_target.flagcarried)
2534 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2542 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2543 CTF flag for team one (Red).
2545 "angle" Angle the flag will point (minus 90 degrees)...
2546 "model" model to use, note this needs red and blue as skins 0 and 1...
2547 "noise" sound played when flag is picked up...
2548 "noise1" sound played when flag is returned by a teammate...
2549 "noise2" sound played when flag is captured...
2550 "noise3" sound played when flag is lost in the field and respawns itself...
2551 "noise4" sound played when flag is dropped by a player...
2552 "noise5" sound played when flag touches the ground... */
2553 spawnfunc(item_flag_team1)
2555 if(!g_ctf) { delete(this); return; }
2557 ctf_FlagSetup(NUM_TEAM_1, this);
2560 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2561 CTF flag for team two (Blue).
2563 "angle" Angle the flag will point (minus 90 degrees)...
2564 "model" model to use, note this needs red and blue as skins 0 and 1...
2565 "noise" sound played when flag is picked up...
2566 "noise1" sound played when flag is returned by a teammate...
2567 "noise2" sound played when flag is captured...
2568 "noise3" sound played when flag is lost in the field and respawns itself...
2569 "noise4" sound played when flag is dropped by a player...
2570 "noise5" sound played when flag touches the ground... */
2571 spawnfunc(item_flag_team2)
2573 if(!g_ctf) { delete(this); return; }
2575 ctf_FlagSetup(NUM_TEAM_2, this);
2578 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2579 CTF flag for team three (Yellow).
2581 "angle" Angle the flag will point (minus 90 degrees)...
2582 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2583 "noise" sound played when flag is picked up...
2584 "noise1" sound played when flag is returned by a teammate...
2585 "noise2" sound played when flag is captured...
2586 "noise3" sound played when flag is lost in the field and respawns itself...
2587 "noise4" sound played when flag is dropped by a player...
2588 "noise5" sound played when flag touches the ground... */
2589 spawnfunc(item_flag_team3)
2591 if(!g_ctf) { delete(this); return; }
2593 ctf_FlagSetup(NUM_TEAM_3, this);
2596 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2597 CTF flag for team four (Pink).
2599 "angle" Angle the flag will point (minus 90 degrees)...
2600 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2601 "noise" sound played when flag is picked up...
2602 "noise1" sound played when flag is returned by a teammate...
2603 "noise2" sound played when flag is captured...
2604 "noise3" sound played when flag is lost in the field and respawns itself...
2605 "noise4" sound played when flag is dropped by a player...
2606 "noise5" sound played when flag touches the ground... */
2607 spawnfunc(item_flag_team4)
2609 if(!g_ctf) { delete(this); return; }
2611 ctf_FlagSetup(NUM_TEAM_4, this);
2614 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2617 "angle" Angle the flag will point (minus 90 degrees)...
2618 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2619 "noise" sound played when flag is picked up...
2620 "noise1" sound played when flag is returned by a teammate...
2621 "noise2" sound played when flag is captured...
2622 "noise3" sound played when flag is lost in the field and respawns itself...
2623 "noise4" sound played when flag is dropped by a player...
2624 "noise5" sound played when flag touches the ground... */
2625 spawnfunc(item_flag_neutral)
2627 if(!g_ctf) { delete(this); return; }
2628 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2630 ctf_FlagSetup(0, this);
2633 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2634 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2635 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.
2637 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2638 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2641 if(!g_ctf) { delete(this); return; }
2643 this.classname = "ctf_team";
2644 this.team = this.cnt + 1;
2647 // compatibility for quake maps
2648 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2649 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2650 spawnfunc(info_player_team1);
2651 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2652 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2653 spawnfunc(info_player_team2);
2654 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2655 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2657 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2658 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2660 // compatibility for wop maps
2661 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2662 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2663 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2664 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2665 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2666 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2674 void ctf_ScoreRules(int teams)
2676 CheckAllowedTeams(NULL);
2677 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
2678 field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2679 field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2680 field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2681 field(SP_CTF_PICKUPS, "pickups", 0);
2682 field(SP_CTF_FCKILLS, "fckills", 0);
2683 field(SP_CTF_RETURNS, "returns", 0);
2684 field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2688 // code from here on is just to support maps that don't have flag and team entities
2689 void ctf_SpawnTeam (string teamname, int teamcolor)
2691 entity this = new_pure(ctf_team);
2692 this.netname = teamname;
2693 this.cnt = teamcolor - 1;
2694 this.spawnfunc_checked = true;
2695 this.team = teamcolor;
2698 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2703 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2705 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2706 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2708 switch(tmp_entity.team)
2710 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2711 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2712 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2713 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2715 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2718 havocbot_ctf_calculate_middlepoint();
2720 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2722 ctf_teams = 0; // so set the default red and blue teams
2723 BITSET_ASSIGN(ctf_teams, BIT(0));
2724 BITSET_ASSIGN(ctf_teams, BIT(1));
2727 //ctf_teams = bound(2, ctf_teams, 4);
2729 // if no teams are found, spawn defaults
2730 if(find(NULL, classname, "ctf_team") == NULL)
2732 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2733 if(ctf_teams & BIT(0))
2734 ctf_SpawnTeam("Red", NUM_TEAM_1);
2735 if(ctf_teams & BIT(1))
2736 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2737 if(ctf_teams & BIT(2))
2738 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2739 if(ctf_teams & BIT(3))
2740 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2743 ctf_ScoreRules(ctf_teams);
2746 void ctf_Initialize()
2748 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2750 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2751 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2752 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2754 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);