3 // TODO: split into sv_ctf
5 #include <common/effects/all.qh>
6 #include <common/vehicles/all.qh>
7 #include <server/teamplay.qh>
9 #include <lib/warpzone/common.qh>
11 bool autocvar_g_ctf_allow_vehicle_carry;
12 bool autocvar_g_ctf_allow_vehicle_touch;
13 bool autocvar_g_ctf_allow_monster_touch;
14 bool autocvar_g_ctf_throw;
15 float autocvar_g_ctf_throw_angle_max;
16 float autocvar_g_ctf_throw_angle_min;
17 int autocvar_g_ctf_throw_punish_count;
18 float autocvar_g_ctf_throw_punish_delay;
19 float autocvar_g_ctf_throw_punish_time;
20 float autocvar_g_ctf_throw_strengthmultiplier;
21 float autocvar_g_ctf_throw_velocity_forward;
22 float autocvar_g_ctf_throw_velocity_up;
23 float autocvar_g_ctf_drop_velocity_up;
24 float autocvar_g_ctf_drop_velocity_side;
25 bool autocvar_g_ctf_oneflag_reverse;
26 bool autocvar_g_ctf_portalteleport;
27 bool autocvar_g_ctf_pass;
28 float autocvar_g_ctf_pass_arc;
29 float autocvar_g_ctf_pass_arc_max;
30 float autocvar_g_ctf_pass_directional_max;
31 float autocvar_g_ctf_pass_directional_min;
32 float autocvar_g_ctf_pass_radius;
33 float autocvar_g_ctf_pass_wait;
34 bool autocvar_g_ctf_pass_request;
35 float autocvar_g_ctf_pass_turnrate;
36 float autocvar_g_ctf_pass_timelimit;
37 float autocvar_g_ctf_pass_velocity;
38 bool autocvar_g_ctf_dynamiclights;
39 float autocvar_g_ctf_flag_collect_delay;
40 float autocvar_g_ctf_flag_damageforcescale;
41 bool autocvar_g_ctf_flag_dropped_waypoint;
42 bool autocvar_g_ctf_flag_dropped_floatinwater;
43 bool autocvar_g_ctf_flag_glowtrails;
44 int autocvar_g_ctf_flag_health;
45 bool autocvar_g_ctf_flag_return;
46 bool autocvar_g_ctf_flag_return_carrying;
47 float autocvar_g_ctf_flag_return_carried_radius;
48 float autocvar_g_ctf_flag_return_time;
49 bool autocvar_g_ctf_flag_return_when_unreachable;
50 float autocvar_g_ctf_flag_return_damage;
51 float autocvar_g_ctf_flag_return_damage_delay;
52 float autocvar_g_ctf_flag_return_dropped;
53 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
54 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
55 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
56 float autocvar_g_ctf_flagcarrier_selfforcefactor;
57 float autocvar_g_ctf_flagcarrier_damagefactor;
58 float autocvar_g_ctf_flagcarrier_forcefactor;
59 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
60 bool autocvar_g_ctf_fullbrightflags;
61 bool autocvar_g_ctf_ignore_frags;
62 bool autocvar_g_ctf_score_ignore_fields;
63 int autocvar_g_ctf_score_capture;
64 int autocvar_g_ctf_score_capture_assist;
65 int autocvar_g_ctf_score_kill;
66 int autocvar_g_ctf_score_penalty_drop;
67 int autocvar_g_ctf_score_penalty_returned;
68 int autocvar_g_ctf_score_pickup_base;
69 int autocvar_g_ctf_score_pickup_dropped_early;
70 int autocvar_g_ctf_score_pickup_dropped_late;
71 int autocvar_g_ctf_score_return;
72 float autocvar_g_ctf_shield_force;
73 float autocvar_g_ctf_shield_max_ratio;
74 int autocvar_g_ctf_shield_min_negscore;
75 bool autocvar_g_ctf_stalemate;
76 int autocvar_g_ctf_stalemate_endcondition;
77 float autocvar_g_ctf_stalemate_time;
78 bool autocvar_g_ctf_reverse;
79 float autocvar_g_ctf_dropped_capture_delay;
80 float autocvar_g_ctf_dropped_capture_radius;
82 void ctf_FakeTimeLimit(entity e, float t)
85 WriteByte(MSG_ONE, 3); // svc_updatestat
86 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
88 WriteCoord(MSG_ONE, autocvar_timelimit);
90 WriteCoord(MSG_ONE, (t + 1) / 60);
93 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
95 if(autocvar_sv_eventlog)
96 GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
97 //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
100 void ctf_CaptureRecord(entity flag, entity player)
102 float cap_record = ctf_captimerecord;
103 float cap_time = (time - flag.ctf_pickuptime);
104 string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
108 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
109 else if(!ctf_captimerecord)
110 Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
111 else if(cap_time < cap_record)
112 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));
114 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));
116 // write that shit in the database
117 if(!ctf_oneflag) // but not in 1-flag mode
118 if((!ctf_captimerecord) || (cap_time < cap_record))
120 ctf_captimerecord = cap_time;
121 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
122 db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
123 write_recordmarker(player, flag.ctf_pickuptime, cap_time);
126 if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
127 race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
130 bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
133 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
135 // automatically return if there's only 1 player on the team
136 return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
140 bool ctf_Return_Customize(entity this, entity client)
142 // only to the carrier
143 return boolean(client == this.owner);
146 void ctf_FlagcarrierWaypoints(entity player)
148 WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
149 WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
150 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
151 WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
153 if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
155 if(!player.wps_enemyflagcarrier)
157 entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
158 wp.colormod = WPCOLOR_ENEMYFC(player.team);
159 setcefc(wp, ctf_Stalemate_Customize);
161 if(IS_REAL_CLIENT(player) && !ctf_stalemate)
162 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
165 if(!player.wps_flagreturn)
167 entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
168 owp.colormod = '0 0.8 0.8';
169 //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
170 setcefc(owp, ctf_Return_Customize);
175 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
177 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
178 float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
179 float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
180 //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
183 if(current_height) // make sure we can actually do this arcing path
185 targpos = (to + ('0 0 1' * current_height));
186 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
187 if(trace_fraction < 1)
189 //print("normal arc line failed, trying to find new pos...");
190 WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
191 targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
192 WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
193 if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
194 /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
197 else { targpos = to; }
199 //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
201 vector desired_direction = normalize(targpos - from);
202 if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
203 else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
206 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
208 if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
210 // directional tracing only
212 makevectors(passer_angle);
214 // find the closest point on the enemy to the center of the attack
215 float h; // hypotenuse, which is the distance between attacker to head
216 float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
218 h = vlen(head_center - passer_center);
219 a = h * (normalize(head_center - passer_center) * v_forward);
221 vector nearest_on_line = (passer_center + a * v_forward);
222 float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
224 spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
225 spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
227 if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
232 else { return true; }
236 // =======================
237 // CaptureShield Functions
238 // =======================
240 bool ctf_CaptureShield_CheckStatus(entity p)
242 int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
243 int players_worseeq, players_total;
245 if(ctf_captureshield_max_ratio <= 0)
248 s = GameRules_scoring_add(p, CTF_CAPS, 0);
249 s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
250 s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
251 s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
253 sr = ((s - s2) + (s3 + s4));
255 if(sr >= -ctf_captureshield_min_negscore)
258 players_total = players_worseeq = 0;
259 FOREACH_CLIENT(IS_PLAYER(it), {
262 se = GameRules_scoring_add(it, CTF_CAPS, 0);
263 se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
264 se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
265 se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0);
267 ser = ((se - se2) + (se3 + se4));
274 // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
275 // use this rule here
277 if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
283 void ctf_CaptureShield_Update(entity player, bool wanted_status)
285 bool updated_status = ctf_CaptureShield_CheckStatus(player);
286 if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
288 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
289 player.ctf_captureshielded = updated_status;
293 bool ctf_CaptureShield_Customize(entity this, entity client)
295 if(!client.ctf_captureshielded) { return false; }
296 if(CTF_SAMETEAM(this, client)) { return false; }
301 void ctf_CaptureShield_Touch(entity this, entity toucher)
303 if(!toucher.ctf_captureshielded) { return; }
304 if(CTF_SAMETEAM(this, toucher)) { return; }
306 vector mymid = (this.absmin + this.absmax) * 0.5;
307 vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
309 Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
310 if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
313 void ctf_CaptureShield_Spawn(entity flag)
315 entity shield = new(ctf_captureshield);
318 shield.team = flag.team;
319 settouch(shield, ctf_CaptureShield_Touch);
320 setcefc(shield, ctf_CaptureShield_Customize);
321 shield.effects = EF_ADDITIVE;
322 set_movetype(shield, MOVETYPE_NOCLIP);
323 shield.solid = SOLID_TRIGGER;
324 shield.avelocity = '7 0 11';
327 setorigin(shield, flag.origin);
328 setmodel(shield, MDL_CTF_SHIELD);
329 setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
333 // ====================
334 // Drop/Pass/Throw Code
335 // ====================
337 void ctf_Handle_Drop(entity flag, entity player, int droptype)
340 player = (player ? player : flag.pass_sender);
343 set_movetype(flag, MOVETYPE_TOSS);
344 flag.takedamage = DAMAGE_YES;
345 flag.angles = '0 0 0';
346 flag.health = flag.max_flag_health;
347 flag.ctf_droptime = time;
348 flag.ctf_dropper = player;
349 flag.ctf_status = FLAG_DROPPED;
351 // messages and sounds
352 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
353 _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
354 ctf_EventLog("dropped", player.team, player);
357 GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
358 GameRules_scoring_add(player, CTF_DROPS, 1);
361 if(autocvar_g_ctf_flag_dropped_waypoint) {
362 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);
363 wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
366 if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
368 WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
369 WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
372 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
374 if(droptype == DROP_PASS)
376 flag.pass_distance = 0;
377 flag.pass_sender = NULL;
378 flag.pass_target = NULL;
382 void ctf_Handle_Retrieve(entity flag, entity player)
384 entity sender = flag.pass_sender;
386 // transfer flag to player
388 flag.owner.flagcarried = flag;
389 GameRules_scoring_vip(player, true);
394 setattachment(flag, player.vehicle, "");
395 setorigin(flag, VEHICLE_FLAG_OFFSET);
396 flag.scale = VEHICLE_FLAG_SCALE;
400 setattachment(flag, player, "");
401 setorigin(flag, FLAG_CARRY_OFFSET);
403 set_movetype(flag, MOVETYPE_NONE);
404 flag.takedamage = DAMAGE_NO;
405 flag.solid = SOLID_NOT;
406 flag.angles = '0 0 0';
407 flag.ctf_status = FLAG_CARRY;
409 // messages and sounds
410 _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
411 ctf_EventLog("receive", flag.team, player);
413 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), {
415 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
416 else if(it == player)
417 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
418 else if(SAME_TEAM(it, sender))
419 Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
422 // create new waypoint
423 ctf_FlagcarrierWaypoints(player);
425 sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
426 player.throw_antispam = sender.throw_antispam;
428 flag.pass_distance = 0;
429 flag.pass_sender = NULL;
430 flag.pass_target = NULL;
433 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
435 entity flag = player.flagcarried;
436 vector targ_origin, flag_velocity;
438 if(!flag) { return; }
439 if((droptype == DROP_PASS) && !receiver) { return; }
441 if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
444 setattachment(flag, NULL, "");
445 setorigin(flag, player.origin + FLAG_DROP_OFFSET);
446 flag.owner.flagcarried = NULL;
447 GameRules_scoring_vip(flag.owner, false);
449 flag.solid = SOLID_TRIGGER;
450 flag.ctf_dropper = player;
451 flag.ctf_droptime = time;
452 navigation_dynamicgoal_set(flag);
454 flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
461 // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
462 // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
463 WarpZone_RefSys_Copy(flag, receiver);
464 WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
465 targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
467 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
468 ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
471 set_movetype(flag, MOVETYPE_FLY);
472 flag.takedamage = DAMAGE_NO;
473 flag.pass_sender = player;
474 flag.pass_target = receiver;
475 flag.ctf_status = FLAG_PASSING;
478 _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
479 WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
480 ctf_EventLog("pass", flag.team, player);
486 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'));
488 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)));
489 flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
490 ctf_Handle_Drop(flag, player, droptype);
496 flag.velocity = '0 0 0'; // do nothing
503 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);
504 ctf_Handle_Drop(flag, player, droptype);
509 // kill old waypointsprite
510 WaypointSprite_Ping(player.wps_flagcarrier);
511 WaypointSprite_Kill(player.wps_flagcarrier);
513 if(player.wps_enemyflagcarrier)
514 WaypointSprite_Kill(player.wps_enemyflagcarrier);
516 if(player.wps_flagreturn)
517 WaypointSprite_Kill(player.wps_flagreturn);
520 ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
523 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
525 return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
532 void nades_GiveBonus(entity player, float score);
534 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
536 entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
537 entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
538 entity player_team_flag = NULL, tmp_entity;
539 float old_time, new_time;
541 if(!player) { return; } // without someone to give the reward to, we can't possibly cap
542 if(CTF_DIFFTEAM(player, flag)) { return; }
543 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)
545 if (toucher.goalentity == flag.bot_basewaypoint)
546 toucher.goalentity_lock_timeout = 0;
549 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
550 if(SAME_TEAM(tmp_entity, player))
552 player_team_flag = tmp_entity;
556 nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
558 player.throw_prevtime = time;
559 player.throw_count = 0;
561 // messages and sounds
562 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
563 ctf_CaptureRecord(enemy_flag, player);
564 _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);
568 case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
569 case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
575 if(enemy_flag.score_capture || flag.score_capture)
576 pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
577 GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
579 if(enemy_flag.score_team_capture || flag.score_team_capture)
580 capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
581 GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
583 old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
584 new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
585 if(!old_time || new_time < old_time)
586 GameRules_scoring_add(player, CTF_CAPTIME, new_time - old_time);
589 Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
590 //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
593 if(capturetype == CAPTURE_NORMAL)
595 WaypointSprite_Kill(player.wps_flagcarrier);
596 if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
598 if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
599 { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
602 flag.enemy = toucher;
605 player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
606 ctf_RespawnFlag(enemy_flag);
609 void ctf_Handle_Return(entity flag, entity player)
611 // messages and sounds
612 if(IS_MONSTER(player))
614 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
618 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
619 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
621 _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
622 ctf_EventLog("return", flag.team, player);
625 if(IS_PLAYER(player))
627 GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
628 GameRules_scoring_add(player, CTF_RETURNS, 1); // add to count of returns
630 nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
633 TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
637 GameRules_scoring_add(flag.ctf_dropper, SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
638 ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
639 flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
643 if(player.flagcarried == flag)
644 WaypointSprite_Kill(player.wps_flagcarrier);
649 ctf_RespawnFlag(flag);
652 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
655 float pickup_dropped_score; // used to calculate dropped pickup score
657 // attach the flag to the player
659 player.flagcarried = flag;
660 GameRules_scoring_vip(player, true);
663 setattachment(flag, player.vehicle, "");
664 setorigin(flag, VEHICLE_FLAG_OFFSET);
665 flag.scale = VEHICLE_FLAG_SCALE;
669 setattachment(flag, player, "");
670 setorigin(flag, FLAG_CARRY_OFFSET);
674 set_movetype(flag, MOVETYPE_NONE);
675 flag.takedamage = DAMAGE_NO;
676 flag.solid = SOLID_NOT;
677 flag.angles = '0 0 0';
678 flag.ctf_status = FLAG_CARRY;
682 case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
683 case PICKUP_DROPPED: flag.health = flag.max_flag_health; break; // reset health/return timelimit
687 // messages and sounds
688 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
690 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
692 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
693 else if(CTF_DIFFTEAM(player, flag))
694 Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
696 Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
698 Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
701 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); });
704 FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
705 if(CTF_SAMETEAM(flag, it))
706 if(SAME_TEAM(player, it))
707 Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
709 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);
712 _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
715 GameRules_scoring_add(player, CTF_PICKUPS, 1);
716 nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
721 GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
722 ctf_EventLog("steal", flag.team, player);
728 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);
729 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);
730 LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
731 GameRules_scoring_add_team(player, SCORE, pickup_dropped_score);
732 ctf_EventLog("pickup", flag.team, player);
740 if(pickuptype == PICKUP_BASE)
742 flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
743 if((player.speedrunning) && (ctf_captimerecord))
744 ctf_FakeTimeLimit(player, time + ctf_captimerecord);
748 Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
751 if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
752 ctf_FlagcarrierWaypoints(player);
753 WaypointSprite_Ping(player.wps_flagcarrier);
757 // ===================
758 // Main Flag Functions
759 // ===================
761 void ctf_CheckFlagReturn(entity flag, int returntype)
763 if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
765 if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
767 if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
772 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
774 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
775 case RETURN_SPEEDRUN:
776 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
777 case RETURN_NEEDKILL:
778 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
781 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
783 _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
784 ctf_EventLog("returned", flag.team, NULL);
786 ctf_RespawnFlag(flag);
791 bool ctf_Stalemate_Customize(entity this, entity client)
793 // make spectators see what the player would see
794 entity e = WaypointSprite_getviewentity(client);
795 entity wp_owner = this.owner;
798 //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
799 if(SAME_TEAM(wp_owner, e)) { return false; }
800 if(!IS_PLAYER(e)) { return false; }
805 void ctf_CheckStalemate()
808 int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
811 entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
813 // build list of stale flags
814 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
816 if(autocvar_g_ctf_stalemate)
817 if(tmp_entity.ctf_status != FLAG_BASE)
818 if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
820 tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
821 ctf_staleflaglist = tmp_entity;
823 switch(tmp_entity.team)
825 case NUM_TEAM_1: ++stale_red_flags; break;
826 case NUM_TEAM_2: ++stale_blue_flags; break;
827 case NUM_TEAM_3: ++stale_yellow_flags; break;
828 case NUM_TEAM_4: ++stale_pink_flags; break;
829 default: ++stale_neutral_flags; break;
835 stale_flags = (stale_neutral_flags >= 1);
837 stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
839 if(ctf_oneflag && stale_flags == 1)
840 ctf_stalemate = true;
841 else if(stale_flags >= 2)
842 ctf_stalemate = true;
843 else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
844 { ctf_stalemate = false; wpforenemy_announced = false; }
845 else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
846 { ctf_stalemate = false; wpforenemy_announced = false; }
848 // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
851 for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
853 if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
855 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);
856 wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
857 setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
861 if (!wpforenemy_announced)
863 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)); });
865 wpforenemy_announced = true;
870 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
872 if(ITEM_DAMAGE_NEEDKILL(deathtype))
874 if(autocvar_g_ctf_flag_return_damage_delay)
875 this.ctf_flagdamaged_byworld = true;
879 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
883 if(autocvar_g_ctf_flag_return_damage)
885 // reduce health and check if it should be returned
886 this.health = this.health - damage;
887 ctf_CheckFlagReturn(this, RETURN_DAMAGE);
892 void ctf_FlagThink(entity this)
897 this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
900 if(this == ctf_worldflaglist) // only for the first flag
901 FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
904 if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
905 LOG_TRACE("wtf the flag got squashed?");
906 tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
907 if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
908 setsize(this, this.m_mins, this.m_maxs);
912 switch(this.ctf_status)
916 if(autocvar_g_ctf_dropped_capture_radius)
918 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
919 if(tmp_entity.ctf_status == FLAG_DROPPED)
920 if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
921 if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
922 ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
929 this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
931 if(autocvar_g_ctf_flag_dropped_floatinwater)
933 vector midpoint = ((this.absmin + this.absmax) * 0.5);
934 if(pointcontents(midpoint) == CONTENT_WATER)
936 this.velocity = this.velocity * 0.5;
938 if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
939 { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
941 { set_movetype(this, MOVETYPE_FLY); }
943 else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
945 if(autocvar_g_ctf_flag_return_dropped)
947 if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
950 ctf_CheckFlagReturn(this, RETURN_DROPPED);
954 if(this.ctf_flagdamaged_byworld)
956 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
957 ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
960 else if(autocvar_g_ctf_flag_return_time)
962 this.health -= ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
963 ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
971 if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
974 ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
976 CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
977 ImpulseCommands(this.owner);
979 if(autocvar_g_ctf_stalemate)
981 if(time >= wpforenemy_nextthink)
983 ctf_CheckStalemate();
984 wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
987 if(CTF_SAMETEAM(this, this.owner) && this.team)
989 if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
990 ctf_Handle_Throw(this.owner, NULL, DROP_THROW);
991 else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
992 ctf_Handle_Return(this, this.owner);
999 vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1000 targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1001 WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1003 if((this.pass_target == NULL)
1004 || (IS_DEAD(this.pass_target))
1005 || (this.pass_target.flagcarried)
1006 || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1007 || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1008 || (time > this.ctf_droptime + autocvar_g_ctf_pass_timelimit))
1010 // give up, pass failed
1011 ctf_Handle_Drop(this, NULL, DROP_PASS);
1015 // still a viable target, go for it
1016 ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1021 default: // this should never happen
1023 LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1029 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1032 if(game_stopped) return;
1033 if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
1035 bool is_not_monster = (!IS_MONSTER(toucher));
1037 // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1038 if(ITEM_TOUCH_NEEDKILL())
1040 if(!autocvar_g_ctf_flag_return_damage_delay)
1043 ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
1045 if(!flag.ctf_flagdamaged_byworld) { return; }
1048 // special touch behaviors
1049 if(STAT(FROZEN, toucher)) { return; }
1050 else if(IS_VEHICLE(toucher))
1052 if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1053 toucher = toucher.owner; // the player is actually the vehicle owner, not other
1055 return; // do nothing
1057 else if(IS_MONSTER(toucher))
1059 if(!autocvar_g_ctf_allow_monster_touch)
1060 return; // do nothing
1062 else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1064 if(time > flag.wait) // if we haven't in a while, play a sound/effect
1066 Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1067 _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1068 flag.wait = time + FLAG_TOUCHRATE;
1072 else if(IS_DEAD(toucher)) { return; }
1074 switch(flag.ctf_status)
1080 if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1081 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1082 else if(!flag.team && (!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 neutral flag
1085 else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1086 ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1087 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)
1089 ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1090 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1092 else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1093 ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1099 if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1100 ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1101 else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1102 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1108 LOG_TRACE("Someone touched a flag even though it was being carried?");
1114 if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1116 if(DIFF_TEAM(toucher, flag.pass_sender))
1118 if(ctf_Immediate_Return_Allowed(flag, toucher))
1119 ctf_Handle_Return(flag, toucher);
1120 else if(is_not_monster && (!toucher.flagcarried))
1121 ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1123 else if(!toucher.flagcarried)
1124 ctf_Handle_Retrieve(flag, toucher);
1131 .float last_respawn;
1132 void ctf_RespawnFlag(entity flag)
1134 // check for flag respawn being called twice in a row
1135 if(flag.last_respawn > time - 0.5)
1136 { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1138 flag.last_respawn = time;
1140 // reset the player (if there is one)
1141 if((flag.owner) && (flag.owner.flagcarried == flag))
1143 WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1144 WaypointSprite_Kill(flag.owner.wps_flagreturn);
1145 WaypointSprite_Kill(flag.wps_flagcarrier);
1147 flag.owner.flagcarried = NULL;
1148 GameRules_scoring_vip(flag.owner, false);
1150 if(flag.speedrunning)
1151 ctf_FakeTimeLimit(flag.owner, -1);
1154 if((flag.owner) && (flag.owner.vehicle))
1155 flag.scale = FLAG_SCALE;
1157 if(flag.ctf_status == FLAG_DROPPED)
1158 { WaypointSprite_Kill(flag.wps_flagdropped); }
1161 setattachment(flag, NULL, "");
1162 setorigin(flag, flag.ctf_spawnorigin);
1164 set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
1165 flag.takedamage = DAMAGE_NO;
1166 flag.health = flag.max_flag_health;
1167 flag.solid = SOLID_TRIGGER;
1168 flag.velocity = '0 0 0';
1169 flag.angles = flag.mangle;
1170 flag.flags = FL_ITEM | FL_NOTARGET;
1172 flag.ctf_status = FLAG_BASE;
1174 flag.pass_distance = 0;
1175 flag.pass_sender = NULL;
1176 flag.pass_target = NULL;
1177 flag.ctf_dropper = NULL;
1178 flag.ctf_pickuptime = 0;
1179 flag.ctf_droptime = 0;
1180 flag.ctf_flagdamaged_byworld = false;
1181 navigation_dynamicgoal_unset(flag);
1183 ctf_CheckStalemate();
1186 void ctf_Reset(entity this)
1188 if(this.owner && IS_PLAYER(this.owner))
1189 ctf_Handle_Throw(this.owner, NULL, DROP_RESET);
1192 ctf_RespawnFlag(this);
1195 bool ctf_FlagBase_Customize(entity this, entity client)
1197 entity e = WaypointSprite_getviewentity(client);
1198 entity wp_owner = this.owner;
1199 entity flag = e.flagcarried;
1200 if(flag && CTF_SAMETEAM(e, flag))
1202 if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
1207 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1210 waypoint_spawnforitem_force(this, this.origin);
1211 navigation_dynamicgoal_init(this, true);
1217 case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1218 case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1219 case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1220 case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1221 default: basename = WP_FlagBaseNeutral; break;
1224 entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1225 wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1226 WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1227 setcefc(wp, ctf_FlagBase_Customize);
1229 // captureshield setup
1230 ctf_CaptureShield_Spawn(this);
1235 void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1238 flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1239 ctf_worldflaglist = flag;
1241 setattachment(flag, NULL, "");
1243 flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
1244 flag.team = teamnumber;
1245 flag.classname = "item_flag_team";
1246 flag.target = "###item###"; // wut?
1247 flag.flags = FL_ITEM | FL_NOTARGET;
1248 IL_PUSH(g_items, flag);
1249 flag.solid = SOLID_TRIGGER;
1250 flag.takedamage = DAMAGE_NO;
1251 flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1252 flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
1253 flag.health = flag.max_flag_health;
1254 flag.event_damage = ctf_FlagDamage;
1255 flag.pushable = true;
1256 flag.teleportable = TELEPORT_NORMAL;
1257 flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1258 flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1259 flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1260 if(flag.damagedbycontents)
1261 IL_PUSH(g_damagedbycontents, flag);
1262 flag.velocity = '0 0 0';
1263 flag.mangle = flag.angles;
1264 flag.reset = ctf_Reset;
1265 settouch(flag, ctf_FlagTouch);
1266 setthink(flag, ctf_FlagThink);
1267 flag.nextthink = time + FLAG_THINKRATE;
1268 flag.ctf_status = FLAG_BASE;
1270 // crudely force them all to 0
1271 if(autocvar_g_ctf_score_ignore_fields)
1272 flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1274 string teamname = Static_Team_ColorName_Lower(teamnumber);
1276 if(!flag.scale) { flag.scale = FLAG_SCALE; }
1277 if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1278 if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1279 if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
1280 if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
1281 if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
1285 if(flag.s == "") flag.s = b; \
1286 precache_sound(flag.s);
1288 X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnumber))))
1289 X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnumber))))
1290 X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnumber))))
1291 X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnumber))))
1292 X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1293 X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1294 X(snd_flag_pass, strzone(SND(CTF_PASS)))
1298 precache_model(flag.model);
1301 _setmodel(flag, flag.model); // precision set below
1302 setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
1303 flag.m_mins = flag.mins; // store these for squash checks
1304 flag.m_maxs = flag.maxs;
1305 setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1307 if(autocvar_g_ctf_flag_glowtrails)
1311 case NUM_TEAM_1: flag.glow_color = 251; break;
1312 case NUM_TEAM_2: flag.glow_color = 210; break;
1313 case NUM_TEAM_3: flag.glow_color = 110; break;
1314 case NUM_TEAM_4: flag.glow_color = 145; break;
1315 default: flag.glow_color = 254; break;
1317 flag.glow_size = 25;
1318 flag.glow_trail = 1;
1321 flag.effects |= EF_LOWPRECISION;
1322 if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1323 if(autocvar_g_ctf_dynamiclights)
1327 case NUM_TEAM_1: flag.effects |= EF_RED; break;
1328 case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1329 case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1330 case NUM_TEAM_4: flag.effects |= EF_RED; break;
1331 default: flag.effects |= EF_DIMLIGHT; break;
1336 if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1338 flag.dropped_origin = flag.origin;
1339 flag.noalign = true;
1340 set_movetype(flag, MOVETYPE_NONE);
1342 else // drop to floor, automatically find a platform and set that as spawn origin
1344 flag.noalign = false;
1346 set_movetype(flag, MOVETYPE_NONE);
1349 InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1357 // NOTE: LEGACY CODE, needs to be re-written!
1359 void havocbot_ctf_calculate_middlepoint()
1363 vector fo = '0 0 0';
1366 f = ctf_worldflaglist;
1371 f = f.ctf_worldflagnext;
1377 havocbot_middlepoint = s / n;
1378 havocbot_middlepoint_radius = vlen(fo - havocbot_middlepoint);
1380 havocbot_symmetry_axis_m = 0;
1381 havocbot_symmetry_axis_q = 0;
1384 // for symmetrical editing of waypoints
1385 entity f1 = ctf_worldflaglist;
1386 entity f2 = f1.ctf_worldflagnext;
1387 float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
1388 float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
1389 havocbot_symmetry_axis_m = m;
1390 havocbot_symmetry_axis_q = q;
1392 havocbot_symmetry_origin_order = n;
1396 entity havocbot_ctf_find_flag(entity bot)
1399 f = ctf_worldflaglist;
1402 if (CTF_SAMETEAM(bot, f))
1404 f = f.ctf_worldflagnext;
1409 entity havocbot_ctf_find_enemy_flag(entity bot)
1412 f = ctf_worldflaglist;
1417 if(CTF_DIFFTEAM(bot, f))
1424 else if(!bot.flagcarried)
1428 else if (CTF_DIFFTEAM(bot, f))
1430 f = f.ctf_worldflagnext;
1435 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1442 FOREACH_CLIENT(IS_PLAYER(it), {
1443 if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1446 if(vdist(it.origin - org, <, tc_radius))
1455 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1458 head = ctf_worldflaglist;
1461 if (CTF_SAMETEAM(this, head))
1463 head = head.ctf_worldflagnext;
1466 navigation_routerating(this, head, ratingscale, 10000);
1470 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1473 head = ctf_worldflaglist;
1476 if (CTF_SAMETEAM(this, head))
1478 if (this.flagcarried)
1479 if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
1481 head = head.ctf_worldflagnext; // skip base if it has a different group
1486 head = head.ctf_worldflagnext;
1491 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1494 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1497 head = ctf_worldflaglist;
1502 if(CTF_DIFFTEAM(this, head))
1506 if(this.flagcarried)
1509 else if(!this.flagcarried)
1513 else if(CTF_DIFFTEAM(this, head))
1515 head = head.ctf_worldflagnext;
1518 navigation_routerating(this, head, ratingscale, 10000);
1521 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1523 if (!bot_waypoints_for_items)
1525 havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1531 head = havocbot_ctf_find_enemy_flag(this);
1536 navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1539 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1543 mf = havocbot_ctf_find_flag(this);
1545 if(mf.ctf_status == FLAG_BASE)
1549 navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1552 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1555 head = ctf_worldflaglist;
1558 // flag is out in the field
1559 if(head.ctf_status != FLAG_BASE)
1560 if(head.tag_entity==NULL) // dropped
1564 if(vdist(org - head.origin, <, df_radius))
1565 navigation_routerating(this, head, ratingscale, 10000);
1568 navigation_routerating(this, head, ratingscale, 10000);
1571 head = head.ctf_worldflagnext;
1575 void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
1577 IL_EACH(g_items, it.bot_pickup,
1579 // gather health and armor only
1581 if (it.health || it.armorvalue)
1582 if (vdist(it.origin - org, <, sradius))
1584 // get the value of the item
1585 float t = it.bot_pickupevalfunc(this, it) * 0.0001;
1587 navigation_routerating(this, it, t * ratingscale, 500);
1592 void havocbot_ctf_reset_role(entity this)
1594 float cdefense, cmiddle, coffense;
1602 if (this.flagcarried)
1604 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1608 mf = havocbot_ctf_find_flag(this);
1609 ef = havocbot_ctf_find_enemy_flag(this);
1611 // Retrieve stolen flag
1612 if(mf.ctf_status!=FLAG_BASE)
1614 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1618 // If enemy flag is taken go to the middle to intercept pursuers
1619 if(ef.ctf_status!=FLAG_BASE)
1621 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1625 // if there is only me on the team switch to offense
1627 FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
1631 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1635 // Evaluate best position to take
1636 // Count mates on middle position
1637 cmiddle = havocbot_ctf_teamcount(this, havocbot_middlepoint, havocbot_middlepoint_radius * 0.5);
1639 // Count mates on defense position
1640 cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
1642 // Count mates on offense position
1643 coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
1645 if(cdefense<=coffense)
1646 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1647 else if(coffense<=cmiddle)
1648 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
1650 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1653 void havocbot_role_ctf_carrier(entity this)
1657 havocbot_ctf_reset_role(this);
1661 if (this.flagcarried == NULL)
1663 havocbot_ctf_reset_role(this);
1667 if (navigation_goalrating_timeout(this))
1669 navigation_goalrating_start(this);
1672 havocbot_goalrating_ctf_enemybase(this, 50000);
1674 havocbot_goalrating_ctf_ourbase(this, 50000);
1677 havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
1679 navigation_goalrating_end(this);
1681 navigation_goalrating_timeout_set(this);
1683 entity head = ctf_worldflaglist;
1686 if (this.goalentity == head.bot_basewaypoint)
1688 this.goalentity_lock_timeout = time + 5;
1691 head = head.ctf_worldflagnext;
1694 if (this.goalentity)
1695 this.havocbot_cantfindflag = time + 10;
1696 else if (time > this.havocbot_cantfindflag)
1698 // Can't navigate to my own base, suicide!
1699 // TODO: drop it and wander around
1700 Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
1706 void havocbot_role_ctf_escort(entity this)
1712 havocbot_ctf_reset_role(this);
1716 if (this.flagcarried)
1718 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1722 // If enemy flag is back on the base switch to previous role
1723 ef = havocbot_ctf_find_enemy_flag(this);
1724 if(ef.ctf_status==FLAG_BASE)
1726 this.havocbot_role = this.havocbot_previous_role;
1727 this.havocbot_role_timeout = 0;
1731 // If the flag carrier reached the base switch to defense
1732 mf = havocbot_ctf_find_flag(this);
1733 if(mf.ctf_status!=FLAG_BASE)
1734 if(vdist(ef.origin - mf.dropped_origin, <, 300))
1736 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
1740 // Set the role timeout if necessary
1741 if (!this.havocbot_role_timeout)
1743 this.havocbot_role_timeout = time + random() * 30 + 60;
1746 // If nothing happened just switch to previous role
1747 if (time > this.havocbot_role_timeout)
1749 this.havocbot_role = this.havocbot_previous_role;
1750 this.havocbot_role_timeout = 0;
1754 // Chase the flag carrier
1755 if (navigation_goalrating_timeout(this))
1757 navigation_goalrating_start(this);
1759 havocbot_goalrating_ctf_enemyflag(this, 30000);
1760 havocbot_goalrating_ctf_ourstolenflag(this, 40000);
1761 havocbot_goalrating_items(this, 10000, this.origin, 10000);
1763 navigation_goalrating_end(this);
1765 navigation_goalrating_timeout_set(this);
1769 void havocbot_role_ctf_offense(entity this)
1776 havocbot_ctf_reset_role(this);
1780 if (this.flagcarried)
1782 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1787 mf = havocbot_ctf_find_flag(this);
1788 ef = havocbot_ctf_find_enemy_flag(this);
1791 if(mf.ctf_status!=FLAG_BASE)
1794 pos = mf.tag_entity.origin;
1798 // Try to get it if closer than the enemy base
1799 if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1801 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1806 // Escort flag carrier
1807 if(ef.ctf_status!=FLAG_BASE)
1810 pos = ef.tag_entity.origin;
1814 if(vdist(pos - mf.dropped_origin, >, 700))
1816 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_ESCORT);
1821 // About to fail, switch to middlefield
1824 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
1828 // Set the role timeout if necessary
1829 if (!this.havocbot_role_timeout)
1830 this.havocbot_role_timeout = time + 120;
1832 if (time > this.havocbot_role_timeout)
1834 havocbot_ctf_reset_role(this);
1838 if (navigation_goalrating_timeout(this))
1840 navigation_goalrating_start(this);
1842 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1843 havocbot_goalrating_ctf_enemybase(this, 20000);
1844 havocbot_goalrating_items(this, 5000, this.origin, 1000);
1845 havocbot_goalrating_items(this, 1000, this.origin, 10000);
1847 navigation_goalrating_end(this);
1849 navigation_goalrating_timeout_set(this);
1853 // Retriever (temporary role):
1854 void havocbot_role_ctf_retriever(entity this)
1860 havocbot_ctf_reset_role(this);
1864 if (this.flagcarried)
1866 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1870 // If flag is back on the base switch to previous role
1871 mf = havocbot_ctf_find_flag(this);
1872 if(mf.ctf_status==FLAG_BASE)
1874 if (mf.enemy == this) // did this bot return the flag?
1875 navigation_goalrating_timeout_force(this);
1876 havocbot_ctf_reset_role(this);
1880 if (!this.havocbot_role_timeout)
1881 this.havocbot_role_timeout = time + 20;
1883 if (time > this.havocbot_role_timeout)
1885 havocbot_ctf_reset_role(this);
1889 if (navigation_goalrating_timeout(this))
1894 navigation_goalrating_start(this);
1896 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1897 havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
1898 havocbot_goalrating_ctf_enemybase(this, 30000);
1899 havocbot_goalrating_items(this, 500, this.origin, rt_radius);
1901 navigation_goalrating_end(this);
1903 navigation_goalrating_timeout_set(this);
1907 void havocbot_role_ctf_middle(entity this)
1913 havocbot_ctf_reset_role(this);
1917 if (this.flagcarried)
1919 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1923 mf = havocbot_ctf_find_flag(this);
1924 if(mf.ctf_status!=FLAG_BASE)
1926 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1930 if (!this.havocbot_role_timeout)
1931 this.havocbot_role_timeout = time + 10;
1933 if (time > this.havocbot_role_timeout)
1935 havocbot_ctf_reset_role(this);
1939 if (navigation_goalrating_timeout(this))
1943 org = havocbot_middlepoint;
1944 org.z = this.origin.z;
1946 navigation_goalrating_start(this);
1948 havocbot_goalrating_ctf_ourstolenflag(this, 50000);
1949 havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
1950 havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
1951 havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
1952 havocbot_goalrating_items(this, 2500, this.origin, 10000);
1953 havocbot_goalrating_ctf_enemybase(this, 2500);
1955 navigation_goalrating_end(this);
1957 navigation_goalrating_timeout_set(this);
1961 void havocbot_role_ctf_defense(entity this)
1967 havocbot_ctf_reset_role(this);
1971 if (this.flagcarried)
1973 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_CARRIER);
1977 // If own flag was captured
1978 mf = havocbot_ctf_find_flag(this);
1979 if(mf.ctf_status!=FLAG_BASE)
1981 havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_RETRIEVER);
1985 if (!this.havocbot_role_timeout)
1986 this.havocbot_role_timeout = time + 30;
1988 if (time > this.havocbot_role_timeout)
1990 havocbot_ctf_reset_role(this);
1993 if (navigation_goalrating_timeout(this))
1995 vector org = mf.dropped_origin;
1997 navigation_goalrating_start(this);
1999 // if enemies are closer to our base, go there
2000 entity closestplayer = NULL;
2001 float distance, bestdistance = 10000;
2002 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
2003 distance = vlen(org - it.origin);
2004 if(distance<bestdistance)
2007 bestdistance = distance;
2012 if(DIFF_TEAM(closestplayer, this))
2013 if(vdist(org - this.origin, >, 1000))
2014 if(checkpvs(this.origin,closestplayer)||random()<0.5)
2015 havocbot_goalrating_ctf_ourbase(this, 30000);
2017 havocbot_goalrating_ctf_ourstolenflag(this, 20000);
2018 havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
2019 havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
2020 havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
2021 havocbot_goalrating_items(this, 5000, this.origin, 10000);
2023 navigation_goalrating_end(this);
2025 navigation_goalrating_timeout_set(this);
2029 void havocbot_role_ctf_setrole(entity bot, int role)
2031 string s = "(null)";
2034 case HAVOCBOT_CTF_ROLE_CARRIER:
2036 bot.havocbot_role = havocbot_role_ctf_carrier;
2037 bot.havocbot_role_timeout = 0;
2038 bot.havocbot_cantfindflag = time + 10;
2039 if (bot.havocbot_previous_role != bot.havocbot_role)
2040 navigation_goalrating_timeout_force(bot);
2042 case HAVOCBOT_CTF_ROLE_DEFENSE:
2044 bot.havocbot_role = havocbot_role_ctf_defense;
2045 bot.havocbot_role_timeout = 0;
2047 case HAVOCBOT_CTF_ROLE_MIDDLE:
2049 bot.havocbot_role = havocbot_role_ctf_middle;
2050 bot.havocbot_role_timeout = 0;
2052 case HAVOCBOT_CTF_ROLE_OFFENSE:
2054 bot.havocbot_role = havocbot_role_ctf_offense;
2055 bot.havocbot_role_timeout = 0;
2057 case HAVOCBOT_CTF_ROLE_RETRIEVER:
2059 bot.havocbot_previous_role = bot.havocbot_role;
2060 bot.havocbot_role = havocbot_role_ctf_retriever;
2061 bot.havocbot_role_timeout = time + 10;
2062 if (bot.havocbot_previous_role != bot.havocbot_role)
2063 navigation_goalrating_timeout_expire(bot, 2);
2065 case HAVOCBOT_CTF_ROLE_ESCORT:
2067 bot.havocbot_previous_role = bot.havocbot_role;
2068 bot.havocbot_role = havocbot_role_ctf_escort;
2069 bot.havocbot_role_timeout = time + 30;
2070 if (bot.havocbot_previous_role != bot.havocbot_role)
2071 navigation_goalrating_timeout_expire(bot, 2);
2074 LOG_TRACE(bot.netname, " switched to ", s);
2082 MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
2084 entity player = M_ARGV(0, entity);
2086 int t = 0, t2 = 0, t3 = 0;
2087 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)
2089 // initially clear items so they can be set as necessary later.
2090 STAT(CTF_FLAGSTATUS, player) &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2091 | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2092 | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2093 | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2094 | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2095 | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2097 // scan through all the flags and notify the client about them
2098 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2100 if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2101 if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2102 if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2103 if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2104 if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
2106 switch(flag.ctf_status)
2111 if((flag.owner == player) || (flag.pass_sender == player))
2112 STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
2114 STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
2119 STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
2125 // item for stopping players from capturing the flag too often
2126 if(player.ctf_captureshielded)
2127 STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
2130 STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
2132 // update the health of the flag carrier waypointsprite
2133 if(player.wps_flagcarrier)
2134 WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(player.health, player.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
2137 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
2139 entity frag_attacker = M_ARGV(1, entity);
2140 entity frag_target = M_ARGV(2, entity);
2141 float frag_damage = M_ARGV(4, float);
2142 vector frag_force = M_ARGV(6, vector);
2144 if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2146 if(frag_target == frag_attacker) // damage done to yourself
2148 frag_damage *= autocvar_g_ctf_flagcarrier_selfdamagefactor;
2149 frag_force *= autocvar_g_ctf_flagcarrier_selfforcefactor;
2151 else // damage done to everyone else
2153 frag_damage *= autocvar_g_ctf_flagcarrier_damagefactor;
2154 frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
2157 M_ARGV(4, float) = frag_damage;
2158 M_ARGV(6, vector) = frag_force;
2160 else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2162 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)))
2163 if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2165 frag_target.wps_helpme_time = time;
2166 WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2168 // todo: add notification for when flag carrier needs help?
2172 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2174 entity frag_attacker = M_ARGV(1, entity);
2175 entity frag_target = M_ARGV(2, entity);
2177 if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2179 GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2180 GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
2183 if(frag_target.flagcarried)
2185 entity tmp_entity = frag_target.flagcarried;
2186 ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2187 tmp_entity.ctf_dropper = NULL;
2191 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2193 M_ARGV(2, float) = 0; // frag score
2194 return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2197 void ctf_RemovePlayer(entity player)
2199 if(player.flagcarried)
2200 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2202 for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2204 if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2205 if(flag.pass_target == player) { flag.pass_target = NULL; }
2206 if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2210 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2212 entity player = M_ARGV(0, entity);
2214 ctf_RemovePlayer(player);
2217 MUTATOR_HOOKFUNCTION(ctf, ClientDisconnect)
2219 entity player = M_ARGV(0, entity);
2221 ctf_RemovePlayer(player);
2224 MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
2226 if(!autocvar_g_ctf_leaderboard)
2229 entity player = M_ARGV(0, entity);
2231 if(IS_REAL_CLIENT(player))
2233 int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
2234 race_send_rankings_cnt(MSG_ONE);
2235 for (int i = 1; i <= m; ++i)
2237 race_SendRankings(i, 0, 0, MSG_ONE);
2242 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
2244 if(!autocvar_g_ctf_leaderboard)
2247 entity player = M_ARGV(0, entity);
2249 if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
2251 if (!player.stored_netname)
2252 player.stored_netname = strzone(uid2name(player.crypto_idfp));
2253 if(player.stored_netname != player.netname)
2255 db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
2256 strcpy(player.stored_netname, player.netname);
2261 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2263 entity player = M_ARGV(0, entity);
2265 if(player.flagcarried)
2266 if(!autocvar_g_ctf_portalteleport)
2267 { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2270 MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
2272 if(MUTATOR_RETURNVALUE || game_stopped) return;
2274 entity player = M_ARGV(0, entity);
2276 if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2278 // pass the flag to a team mate
2279 if(autocvar_g_ctf_pass)
2281 entity head, closest_target = NULL;
2282 head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2284 while(head) // find the closest acceptable target to pass to
2286 if(IS_PLAYER(head) && !IS_DEAD(head))
2287 if(head != player && SAME_TEAM(head, player))
2288 if(!head.speedrunning && !head.vehicle)
2290 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
2291 vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2292 vector passer_center = CENTER_OR_VIEWOFS(player);
2294 if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2296 if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2298 if(IS_BOT_CLIENT(head))
2300 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2301 ctf_Handle_Throw(head, player, DROP_PASS);
2305 Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2306 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2308 player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2311 else if(player.flagcarried && !head.flagcarried)
2315 vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2316 if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2317 { closest_target = head; }
2319 else { closest_target = head; }
2326 if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2329 // throw the flag in front of you
2330 if(autocvar_g_ctf_throw && player.flagcarried)
2332 if(player.throw_count == -1)
2334 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2336 player.throw_prevtime = time;
2337 player.throw_count = 1;
2338 ctf_Handle_Throw(player, NULL, DROP_THROW);
2343 Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2349 if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2350 else { player.throw_count += 1; }
2351 if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2353 player.throw_prevtime = time;
2354 ctf_Handle_Throw(player, NULL, DROP_THROW);
2361 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2363 entity player = M_ARGV(0, entity);
2365 if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2367 player.wps_helpme_time = time;
2368 WaypointSprite_HelpMePing(player.wps_flagcarrier);
2370 else // create a normal help me waypointsprite
2372 WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2373 WaypointSprite_Ping(player.wps_helpme);
2379 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2381 entity player = M_ARGV(0, entity);
2382 entity veh = M_ARGV(1, entity);
2384 if(player.flagcarried)
2386 if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
2388 ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2392 player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2393 setattachment(player.flagcarried, veh, "");
2394 setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2395 player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2396 //player.flagcarried.angles = '0 0 0';
2402 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2404 entity player = M_ARGV(0, entity);
2406 if(player.flagcarried)
2408 setattachment(player.flagcarried, player, "");
2409 setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2410 player.flagcarried.scale = FLAG_SCALE;
2411 player.flagcarried.angles = '0 0 0';
2412 player.flagcarried.nodrawtoclient = NULL;
2417 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2419 entity player = M_ARGV(0, entity);
2421 if(player.flagcarried)
2423 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2424 ctf_RespawnFlag(player.flagcarried);
2429 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2431 entity flag; // temporary entity for the search method
2433 for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2435 switch(flag.ctf_status)
2440 // lock the flag, game is over
2441 set_movetype(flag, MOVETYPE_NONE);
2442 flag.takedamage = DAMAGE_NO;
2443 flag.solid = SOLID_NOT;
2444 flag.nextthink = false; // stop thinking
2446 //dprint("stopping the ", flag.netname, " from moving.\n");
2454 // do nothing for these flags
2461 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2463 entity bot = M_ARGV(0, entity);
2465 havocbot_ctf_reset_role(bot);
2469 MUTATOR_HOOKFUNCTION(ctf, CheckAllowedTeams)
2471 //M_ARGV(0, float) = ctf_teams;
2472 M_ARGV(1, string) = "ctf_team";
2476 MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
2478 entity spectatee = M_ARGV(0, entity);
2479 entity client = M_ARGV(1, entity);
2481 STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
2484 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2486 int record_page = M_ARGV(0, int);
2487 string ret_string = M_ARGV(1, string);
2489 for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2491 if (MapInfo_Get_ByID(i))
2493 float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2499 string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2500 ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2504 M_ARGV(1, string) = ret_string;
2507 bool superspec_Spectate(entity this, entity targ); // TODO
2508 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2509 MUTATOR_HOOKFUNCTION(ctf, SV_ParseClientCommand)
2511 entity player = M_ARGV(0, entity);
2512 string cmd_name = M_ARGV(1, string);
2513 int cmd_argc = M_ARGV(2, int);
2515 if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2517 if(cmd_name == "followfc")
2529 case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2530 case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2531 case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2532 case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2536 FOREACH_CLIENT(IS_PLAYER(it), {
2537 if(it.flagcarried && (it.team == _team || _team == 0))
2540 if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2541 continue; // already spectating this fc, try another
2542 return superspec_Spectate(player, it);
2547 superspec_msg("", "", player, "No active flag carrier\n", 1);
2552 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2554 entity frag_target = M_ARGV(0, entity);
2556 if(frag_target.flagcarried)
2557 ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2565 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2566 CTF flag for team one (Red).
2568 "angle" Angle the flag will point (minus 90 degrees)...
2569 "model" model to use, note this needs red and blue as skins 0 and 1...
2570 "noise" sound played when flag is picked up...
2571 "noise1" sound played when flag is returned by a teammate...
2572 "noise2" sound played when flag is captured...
2573 "noise3" sound played when flag is lost in the field and respawns itself...
2574 "noise4" sound played when flag is dropped by a player...
2575 "noise5" sound played when flag touches the ground... */
2576 spawnfunc(item_flag_team1)
2578 if(!g_ctf) { delete(this); return; }
2580 ctf_FlagSetup(NUM_TEAM_1, this);
2583 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2584 CTF flag for team two (Blue).
2586 "angle" Angle the flag will point (minus 90 degrees)...
2587 "model" model to use, note this needs red and blue as skins 0 and 1...
2588 "noise" sound played when flag is picked up...
2589 "noise1" sound played when flag is returned by a teammate...
2590 "noise2" sound played when flag is captured...
2591 "noise3" sound played when flag is lost in the field and respawns itself...
2592 "noise4" sound played when flag is dropped by a player...
2593 "noise5" sound played when flag touches the ground... */
2594 spawnfunc(item_flag_team2)
2596 if(!g_ctf) { delete(this); return; }
2598 ctf_FlagSetup(NUM_TEAM_2, this);
2601 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2602 CTF flag for team three (Yellow).
2604 "angle" Angle the flag will point (minus 90 degrees)...
2605 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2606 "noise" sound played when flag is picked up...
2607 "noise1" sound played when flag is returned by a teammate...
2608 "noise2" sound played when flag is captured...
2609 "noise3" sound played when flag is lost in the field and respawns itself...
2610 "noise4" sound played when flag is dropped by a player...
2611 "noise5" sound played when flag touches the ground... */
2612 spawnfunc(item_flag_team3)
2614 if(!g_ctf) { delete(this); return; }
2616 ctf_FlagSetup(NUM_TEAM_3, this);
2619 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2620 CTF flag for team four (Pink).
2622 "angle" Angle the flag will point (minus 90 degrees)...
2623 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2624 "noise" sound played when flag is picked up...
2625 "noise1" sound played when flag is returned by a teammate...
2626 "noise2" sound played when flag is captured...
2627 "noise3" sound played when flag is lost in the field and respawns itself...
2628 "noise4" sound played when flag is dropped by a player...
2629 "noise5" sound played when flag touches the ground... */
2630 spawnfunc(item_flag_team4)
2632 if(!g_ctf) { delete(this); return; }
2634 ctf_FlagSetup(NUM_TEAM_4, this);
2637 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2640 "angle" Angle the flag will point (minus 90 degrees)...
2641 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2642 "noise" sound played when flag is picked up...
2643 "noise1" sound played when flag is returned by a teammate...
2644 "noise2" sound played when flag is captured...
2645 "noise3" sound played when flag is lost in the field and respawns itself...
2646 "noise4" sound played when flag is dropped by a player...
2647 "noise5" sound played when flag touches the ground... */
2648 spawnfunc(item_flag_neutral)
2650 if(!g_ctf) { delete(this); return; }
2651 if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2653 ctf_FlagSetup(0, this);
2656 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2657 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2658 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.
2660 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2661 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2664 if(!g_ctf) { delete(this); return; }
2666 this.classname = "ctf_team";
2667 this.team = this.cnt + 1;
2670 // compatibility for quake maps
2671 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2672 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2673 spawnfunc(info_player_team1);
2674 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2675 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2676 spawnfunc(info_player_team2);
2677 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2678 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2680 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2681 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2683 // compatibility for wop maps
2684 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2685 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2686 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2687 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2688 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2689 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2697 void ctf_ScoreRules(int teams)
2699 CheckAllowedTeams(NULL);
2700 GameRules_scoring(teams, SFL_SORT_PRIO_PRIMARY, 0, {
2701 field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2702 field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2703 field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2704 field(SP_CTF_PICKUPS, "pickups", 0);
2705 field(SP_CTF_FCKILLS, "fckills", 0);
2706 field(SP_CTF_RETURNS, "returns", 0);
2707 field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2711 // code from here on is just to support maps that don't have flag and team entities
2712 void ctf_SpawnTeam (string teamname, int teamcolor)
2714 entity this = new_pure(ctf_team);
2715 this.netname = teamname;
2716 this.cnt = teamcolor - 1;
2717 this.spawnfunc_checked = true;
2718 this.team = teamcolor;
2721 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2726 for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2728 //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2729 //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2731 switch(tmp_entity.team)
2733 case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2734 case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2735 case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2736 case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2738 if(tmp_entity.team == 0) { ctf_oneflag = true; }
2741 havocbot_ctf_calculate_middlepoint();
2743 if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2745 ctf_teams = 0; // so set the default red and blue teams
2746 BITSET_ASSIGN(ctf_teams, BIT(0));
2747 BITSET_ASSIGN(ctf_teams, BIT(1));
2750 //ctf_teams = bound(2, ctf_teams, 4);
2752 // if no teams are found, spawn defaults
2753 if(find(NULL, classname, "ctf_team") == NULL)
2755 LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2756 if(ctf_teams & BIT(0))
2757 ctf_SpawnTeam("Red", NUM_TEAM_1);
2758 if(ctf_teams & BIT(1))
2759 ctf_SpawnTeam("Blue", NUM_TEAM_2);
2760 if(ctf_teams & BIT(2))
2761 ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2762 if(ctf_teams & BIT(3))
2763 ctf_SpawnTeam("Pink", NUM_TEAM_4);
2766 ctf_ScoreRules(ctf_teams);
2769 void ctf_Initialize()
2771 ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2773 ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;
2774 ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
2775 ctf_captureshield_force = autocvar_g_ctf_shield_force;
2777 InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);