Xonotic
sv_ctf.qc
Go to the documentation of this file.
1 #include "sv_ctf.qh"
2 
3 #include <common/effects/all.qh>
7 #include <common/vehicles/all.qh>
8 #include <server/command/vote.qh>
9 #include <server/client.qh>
10 #include <server/gamelog.qh>
11 #include <server/intermission.qh>
12 #include <server/damage.qh>
13 #include <server/world.qh>
14 #include <server/items/items.qh>
15 #include <server/race.qh>
16 #include <server/teamplay.qh>
17 
18 #include <lib/warpzone/common.qh>
19 
70 //float autocvar_g_ctf_flagcarrier_waypointforenemy_spotting;
92 
93 void ctf_FakeTimeLimit(entity e, float t)
94 {
95  msg_entity = e;
96  WriteByte(MSG_ONE, 3); // svc_updatestat
97  WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
98  if(t < 0)
99  WriteCoord(MSG_ONE, autocvar_timelimit);
100  else
101  WriteCoord(MSG_ONE, (t + 1) / 60);
102 }
103 
104 void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
105 {
107  GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != NULL) ? ftos(actor.playerid) : "")));
108  //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != NULL) ? (strcat(":", ftos(actor.playerid))) : "")));
109 }
110 
111 void ctf_CaptureRecord(entity flag, entity player)
112 {
113  float cap_record = ctf_captimerecord;
114  float cap_time = (time - flag.ctf_pickuptime);
115  string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
116 
117  // notify about shit
118  if(ctf_oneflag)
119  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname);
120  else if(!ctf_captimerecord)
121  Send_Notification(NOTIF_ALL, NULL, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_CAPTURE_TIME), player.netname, TIME_ENCODE(cap_time));
122  else if(cap_time < cap_record)
123  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));
124  else
125  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));
126 
127  // write that shit in the database
128  if(!ctf_oneflag) // but not in 1-flag mode
129  if((!ctf_captimerecord) || (cap_time < cap_record))
130  {
131  ctf_captimerecord = cap_time;
132  db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
133  db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
134  write_recordmarker(player, flag.ctf_pickuptime, cap_time);
135  }
136 
137  if(autocvar_g_ctf_leaderboard && !ctf_oneflag)
138  race_setTime(GetMapname(), TIME_ENCODE(cap_time), player.crypto_idfp, player.netname, player, false);
139 }
140 
142 {
143  int num_perteam = 0;
144  FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(toucher, it), { ++num_perteam; });
145 
146  // automatically return if there's only 1 player on the team
147  return ((autocvar_g_ctf_flag_return || num_perteam <= 1 || (autocvar_g_ctf_flag_return_carrying && toucher.flagcarried))
148  && flag.team);
149 }
150 
152 {
153  // only to the carrier
154  return boolean(client == this.owner);
155 }
156 
158 {
159  WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
160  WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, 2 * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
161  WaypointSprite_UpdateHealth(player.wps_flagcarrier, healtharmor_maxdamage(GetResource(player, RES_HEALTH), GetResource(player, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
162  WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
163 
164  if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
165  {
166  if(!player.wps_enemyflagcarrier)
167  {
168  entity wp = WaypointSprite_Spawn(((ctf_oneflag) ? WP_FlagCarrier : WP_FlagCarrierEnemy), 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, 0, player, wps_enemyflagcarrier, true, RADARICON_FLAG);
169  wp.colormod = WPCOLOR_ENEMYFC(player.team);
170  setcefc(wp, ctf_Stalemate_Customize);
171 
172  if(IS_REAL_CLIENT(player) && !ctf_stalemate)
173  Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_VISIBLE);
174  }
175 
176  if(!player.wps_flagreturn)
177  {
178  entity owp = WaypointSprite_SpawnFixed(WP_FlagReturn, player.flagcarried.ctf_spawnorigin + FLAG_WAYPOINT_OFFSET, player, wps_flagreturn, RADARICON_FLAG);
179  owp.colormod = '0 0.8 0.8';
180  //WaypointSprite_UpdateTeamRadar(player.wps_flagreturn, RADARICON_FLAG, ((player.team) ? colormapPaletteColor(player.team - 1, false) : '1 1 1'));
181  setcefc(owp, ctf_Return_Customize);
182  }
183  }
184 }
185 
186 void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
187 {
188  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
189  float initial_height = min(autocvar_g_ctf_pass_arc_max, (flag.pass_distance * tanh(autocvar_g_ctf_pass_arc)));
190  float current_height = (initial_height * min(1, (current_distance / flag.pass_distance)));
191  //print("current_height = ", ftos(current_height), ", initial_height = ", ftos(initial_height), ".\n");
192 
193  vector targpos;
194  if(current_height) // make sure we can actually do this arcing path
195  {
196  targpos = (to + ('0 0 1' * current_height));
197  WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
198  if(trace_fraction < 1)
199  {
200  //print("normal arc line failed, trying to find new pos...");
201  WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
202  targpos = (trace_endpos + eZ * FLAG_PASS_ARC_OFFSET_Z);
203  WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
204  if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
205  /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
206  }
207  }
208  else { targpos = to; }
209 
210  //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
211 
212  vector desired_direction = normalize(targpos - from);
213  if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
214  else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
215 }
216 
217 bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
218 {
220  {
221  // directional tracing only
222  float spreadlimit;
223  makevectors(passer_angle);
224 
225  // find the closest point on the enemy to the center of the attack
226  float h; // hypotenuse, which is the distance between attacker to head
227  float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
228 
229  h = vlen(head_center - passer_center);
230  a = h * (normalize(head_center - passer_center) * v_forward);
231 
232  vector nearest_on_line = (passer_center + a * v_forward);
233  float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
234 
235  spreadlimit = (autocvar_g_ctf_pass_radius ? min(1, (vlen(passer_center - nearest_on_line) / autocvar_g_ctf_pass_radius)) : 1);
236  spreadlimit = (autocvar_g_ctf_pass_directional_min * (1 - spreadlimit) + autocvar_g_ctf_pass_directional_max * spreadlimit);
237 
238  if(spreadlimit && (distance_from_line <= spreadlimit) && ((vlen(normalize(head_center - passer_center) - v_forward) * RAD2DEG) <= 90))
239  { return true; }
240  else
241  { return false; }
242  }
243  else { return true; }
244 }
245 
246 
247 // =======================
248 // CaptureShield Functions
249 // =======================
250 
252 {
253  int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
254  int players_worseeq, players_total;
255 
257  return false;
258 
259  s = GameRules_scoring_add(p, CTF_CAPS, 0);
260  s2 = GameRules_scoring_add(p, CTF_PICKUPS, 0);
261  s3 = GameRules_scoring_add(p, CTF_RETURNS, 0);
262  s4 = GameRules_scoring_add(p, CTF_FCKILLS, 0);
263 
264  sr = ((s - s2) + (s3 + s4));
265 
267  return false;
268 
269  players_total = players_worseeq = 0;
271  if(DIFF_TEAM(it, p))
272  continue;
273  se = GameRules_scoring_add(it, CTF_CAPS, 0);
274  se2 = GameRules_scoring_add(it, CTF_PICKUPS, 0);
275  se3 = GameRules_scoring_add(it, CTF_RETURNS, 0);
276  se4 = GameRules_scoring_add(it, CTF_FCKILLS, 0);
277 
278  ser = ((se - se2) + (se3 + se4));
279 
280  if(ser <= sr)
281  ++players_worseeq;
282  ++players_total;
283  });
284 
285  // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
286  // use this rule here
287 
288  if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
289  return false;
290 
291  return true;
292 }
293 
294 void ctf_CaptureShield_Update(entity player, bool wanted_status)
295 {
296  bool updated_status = ctf_CaptureShield_CheckStatus(player);
297  if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
298  {
299  Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
300  player.ctf_captureshielded = updated_status;
301  }
302 }
303 
305 {
306  if(!client.ctf_captureshielded) { return false; }
307  if(CTF_SAMETEAM(this, client)) { return false; }
308 
309  return true;
310 }
311 
313 {
314  if(!toucher.ctf_captureshielded) { return; }
315  if(CTF_SAMETEAM(this, toucher)) { return; }
316 
317  vector mymid = (this.absmin + this.absmax) * 0.5;
318  vector theirmid = (toucher.absmin + toucher.absmax) * 0.5;
319 
320  Damage(toucher, this, this, 0, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, mymid, normalize(theirmid - mymid) * ctf_captureshield_force);
321  if(IS_REAL_CLIENT(toucher)) { Send_Notification(NOTIF_ONE, toucher, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
322 }
323 
325 {
326  entity shield = new(ctf_captureshield);
327 
328  shield.enemy = flag;
329  shield.team = flag.team;
330  settouch(shield, ctf_CaptureShield_Touch);
331  setcefc(shield, ctf_CaptureShield_Customize);
332  shield.effects = EF_ADDITIVE;
333  set_movetype(shield, MOVETYPE_NOCLIP);
334  shield.solid = SOLID_TRIGGER;
335  shield.avelocity = '7 0 11';
336  shield.scale = 0.5;
337 
338  setorigin(shield, flag.origin);
339  setmodel(shield, MDL_CTF_SHIELD);
340  setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
341 }
342 
343 
344 // ====================
345 // Drop/Pass/Throw Code
346 // ====================
347 
348 void ctf_Handle_Drop(entity flag, entity player, int droptype)
349 {
350  // declarations
351  player = (player ? player : flag.pass_sender);
352 
353  // main
355  flag.takedamage = DAMAGE_YES;
356  flag.angles = '0 0 0';
357  SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
358  flag.ctf_droptime = time;
359  flag.ctf_dropper = player;
360  flag.ctf_status = FLAG_DROPPED;
361 
362  // messages and sounds
363  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_LOST), player.netname);
364  _sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
365  ctf_EventLog("dropped", player.team, player);
366 
367  // scoring
368  GameRules_scoring_add_team(player, SCORE, -((flag.score_drop) ? flag.score_drop : autocvar_g_ctf_score_penalty_drop));
369  GameRules_scoring_add(player, CTF_DROPS, 1);
370 
371  // waypoints
373  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);
374  wp.colormod = WPCOLOR_DROPPEDFLAG(flag.team);
375  }
376 
378  {
379  WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_health);
380  WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResource(flag, RES_HEALTH));
381  }
382 
383  player.throw_antispam = time + autocvar_g_ctf_pass_wait;
384 
385  if(droptype == DROP_PASS)
386  {
387  flag.pass_distance = 0;
388  flag.pass_sender = NULL;
389  flag.pass_target = NULL;
390  }
391 }
392 
394 {
395  entity sender = flag.pass_sender;
396 
397  // transfer flag to player
398  flag.owner = player;
399  flag.owner.flagcarried = flag;
400  GameRules_scoring_vip(player, true);
401 
402  // reset flag
403  if(player.vehicle)
404  {
405  setattachment(flag, player.vehicle, "");
407  flag.scale = VEHICLE_FLAG_SCALE;
408  }
409  else
410  {
411  setattachment(flag, player, "");
413  }
415  flag.takedamage = DAMAGE_NO;
416  flag.solid = SOLID_NOT;
417  flag.angles = '0 0 0';
418  flag.ctf_status = FLAG_CARRY;
419 
420  // messages and sounds
421  _sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
422  ctf_EventLog("receive", flag.team, player);
423 
425  if(it == sender)
426  Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_SENT), player.netname);
427  else if(it == player)
428  Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_RECEIVED), sender.netname);
429  else if(SAME_TEAM(it, sender))
430  Send_Notification(NOTIF_ONE, it, MSG_CENTER, APP_NUM(flag.team, CENTER_CTF_PASS_OTHER), sender.netname, player.netname);
431  });
432 
433  // create new waypoint
434  ctf_FlagcarrierWaypoints(player);
435 
436  sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
437  player.throw_antispam = sender.throw_antispam;
438 
439  flag.pass_distance = 0;
440  flag.pass_sender = NULL;
441  flag.pass_target = NULL;
442 }
443 
444 void ctf_Handle_Throw(entity player, entity receiver, int droptype)
445 {
446  entity flag = player.flagcarried;
447  vector targ_origin, flag_velocity;
448 
449  if(!flag) { return; }
450  if((droptype == DROP_PASS) && !receiver) { return; }
451 
452  if(flag.speedrunning)
453  {
454  // ensure old waypoints are removed before resetting the flag
455  WaypointSprite_Kill(player.wps_flagcarrier);
456 
457  if(player.wps_enemyflagcarrier)
458  WaypointSprite_Kill(player.wps_enemyflagcarrier);
459 
460  if(player.wps_flagreturn)
461  WaypointSprite_Kill(player.wps_flagreturn);
462  ctf_RespawnFlag(flag);
463  return;
464  }
465 
466  // reset the flag
467  setattachment(flag, NULL, "");
468  tracebox(player.origin - FLAG_DROP_OFFSET, flag.m_mins, flag.m_maxs, player.origin + FLAG_DROP_OFFSET, MOVE_NOMONSTERS, flag);
469  setorigin(flag, trace_endpos);
470  flag.owner.flagcarried = NULL;
471  GameRules_scoring_vip(flag.owner, false);
472  flag.owner = NULL;
473  flag.solid = SOLID_TRIGGER;
474  flag.ctf_dropper = player;
475  flag.ctf_droptime = time;
476 
477  flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
478 
479  switch(droptype)
480  {
481  case DROP_PASS:
482  {
483  // warpzone support:
484  // for the examples, we assume player -> wz1 -> ... -> wzn -> receiver
485  // findradius has already put wzn ... wz1 into receiver's warpzone parameters!
486  WarpZone_RefSys_Copy(flag, receiver);
487  WarpZone_RefSys_AddInverse(flag, receiver); // wz1^-1 ... wzn^-1 receiver
488  targ_origin = WarpZone_RefSys_TransformOrigin(receiver, flag, (0.5 * (receiver.absmin + receiver.absmax))); // this is target origin as seen by the flag
489 
490  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
491  ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
492 
493  // main
494  set_movetype(flag, MOVETYPE_FLY);
495  flag.takedamage = DAMAGE_NO;
496  flag.pass_sender = player;
497  flag.pass_target = receiver;
498  flag.ctf_status = FLAG_PASSING;
499 
500  // other
501  _sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
502  WarpZone_TrailParticles(NULL, _particleeffectnum(flag.passeffect), player.origin, targ_origin);
503  ctf_EventLog("pass", flag.team, player);
504  break;
505  }
506 
507  case DROP_THROW:
508  {
509  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'));
510 
511  flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((StatusEffects_active(STATUSEFFECT_Strength, player)) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
512  flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
513  ctf_Handle_Drop(flag, player, droptype);
514  navigation_dynamicgoal_set(flag, player);
515  break;
516  }
517 
518  case DROP_RESET:
519  {
520  flag.velocity = '0 0 0'; // do nothing
521  break;
522  }
523 
524  default:
525  case DROP_NORMAL:
526  {
527  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);
528  ctf_Handle_Drop(flag, player, droptype);
529  navigation_dynamicgoal_set(flag, player);
530  break;
531  }
532  }
533 
534  // kill old waypointsprite
535  WaypointSprite_Ping(player.wps_flagcarrier);
536  WaypointSprite_Kill(player.wps_flagcarrier);
537 
538  if(player.wps_enemyflagcarrier)
539  WaypointSprite_Kill(player.wps_enemyflagcarrier);
540 
541  if(player.wps_flagreturn)
542  WaypointSprite_Kill(player.wps_flagreturn);
543 
544  // captureshield
545  ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
546 }
547 
548 #if 0
549 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
550 {
551  return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
552 }
553 #endif
554 
555 // ==============
556 // Event Handlers
557 // ==============
558 
559 void nades_GiveBonus(entity player, float score);
560 
561 void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
562 {
563  entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
564  entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
565  entity player_team_flag = NULL, tmp_entity;
566  float old_time, new_time;
567 
568  if(!player) { return; } // without someone to give the reward to, we can't possibly cap
569  if(CTF_DIFFTEAM(player, flag)) { return; }
570  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)
571 
572  if (toucher.goalentity == flag.bot_basewaypoint)
573  toucher.goalentity_lock_timeout = 0;
574 
575  if(ctf_oneflag)
576  for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
577  if(SAME_TEAM(tmp_entity, player))
578  {
579  player_team_flag = tmp_entity;
580  break;
581  }
582 
583  nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
584 
585  player.throw_prevtime = time;
586  player.throw_count = 0;
587 
588  // messages and sounds
589  Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_NUM(enemy_flag.team, CENTER_CTF_CAPTURE));
590  ctf_CaptureRecord(enemy_flag, player);
591  _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);
592 
593  switch(capturetype)
594  {
595  case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
596  case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
597  default: break;
598  }
599 
600  // scoring
601  float pscore = 0;
602  if(enemy_flag.score_capture || flag.score_capture)
603  pscore = floor((max(1, enemy_flag.score_capture) + max(1, flag.score_capture)) * 0.5);
604  GameRules_scoring_add_team(player, SCORE, ((pscore) ? pscore : autocvar_g_ctf_score_capture));
605  float capscore = 0;
606  if(enemy_flag.score_team_capture || flag.score_team_capture)
607  capscore = floor((max(1, enemy_flag.score_team_capture) + max(1, flag.score_team_capture)) * 0.5);
608  GameRules_scoring_add_team(player, CTF_CAPS, ((capscore) ? capscore : 1));
609 
610  old_time = GameRules_scoring_add(player, CTF_CAPTIME, 0);
611  new_time = TIME_ENCODE(time - enemy_flag.ctf_pickuptime);
612  if(!old_time || new_time < old_time)
613  GameRules_scoring_add(player, CTF_CAPTIME, new_time - old_time);
614 
615  // effects
616  Send_Effect_(flag.capeffect, flag.origin, '0 0 0', 1);
617 #if 0
618  shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
619 #endif
620 
621  // other
622  if(capturetype == CAPTURE_NORMAL)
623  {
624  WaypointSprite_Kill(player.wps_flagcarrier);
625  if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
626 
627  if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
628  { GameRules_scoring_add_team(enemy_flag.ctf_dropper, SCORE, ((enemy_flag.score_assist) ? enemy_flag.score_assist : autocvar_g_ctf_score_capture_assist)); }
629  }
630 
631  flag.enemy = toucher;
632 
633  // reset the flag
634  player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
635  ctf_RespawnFlag(enemy_flag);
636 }
637 
638 void ctf_Handle_Return(entity flag, entity player)
639 {
640  // messages and sounds
641  if(IS_MONSTER(player))
642  {
643  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN_MONSTER), player.monster_name);
644  }
645  else if(flag.team)
646  {
647  Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_RETURN));
648  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(flag.team, INFO_CTF_RETURN), player.netname);
649  }
650  _sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
651  ctf_EventLog("return", flag.team, player);
652 
653  // scoring
654  if(IS_PLAYER(player))
655  {
656  GameRules_scoring_add_team(player, SCORE, ((flag.score_return) ? flag.score_return : autocvar_g_ctf_score_return)); // reward for return
657  GameRules_scoring_add(player, CTF_RETURNS, 1); // add to count of returns
658 
659  nades_GiveBonus(player,autocvar_g_nades_bonus_score_medium);
660  }
661 
662  TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
663 
664  if(flag.ctf_dropper)
665  {
666  GameRules_scoring_add(flag.ctf_dropper, SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
667  ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
668  flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
669  }
670 
671  // other
672  if(player.flagcarried == flag)
673  WaypointSprite_Kill(player.wps_flagcarrier);
674 
675  flag.enemy = player;
676 
677  // reset the flag
678  ctf_RespawnFlag(flag);
679 }
680 
681 void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
682 {
683  // declarations
684  float pickup_dropped_score; // used to calculate dropped pickup score
685 
686  // attach the flag to the player
687  flag.owner = player;
688  player.flagcarried = flag;
689  GameRules_scoring_vip(player, true);
690  if(player.vehicle)
691  {
692  setattachment(flag, player.vehicle, "");
694  flag.scale = VEHICLE_FLAG_SCALE;
695  }
696  else
697  {
698  setattachment(flag, player, "");
700  }
701 
702  // flag setup
704  flag.takedamage = DAMAGE_NO;
705  flag.solid = SOLID_NOT;
706  flag.angles = '0 0 0';
707  flag.ctf_status = FLAG_CARRY;
708 
709  switch(pickuptype)
710  {
711  case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
712  case PICKUP_DROPPED: SetResourceExplicit(flag, RES_HEALTH, flag.max_health); break; // reset health/return timelimit
713  default: break;
714  }
715 
716  // messages and sounds
717  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_PICKUP), player.netname);
718  if(ctf_stalemate)
719  Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER);
720  if(!flag.team)
721  Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL);
722  else if(CTF_DIFFTEAM(player, flag))
723  Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_NUM(flag.team, CENTER_CTF_PICKUP));
724  else
725  Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_RETURN : CENTER_CTF_PICKUP_RETURN_ENEMY), Team_ColorCode(flag.team));
726 
727  Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, APP_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
728 
729  if(!flag.team)
730  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); });
731 
732  if(flag.team)
733  FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
734  if(CTF_SAMETEAM(flag, it))
735  {
736  if(SAME_TEAM(player, it))
737  Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
738  else
739  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);
740  }
741  });
742 
743  _sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
744 
745  // scoring
746  GameRules_scoring_add(player, CTF_PICKUPS, 1);
747  nades_GiveBonus(player, autocvar_g_nades_bonus_score_minor);
748  switch(pickuptype)
749  {
750  case PICKUP_BASE:
751  {
752  GameRules_scoring_add_team(player, SCORE, ((flag.score_pickup) ? flag.score_pickup : autocvar_g_ctf_score_pickup_base));
753  ctf_EventLog("steal", flag.team, player);
754  break;
755  }
756 
757  case PICKUP_DROPPED:
758  {
759  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);
760  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);
761  LOG_TRACE("pickup_dropped_score is ", ftos(pickup_dropped_score));
762  GameRules_scoring_add_team(player, SCORE, pickup_dropped_score);
763  ctf_EventLog("pickup", flag.team, player);
764  break;
765  }
766 
767  default: break;
768  }
769 
770  // speedrunning
771  if(pickuptype == PICKUP_BASE)
772  {
773  flag.speedrunning = player.speedrunning; // if speedrunning, flag will flag-return and teleport the owner back after the record
774  if((player.speedrunning) && (ctf_captimerecord))
776  }
777 
778  // effects
779  Send_Effect_(flag.toucheffect, player.origin, '0 0 0', 1);
780 
781  // waypoints
782  if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
783  ctf_FlagcarrierWaypoints(player);
784  WaypointSprite_Ping(player.wps_flagcarrier);
785 }
786 
787 
788 // ===================
789 // Main Flag Functions
790 // ===================
791 
792 void ctf_CheckFlagReturn(entity flag, int returntype)
793 {
794  if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
795  {
796  if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResource(flag, RES_HEALTH)); }
797 
798  if((GetResource(flag, RES_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
799  {
800  switch(returntype)
801  {
802  case RETURN_DROPPED:
803  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DROPPED)); break;
804  case RETURN_DAMAGE:
805  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_DAMAGED)); break;
806  case RETURN_SPEEDRUN:
807  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_SPEEDRUN), TIME_ENCODE(ctf_captimerecord)); break;
808  case RETURN_NEEDKILL:
809  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_NEEDKILL)); break;
810  default:
811  case RETURN_TIMEOUT:
812  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(flag.team, INFO_CTF_FLAGRETURN_TIMEOUT)); break;
813  }
814  _sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
815  ctf_EventLog("returned", flag.team, NULL);
816  flag.enemy = NULL;
817  ctf_RespawnFlag(flag);
818  }
819  }
820 }
821 
823 {
824  // make spectators see what the player would see
825  entity e = WaypointSprite_getviewentity(client);
826  entity wp_owner = this.owner;
827 
828  // team waypoints
829  //if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
830  if(SAME_TEAM(wp_owner, e)) { return false; }
831  if(!IS_PLAYER(e)) { return false; }
832 
833  return true;
834 }
835 
837 {
838  // declarations
839  int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
840  entity tmp_entity;
841 
842  entity ctf_staleflaglist = NULL; // reset the list, we need to build the list each time this function runs
843 
844  // build list of stale flags
845  for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
846  {
848  if(tmp_entity.ctf_status != FLAG_BASE)
849  if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
850  {
851  tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
852  ctf_staleflaglist = tmp_entity;
853 
854  switch(tmp_entity.team)
855  {
856  case NUM_TEAM_1: ++stale_red_flags; break;
857  case NUM_TEAM_2: ++stale_blue_flags; break;
858  case NUM_TEAM_3: ++stale_yellow_flags; break;
859  case NUM_TEAM_4: ++stale_pink_flags; break;
860  default: ++stale_neutral_flags; break;
861  }
862  }
863  }
864 
865  if(ctf_oneflag)
866  stale_flags = (stale_neutral_flags >= 1);
867  else
868  stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
869 
870  if(ctf_oneflag && stale_flags == 1)
871  ctf_stalemate = true;
872  else if(stale_flags >= 2)
873  ctf_stalemate = true;
874  else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
875  { ctf_stalemate = false; wpforenemy_announced = false; }
876  else if(stale_flags < 2 && autocvar_g_ctf_stalemate_endcondition == 1)
877  { ctf_stalemate = false; wpforenemy_announced = false; }
878 
879  // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
880  if(ctf_stalemate)
881  {
882  for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
883  {
884  if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
885  {
886  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);
887  wp.colormod = WPCOLOR_ENEMYFC(tmp_entity.owner.team);
888  setcefc(tmp_entity.owner.wps_enemyflagcarrier, ctf_Stalemate_Customize);
889  }
890  }
891 
893  {
894  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)); });
895 
896  wpforenemy_announced = true;
897  }
898  }
899 }
900 
901 void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force)
902 {
903  if(ITEM_DAMAGE_NEEDKILL(deathtype))
904  {
906  this.ctf_flagdamaged_byworld = true;
907  else
908  {
911  }
912  return;
913  }
915  {
916  // reduce health and check if it should be returned
917  TakeResource(this, RES_HEALTH, damage);
919  return;
920  }
921 }
922 
924 {
925  // declarations
926  entity tmp_entity;
927 
928  this.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
929 
930  // captureshield
931  if(this == ctf_worldflaglist) // only for the first flag
932  FOREACH_CLIENT(true, { ctf_CaptureShield_Update(it, 1); }); // release shield only
933 
934  // sanity checks
935  if(this.mins != this.m_mins || this.maxs != this.m_maxs) { // reset the flag boundaries in case it got squished
936  LOG_TRACE("wtf the flag got squashed?");
937  tracebox(this.origin, this.m_mins, this.m_maxs, this.origin, MOVE_NOMONSTERS, this);
938  if(!trace_startsolid || this.noalign) // can we resize it without getting stuck?
939  setsize(this, this.m_mins, this.m_maxs);
940  }
941 
942  // main think method
943  switch(this.ctf_status)
944  {
945  case FLAG_BASE:
946  {
948  {
949  for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
950  if(tmp_entity.ctf_status == FLAG_DROPPED)
951  if(vdist(this.origin - tmp_entity.origin, <, autocvar_g_ctf_dropped_capture_radius))
952  if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
953  ctf_Handle_Capture(this, tmp_entity, CAPTURE_DROPPED);
954  }
955  return;
956  }
957 
958  case FLAG_DROPPED:
959  {
960  this.angles = '0 0 0'; // reset flag angles in case warpzones adjust it
961 
963  {
964  vector midpoint = ((this.absmin + this.absmax) * 0.5);
965  if(pointcontents(midpoint) == CONTENT_WATER)
966  {
967  this.velocity = this.velocity * 0.5;
968 
969  if (pointcontents(midpoint + eZ * FLAG_FLOAT_OFFSET_Z) == CONTENT_WATER)
970  { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
971  else
972  { set_movetype(this, MOVETYPE_FLY); }
973  }
974  else if(this.move_movetype == MOVETYPE_FLY) { set_movetype(this, MOVETYPE_TOSS); }
975  }
977  {
979  {
982  return;
983  }
984  }
985  if(this.ctf_flagdamaged_byworld)
986  {
989  return;
990  }
992  {
993  TakeResource(this, RES_HEALTH, (this.max_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
995  return;
996  }
997  return;
998  }
999 
1000  case FLAG_CARRY:
1001  {
1003  {
1004  SetResourceExplicit(this, RES_HEALTH, 0);
1006 
1007  CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
1008  ImpulseCommands(this.owner);
1009  }
1011  {
1012  if(time >= wpforenemy_nextthink)
1013  {
1015  wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
1016  }
1017  }
1018  if(CTF_SAMETEAM(this, this.owner) && this.team)
1019  {
1020  if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
1022  else if(vdist(this.owner.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_carried_radius))
1023  ctf_Handle_Return(this, this.owner);
1024  }
1025  return;
1026  }
1027 
1028  case FLAG_PASSING:
1029  {
1030  vector targ_origin = ((this.pass_target.absmin + this.pass_target.absmax) * 0.5);
1031  targ_origin = WarpZone_RefSys_TransformOrigin(this.pass_target, this, targ_origin); // origin of target as seen by the flag (us)
1032  WarpZone_TraceLine(this.origin, targ_origin, MOVE_NOMONSTERS, this);
1033 
1034  if((this.pass_target == NULL)
1035  || (IS_DEAD(this.pass_target))
1036  || (this.pass_target.flagcarried)
1037  || (vdist(this.origin - targ_origin, >, autocvar_g_ctf_pass_radius))
1038  || ((trace_fraction < 1) && (trace_ent != this.pass_target))
1040  {
1041  // give up, pass failed
1042  ctf_Handle_Drop(this, NULL, DROP_PASS);
1043  }
1044  else
1045  {
1046  // still a viable target, go for it
1047  ctf_CalculatePassVelocity(this, targ_origin, this.origin, true);
1048  }
1049  return;
1050  }
1051 
1052  default: // this should never happen
1053  {
1054  LOG_TRACE("ctf_FlagThink(): Flag exists with no status?");
1055  return;
1056  }
1057  }
1058 }
1059 
1060 METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
1061 {
1062  return = false;
1063  if(game_stopped) return;
1065 
1066  bool is_not_monster = (!IS_MONSTER(toucher));
1067 
1068  // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
1069  if(ITEM_TOUCH_NEEDKILL())
1070  {
1072  {
1073  SetResourceExplicit(flag, RES_HEALTH, 0);
1075  }
1076  if(!flag.ctf_flagdamaged_byworld) { return; }
1077  }
1078 
1079  // special touch behaviors
1080  if(STAT(FROZEN, toucher)) { return; }
1081  else if(IS_VEHICLE(toucher))
1082  {
1083  if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
1084  toucher = toucher.owner; // the player is actually the vehicle owner, not other
1085  else
1086  return; // do nothing
1087  }
1088  else if(IS_MONSTER(toucher))
1089  {
1091  return; // do nothing
1092  }
1093  else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
1094  {
1095  if(time > flag.wait) // if we haven't in a while, play a sound/effect
1096  {
1097  Send_Effect_(flag.toucheffect, flag.origin, '0 0 0', 1);
1098  _sound(flag, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
1099  flag.wait = time + FLAG_TOUCHRATE;
1100  }
1101  return;
1102  }
1103  else if(IS_DEAD(toucher)) { return; }
1104 
1105  switch(flag.ctf_status)
1106  {
1107  case FLAG_BASE:
1108  {
1109  if(ctf_oneflag)
1110  {
1111  if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
1112  ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
1113  else if(!flag.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1114  ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the neutral flag
1115  }
1116  else if(CTF_SAMETEAM(toucher, flag) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, flag) && is_not_monster)
1117  ctf_Handle_Capture(flag, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
1118  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)
1119  {
1120  ctf_Handle_Return(toucher.flagcarried, toucher); // return their current flag
1121  ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // now pickup the flag
1122  }
1123  else if(CTF_DIFFTEAM(toucher, flag) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
1124  ctf_Handle_Pickup(flag, toucher, PICKUP_BASE); // toucher just stole the enemies flag
1125  break;
1126  }
1127 
1128  case FLAG_DROPPED:
1129  {
1130  if(CTF_SAMETEAM(toucher, flag) && ctf_Immediate_Return_Allowed(flag, toucher))
1131  ctf_Handle_Return(flag, toucher); // toucher just returned his own flag
1132  else if(is_not_monster && (!toucher.flagcarried) && ((toucher != flag.ctf_dropper) || (time > flag.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
1133  ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
1134  break;
1135  }
1136 
1137  case FLAG_CARRY:
1138  {
1139  LOG_TRACE("Someone touched a flag even though it was being carried?");
1140  break;
1141  }
1142 
1143  case FLAG_PASSING:
1144  {
1145  if((IS_PLAYER(toucher)) && !IS_DEAD(toucher) && (toucher != flag.pass_sender))
1146  {
1147  if(DIFF_TEAM(toucher, flag.pass_sender))
1148  {
1149  if(ctf_Immediate_Return_Allowed(flag, toucher))
1150  ctf_Handle_Return(flag, toucher);
1151  else if(is_not_monster && (!toucher.flagcarried))
1152  ctf_Handle_Pickup(flag, toucher, PICKUP_DROPPED);
1153  }
1154  else if(!toucher.flagcarried)
1155  ctf_Handle_Retrieve(flag, toucher);
1156  }
1157  break;
1158  }
1159  }
1160 }
1161 
1164 {
1165  flag.watertype = CONTENT_EMPTY; // TODO: it is unclear why this workaround is needed, likely many other potential breakage points!!
1166  // check for flag respawn being called twice in a row
1167  if(flag.last_respawn > time - 0.5)
1168  { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
1169 
1170  flag.last_respawn = time;
1171 
1172  // reset the player (if there is one)
1173  if((flag.owner) && (flag.owner.flagcarried == flag))
1174  {
1175  WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
1176  WaypointSprite_Kill(flag.owner.wps_flagreturn);
1177  WaypointSprite_Kill(flag.wps_flagcarrier);
1178 
1179  flag.owner.flagcarried = NULL;
1180  GameRules_scoring_vip(flag.owner, false);
1181 
1182  if(flag.speedrunning)
1183  ctf_FakeTimeLimit(flag.owner, -1);
1184  }
1185 
1186  if((flag.owner) && (flag.owner.vehicle))
1187  flag.scale = FLAG_SCALE;
1188 
1189  if(flag.ctf_status == FLAG_DROPPED)
1190  { WaypointSprite_Kill(flag.wps_flagdropped); }
1191 
1192  // reset the flag
1193  setattachment(flag, NULL, "");
1194  setorigin(flag, flag.ctf_spawnorigin);
1195 
1196  //set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS)); // would be desired, except maps that want floating flags have it set to fall!
1197  set_movetype(flag, MOVETYPE_NONE); // match the initial setup handling (flag doesn't move when spawned)
1198  flag.takedamage = DAMAGE_NO;
1199  SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
1200  flag.solid = SOLID_TRIGGER;
1201  flag.velocity = '0 0 0';
1202  flag.angles = flag.mangle;
1203  flag.flags = FL_ITEM | FL_NOTARGET;
1204 
1205  flag.ctf_status = FLAG_BASE;
1206  flag.owner = NULL;
1207  flag.pass_distance = 0;
1208  flag.pass_sender = NULL;
1209  flag.pass_target = NULL;
1210  flag.ctf_dropper = NULL;
1211  flag.ctf_pickuptime = 0;
1212  flag.ctf_droptime = 0;
1213  flag.ctf_flagdamaged_byworld = false;
1215 
1217 }
1218 
1219 void ctf_Reset(entity this)
1220 {
1221  if(this.owner && IS_PLAYER(this.owner))
1223 
1224  this.enemy = NULL;
1225  ctf_RespawnFlag(this);
1226 }
1227 
1229 {
1230  entity e = WaypointSprite_getviewentity(client);
1231  entity wp_owner = this.owner;
1232  entity flag = e.flagcarried;
1233  if(flag && CTF_SAMETEAM(e, flag))
1234  return false;
1235  if(flag && (flag.cnt || wp_owner.cnt) && wp_owner.cnt != flag.cnt)
1236  return false;
1237  return true;
1238 }
1239 
1240 void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map by ctf_FlagSetup()
1241 {
1242  // bot waypoints
1243  waypoint_spawnforitem_force(this, this.origin);
1244  navigation_dynamicgoal_init(this, true);
1245 
1246  // waypointsprites
1247  entity basename;
1248  switch (this.team)
1249  {
1250  case NUM_TEAM_1: basename = WP_FlagBaseRed; break;
1251  case NUM_TEAM_2: basename = WP_FlagBaseBlue; break;
1252  case NUM_TEAM_3: basename = WP_FlagBaseYellow; break;
1253  case NUM_TEAM_4: basename = WP_FlagBasePink; break;
1254  default: basename = WP_FlagBaseNeutral; break;
1255  }
1256 
1258  {
1259  entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
1260  wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
1262  WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
1263  setcefc(wp, ctf_FlagBase_Customize);
1264  }
1265 
1266  // captureshield setup
1268 }
1269 
1270 .bool pushable;
1271 
1272 void ctf_FlagSetup(int teamnum, entity flag) // called when spawning a flag entity on the map as a spawnfunc
1273 {
1274  // main setup
1275  flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
1276  ctf_worldflaglist = flag;
1277 
1278  setattachment(flag, NULL, "");
1279 
1280  flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnum), Team_ColorName_Upper(teamnum)));
1281  flag.team = teamnum;
1282  flag.classname = "item_flag_team";
1283  flag.target = "###item###"; // for finding the nearest item using findnearest
1284  flag.flags = FL_ITEM | FL_NOTARGET;
1285  IL_PUSH(g_items, flag);
1286  flag.solid = SOLID_TRIGGER;
1287  flag.takedamage = DAMAGE_NO;
1288  flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
1290  SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
1291  flag.event_damage = ctf_FlagDamage;
1292  flag.pushable = true;
1293  flag.teleportable = TELEPORT_NORMAL;
1294  flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP;
1295  flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
1296  flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
1297  if(flag.damagedbycontents)
1299  flag.velocity = '0 0 0';
1300  flag.mangle = flag.angles;
1301  flag.reset = ctf_Reset;
1302  settouch(flag, ctf_FlagTouch);
1303  setthink(flag, ctf_FlagThink);
1304  flag.nextthink = time + FLAG_THINKRATE;
1305  flag.ctf_status = FLAG_BASE;
1306 
1307  // crudely force them all to 0
1309  flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
1310 
1311  string teamname = Static_Team_ColorName_Lower(teamnum);
1312  // appearence
1313  if(!flag.scale) { flag.scale = FLAG_SCALE; }
1314  if(flag.skin == 0) { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
1315  if(flag.model == "") { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
1316  if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnum).eent_eff_name; }
1317  if (flag.passeffect == "") { flag.passeffect = EFFECT_PASS(teamnum).eent_eff_name; }
1318  if (flag.capeffect == "") { flag.capeffect = EFFECT_CAP(teamnum).eent_eff_name; }
1319 
1320  // sounds
1321 #define X(s,b) \
1322  if(flag.s == "") flag.s = b; \
1323  precache_sound(flag.s);
1324 
1325  X(snd_flag_taken, strzone(SND(CTF_TAKEN(teamnum))))
1326  X(snd_flag_returned, strzone(SND(CTF_RETURNED(teamnum))))
1327  X(snd_flag_capture, strzone(SND(CTF_CAPTURE(teamnum))))
1328  X(snd_flag_dropped, strzone(SND(CTF_DROPPED(teamnum))))
1329  X(snd_flag_respawn, strzone(SND(CTF_RESPAWN)))
1330  X(snd_flag_touch, strzone(SND(CTF_TOUCH)))
1331  X(snd_flag_pass, strzone(SND(CTF_PASS)))
1332 #undef X
1333 
1334  // precache
1335  precache_model(flag.model);
1336 
1337  // appearence
1338  _setmodel(flag, flag.model); // precision set below
1339  setsize(flag, CTF_FLAG.m_mins * flag.scale, CTF_FLAG.m_maxs * flag.scale);
1340  flag.m_mins = flag.mins; // store these for squash checks
1341  flag.m_maxs = flag.maxs;
1342  setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
1343 
1345  {
1346  switch(teamnum)
1347  {
1348  case NUM_TEAM_1: flag.glow_color = 251; break;
1349  case NUM_TEAM_2: flag.glow_color = 210; break;
1350  case NUM_TEAM_3: flag.glow_color = 110; break;
1351  case NUM_TEAM_4: flag.glow_color = 145; break;
1352  default: flag.glow_color = 254; break;
1353  }
1354  flag.glow_size = 25;
1355  flag.glow_trail = 1;
1356  }
1357 
1358  flag.effects |= EF_LOWPRECISION;
1359  if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
1361  {
1362  switch(teamnum)
1363  {
1364  case NUM_TEAM_1: flag.effects |= EF_RED; break;
1365  case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
1366  case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
1367  case NUM_TEAM_4: flag.effects |= EF_RED; break;
1368  default: flag.effects |= EF_DIMLIGHT; break;
1369  }
1370  }
1371 
1372  // flag placement
1373  if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
1374  {
1375  flag.dropped_origin = flag.origin;
1376  flag.noalign = true;
1377  set_movetype(flag, MOVETYPE_NONE);
1378  }
1379  else // drop to floor, automatically find a platform and set that as spawn origin
1380  {
1381  flag.noalign = false;
1382  droptofloor(flag);
1383  set_movetype(flag, MOVETYPE_NONE);
1384  }
1385 
1386  InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
1387 }
1388 
1389 
1390 // ================
1391 // Bot player logic
1392 // ================
1393 
1394 // NOTE: LEGACY CODE, needs to be re-written!
1395 
1397 {
1398  entity f;
1399  vector s = '0 0 0';
1400  vector fo = '0 0 0';
1401  int n = 0;
1402 
1403  f = ctf_worldflaglist;
1404  while (f)
1405  {
1406  fo = f.origin;
1407  s = s + fo;
1408  f = f.ctf_worldflagnext;
1409  n++;
1410  }
1411  if(!n)
1412  return;
1413 
1414  havocbot_middlepoint = s / n;
1416 
1419  if(n == 2)
1420  {
1421  // for symmetrical editing of waypoints
1423  entity f2 = f1.ctf_worldflagnext;
1424  float m = -(f1.origin.y - f2.origin.y) / (max(f1.origin.x - f2.origin.x, FLOAT_EPSILON));
1425  float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
1428  }
1430 }
1431 
1432 
1434 {
1435  entity f;
1436  f = ctf_worldflaglist;
1437  while (f)
1438  {
1439  if (CTF_SAMETEAM(bot, f))
1440  return f;
1441  f = f.ctf_worldflagnext;
1442  }
1443  return NULL;
1444 }
1445 
1447 {
1448  entity f;
1449  f = ctf_worldflaglist;
1450  while (f)
1451  {
1452  if(ctf_oneflag)
1453  {
1454  if(CTF_DIFFTEAM(bot, f))
1455  {
1456  if(f.team)
1457  {
1458  if(bot.flagcarried)
1459  return f;
1460  }
1461  else if(!bot.flagcarried)
1462  return f;
1463  }
1464  }
1465  else if (CTF_DIFFTEAM(bot, f))
1466  return f;
1467  f = f.ctf_worldflagnext;
1468  }
1469  return NULL;
1470 }
1471 
1472 int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
1473 {
1474  if (!teamplay)
1475  return 0;
1476 
1477  int c = 0;
1478 
1479  FOREACH_CLIENT(IS_PLAYER(it), {
1480  if(DIFF_TEAM(it, bot) || IS_DEAD(it) || it == bot)
1481  continue;
1482 
1483  if(vdist(it.origin - org, <, tc_radius))
1484  ++c;
1485  });
1486 
1487  return c;
1488 }
1489 
1490 // unused
1491 #if 0
1492 void havocbot_goalrating_ctf_ourflag(entity this, float ratingscale)
1493 {
1494  entity head;
1495  head = ctf_worldflaglist;
1496  while (head)
1497  {
1498  if (CTF_SAMETEAM(this, head))
1499  break;
1500  head = head.ctf_worldflagnext;
1501  }
1502  if (head)
1503  navigation_routerating(this, head, ratingscale, 10000);
1504 }
1505 #endif
1506 
1507 void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
1508 {
1509  entity head;
1510  head = ctf_worldflaglist;
1511  while (head)
1512  {
1513  if (CTF_SAMETEAM(this, head))
1514  {
1515  if (this.flagcarried)
1516  if ((this.flagcarried.cnt || head.cnt) && this.flagcarried.cnt != head.cnt)
1517  {
1518  head = head.ctf_worldflagnext; // skip base if it has a different group
1519  continue;
1520  }
1521  break;
1522  }
1523  head = head.ctf_worldflagnext;
1524  }
1525  if (!head)
1526  return;
1527 
1528  navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1529 }
1530 
1531 void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
1532 {
1533  entity head;
1534  head = ctf_worldflaglist;
1535  while (head)
1536  {
1537  if(ctf_oneflag)
1538  {
1539  if(CTF_DIFFTEAM(this, head))
1540  {
1541  if(head.team)
1542  {
1543  if(this.flagcarried)
1544  break;
1545  }
1546  else if(!this.flagcarried)
1547  break;
1548  }
1549  }
1550  else if(CTF_DIFFTEAM(this, head))
1551  break;
1552  head = head.ctf_worldflagnext;
1553  }
1554  if (head)
1555  {
1556  if (head.ctf_status == FLAG_CARRY)
1557  {
1558  // adjust rating of our flag carrier depending on his health
1559  head = head.tag_entity;
1560  float f = bound(0, (GetResource(head, RES_HEALTH) + GetResource(head, RES_ARMOR)) / 100, 2) - 1;
1561  ratingscale += ratingscale * f * 0.1;
1562  }
1563  navigation_routerating(this, head, ratingscale, 10000);
1564  }
1565 }
1566 
1567 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
1568 {
1569  // disabled because we always spawn waypoints for flags with waypoint_spawnforitem_force
1570  /*
1571  if (!bot_waypoints_for_items)
1572  {
1573  havocbot_goalrating_ctf_enemyflag(this, ratingscale);
1574  return;
1575  }
1576  */
1577  entity head;
1578 
1579  head = havocbot_ctf_find_enemy_flag(this);
1580 
1581  if (!head)
1582  return;
1583 
1584  navigation_routerating(this, head.bot_basewaypoint, ratingscale, 10000);
1585 }
1586 
1587 void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
1588 {
1589  entity mf;
1590 
1591  mf = havocbot_ctf_find_flag(this);
1592 
1593  if(mf.ctf_status == FLAG_BASE)
1594  return;
1595 
1596  if(mf.tag_entity)
1597  navigation_routerating(this, mf.tag_entity, ratingscale, 10000);
1598 }
1599 
1600 void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
1601 {
1602  entity head;
1603  head = ctf_worldflaglist;
1604  while (head)
1605  {
1606  // flag is out in the field
1607  if(head.ctf_status != FLAG_BASE)
1608  if(head.tag_entity==NULL) // dropped
1609  {
1610  if(df_radius)
1611  {
1612  if(vdist(org - head.origin, <, df_radius))
1613  navigation_routerating(this, head, ratingscale, 10000);
1614  }
1615  else
1616  navigation_routerating(this, head, ratingscale, 10000);
1617  }
1618 
1619  head = head.ctf_worldflagnext;
1620  }
1621 }
1622 
1624 {
1625  float cdefense, cmiddle, coffense;
1626  entity mf, ef;
1627 
1628  if(IS_DEAD(this))
1629  return;
1630 
1631  // Check ctf flags
1632  if (this.flagcarried)
1633  {
1635  return;
1636  }
1637 
1638  mf = havocbot_ctf_find_flag(this);
1639  ef = havocbot_ctf_find_enemy_flag(this);
1640 
1641  // Retrieve stolen flag
1642  if(mf.ctf_status!=FLAG_BASE)
1643  {
1645  return;
1646  }
1647 
1648  // If enemy flag is taken go to the middle to intercept pursuers
1649  if(ef.ctf_status!=FLAG_BASE)
1650  {
1652  return;
1653  }
1654 
1655  // if there is no one else on the team switch to offense
1656  int count = 0;
1657  // don't check if this bot is a player since it isn't true when the bot is added to the server
1658  FOREACH_CLIENT(it != this && IS_PLAYER(it) && SAME_TEAM(it, this), { ++count; });
1659 
1660  if (count == 0)
1661  {
1663  return;
1664  }
1665  else if (time < CS(this).jointime + 1)
1666  {
1667  // if bots spawn all at once set good default roles
1668  if (count == 1)
1669  {
1671  return;
1672  }
1673  else if (count == 2)
1674  {
1676  return;
1677  }
1678  }
1679 
1680  // Evaluate best position to take
1681  // Count mates on middle position
1683 
1684  // Count mates on defense position
1685  cdefense = havocbot_ctf_teamcount(this, mf.dropped_origin, havocbot_middlepoint_radius * 0.5);
1686 
1687  // Count mates on offense position
1688  coffense = havocbot_ctf_teamcount(this, ef.dropped_origin, havocbot_middlepoint_radius);
1689 
1690  if(cdefense<=coffense)
1692  else if(coffense<=cmiddle)
1694  else
1696 
1697  // if bots spawn all at once assign them a more appropriated role after a while
1698  if (time < CS(this).jointime + 1 && count > 2)
1699  this.havocbot_role_timeout = time + 10 + random() * 10;
1700 }
1701 
1703 {
1704  if (item.classname != "waypoint")
1705  return false;
1706 
1707  entity head = ctf_worldflaglist;
1708  while (head)
1709  {
1710  if (item == head.bot_basewaypoint)
1711  return true;
1712  head = head.ctf_worldflagnext;
1713  }
1714  return false;
1715 }
1716 
1718 {
1719  if(IS_DEAD(this))
1720  {
1722  return;
1723  }
1724 
1725  if (this.flagcarried == NULL)
1726  {
1728  return;
1729  }
1730 
1732  {
1734 
1735  // role: carrier
1736  entity mf = havocbot_ctf_find_flag(this);
1737  vector base_org = mf.dropped_origin;
1738  float base_rating = (mf.ctf_status == FLAG_BASE) ? 10000 : (vdist(this.origin - base_org, >, 100) ? 2000 : 1000);
1739  if(ctf_oneflag)
1740  havocbot_goalrating_ctf_enemybase(this, base_rating);
1741  else
1742  havocbot_goalrating_ctf_ourbase(this, base_rating);
1743 
1744  // start collecting items very close to the bot but only inside of own base radius
1745  if (vdist(this.origin - base_org, <, havocbot_middlepoint_radius))
1746  havocbot_goalrating_items(this, 15000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
1747 
1748  havocbot_goalrating_items(this, 10000, base_org, havocbot_middlepoint_radius * 0.5);
1749 
1751 
1753 
1754  entity goal = this.goalentity;
1755  if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
1756  this.goalentity_lock_timeout = time + ((this.bot_aimtarg) ? 2 : 3);
1757 
1758  if (goal)
1759  this.havocbot_cantfindflag = time + 10;
1760  else if (time > this.havocbot_cantfindflag)
1761  {
1762  // Can't navigate to my own base, suicide!
1763  // TODO: drop it and wander around
1764  Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
1765  return;
1766  }
1767  }
1768 }
1769 
1771 {
1772  entity mf, ef;
1773 
1774  if(IS_DEAD(this))
1775  {
1777  return;
1778  }
1779 
1780  if (this.flagcarried)
1781  {
1783  return;
1784  }
1785 
1786  // If enemy flag is back on the base switch to previous role
1787  ef = havocbot_ctf_find_enemy_flag(this);
1788  if(ef.ctf_status==FLAG_BASE)
1789  {
1790  this.havocbot_role = this.havocbot_previous_role;
1791  this.havocbot_role_timeout = 0;
1792  return;
1793  }
1794  if (ef.ctf_status == FLAG_DROPPED)
1795  {
1797  return;
1798  }
1799 
1800  // If the flag carrier reached the base switch to defense
1801  mf = havocbot_ctf_find_flag(this);
1802  if (mf.ctf_status != FLAG_BASE && vdist(ef.origin - mf.dropped_origin, <, 900))
1803  {
1805  return;
1806  }
1807 
1808  // Set the role timeout if necessary
1809  if (!this.havocbot_role_timeout)
1810  {
1811  this.havocbot_role_timeout = time + random() * 30 + 60;
1812  }
1813 
1814  // If nothing happened just switch to previous role
1815  if (time > this.havocbot_role_timeout)
1816  {
1817  this.havocbot_role = this.havocbot_previous_role;
1818  this.havocbot_role_timeout = 0;
1819  return;
1820  }
1821 
1822  // Chase the flag carrier
1824  {
1826 
1827  // role: escort
1828  havocbot_goalrating_ctf_enemyflag(this, 10000);
1830  havocbot_goalrating_items(this, 21000, this.origin, 10000);
1831 
1833 
1835  }
1836 }
1837 
1839 {
1840  entity mf, ef;
1841  vector pos;
1842 
1843  if(IS_DEAD(this))
1844  {
1846  return;
1847  }
1848 
1849  if (this.flagcarried)
1850  {
1852  return;
1853  }
1854 
1855  // Check flags
1856  mf = havocbot_ctf_find_flag(this);
1857  ef = havocbot_ctf_find_enemy_flag(this);
1858 
1859  // Own flag stolen
1860  if(mf.ctf_status!=FLAG_BASE)
1861  {
1862  if(mf.tag_entity)
1863  pos = mf.tag_entity.origin;
1864  else
1865  pos = mf.origin;
1866 
1867  // Try to get it if closer than the enemy base
1868  if(vlen2(this.origin-ef.dropped_origin)>vlen2(this.origin-pos))
1869  {
1871  return;
1872  }
1873  }
1874 
1875  // Escort flag carrier
1876  if(ef.ctf_status!=FLAG_BASE)
1877  {
1878  if(ef.tag_entity)
1879  pos = ef.tag_entity.origin;
1880  else
1881  pos = ef.origin;
1882 
1883  if(vdist(pos - mf.dropped_origin, >, 700))
1884  {
1886  return;
1887  }
1888  }
1889 
1890  // Set the role timeout if necessary
1891  if (!this.havocbot_role_timeout)
1892  this.havocbot_role_timeout = time + 120;
1893 
1894  if (time > this.havocbot_role_timeout)
1895  {
1897  return;
1898  }
1899 
1901  {
1903 
1904  // role: offense
1906  havocbot_goalrating_ctf_enemybase(this, 10000);
1907  havocbot_goalrating_items(this, 22000, this.origin, 10000);
1908 
1910 
1912  }
1913 }
1914 
1915 // Retriever (temporary role):
1917 {
1918  entity mf;
1919 
1920  if(IS_DEAD(this))
1921  {
1923  return;
1924  }
1925 
1926  if (this.flagcarried)
1927  {
1929  return;
1930  }
1931 
1932  // If flag is back on the base switch to previous role
1933  mf = havocbot_ctf_find_flag(this);
1934  if(mf.ctf_status==FLAG_BASE)
1935  {
1936  if (mf.enemy == this) // did this bot return the flag?
1939  return;
1940  }
1941 
1942  if (!this.havocbot_role_timeout)
1943  this.havocbot_role_timeout = time + 20;
1944 
1945  if (time > this.havocbot_role_timeout)
1946  {
1948  return;
1949  }
1950 
1952  {
1953  const float RT_RADIUS = 10000;
1954 
1956 
1957  // role: retriever
1959  havocbot_goalrating_ctf_droppedflags(this, 12000, this.origin, RT_RADIUS);
1962  vector enemy_base_org = ef.dropped_origin;
1963  // start collecting items very close to the bot but only inside of enemy base radius
1964  if (vdist(this.origin - enemy_base_org, <, havocbot_middlepoint_radius))
1965  havocbot_goalrating_items(this, 27000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
1967 
1969 
1971  }
1972 }
1973 
1975 {
1976  entity mf;
1977 
1978  if(IS_DEAD(this))
1979  {
1981  return;
1982  }
1983 
1984  if (this.flagcarried)
1985  {
1987  return;
1988  }
1989 
1990  mf = havocbot_ctf_find_flag(this);
1991  if(mf.ctf_status!=FLAG_BASE)
1992  {
1994  return;
1995  }
1996 
1997  if (!this.havocbot_role_timeout)
1998  this.havocbot_role_timeout = time + 10;
1999 
2000  if (time > this.havocbot_role_timeout)
2001  {
2003  return;
2004  }
2005 
2007  {
2008  vector org;
2009 
2010  org = havocbot_middlepoint;
2011  org.z = this.origin.z;
2012 
2014 
2015  // role: middle
2017  havocbot_goalrating_ctf_droppedflags(this, 9000, this.origin, 10000);
2019  havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius * 0.5);
2020  havocbot_goalrating_items(this, 18000, this.origin, 10000);
2022 
2024 
2025  entity goal = this.goalentity;
2026  if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
2027  this.goalentity_lock_timeout = time + 2;
2028 
2030  }
2031 }
2032 
2034 {
2035  entity mf;
2036 
2037  if(IS_DEAD(this))
2038  {
2040  return;
2041  }
2042 
2043  if (this.flagcarried)
2044  {
2046  return;
2047  }
2048 
2049  // If own flag was captured
2050  mf = havocbot_ctf_find_flag(this);
2051  if(mf.ctf_status!=FLAG_BASE)
2052  {
2054  return;
2055  }
2056 
2057  if (!this.havocbot_role_timeout)
2058  this.havocbot_role_timeout = time + 30;
2059 
2060  if (time > this.havocbot_role_timeout)
2061  {
2063  return;
2064  }
2066  {
2067  vector org = mf.dropped_origin;
2068 
2070 
2071  // if enemies are closer to our base, go there
2072  entity closestplayer = NULL;
2073  float distance, bestdistance = 10000;
2074  FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), {
2075  distance = vlen(org - it.origin);
2076  if(distance<bestdistance)
2077  {
2078  closestplayer = it;
2079  bestdistance = distance;
2080  }
2081  });
2082 
2083  // role: defense
2084  if(closestplayer)
2085  if(DIFF_TEAM(closestplayer, this))
2086  if(vdist(org - this.origin, >, 1000))
2087  if(checkpvs(this.origin,closestplayer)||random()<0.5)
2088  havocbot_goalrating_ctf_ourbase(this, 10000);
2089 
2094  havocbot_goalrating_items(this, 18000, this.origin, 10000);
2095 
2097 
2099  }
2100 }
2101 
2103 {
2104  string s = "(null)";
2105  switch(role)
2106  {
2108  s = "carrier";
2109  bot.havocbot_role = havocbot_role_ctf_carrier;
2110  bot.havocbot_role_timeout = 0;
2111  bot.havocbot_cantfindflag = time + 10;
2112  if (bot.havocbot_previous_role != bot.havocbot_role)
2114  break;
2116  s = "defense";
2117  bot.havocbot_role = havocbot_role_ctf_defense;
2118  bot.havocbot_role_timeout = 0;
2119  break;
2121  s = "middle";
2122  bot.havocbot_role = havocbot_role_ctf_middle;
2123  bot.havocbot_role_timeout = 0;
2124  break;
2126  s = "offense";
2127  bot.havocbot_role = havocbot_role_ctf_offense;
2128  bot.havocbot_role_timeout = 0;
2129  break;
2131  s = "retriever";
2132  bot.havocbot_previous_role = bot.havocbot_role;
2133  bot.havocbot_role = havocbot_role_ctf_retriever;
2134  bot.havocbot_role_timeout = time + 10;
2135  if (bot.havocbot_previous_role != bot.havocbot_role)
2137  break;
2139  s = "escort";
2140  bot.havocbot_previous_role = bot.havocbot_role;
2141  bot.havocbot_role = havocbot_role_ctf_escort;
2142  bot.havocbot_role_timeout = time + 30;
2143  if (bot.havocbot_previous_role != bot.havocbot_role)
2145  break;
2146  }
2147  LOG_TRACE(bot.netname, " switched to ", s);
2148 }
2149 
2150 
2151 // ==============
2152 // Hook Functions
2153 // ==============
2154 
2156 {
2157  entity player = M_ARGV(0, entity);
2158 
2159  int t = 0, t2 = 0, t3 = 0;
2160  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)
2161 
2162  // initially clear items so they can be set as necessary later.
2163  STAT(OBJECTIVE_STATUS, player) &= ~(CTF_RED_FLAG_CARRYING | CTF_RED_FLAG_TAKEN | CTF_RED_FLAG_LOST
2164  | CTF_BLUE_FLAG_CARRYING | CTF_BLUE_FLAG_TAKEN | CTF_BLUE_FLAG_LOST
2165  | CTF_YELLOW_FLAG_CARRYING | CTF_YELLOW_FLAG_TAKEN | CTF_YELLOW_FLAG_LOST
2166  | CTF_PINK_FLAG_CARRYING | CTF_PINK_FLAG_TAKEN | CTF_PINK_FLAG_LOST
2167  | CTF_NEUTRAL_FLAG_CARRYING | CTF_NEUTRAL_FLAG_TAKEN | CTF_NEUTRAL_FLAG_LOST
2168  | CTF_FLAG_NEUTRAL | CTF_SHIELDED | CTF_STALEMATE);
2169 
2170  // scan through all the flags and notify the client about them
2171  for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2172  {
2173  if(flag.team == NUM_TEAM_1 && !b1) { b1 = true; t = CTF_RED_FLAG_CARRYING; t2 = CTF_RED_FLAG_TAKEN; t3 = CTF_RED_FLAG_LOST; }
2174  if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING; t2 = CTF_BLUE_FLAG_TAKEN; t3 = CTF_BLUE_FLAG_LOST; }
2175  if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING; t2 = CTF_YELLOW_FLAG_TAKEN; t3 = CTF_YELLOW_FLAG_LOST; }
2176  if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING; t2 = CTF_PINK_FLAG_TAKEN; t3 = CTF_PINK_FLAG_LOST; }
2177  if(flag.team == 0 && !b5) { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING; t2 = CTF_NEUTRAL_FLAG_TAKEN; t3 = CTF_NEUTRAL_FLAG_LOST; STAT(OBJECTIVE_STATUS, player) |= CTF_FLAG_NEUTRAL; }
2178 
2179  switch(flag.ctf_status)
2180  {
2181  case FLAG_PASSING:
2182  case FLAG_CARRY:
2183  {
2184  if((flag.owner == player) || (flag.pass_sender == player))
2185  STAT(OBJECTIVE_STATUS, player) |= t; // carrying: player is currently carrying the flag
2186  else
2187  STAT(OBJECTIVE_STATUS, player) |= t2; // taken: someone else is carrying the flag
2188  break;
2189  }
2190  case FLAG_DROPPED:
2191  {
2192  STAT(OBJECTIVE_STATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
2193  break;
2194  }
2195  }
2196  }
2197 
2198  // item for stopping players from capturing the flag too often
2199  if(player.ctf_captureshielded)
2200  STAT(OBJECTIVE_STATUS, player) |= CTF_SHIELDED;
2201 
2202  if(ctf_stalemate)
2203  STAT(OBJECTIVE_STATUS, player) |= CTF_STALEMATE;
2204 
2205  // update the health of the flag carrier waypointsprite
2206  if(player.wps_flagcarrier)
2207  WaypointSprite_UpdateHealth(player.wps_flagcarrier, healtharmor_maxdamage(GetResource(player, RES_HEALTH), GetResource(player, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
2208 }
2209 
2210 MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in damage.qc
2211 {
2212  entity frag_attacker = M_ARGV(1, entity);
2213  entity frag_target = M_ARGV(2, entity);
2214  float frag_damage = M_ARGV(4, float);
2215  vector frag_force = M_ARGV(6, vector);
2216 
2217  if(frag_attacker.flagcarried) // if the attacker is a flagcarrier
2218  {
2219  if(frag_target == frag_attacker) // damage done to yourself
2220  {
2223  }
2224  else // damage done to everyone else
2225  {
2228  }
2229 
2230  M_ARGV(4, float) = frag_damage;
2231  M_ARGV(6, vector) = frag_force;
2232  }
2233  else if(frag_target.flagcarried && !IS_DEAD(frag_target) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
2234  {
2235  if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > healtharmor_maxdamage(GetResource(frag_target, RES_HEALTH), GetResource(frag_target, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x
2236  && time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
2237  {
2238  frag_target.wps_helpme_time = time;
2239  WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
2240  }
2241  // todo: add notification for when flag carrier needs help?
2242  }
2243 }
2244 
2245 MUTATOR_HOOKFUNCTION(ctf, PlayerDies)
2246 {
2247  entity frag_attacker = M_ARGV(1, entity);
2248  entity frag_target = M_ARGV(2, entity);
2249 
2250  if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
2251  {
2252  GameRules_scoring_add_team(frag_attacker, SCORE, ((SAME_TEAM(frag_attacker, frag_target)) ? -autocvar_g_ctf_score_kill : autocvar_g_ctf_score_kill));
2253  GameRules_scoring_add(frag_attacker, CTF_FCKILLS, 1);
2254  }
2255 
2256  if(frag_target.flagcarried)
2257  {
2258  entity tmp_entity = frag_target.flagcarried;
2259  ctf_Handle_Throw(frag_target, NULL, DROP_NORMAL);
2260  tmp_entity.ctf_dropper = NULL;
2261  }
2262 }
2263 
2264 MUTATOR_HOOKFUNCTION(ctf, GiveFragsForKill)
2265 {
2266  M_ARGV(2, float) = 0; // frag score
2267  return (autocvar_g_ctf_ignore_frags); // no frags counted in ctf if this is true
2268 }
2269 
2271 {
2272  if(player.flagcarried)
2273  { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2274 
2275  for(entity flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2276  {
2277  if(flag.pass_sender == player) { flag.pass_sender = NULL; }
2278  if(flag.pass_target == player) { flag.pass_target = NULL; }
2279  if(flag.ctf_dropper == player) { flag.ctf_dropper = NULL; }
2280  }
2281 }
2282 
2283 MUTATOR_HOOKFUNCTION(ctf, MakePlayerObserver)
2284 {
2285  entity player = M_ARGV(0, entity);
2286 
2287  ctf_RemovePlayer(player);
2288 }
2289 
2291 {
2292  entity player = M_ARGV(0, entity);
2293 
2294  ctf_RemovePlayer(player);
2295 }
2296 
2298 {
2299  if(!autocvar_g_ctf_leaderboard)
2300  return;
2301 
2302  entity player = M_ARGV(0, entity);
2303 
2304  race_SendAll(player, true);
2305 }
2306 
2308 {
2309  if(!autocvar_g_ctf_leaderboard)
2310  return;
2311 
2312  entity player = M_ARGV(0, entity);
2313 
2314  race_checkAndWriteName(player);
2315 }
2316 
2317 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
2318 {
2319  entity player = M_ARGV(0, entity);
2320 
2321  if(player.flagcarried)
2323  { ctf_Handle_Throw(player, NULL, DROP_NORMAL); }
2324 }
2325 
2327 {
2328  if(MUTATOR_RETURNVALUE || game_stopped) return;
2329 
2330  entity player = M_ARGV(0, entity);
2331 
2332  if((time > player.throw_antispam) && !IS_DEAD(player) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
2333  {
2334  // pass the flag to a team mate
2336  {
2337  entity head, closest_target = NULL;
2338  head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, true);
2339 
2340  while(head) // find the closest acceptable target to pass to
2341  {
2342  if(IS_PLAYER(head) && !IS_DEAD(head))
2343  if(head != player && SAME_TEAM(head, player))
2344  if(!head.speedrunning && !head.vehicle)
2345  {
2346  // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc)
2347  vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
2348  vector passer_center = CENTER_OR_VIEWOFS(player);
2349 
2350  if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
2351  {
2352  if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
2353  {
2354  if(IS_BOT_CLIENT(head))
2355  {
2356  Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2357  ctf_Handle_Throw(head, player, DROP_PASS);
2358  }
2359  else
2360  {
2361  Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
2362  Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
2363  }
2364  player.throw_antispam = time + autocvar_g_ctf_pass_wait;
2365  return true;
2366  }
2367  else if(player.flagcarried && !head.flagcarried)
2368  {
2369  if(closest_target)
2370  {
2371  vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
2372  if(vlen2(passer_center - head_center) < vlen2(passer_center - closest_target_center))
2373  { closest_target = head; }
2374  }
2375  else { closest_target = head; }
2376  }
2377  }
2378  }
2379  head = head.chain;
2380  }
2381 
2382  if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return true; }
2383  }
2384 
2385  // throw the flag in front of you
2386  if(autocvar_g_ctf_throw && player.flagcarried)
2387  {
2388  if(player.throw_count == -1)
2389  {
2390  if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
2391  {
2392  player.throw_prevtime = time;
2393  player.throw_count = 1;
2394  ctf_Handle_Throw(player, NULL, DROP_THROW);
2395  return true;
2396  }
2397  else
2398  {
2399  Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
2400  return false;
2401  }
2402  }
2403  else
2404  {
2405  if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
2406  else { player.throw_count += 1; }
2407  if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
2408 
2409  player.throw_prevtime = time;
2410  ctf_Handle_Throw(player, NULL, DROP_THROW);
2411  return true;
2412  }
2413  }
2414  }
2415 }
2416 
2417 MUTATOR_HOOKFUNCTION(ctf, HelpMePing)
2418 {
2419  entity player = M_ARGV(0, entity);
2420 
2421  if(player.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
2422  {
2423  player.wps_helpme_time = time;
2424  WaypointSprite_HelpMePing(player.wps_flagcarrier);
2425  }
2426  else // create a normal help me waypointsprite
2427  {
2428  WaypointSprite_Spawn(WP_Helpme, waypointsprite_deployed_lifetime, waypointsprite_limitedrange, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_helpme, false, RADARICON_HELPME);
2429  WaypointSprite_Ping(player.wps_helpme);
2430  }
2431 
2432  return true;
2433 }
2434 
2435 MUTATOR_HOOKFUNCTION(ctf, VehicleEnter)
2436 {
2437  entity player = M_ARGV(0, entity);
2438  entity veh = M_ARGV(1, entity);
2439 
2440  if(player.flagcarried)
2441  {
2443  {
2444  ctf_Handle_Throw(player, NULL, DROP_NORMAL);
2445  }
2446  else
2447  {
2448  player.flagcarried.nodrawtoclient = player; // hide the flag from the driver
2449  setattachment(player.flagcarried, veh, "");
2450  setorigin(player.flagcarried, VEHICLE_FLAG_OFFSET);
2451  player.flagcarried.scale = VEHICLE_FLAG_SCALE;
2452  //player.flagcarried.angles = '0 0 0';
2453  }
2454  return true;
2455  }
2456 }
2457 
2458 MUTATOR_HOOKFUNCTION(ctf, VehicleExit)
2459 {
2460  entity player = M_ARGV(0, entity);
2461 
2462  if(player.flagcarried)
2463  {
2464  setattachment(player.flagcarried, player, "");
2465  setorigin(player.flagcarried, FLAG_CARRY_OFFSET);
2466  player.flagcarried.scale = FLAG_SCALE;
2467  player.flagcarried.angles = '0 0 0';
2468  player.flagcarried.nodrawtoclient = NULL;
2469  return true;
2470  }
2471 }
2472 
2473 MUTATOR_HOOKFUNCTION(ctf, AbortSpeedrun)
2474 {
2475  entity player = M_ARGV(0, entity);
2476 
2477  if(player.flagcarried)
2478  {
2479  Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_NUM(player.flagcarried.team, INFO_CTF_FLAGRETURN_ABORTRUN));
2480  ctf_RespawnFlag(player.flagcarried);
2481  return true;
2482  }
2483 }
2484 
2485 MUTATOR_HOOKFUNCTION(ctf, MatchEnd)
2486 {
2487  entity flag; // temporary entity for the search method
2488 
2489  for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
2490  {
2491  switch(flag.ctf_status)
2492  {
2493  case FLAG_DROPPED:
2494  case FLAG_PASSING:
2495  {
2496  // lock the flag, game is over
2497  set_movetype(flag, MOVETYPE_NONE);
2498  flag.takedamage = DAMAGE_NO;
2499  flag.solid = SOLID_NOT;
2500  flag.nextthink = false; // stop thinking
2501 
2502  //dprint("stopping the ", flag.netname, " from moving.\n");
2503  break;
2504  }
2505 
2506  default:
2507  case FLAG_BASE:
2508  case FLAG_CARRY:
2509  {
2510  // do nothing for these flags
2511  break;
2512  }
2513  }
2514  }
2515 }
2516 
2517 MUTATOR_HOOKFUNCTION(ctf, HavocBot_ChooseRole)
2518 {
2519  entity bot = M_ARGV(0, entity);
2520 
2522  return true;
2523 }
2524 
2526 {
2527  M_ARGV(1, string) = "ctf_team";
2528 }
2529 
2530 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
2531 {
2532  int record_page = M_ARGV(0, int);
2533  string ret_string = M_ARGV(1, string);
2534 
2535  for(int i = record_page * 200; i < MapInfo_count && i < record_page * 200 + 200; ++i)
2536  {
2537  if (MapInfo_Get_ByID(i))
2538  {
2539  float r = stof(db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/time")));
2540 
2541  if(!r)
2542  continue;
2543 
2544  // TODO: uid2name
2545  string h = db_get(ServerProgsDB, strcat(MapInfo_Map_bspname, "/captimerecord/netname"));
2546  ret_string = strcat(ret_string, strpad(32, MapInfo_Map_bspname), " ", strpad(-6, ftos_decimals(r, 2)), " ", h, "\n");
2547  }
2548  }
2549 
2550  M_ARGV(1, string) = ret_string;
2551 }
2552 
2553 bool superspec_Spectate(entity this, entity targ); // TODO
2554 void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel); // TODO
2556 {
2557  entity player = M_ARGV(0, entity);
2558  string cmd_name = M_ARGV(1, string);
2559  int cmd_argc = M_ARGV(2, int);
2560 
2561  if(IS_PLAYER(player) || MUTATOR_RETURNVALUE || !cvar("g_superspectate")) { return false; }
2562 
2563  if(cmd_name == "followfc")
2564  {
2565  if(!g_ctf)
2566  return true;
2567 
2568  int _team = 0;
2569  bool found = false;
2570 
2571  if(cmd_argc == 2)
2572  {
2573  switch(argv(1))
2574  {
2575  case "red": if(ctf_teams & BIT(0)) _team = NUM_TEAM_1; break;
2576  case "blue": if(ctf_teams & BIT(1)) _team = NUM_TEAM_2; break;
2577  case "yellow": if(ctf_teams & BIT(2)) _team = NUM_TEAM_3; break;
2578  case "pink": if(ctf_teams & BIT(3)) _team = NUM_TEAM_4; break;
2579  }
2580  }
2581 
2582  FOREACH_CLIENT(IS_PLAYER(it), {
2583  if(it.flagcarried && (it.team == _team || _team == 0))
2584  {
2585  found = true;
2586  if(_team == 0 && IS_SPEC(player) && player.enemy == it)
2587  continue; // already spectating this fc, try another
2588  return superspec_Spectate(player, it);
2589  }
2590  });
2591 
2592  if(!found)
2593  superspec_msg("", "", player, "No active flag carrier\n", 1);
2594  return true;
2595  }
2596 }
2597 
2598 MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
2599 {
2600  entity frag_target = M_ARGV(0, entity);
2601 
2602  if(frag_target.flagcarried)
2603  ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
2604 }
2605 
2606 MUTATOR_HOOKFUNCTION(ctf, LogDeath_AppendItemCodes)
2607 {
2608  entity player = M_ARGV(0, entity);
2609  if(player.flagcarried)
2610  M_ARGV(1, string) = strcat(M_ARGV(1, string), "F"); // item codes
2611 }
2612 
2613 
2614 // ==========
2615 // Spawnfuncs
2616 // ==========
2617 
2618 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2619 CTF flag for team one (Red).
2620 Keys:
2621 "angle" Angle the flag will point (minus 90 degrees)...
2622 "model" model to use, note this needs red and blue as skins 0 and 1...
2623 "noise" sound played when flag is picked up...
2624 "noise1" sound played when flag is returned by a teammate...
2625 "noise2" sound played when flag is captured...
2626 "noise3" sound played when flag is lost in the field and respawns itself...
2627 "noise4" sound played when flag is dropped by a player...
2628 "noise5" sound played when flag touches the ground... */
2629 spawnfunc(item_flag_team1)
2630 {
2631  if(!g_ctf) { delete(this); return; }
2632 
2633  ctf_FlagSetup(NUM_TEAM_1, this);
2634 }
2635 
2636 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2637 CTF flag for team two (Blue).
2638 Keys:
2639 "angle" Angle the flag will point (minus 90 degrees)...
2640 "model" model to use, note this needs red and blue as skins 0 and 1...
2641 "noise" sound played when flag is picked up...
2642 "noise1" sound played when flag is returned by a teammate...
2643 "noise2" sound played when flag is captured...
2644 "noise3" sound played when flag is lost in the field and respawns itself...
2645 "noise4" sound played when flag is dropped by a player...
2646 "noise5" sound played when flag touches the ground... */
2647 spawnfunc(item_flag_team2)
2648 {
2649  if(!g_ctf) { delete(this); return; }
2650 
2651  ctf_FlagSetup(NUM_TEAM_2, this);
2652 }
2653 
2654 /*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2655 CTF flag for team three (Yellow).
2656 Keys:
2657 "angle" Angle the flag will point (minus 90 degrees)...
2658 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2659 "noise" sound played when flag is picked up...
2660 "noise1" sound played when flag is returned by a teammate...
2661 "noise2" sound played when flag is captured...
2662 "noise3" sound played when flag is lost in the field and respawns itself...
2663 "noise4" sound played when flag is dropped by a player...
2664 "noise5" sound played when flag touches the ground... */
2665 spawnfunc(item_flag_team3)
2666 {
2667  if(!g_ctf) { delete(this); return; }
2668 
2669  ctf_FlagSetup(NUM_TEAM_3, this);
2670 }
2671 
2672 /*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2673 CTF flag for team four (Pink).
2674 Keys:
2675 "angle" Angle the flag will point (minus 90 degrees)...
2676 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2677 "noise" sound played when flag is picked up...
2678 "noise1" sound played when flag is returned by a teammate...
2679 "noise2" sound played when flag is captured...
2680 "noise3" sound played when flag is lost in the field and respawns itself...
2681 "noise4" sound played when flag is dropped by a player...
2682 "noise5" sound played when flag touches the ground... */
2683 spawnfunc(item_flag_team4)
2684 {
2685  if(!g_ctf) { delete(this); return; }
2686 
2687  ctf_FlagSetup(NUM_TEAM_4, this);
2688 }
2689 
2690 /*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
2691 CTF flag (Neutral).
2692 Keys:
2693 "angle" Angle the flag will point (minus 90 degrees)...
2694 "model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
2695 "noise" sound played when flag is picked up...
2696 "noise1" sound played when flag is returned by a teammate...
2697 "noise2" sound played when flag is captured...
2698 "noise3" sound played when flag is lost in the field and respawns itself...
2699 "noise4" sound played when flag is dropped by a player...
2700 "noise5" sound played when flag touches the ground... */
2701 spawnfunc(item_flag_neutral)
2702 {
2703  if(!g_ctf) { delete(this); return; }
2704  if(!cvar("g_ctf_oneflag")) { delete(this); return; }
2705 
2706  ctf_FlagSetup(0, this);
2707 }
2708 
2709 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
2710 Team declaration for CTF gameplay, this allows you to decide what team names and control point models are used in your map.
2711 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.
2712 Keys:
2713 "netname" Name of the team (for example Red, Blue, Green, Yellow, Life, Death, Offense, Defense, etc)...
2714 "cnt" Scoreboard color of the team (for example 4 is red and 13 is blue)... */
2715 spawnfunc(ctf_team)
2716 {
2717  if(!g_ctf) { delete(this); return; }
2718 
2719  this.team = this.cnt + 1;
2720 }
2721 
2722 // compatibility for quake maps
2723 spawnfunc(team_CTF_redflag) { spawnfunc_item_flag_team1(this); }
2724 spawnfunc(team_CTF_blueflag) { spawnfunc_item_flag_team2(this); }
2725 spawnfunc(info_player_team1);
2726 spawnfunc(team_CTF_redplayer) { spawnfunc_info_player_team1(this); }
2727 spawnfunc(team_CTF_redspawn) { spawnfunc_info_player_team1(this); }
2728 spawnfunc(info_player_team2);
2729 spawnfunc(team_CTF_blueplayer) { spawnfunc_info_player_team2(this); }
2730 spawnfunc(team_CTF_bluespawn) { spawnfunc_info_player_team2(this); }
2731 
2732 spawnfunc(team_CTF_neutralflag) { spawnfunc_item_flag_neutral(this); }
2733 spawnfunc(team_neutralobelisk) { spawnfunc_item_flag_neutral(this); }
2734 
2735 // compatibility for wop maps
2736 spawnfunc(team_redplayer) { spawnfunc_info_player_team1(this); }
2737 spawnfunc(team_blueplayer) { spawnfunc_info_player_team2(this); }
2738 spawnfunc(team_ctl_redlolly) { spawnfunc_item_flag_team1(this); }
2739 spawnfunc(team_CTL_redlolly) { spawnfunc_item_flag_team1(this); }
2740 spawnfunc(team_ctl_bluelolly) { spawnfunc_item_flag_team2(this); }
2741 spawnfunc(team_CTL_bluelolly) { spawnfunc_item_flag_team2(this); }
2742 
2743 
2744 // ==============
2745 // Initialization
2746 // ==============
2747 
2748 // scoreboard setup
2750 {
2752  field_team(ST_CTF_CAPS, "caps", SFL_SORT_PRIO_PRIMARY);
2753  field(SP_CTF_CAPS, "caps", SFL_SORT_PRIO_SECONDARY);
2754  field(SP_CTF_CAPTIME, "captime", SFL_LOWER_IS_BETTER | SFL_TIME);
2755  field(SP_CTF_PICKUPS, "pickups", 0);
2756  field(SP_CTF_FCKILLS, "fckills", 0);
2757  field(SP_CTF_RETURNS, "returns", 0);
2758  field(SP_CTF_DROPS, "drops", SFL_LOWER_IS_BETTER);
2759  });
2760 }
2761 
2762 // code from here on is just to support maps that don't have flag and team entities
2763 void ctf_SpawnTeam (string teamname, int teamcolor)
2764 {
2765  entity this = new_pure(ctf_team);
2766  this.netname = teamname;
2767  this.cnt = teamcolor - 1;
2768  this.spawnfunc_checked = true;
2769  this.team = teamcolor;
2770 }
2771 
2772 void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait for teams to be set up.
2773 {
2774  ctf_teams = 0;
2775 
2776  entity tmp_entity;
2777  for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
2778  {
2779  //if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
2780  //if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
2781 
2782  switch(tmp_entity.team)
2783  {
2784  case NUM_TEAM_1: BITSET_ASSIGN(ctf_teams, BIT(0)); break;
2785  case NUM_TEAM_2: BITSET_ASSIGN(ctf_teams, BIT(1)); break;
2786  case NUM_TEAM_3: BITSET_ASSIGN(ctf_teams, BIT(2)); break;
2787  case NUM_TEAM_4: BITSET_ASSIGN(ctf_teams, BIT(3)); break;
2788  }
2789  if(tmp_entity.team == 0) { ctf_oneflag = true; }
2790  }
2791 
2793 
2794  if(NumTeams(ctf_teams) < 2) // somehow, there's not enough flags!
2795  {
2796  ctf_teams = 0; // so set the default red and blue teams
2799  }
2800 
2801  //ctf_teams = bound(2, ctf_teams, 4);
2802 
2803  // if no teams are found, spawn defaults
2804  if(find(NULL, classname, "ctf_team") == NULL)
2805  {
2806  LOG_TRACE("No \"ctf_team\" entities found on this map, creating them anyway.");
2807  if(ctf_teams & BIT(0))
2808  ctf_SpawnTeam("Red", NUM_TEAM_1);
2809  if(ctf_teams & BIT(1))
2810  ctf_SpawnTeam("Blue", NUM_TEAM_2);
2811  if(ctf_teams & BIT(2))
2812  ctf_SpawnTeam("Yellow", NUM_TEAM_3);
2813  if(ctf_teams & BIT(3))
2814  ctf_SpawnTeam("Pink", NUM_TEAM_4);
2815  }
2816 
2818 }
2819 
2821 {
2822  CTF_FLAG = NEW(Flag);
2824  ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
2825 
2829 
2830  InitializeEntity(NULL, ctf_DelayedInit, INITPRIO_GAMETYPE);
2831 }
void race_SendAll(entity player, bool only_rankings)
Definition: race.qc:324
#define FLAG_SPAWN_OFFSET
Definition: sv_ctf.qh:50
const float SOLID_NOT
Definition: csprogsdefs.qc:244
void ctf_FakeTimeLimit(entity e, float t)
Definition: sv_ctf.qc:93
bool ctf_Stalemate_Customize(entity this, entity client)
Definition: sv_ctf.qc:822
const float WPFE_THINKRATE
Definition: sv_ctf.qh:46
bool autocvar_g_ctf_allow_vehicle_touch
Definition: sv_ctf.qc:21
const vector FLAG_DROP_OFFSET
Definition: sv_ctf.qh:48
#define APP_TEAM_NUM(num, prefix)
Definition: all.qh:85
#define GameRules_scoring(teams, spprio, stprio, fields)
Definition: sv_rules.qh:53
bool wpforenemy_announced
Definition: sv_ctf.qh:102
float MOVETYPE_NONE
Definition: progsdefs.qc:246
float last_respawn
Definition: sv_ctf.qc:1162
float havocbot_role_timeout
Definition: api.qh:46
bool autocvar_g_ctf_oneflag_reverse
Definition: sv_ctf.qc:34
float autocvar_g_ctf_pass_directional_min
Definition: sv_ctf.qc:40
int autocvar_g_ctf_score_pickup_dropped_late
Definition: sv_ctf.qc:81
ERASEABLE void db_put(int db, string key, string value)
Definition: map.qh:101
const int FLAG_CARRY
Definition: sv_ctf.qh:108
float autocvar_g_ctf_flag_collect_delay
Definition: sv_ctf.qc:48
int autocvar_g_ctf_score_pickup_base
Definition: sv_ctf.qc:79
void havocbot_ctf_reset_role(entity this)
Definition: sv_ctf.qc:1623
void navigation_goalrating_start(entity this)
Definition: navigation.qc:1830
ERASEABLE string ftos_decimals(float number, int decimals)
converts a number to a string with the indicated number of decimals
Definition: string.qh:450
int autocvar_g_ctf_stalemate_endcondition
Definition: sv_ctf.qc:87
const int RETURN_DAMAGE
Definition: sv_ctf.qh:124
int autocvar_g_ctf_score_return
Definition: sv_ctf.qc:82
bool ctf_CaptureShield_Customize(entity this, entity client)
Definition: sv_ctf.qc:304
const int NUM_TEAM_2
Definition: teams.qh:19
int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
Definition: sv_ctf.qc:1472
void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius)
Definition: roles.qc:106
#define SND(id)
Definition: all.qh:35
float autocvar_g_ctf_drop_velocity_up
Definition: sv_ctf.qc:32
#define NEW(cname,...)
Definition: oo.qh:105
void ClientConnect(entity this)
ClientConnect
Definition: client.qc:1096
int autocvar_g_ctf_flag_health
Definition: sv_ctf.qc:53
bool SetResourceExplicit(entity e, Resource res_type, float amount)
Sets the resource amount of an entity without calling any hooks.
Definition: cl_resources.qc:15
const int HAVOCBOT_CTF_ROLE_MIDDLE
Definition: sv_ctf.qh:165
spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2 f1 f1points s1 s2
Definition: all.inc:438
#define WPCOLOR_DROPPEDFLAG(t)
Definition: sv_ctf.qh:62
void ctf_SpawnTeam(string teamname, int teamcolor)
Definition: sv_ctf.qc:2763
#define g_ctf
Definition: ctf.qh:39
int NumTeams(int teams)
Definition: scores_rules.qc:17
const int ST_SCORE
Definition: scores.qh:151
void navigation_goalrating_end(entity this)
Definition: navigation.qc:1845
void WarpZone_RefSys_AddInverse(entity me, entity wz)
Definition: common.qc:726
void havocbot_goalrating_ctf_ourbase(entity this, float ratingscale)
Definition: sv_ctf.qc:1507
float autocvar_g_ctf_pass_arc_max
Definition: sv_ctf.qc:38
bool havocbot_ctf_is_basewaypoint(entity item)
Definition: sv_ctf.qc:1702
bool superspec_Spectate(entity this, entity targ)
Definition: sv_superspec.qc:28
float autocvar_g_ctf_pass_directional_max
Definition: sv_ctf.qc:39
float autocvar_g_ctf_shield_force
Definition: sv_ctf.qc:83
const int TELEPORT_NORMAL
Definition: teleporters.qh:26
const int DROP_NORMAL
Definition: sv_ctf.qh:111
void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition: sv_ctf.qc:901
int team
Definition: main.qh:157
float MOVETYPE_TOSS
Definition: progsdefs.qc:252
string GetMapname()
Definition: intermission.qc:18
float autocvar_g_ctf_throw_punish_delay
Definition: sv_ctf.qc:27
#define snd_flag_respawn
Definition: sv_ctf.qh:68
const int FLAG_PASSING
Definition: sv_ctf.qh:109
float DPCONTENTS_MONSTERCLIP
entity wps_flagbase
Definition: sv_ctf.qh:96
vector Team_ColorRGB(int teamid)
Definition: teams.qh:76
const int EF_DIMLIGHT
float autocvar_g_ctf_flag_damageforcescale
Definition: sv_ctf.qc:49
const int FLAG_DROPPED
Definition: sv_ctf.qh:107
float autocvar_g_ctf_flag_return_time
Definition: sv_ctf.qc:57
float TeamScore_AddToTeam(int t, float scorefield, float score)
Adds a score to the given team.
Definition: scores.qc:108
void GameRules_scoring_vip(entity player, bool value)
Mark a player as being &#39;important&#39; (flag carrier, ball carrier, etc)
Definition: sv_rules.qc:98
const float FLAG_TOUCHRATE
Definition: sv_ctf.qh:45
float autocvar_g_ctf_drop_velocity_side
Definition: sv_ctf.qc:33
IntrusiveList g_damagedbycontents
Definition: damage.qh:155
const int SFL_SORT_PRIO_SECONDARY
Scoring priority (NOTE: PRIMARY is used for fraglimit)
Definition: scores.qh:126
float goalentity_lock_timeout
Definition: api.qh:97
float ServerProgsDB
Definition: world.qh:131
void WarpZone_RefSys_Copy(entity me, entity from)
Definition: common.qc:780
float trace_dphitcontents
entity() spawn
void navigation_dynamicgoal_init(entity this, bool initially_static)
Definition: navigation.qc:76
bool ctf_CaptureShield_CheckStatus(entity p)
Definition: sv_ctf.qc:251
void ctf_FlagcarrierWaypoints(entity player)
Definition: sv_ctf.qc:157
const int FLAG_FLOAT_OFFSET_Z
Definition: sv_ctf.qh:52
ClientState CS(Client this)
Definition: state.qh:47
const int HAVOCBOT_CTF_ROLE_RETRIEVER
Definition: sv_ctf.qh:168
const vector FLAG_WAYPOINT_OFFSET
Definition: sv_ctf.qh:51
bool speedrunning
Definition: cheats.qh:21
#define FOREACH_CLIENT(cond, body)
Definition: utils.qh:49
const float ATTEN_NONE
Definition: sound.qh:27
void havocbot_role_ctf_defense(entity this)
Definition: sv_ctf.qc:2033
#define GameRules_scoring_add(client, fld, value)
Definition: sv_rules.qh:78
vector maxs
Definition: csprogsdefs.qc:113
#define CTF_RECORD
Definition: util.qh:60
Definition: sv_ctf.qh:8
void race_checkAndWriteName(entity player)
Definition: race.qc:143
bool autocvar_g_ctf_flag_return_when_unreachable
Definition: sv_ctf.qc:58
const int PICKUP_DROPPED
Definition: sv_ctf.qh:117
float checkpvs(vector viewpos, entity viewee)
float autocvar_g_ctf_flagcarrier_damagefactor
Definition: sv_ctf.qc:68
const int SFL_SORT_PRIO_PRIMARY
Definition: scores.qh:127
float autocvar_g_ctf_dropped_capture_delay
Definition: sv_ctf.qc:90
string netname
Definition: powerups.qc:20
float ctf_captureshield_force
Definition: sv_ctf.qh:157
entity flagcarried
Definition: sv_ctf.qh:82
#define IS_MONSTER(v)
Definition: utils.qh:21
bool autocvar_g_ctf_flag_dropped_floatinwater
Definition: sv_ctf.qc:51
void navigation_dynamicgoal_set(entity this, entity dropper)
Definition: navigation.qc:86
void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
Definition: sv_ctf.qc:561
bool ctf_FlagBase_Customize(entity this, entity client)
Definition: sv_ctf.qc:1228
bool autocvar_g_ctf_pass
Definition: sv_ctf.qc:36
float DPCONTENTS_PLAYERCLIP
const int RETURN_NEEDKILL
Definition: sv_ctf.qh:126
void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
Definition: sv_ctf.qc:1531
float autocvar_g_ctf_flagcarrier_forcefactor
Definition: sv_ctf.qc:69
entity to
Definition: self.qh:96
bool MapInfo_Get_ByID(int i)
Definition: mapinfo.qc:256
float ctf_droptime
Definition: sv_ctf.qh:137
#define APP_NUM(num, prefix)
Definition: all.qh:86
entity bot_aimtarg
Definition: aim.qh:83
float autocvar_g_ctf_flag_return_carried_radius
Definition: sv_ctf.qc:56
origin
Definition: ent_cs.qc:114
#define droptofloor
Definition: pre.qh:5
string classname
Definition: csprogsdefs.qc:107
float autocvar_g_ctf_pass_wait
Definition: sv_ctf.qc:42
bool autocvar_g_ctf_pass_request
Definition: sv_ctf.qc:43
#define METHOD(cname, name, prototype)
Definition: oo.qh:257
float noalign
Definition: items.qh:42
#define WPCOLOR_FLAGCARRIER(t)
Definition: sv_ctf.qh:60
#define DIFF_TEAM(a, b)
Definition: teams.qh:240
float autocvar_g_ctf_pass_timelimit
Definition: sv_ctf.qc:45
float FL_ITEM
Definition: progsdefs.qc:239
void ctf_DelayedFlagSetup(entity this)
Definition: sv_ctf.qc:1240
bool autocvar_g_ctf_allow_monster_touch
Definition: sv_ctf.qc:22
float havocbot_middlepoint_radius
Definition: api.qh:92
const int ST_CTF_CAPS
Definition: sv_ctf.qh:36
vector m_maxs
Definition: sv_ctf.qh:10
void navigation_goalrating_timeout_force(entity this)
Definition: navigation.qc:28
bool havocbot_cantfindflag
Definition: sv_ctf.qh:171
void havocbot_role_ctf_retriever(entity this)
Definition: sv_ctf.qc:1916
void PlayerPreThink(entity this)
Definition: client.qc:2402
void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org, float sradius)
Definition: roles.qc:176
const int HAVOCBOT_CTF_ROLE_CARRIER
Definition: sv_ctf.qh:167
void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnrate)
Definition: sv_ctf.qc:186
void havocbot_goalrating_ctf_ourstolenflag(entity this, float ratingscale)
Definition: sv_ctf.qc:1587
void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector org, float df_radius)
Definition: sv_ctf.qc:1600
entity havocbot_ctf_find_enemy_flag(entity bot)
Definition: sv_ctf.qc:1446
#define Static_Team_ColorName_Lower(teamid)
Definition: teams.qh:226
#define DMG_NOWEP
Definition: damage.qh:126
entity owner
Definition: main.qh:73
int autocvar_g_ctf_score_penalty_returned
Definition: sv_ctf.qc:78
void write_recordmarker(entity pl, float tstart, float dt)
Definition: race.qc:57
void havocbot_role_ctf_escort(entity this)
Definition: sv_ctf.qc:1770
MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
Definition: sv_ctf.qc:2155
bool autocvar_g_ctf_allow_vehicle_carry
Definition: sv_ctf.qc:20
ClientDisconnect(this)
float move_movetype
Definition: movetypes.qh:76
string snd_flag_touch
Definition: sv_ctf.qh:70
bool ctf_Return_Customize(entity this, entity client)
Definition: sv_ctf.qc:151
void ctf_FlagSetup(int teamnum, entity flag)
Definition: sv_ctf.qc:1272
const int SFL_LOWER_IS_BETTER
Lower scores are better (e.g.
Definition: scores.qh:98
#define BITSET_ASSIGN(a, b)
Definition: common.qh:104
bool autocvar_g_ctf_fullbrightflags
Definition: sv_ctf.qc:71
void navigation_routerating(entity this, entity e, float f, float rangebias)
Definition: navigation.qc:1220
const int FLAG_BASE
Definition: sv_ctf.qh:106
void ctf_Handle_Drop(entity flag, entity player, int droptype)
Definition: sv_ctf.qc:348
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1
Definition: all.inc:654
int autocvar_g_ctf_score_capture_assist
Definition: sv_ctf.qc:75
void havocbot_role_ctf_offense(entity this)
Definition: sv_ctf.qc:1838
#define IS_REAL_CLIENT(v)
Definition: utils.qh:17
entity trace_ent
Definition: csprogsdefs.qc:40
entity EFFECT_PASS(int teamid)
Definition: all.inc:205
vector WarpZone_UnTransformOrigin(entity wz, vector v)
Definition: common.qc:535
float autocvar_g_ctf_flagcarrier_selfforcefactor
Definition: sv_ctf.qc:67
void TakeResource(entity receiver, Resource res_type, float amount)
Takes an entity some resource.
Definition: cl_resources.qc:31
#define CTF_DIFFTEAM(a, b)
Definition: sv_ctf.qh:177
#define ctf_spawnorigin
Definition: sv_ctf.qh:133
const float EF_ADDITIVE
Definition: csprogsdefs.qc:300
bool autocvar_g_ctf_reverse
Definition: sv_ctf.qc:89
#define setmodel(this, m)
Definition: model.qh:26
const vector VEHICLE_FLAG_OFFSET
Definition: sv_ctf.qh:55
entity teams
Definition: main.qh:44
vector absmax
Definition: csprogsdefs.qc:92
RES_HEALTH
Definition: ent_cs.qc:126
void ctf_FlagThink(entity this)
Definition: sv_ctf.qc:923
entity wps_enemyflagcarrier
Definition: sv_ctf.qh:100
bool autocvar_g_ctf_flag_dropped_waypoint
Definition: sv_ctf.qc:50
float autocvar_g_ctf_flag_waypoint_maxdistance
Definition: sv_ctf.qc:63
const float CONTENT_EMPTY
Definition: csprogsdefs.qc:236
#define TIME_ENCODE(t)
Definition: util.qh:61
const float EF_FULLBRIGHT
Definition: csprogsdefs.qc:303
void havocbot_role_ctf_middle(entity this)
Definition: sv_ctf.qc:1974
const int HAVOCBOT_CTF_ROLE_ESCORT
Definition: sv_ctf.qh:169
void ctf_CaptureShield_Spawn(entity flag)
Definition: sv_ctf.qc:324
vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
Definition: common.qc:748
float havocbot_symmetry_axis_q
Definition: api.qh:94
bool ctf_flagdamaged_byworld
Definition: sv_ctf.qh:141
#define BIT(n)
Only ever assign into the first 24 bits in QC (so max is BIT(23)).
Definition: bits.qh:8
float MapInfo_count
Definition: mapinfo.qh:144
float havocbot_symmetry_axis_m
Definition: api.qh:93
entity enemy
Definition: sv_ctf.qh:143
void SV_ParseClientCommand(entity this, string command)
Definition: cmd.qc:866
void ctf_CaptureRecord(entity flag, entity player)
Definition: sv_ctf.qc:111
float wpforenemy_nextthink
Definition: sv_ctf.qh:103
float autocvar_g_ctf_flagcarrier_auto_helpme_damage
Definition: sv_ctf.qc:64
float autocvar_g_ctf_pass_arc
Definition: sv_ctf.qc:37
const float MOVE_NOMONSTERS
Definition: csprogsdefs.qc:253
entity msg_entity
Definition: progsdefs.qc:63
spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 spree_inf s1 s2 s3loc s2 s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2loc s1 s2 f1 f1points f2
Definition: all.inc:348
vector mins
Definition: csprogsdefs.qc:113
#define vlen2(v)
Definition: vector.qh:4
entity pass_target
Definition: sv_ctf.qh:148
const int HAVOCBOT_CTF_ROLE_OFFENSE
Definition: sv_ctf.qh:166
float cnt
Definition: powerups.qc:24
int autocvar_g_ctf_shield_min_negscore
Definition: sv_ctf.qc:85
ERASEABLE entity IL_PUSH(IntrusiveList this, entity it)
Push to tail.
void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
Definition: sv_ctf.qc:1567
bool autocvar_g_ctf_flag_return
Definition: sv_ctf.qc:54
const float VEHICLE_FLAG_SCALE
Definition: sv_ctf.qh:56
float autocvar_g_ctf_throw_velocity_up
Definition: sv_ctf.qc:31
int autocvar_g_ctf_throw_punish_count
Definition: sv_ctf.qc:26
void waypoint_spawnforitem_force(entity e, vector org)
Definition: waypoints.qc:1978
float autocvar_g_ctf_stalemate_time
Definition: sv_ctf.qc:88
const float EF_BLUE
Definition: csprogsdefs.qc:301
bool navigation_goalrating_timeout(entity this)
Definition: navigation.qc:43
string snd_flag_dropped
Definition: sv_ctf.qh:69
#define snd_flag_taken
Definition: sv_ctf.qh:65
string record_type
Definition: world.qh:49
spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 spree_cen s1 f1 s1 strcat(_("Level %s: "), "^BG%s\3\, _("^BGPress ^F2%s^BG to enter the game"))
bool ctf_Immediate_Return_Allowed(entity flag, entity toucher)
Definition: sv_ctf.qc:141
void GetPressedKeys(entity this)
Definition: client.qc:1681
#define GameRules_scoring_add_team(client, fld, value)
Definition: sv_rules.qh:80
bool autocvar_g_ctf_dynamiclights
Definition: sv_ctf.qc:47
void ctf_Handle_Throw(entity player, entity receiver, int droptype)
Definition: sv_ctf.qc:444
const int CH_TRIGGER
Definition: sound.qh:12
bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
Definition: sv_ctf.qc:217
void PlayerUseKey(entity this)
Definition: client.qc:2349
const float CONTENT_WATER
Definition: csprogsdefs.qc:238
IntrusiveList g_items
Definition: items.qh:126
void ctf_CheckFlagReturn(entity flag, int returntype)
Definition: sv_ctf.qc:792
vector havocbot_middlepoint
Definition: api.qh:91
const vector FLAG_CARRY_OFFSET
Definition: sv_ctf.qh:49
entity goalentity
Definition: progsdefs.qc:189
entity wps_helpme
Definition: sv_ctf.qh:95
float autocvar_g_ctf_shield_max_ratio
Definition: sv_ctf.qc:84
const int CAPTURE_NORMAL
Definition: sv_ctf.qh:119
#define NULL
Definition: post.qh:17
float autocvar_g_ctf_pass_turnrate
Definition: sv_ctf.qc:44
bool autocvar_g_ctf_stalemate
Definition: sv_ctf.qc:86
float DPCONTENTS_SOLID
float max_health
#define backtrace(msg)
Definition: log.qh:105
#define crandom()
Returns a random number between -1.0 and 1.0.
Definition: math.qh:27
int ctf_teams
Definition: sv_ctf.qh:142
const float VOL_BASE
Definition: sound.qh:36
int autocvar_g_ctf_score_capture
Definition: sv_ctf.qc:74
float autocvar_g_ctf_throw_angle_min
Definition: sv_ctf.qc:25
float jointime
Definition: client.qh:64
vector trace_endpos
Definition: csprogsdefs.qc:37
bool autocvar_g_ctf_throw
Definition: sv_ctf.qc:23
void havocbot_role_ctf_carrier(entity this)
Definition: sv_ctf.qc:1717
float autocvar_g_ctf_throw_punish_time
Definition: sv_ctf.qc:28
void GameLogEcho(string s)
Definition: gamelog.qc:12
const int FLAG_PASS_ARC_OFFSET_Z
Definition: sv_ctf.qh:53
ERASEABLE string db_get(int db, string key)
Definition: map.qh:91
float autocvar_g_ctf_throw_velocity_forward
Definition: sv_ctf.qc:30
float ctf_captureshield_min_negscore
Definition: sv_ctf.qh:155
void Damage(entity targ, entity inflictor, entity attacker, float damage, int deathtype,.entity weaponentity, vector hitloc, vector force)
Definition: damage.qc:583
#define MUTATOR_RETURNVALUE
Definition: base.qh:303
#define SAME_TEAM(a, b)
Definition: teams.qh:239
#define Team_ColorName_Upper(teamid)
Definition: teams.qh:223
void nades_GiveBonus(entity player, float score)
float teamplay
Definition: progsdefs.qc:31
#define M_ARGV(x, type)
Definition: events.qh:17
#define IS_DEAD(s)
Definition: utils.qh:26
entity WarpZone_FindRadius(vector org, float rad, bool needlineofsight)
Definition: common.qc:669
const float ATTEN_NORM
Definition: sound.qh:30
float nextthink
Definition: csprogsdefs.qc:121
void ctf_EventLog(string mode, int flagteam, entity actor)
Definition: sv_ctf.qc:104
bool autocvar_g_ctf_flag_waypoint
Definition: sv_ctf.qc:62
void navigation_goalrating_timeout_set(entity this)
Definition: navigation.qc:19
void ctf_CaptureShield_Touch(entity this, entity toucher)
Definition: sv_ctf.qc:312
vector m_mins
Definition: sv_ctf.qh:9
void navigation_goalrating_timeout_expire(entity this, float seconds)
Definition: navigation.qc:35
const int HAVOCBOT_CTF_ROLE_DEFENSE
Definition: sv_ctf.qh:164
void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
Definition: sv_ctf.qc:681
float autocvar_g_ctf_flag_return_damage_delay
Definition: sv_ctf.qc:60
const float FLAG_SCALE
Definition: sv_ctf.qh:42
#define CTF_SAMETEAM(a, b)
Definition: sv_ctf.qh:176
void ctf_RespawnFlag(entity flag)
Definition: sv_ctf.qc:1163
#define CENTER_OR_VIEWOFS(ent)
Definition: utils.qh:28
vector(float skel, float bonenum) _skel_get_boneabs_hidden
#define IS_VEHICLE(v)
Definition: utils.qh:22
#define ITEM_DAMAGE_NEEDKILL(dt)
Definition: items.qh:130
const int NUM_TEAM_4
Definition: teams.qh:21
void navigation_dynamicgoal_unset(entity this)
Definition: navigation.qc:95
void ctf_DelayedInit(entity this)
Definition: sv_ctf.qc:2772
const float EF_RED
Definition: csprogsdefs.qc:307
float autocvar_g_ctf_flag_return_damage
Definition: sv_ctf.qc:59
string Team_ColorCode(int teamid)
Definition: teams.qh:63
float MOVETYPE_NOCLIP
Definition: progsdefs.qc:254
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
Definition: cl_resources.qc:10
const float FLAG_THINKRATE
Definition: sv_ctf.qh:44
void ctf_Handle_Retrieve(entity flag, entity player)
Definition: sv_ctf.qc:393
#define ITEM_TOUCH_NEEDKILL()
Definition: items.qh:129
int autocvar_g_ctf_score_kill
Definition: sv_ctf.qc:76
float start_health
Definition: world.qh:98
void InitializeEntity(entity e, void(entity this) func, int order)
Definition: world.qc:2146
const int DROP_RESET
Definition: sv_ctf.qh:114
float RAD2DEG
Definition: csprogsdefs.qc:962
string MapInfo_Map_bspname
Definition: mapinfo.qh:6
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
float count
Definition: powerups.qc:22
const vector eZ
Definition: vector.qh:46
float ctf_captimerecord
Definition: sv_ctf.qh:135
#define LOG_TRACE(...)
Definition: log.qh:81
bool autocvar_g_ctf_flag_return_carrying
Definition: sv_ctf.qc:55
const int DROP_PASS
Definition: sv_ctf.qh:113
int autocvar_g_ctf_score_pickup_dropped_early
Definition: sv_ctf.qc:80
void WarpZone_TraceLine(vector org, vector end, float nomonsters, entity forent)
Definition: common.qc:338
bool autocvar_g_ctf_ignore_frags
Definition: sv_ctf.qc:72
Flag CTF_FLAG
Definition: sv_ctf.qh:12
entity wps_flagcarrier
Definition: sv_ctf.qh:97
#define X(s, b)
#define _sound(e, c, s, v, a)
Definition: sound.qh:50
float autocvar_g_ctf_pass_radius
Definition: sv_ctf.qc:41
const float SOLID_TRIGGER
Definition: csprogsdefs.qc:245
entity EFFECT_FLAG_TOUCH(int teamid)
Definition: all.inc:189
#define snd_flag_returned
Definition: sv_ctf.qh:66
void WarpZone_TrailParticles(entity own, float eff, vector org, vector end)
Definition: common.qc:464
entity wps_flagdropped
Definition: sv_ctf.qh:98
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition: utils.qh:15
float autocvar_g_ctf_pass_velocity
Definition: sv_ctf.qc:46
float tanh(float e)
Definition: mathlib.qc:68
float autocvar_g_ctf_flagcarrier_auto_helpme_time
Definition: sv_ctf.qc:65
const int DROP_THROW
Definition: sv_ctf.qh:112
float havocbot_symmetry_origin_order
Definition: api.qh:95
#define new_pure(class)
purely logical entities (.origin doesn&#39;t work)
Definition: oo.qh:62
setorigin(ent, v)
void ctf_Handle_Return(entity flag, entity player)
Definition: sv_ctf.qc:638
void ctf_Initialize()
Definition: sv_ctf.qc:2820
#define setthink(e, f)
void ctf_ScoreRules(int teams)
Definition: sv_ctf.qc:2749
entity havocbot_ctf_find_flag(entity bot)
Definition: sv_ctf.qc:1433
entity wps_flagreturn
Definition: sv_ctf.qh:99
float ctf_pickuptime
Definition: sv_ctf.qh:136
float ctf_captureshield_max_ratio
Definition: sv_ctf.qh:156
float autocvar_g_ctf_dropped_capture_radius
Definition: sv_ctf.qc:91
vector angles
Definition: csprogsdefs.qc:104
const int NUM_TEAM_1
Definition: teams.qh:18
const int PICKUP_BASE
Definition: sv_ctf.qh:116
string snd_flag_pass
Definition: sv_ctf.qh:71
void superspec_msg(string _center_title, string _con_title, entity _to, string _msg, float _spamlevel)
Definition: sv_superspec.qc:68
float trace_startsolid
Definition: csprogsdefs.qc:35
float autocvar_g_ctf_flag_return_dropped
Definition: sv_ctf.qc:61
void havocbot_role_ctf_setrole(entity bot, int role)
Definition: sv_ctf.qc:2102
entity TeamBalance_CheckAllowedTeams(entity for_whom)
Checks whether the player can join teams according to global configuration and mutator settings...
Definition: teamplay.qc:487
float FL_NOTARGET
Definition: progsdefs.qc:238
void ctf_RemovePlayer(entity player)
Definition: sv_ctf.qc:2270
void ctf_FlagTouch(entity this, entity toucher)
Definition: sv_ctf.qh:38
vector W_CalculateProjectileVelocity(entity actor, vector pvelocity, vector mvelocity, float forceAbsolute)
Definition: tracing.qc:169
vector absmin
Definition: csprogsdefs.qc:92
#define snd_flag_capture
Definition: sv_ctf.qh:67
bool pushable
Definition: sv_ctf.qc:1270
bool autocvar_sv_eventlog
Definition: gamelog.qh:3
float autocvar_g_ctf_throw_strengthmultiplier
Definition: sv_ctf.qc:29
#define WPCOLOR_ENEMYFC(t)
Definition: sv_ctf.qh:59
void ctf_CaptureShield_Update(entity player, bool wanted_status)
Definition: sv_ctf.qc:294
float autocvar_g_balance_armor_blockpercent
Definition: damage.qh:21
const int RETURN_SPEEDRUN
Definition: sv_ctf.qh:125
float time
Definition: csprogsdefs.qc:16
entity ctf_worldflaglist
Definition: sv_ctf.qh:90
vector velocity
Definition: csprogsdefs.qc:103
const int RETURN_TIMEOUT
Definition: sv_ctf.qh:122
float start_armorvalue
Definition: world.qh:99
const float FLOAT_EPSILON
Definition: float.qh:4
bool autocvar_g_ctf_portalteleport
Definition: sv_ctf.qc:35
bool autocvar_g_ctf_flag_glowtrails
Definition: sv_ctf.qc:52
bool ctf_stalemate
Definition: sv_ctf.qh:134
float autocvar_g_ctf_throw_angle_max
Definition: sv_ctf.qc:24
void race_setTime(string map, float t, string myuid, string mynetname, entity e, bool showmessage)
Definition: race.qc:365
#define makevectors
Definition: post.qh:21
float trace_fraction
Definition: csprogsdefs.qc:36
#define boolean(value)
Definition: bool.qh:9
spawnfunc(item_flag_team1)
Definition: sv_ctf.qc:2629
float DAMAGE_NO
Definition: progsdefs.qc:282
const int SFL_TIME
Display as mm:ss.s, value is stored as 10ths of a second (AND 0 is the worst possible value!) ...
Definition: scores.qh:118
bool ctf_oneflag
Definition: sv_ctf.qh:160
const int CAPTURE_DROPPED
Definition: sv_ctf.qh:120
float autocvar_g_ctf_flagcarrier_selfdamagefactor
Definition: sv_ctf.qc:66
void set_movetype(entity this, int mt)
entity EFFECT_CAP(int teamid)
Definition: all.inc:221
float MOVETYPE_FLY
Definition: progsdefs.qc:251
void ctf_CheckStalemate()
Definition: sv_ctf.qc:836
#define IS_PLAYER(v)
Definition: utils.qh:9
#define colormapPaletteColor(c, isPants)
Definition: color.qh:5
float EF_LOWPRECISION
const int RETURN_DROPPED
Definition: sv_ctf.qh:123
int ctf_status
Definition: sv_ctf.qh:138
const int NUM_TEAM_3
Definition: teams.qh:20
int autocvar_g_ctf_score_penalty_drop
Definition: sv_ctf.qc:77
void havocbot_ctf_calculate_middlepoint()
Definition: sv_ctf.qc:1396
vector v_forward
Definition: csprogsdefs.qc:31
void ImpulseCommands(entity this)
Definition: impulse.qc:372
float DAMAGE_YES
Definition: progsdefs.qc:283
bool autocvar_g_ctf_score_ignore_fields
Definition: sv_ctf.qc:73
void ctf_Reset(entity this)
Definition: sv_ctf.qc:1219