Xonotic
havocbot.qc
Go to the documentation of this file.
1 #include "havocbot.qh"
2 
3 #include <common/constants.qh>
4 #include <common/impulses/all.qh>
5 #include <common/items/_mod.qh>
12 #include <common/net_linked.qh>
13 #include <common/physics/player.qh>
14 #include <common/state.qh>
15 #include <common/stats.qh>
16 #include <common/weapons/_all.qh>
17 #include <common/wepent.qh>
18 #include <lib/warpzone/common.qh>
19 #include <server/bot/api.qh>
27 #include <server/client.qh>
28 #include <server/damage.qh>
29 #include <server/items/items.qh>
30 #include <server/mutators/_mod.qh>
33 #include <server/world.qh>
34 
35 void havocbot_ai(entity this)
36 {
37  if(this.draggedby)
38  return;
39 
40  this.bot_aimdir_executed = false;
41  // lock aim if teleported or passing through a warpzone
42  if (this.lastteleporttime && !this.jumppadcount)
43  this.bot_aimdir_executed = true;
44 
45  if(bot_execute_commands(this))
46  return;
47 
48  // after bot_execute_commands otherwise bots can't be unpaused
49  if (bot_ispaused(this))
50  return;
51 
53  {
54  if(this.havocbot_blockhead)
55  {
56  this.havocbot_blockhead = false;
57  }
58  else
59  {
60  if (!this.jumppadcount && !STAT(FROZEN, this)
61  && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP) && !IS_ONGROUND(this)))
62  {
63  // find a new goal
64  this.havocbot_role(this); // little too far down the rabbit hole
65  }
66  }
67 
68  // if we don't have a goal and we're under water look for a waypoint near the "shore" and push it
69  if(!(IS_DEAD(this) || STAT(FROZEN, this)))
70  if(!this.goalcurrent)
72  {
73  // Look for the closest waypoint out of water
74  entity newgoal = NULL;
75  IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 10000),
76  {
77  if(it.origin.z < this.origin.z)
78  continue;
79 
80  if(it.origin.z - this.origin.z - this.view_ofs.z > 100)
81  continue;
82 
83  if (pointcontents(it.origin + it.maxs + '0 0 1') != CONTENT_EMPTY)
84  continue;
85 
86  traceline(this.origin + this.view_ofs, ((it.absmin + it.absmax) * 0.5), true, this);
87 
88  if(trace_fraction < 1)
89  continue;
90 
91  if(!newgoal || vlen2(it.origin - this.origin) < vlen2(newgoal.origin - this.origin))
92  newgoal = it;
93  });
94 
95  if(newgoal)
96  {
97  // te_wizspike(newgoal.origin);
98  navigation_pushroute(this, newgoal);
99  }
100  }
101 
102  // token has been used this frame
104  }
105 
106  if (this.goalcurrent && wasfreed(this.goalcurrent))
107  {
108  navigation_clearroute(this);
110  return;
111  }
112 
113  if(IS_DEAD(this) || STAT(FROZEN, this))
114  {
115  if (this.goalcurrent)
116  navigation_clearroute(this);
117  this.enemy = NULL;
118  this.bot_aimtarg = NULL;
119  return;
120  }
121 
122  havocbot_chooseenemy(this);
123 
124  for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
125  {
126  .entity weaponentity = weaponentities[slot];
127  if(this.(weaponentity).m_weapon != WEP_Null || slot == 0)
128  if(this.(weaponentity).bot_chooseweapontime < time)
129  {
131  havocbot_chooseweapon(this, weaponentity);
132  }
133  }
134  havocbot_aim(this);
135  lag_update(this);
136 
137  if (this.bot_aimtarg)
138  {
140  this.aistatus &= ~AI_STATUS_ROAMING;
141 
142  if(STAT(WEAPONS, this))
143  {
145  {
146  PHYS_INPUT_BUTTON_ATCK(this) = false;
147  PHYS_INPUT_BUTTON_ATCK2(this) = false;
148  }
149  else
150  {
151  for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
152  {
153  .entity weaponentity = weaponentities[slot];
154  Weapon w = this.(weaponentity).m_weapon;
155  if(w == WEP_Null && slot != 0)
156  continue;
157  w.wr_aim(w, this, weaponentity);
158  if(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)) // TODO: what if we didn't fire this weapon, but the previous?
159  this.(weaponentity).lastfiredweapon = this.(weaponentity).m_weapon.m_id;
160  }
161  }
162  }
163  else
164  {
165  if(IS_PLAYER(this.bot_aimtarg))
166  bot_aimdir(this, this.bot_aimtarg.origin + this.bot_aimtarg.view_ofs - this.origin - this.view_ofs, 0);
167  }
168  }
169  else if (this.goalcurrent)
170  {
171  this.aistatus |= AI_STATUS_ROAMING;
172  this.aistatus &= ~AI_STATUS_ATTACKING;
173  }
174 
175  havocbot_movetogoal(this);
176  if (!this.bot_aimdir_executed && this.goalcurrent)
177  {
178  // Heading
180  dir -= this.origin + this.view_ofs;
181  dir.z = 0;
182  bot_aimdir(this, dir, 0);
183  }
184 
185  // if the bot is not attacking, consider reloading weapons
186  if (!(this.aistatus & AI_STATUS_ATTACKING))
187  {
188  for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
189  {
190  .entity weaponentity = weaponentities[slot];
191 
192  if(this.(weaponentity).m_weapon == WEP_Null && slot != 0)
193  continue;
194 
195  // we are currently holding a weapon that's not fully loaded, reload it
196  if(skill >= 2) // bots can only reload the held weapon on purpose past this skill
197  if(this.(weaponentity).clip_load < this.(weaponentity).clip_size)
198  CS(this).impulse = IMP_weapon_reload.impulse; // not sure if this is done right
199 
200  // if we're not reloading a weapon, switch to any weapon in our invnetory that's not fully loaded to reload it next
201  // the code above executes next frame, starting the reloading then
202  if(skill >= 5) // bots can only look for unloaded weapons past this skill
203  if(this.(weaponentity).clip_load >= 0) // only if we're not reloading a weapon already
204  {
205  FOREACH(Weapons, it != WEP_Null, {
206  if((STAT(WEAPONS, this) & (it.m_wepset)) && (it.spawnflags & WEP_FLAG_RELOADABLE) && (this.(weaponentity).weapon_load[it.m_id] < it.reloading_ammo))
207  {
208  this.(weaponentity).m_switchweapon = it;
209  break;
210  }
211  });
212  }
213  }
214  }
215 }
216 
218 {
219  bool can_run = false;
220  if (!(this.aistatus & AI_STATUS_ATTACKING) && this.goalcurrent && !IS_PLAYER(this.goalcurrent)
221  && vdist(vec2(this.velocity), >=, autocvar_sv_maxspeed) && !(this.aistatus & AI_STATUS_DANGER_AHEAD)
222  && this.waterlevel <= WATERLEVEL_WETFEET && !IS_DUCKED(this)
223  && IS_ONGROUND(this) && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)))
224  {
225  vector vel_angles = vectoangles(this.velocity);
226  vector deviation = vel_angles - vectoangles(dir);
227  while (deviation.y < -180) deviation.y = deviation.y + 360;
228  while (deviation.y > 180) deviation.y = deviation.y - 360;
230  {
231  vector gco = get_closer_dest(this.goalcurrent, this.origin);
232  float vel = vlen(vec2(this.velocity));
233 
234  // with the current physics, jump distance grows linearly with the speed
235  float jump_distance = 52.661 + 0.606 * vel;
236  jump_distance += this.origin.z - gco.z; // roughly take into account vertical distance too
237  if (vdist(vec2(gco - this.origin), >, max(0, jump_distance)))
238  can_run = true;
239  else if (!(this.goalcurrent.wpflags & WAYPOINTFLAG_JUMP)
240  && !(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
241  && this.goalstack01 && !wasfreed(this.goalstack01) && !(this.goalstack01.wpflags & WAYPOINTFLAG_JUMP)
242  && vdist(vec2(gco - this.goalstack01.origin), >, 70))
243  {
244  vector gno = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
245  vector ang = vectoangles(gco - this.origin);
246  deviation = vectoangles(gno - gco) - vel_angles;
247  while (deviation.y < -180) deviation.y = deviation.y + 360;
248  while (deviation.y > 180) deviation.y = deviation.y - 360;
249 
250  float max_turn_angle = autocvar_bot_ai_bunnyhop_turn_angle_max;
251  max_turn_angle -= autocvar_bot_ai_bunnyhop_turn_angle_reduction * ((vel - autocvar_sv_maxspeed) / autocvar_sv_maxspeed);
252  if ((ang.x < 90 || ang.x > 360 - autocvar_bot_ai_bunnyhop_downward_pitch_max)
253  && fabs(deviation.y) < max(autocvar_bot_ai_bunnyhop_turn_angle_min, max_turn_angle))
254  {
255  can_run = true;
256  }
257  }
258  }
259  }
260 
261  if (can_run)
262  {
263  PHYS_INPUT_BUTTON_JUMP(this) = true;
264  this.bot_jump_time = time;
265  this.aistatus |= AI_STATUS_RUNNING;
266  }
267  else
268  {
269  if (IS_ONGROUND(this) || this.waterlevel > WATERLEVEL_WETFEET)
270  this.aistatus &= ~AI_STATUS_RUNNING;
271  }
272 }
273 
275 {
276  if(time <= this.havocbot_keyboardtime)
277  return;
278 
279  float sk = skill + this.bot_moveskill;
280  this.havocbot_keyboardtime =
281  max(
283  + 0.05 / max(1, sk + this.havocbot_keyboardskill)
284  + random() * 0.025 / max(0.00025, skill + this.havocbot_keyboardskill)
285  , time);
286  vector keyboard = CS(this).movement / autocvar_sv_maxspeed;
287 
288  float trigger = autocvar_bot_ai_keyboard_threshold;
289 
290  // categorize forward movement
291  // at skill < 1.5 only forward
292  // at skill < 2.5 only individual directions
293  // at skill < 4.5 only individual directions, and forward diagonals
294  // at skill >= 4.5, all cases allowed
295  if (keyboard.x > trigger)
296  {
297  keyboard.x = 1;
298  if (sk < 2.5)
299  keyboard.y = 0;
300  }
301  else if (keyboard.x < -trigger && sk > 1.5)
302  {
303  keyboard.x = -1;
304  if (sk < 4.5)
305  keyboard.y = 0;
306  }
307  else
308  {
309  keyboard.x = 0;
310  if (sk < 1.5)
311  keyboard.y = 0;
312  }
313  if (sk < 4.5)
314  keyboard.z = 0;
315 
316  if (keyboard.y > trigger)
317  keyboard.y = 1;
318  else if (keyboard.y < -trigger)
319  keyboard.y = -1;
320  else
321  keyboard.y = 0;
322 
323  if (keyboard.z > trigger)
324  keyboard.z = 1;
325  else if (keyboard.z < -trigger)
326  keyboard.z = -1;
327  else
328  keyboard.z = 0;
329 
330  // make sure bots don't get stuck if havocbot_keyboardtime is very high
331  if (keyboard == '0 0 0')
333 
334  this.havocbot_keyboard = keyboard * autocvar_sv_maxspeed;
335  if (this.havocbot_ducktime > time)
336  PHYS_INPUT_BUTTON_CROUCH(this) = true;
337 
338  keyboard = this.havocbot_keyboard;
339  float blend = bound(0, vlen(destorg - this.origin) / autocvar_bot_ai_keyboard_distance, 1); // When getting close move with 360 degree
340  //dprint("movement ", vtos(CS(this).movement), " keyboard ", vtos(keyboard), " blend ", ftos(blend), "\n");
341  CS(this).movement = CS(this).movement + (keyboard - CS(this).movement) * blend;
342 }
343 
344 // return true when bot isn't getting closer to the current goal
346 {
347  if (this.bot_stop_moving_timeout > time)
348  return false;
349  float curr_dist_z = max(20, fabs(this.origin.z - gco.z));
350  float curr_dist_2d = max(20, vlen(vec2(this.origin - gco)));
351  float distance_time = this.goalcurrent_distance_time;
352  if(distance_time < 0)
353  distance_time = -distance_time;
354  if(curr_dist_z >= this.goalcurrent_distance_z && curr_dist_2d >= this.goalcurrent_distance_2d)
355  {
356  if(!distance_time)
358  else if (time - distance_time > 0.5)
359  return true;
360  }
361  else
362  {
363  // reduce it a little bit so it works even with very small approaches to the goal
364  this.goalcurrent_distance_z = max(20, curr_dist_z - 10);
365  this.goalcurrent_distance_2d = max(20, curr_dist_2d - 10);
366  this.goalcurrent_distance_time = 0;
367  }
368  return false;
369 }
370 
372 {
373  entity selected = NULL;
374  float selected_dist2 = 0;
375  // select farthest item of this group from bot's position
376  IL_EACH(g_items, it.item_group == gr && it.solid,
377  {
378  float dist2 = vlen2(this.origin - it.origin);
379  if (dist2 < 600 ** 2 && dist2 > selected_dist2)
380  {
381  selected = it;
382  selected_dist2 = vlen2(this.origin - selected.origin);
383  }
384  });
385 
386  if (!selected)
387  return NULL;
388 
389  set_tracewalk_dest(selected, this.origin, false);
390  if (!tracewalk(this, this.origin, STAT(PL_MIN, this), STAT(PL_MAX, this),
392  {
393  return NULL;
394  }
395 
396  return selected;
397 }
398 
400 {
401  vector diff;
402  vector dir;
403  vector flatdir;
404  float dodge_enemy_factor = 1;
405  float maxspeed;
406  //float dist;
407  vector dodge;
408  //if (this.goalentity)
409  // te_lightning2(this, this.origin, (this.goalentity.absmin + this.goalentity.absmax) * 0.5);
410  CS(this).movement = '0 0 0';
411  maxspeed = autocvar_sv_maxspeed;
412 
414 
415  PHYS_INPUT_BUTTON_JETPACK(this) = false;
416  // Jetpack navigation
417  if(this.navigation_jetpack_goal)
418  if(this.goalcurrent==this.navigation_jetpack_goal)
419  if(GetResource(this, RES_FUEL))
420  {
422  {
423  debuggoalstack(this);
424  te_wizspike(this.navigation_jetpack_point);
425  }
426 
427  // Take off
428  if (!(this.aistatus & AI_STATUS_JETPACK_FLYING))
429  {
430  // Brake almost completely so it can get a good direction
431  if(vdist(this.velocity, >, 10))
432  return;
434  }
435 
436  makevectors(this.v_angle.y * '0 1 0');
437  dir = normalize(this.navigation_jetpack_point - this.origin);
438 
439  // Landing
441  {
442  // Calculate brake distance in xy
443  float d = vlen(vec2(this.origin - (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5));
444  float vel2 = vlen2(vec2(this.velocity));
445  float db = (vel2 / (autocvar_g_jetpack_acceleration_side * 2)) + 100;
446  //LOG_INFOF("distance %d, velocity %d, brake at %d ", ceil(d), ceil(v), ceil(db));
447  if(d < db || d < 500)
448  {
449  // Brake
450  if (vel2 > (maxspeed * 0.3) ** 2)
451  {
452  CS(this).movement_x = dir * v_forward * -maxspeed;
453  return;
454  }
455  // Switch to normal mode
457  this.aistatus &= ~AI_STATUS_JETPACK_LANDING;
458  this.aistatus &= ~AI_STATUS_JETPACK_FLYING;
459  return;
460  }
461  }
462  else if(checkpvs(this.origin,this.goalcurrent))
463  {
464  // If I can see the goal switch to landing code
465  this.aistatus &= ~AI_STATUS_JETPACK_FLYING;
467  return;
468  }
469 
470  // Flying
471  PHYS_INPUT_BUTTON_JETPACK(this) = true;
472  if(this.navigation_jetpack_point.z - STAT(PL_MAX, this).z + STAT(PL_MIN, this).z < this.origin.z)
473  {
474  CS(this).movement_x = dir * v_forward * maxspeed;
475  CS(this).movement_y = dir * v_right * maxspeed;
476  }
477  return;
478  }
479 
480  // Handling of jump pads
481  if(this.jumppadcount)
482  {
483  if(this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
484  {
487  return;
488  }
489  else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
490  {
491  // If got stuck on the jump pad try to reach the farthest visible waypoint
492  // but with some randomness so it can try out different paths
493  if(!this.goalcurrent)
494  {
495  entity newgoal = NULL;
496  IL_EACH(g_waypoints, vdist(it.origin - this.origin, <=, 1000),
497  {
498  if(it.wpflags & WAYPOINTFLAG_TELEPORT)
499  if(it.origin.z < this.origin.z - 100 && vdist(vec2(it.origin - this.origin), <, 100))
500  continue;
501 
502  traceline(this.origin + this.view_ofs, ((it.absmin + it.absmax) * 0.5), true, this);
503 
504  if(trace_fraction < 1)
505  continue;
506 
507  if(!newgoal || ((random() < 0.8) && vlen2(it.origin - this.origin) > vlen2(newgoal.origin - this.origin)))
508  newgoal = it;
509  });
510 
511  if(newgoal)
512  {
513  this.ignoregoal = this.goalcurrent;
515  navigation_clearroute(this);
516  navigation_routetogoal(this, newgoal, this.origin);
518  debuggoalstack(this);
519  this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
520  }
521  }
522  else //if (this.goalcurrent)
523  {
524  if (this.goalcurrent.bot_pickup)
525  {
526  entity jumppad_wp = this.goalcurrent_prev;
528  if(!this.goalcurrent && jumppad_wp.wp00)
529  {
530  // head to the jumppad destination once bot reaches the goal item
531  navigation_pushroute(this, jumppad_wp.wp00);
532  }
533  }
534  vector gco = (this.goalcurrent.absmin + this.goalcurrent.absmax) * 0.5;
535  if (this.origin.z > gco.z && vdist(vec2(this.velocity), <, autocvar_sv_maxspeed))
536  {
537  if (this.velocity.z < 0)
538  this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
539  }
540  else if(havocbot_checkgoaldistance(this, gco))
541  {
542  navigation_clearroute(this);
544  }
545  else
546  return;
547  }
548  }
549  else //if (!(this.aistatus & AI_STATUS_OUT_JUMPPAD))
550  {
551  if(this.velocity.z > 0 && this.origin.z - this.lastteleport_origin.z > (this.maxs.z - this.mins.z) * 0.5)
552  {
553  vector velxy = this.velocity; velxy_z = 0;
554  if(vdist(velxy, <, autocvar_sv_maxspeed * 0.2))
555  {
556  LOG_TRACE("Warning: ", this.netname, " got stuck on a jumppad (velocity in xy is ", vtos(velxy), "), trying to get out of it now");
558  }
559  return;
560  }
561 
562  // Don't chase players while using a jump pad
563  if(IS_PLAYER(this.goalcurrent) || IS_PLAYER(this.goalstack01))
564  return;
565  }
566  }
567  else if(this.aistatus & AI_STATUS_OUT_JUMPPAD)
568  this.aistatus &= ~AI_STATUS_OUT_JUMPPAD;
569 
570  // If there is a trigger_hurt right below try to use the jetpack or make a rocketjump
571  if (skill > 6 && !(IS_ONGROUND(this)))
572  {
573  #define ROCKETJUMP_DAMAGE() WEP_CVAR(devastator, damage) * 0.8 \
574  * ((StatusEffects_active(STATUSEFFECT_Strength, this)) ? autocvar_g_balance_powerup_strength_selfdamage : 1) \
575  * ((StatusEffects_active(STATUSEFFECT_Shield, this)) ? autocvar_g_balance_powerup_invincible_takedamage : 1)
576 
577  // save some CPU cycles by checking trigger_hurt after checking
578  // that something can be done to evade it (cheaper checks)
579  int action_for_trigger_hurt = 0;
580  if (this.items & IT_JETPACK)
581  action_for_trigger_hurt = 1;
583  && !(this.goalcurrent_prev && this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)
584  && GetResource(this, RES_HEALTH) + GetResource(this, RES_ARMOR) > ROCKETJUMP_DAMAGE())
585  {
586  action_for_trigger_hurt = 2;
587  }
588  else if (!this.goalcurrent)
589  action_for_trigger_hurt = 3;
590 
591  if (action_for_trigger_hurt)
592  {
593  tracebox(this.origin, this.mins, this.maxs, this.origin + '0 0 -65536', MOVE_NOMONSTERS, this);
594  if(!tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos))
595  action_for_trigger_hurt = 0;
596  }
597 
598  if(action_for_trigger_hurt == 1) // jetpack
599  {
600  tracebox(this.origin, this.mins, this.maxs, this.origin + '0 0 65536', MOVE_NOMONSTERS, this);
601  if(tracebox_hits_trigger_hurt(this.origin, this.mins, this.maxs, trace_endpos + '0 0 1' ))
602  {
603  if(this.velocity.z<0)
604  PHYS_INPUT_BUTTON_JETPACK(this) = true;
605  }
606  else
607  PHYS_INPUT_BUTTON_JETPACK(this) = true;
608 
609  // If there is no goal try to move forward
610 
611  if(this.goalcurrent==NULL)
612  dir = v_forward;
613  else
614  dir = normalize(( ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5 ) - this.origin);
615 
616  vector xyvelocity = this.velocity; xyvelocity_z = 0;
617  float xyspeed = xyvelocity * dir;
618 
619  if(xyspeed < (maxspeed / 2))
620  {
621  makevectors(this.v_angle.y * '0 1 0');
622  tracebox(this.origin, this.mins, this.maxs, this.origin + (dir * maxspeed * 3), MOVE_NOMONSTERS, this);
623  if(trace_fraction==1)
624  {
625  CS(this).movement_x = dir * v_forward * maxspeed;
626  CS(this).movement_y = dir * v_right * maxspeed;
627  if (skill < 10)
628  havocbot_keyboard_movement(this, this.origin + dir * 100);
629  }
630  }
631 
632  this.havocbot_blockhead = true;
633 
634  return;
635  }
636  else if(action_for_trigger_hurt == 2) // rocketjump
637  {
638  if(this.velocity.z < 0)
639  {
640  for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
641  {
642  .entity weaponentity = weaponentities[slot];
643 
644  if(this.(weaponentity).m_weapon == WEP_Null && slot != 0)
645  continue;
646 
647  if(client_hasweapon(this, WEP_DEVASTATOR, weaponentity, true, false))
648  {
649  CS(this).movement_x = maxspeed;
650 
651  if(this.rocketjumptime)
652  {
653  if(time > this.rocketjumptime)
654  {
655  PHYS_INPUT_BUTTON_ATCK2(this) = true;
656  this.rocketjumptime = 0;
657  }
658  return;
659  }
660 
661  this.(weaponentity).m_switchweapon = WEP_DEVASTATOR;
662  this.v_angle_x = 90;
663  PHYS_INPUT_BUTTON_ATCK(this) = true;
664  this.rocketjumptime = time + WEP_CVAR(devastator, detonatedelay);
665  return;
666  }
667  }
668  }
669  }
670  else if(action_for_trigger_hurt == 3) // no goal
671  {
672  // If there is no goal try to move forward
673  CS(this).movement_x = maxspeed;
674  }
675  }
676 
677  // If we are under water with no goals, swim up
678  if(this.waterlevel && !this.goalcurrent)
679  {
680  dir = '0 0 0';
682  dir.z = 1;
683  else if(this.velocity.z >= 0 && !(this.waterlevel == WATERLEVEL_WETFEET && this.watertype == CONTENT_WATER))
684  PHYS_INPUT_BUTTON_JUMP(this) = true;
685  makevectors(this.v_angle.y * '0 1 0');
686  vector v = dir * maxspeed;
687  CS(this).movement.x = v * v_forward;
688  CS(this).movement.y = v * v_right;
689  CS(this).movement.z = v * v_up;
690  }
691 
692  // if there is nowhere to go, exit
693  if (this.goalcurrent == NULL)
694  return;
695 
696 
697  bool locked_goal = false;
698  if((this.goalentity && wasfreed(this.goalentity))
699  || (this.goalcurrent == this.goalentity && this.goalentity.tag_entity))
700  {
701  navigation_clearroute(this);
703  return;
704  }
705  else if(this.goalentity.tag_entity)
706  {
708  }
709  else if(this.goalentity.bot_pickup)
710  {
711  if(this.goalentity.bot_pickup_respawning)
712  {
713  if(this.goalentity.solid) // item respawned
714  this.goalentity.bot_pickup_respawning = false;
715  else if(time < this.goalentity.scheduledrespawntime - 10) // item already taken (by someone else)
716  {
717  if(checkpvs(this.origin, this.goalentity))
718  {
719  this.goalentity.bot_pickup_respawning = false;
721  }
722  locked_goal = true; // wait for item to respawn
723  }
724  else if(this.goalentity == this.goalcurrent)
725  locked_goal = true; // wait for item to respawn
726  }
727  else if(!this.goalentity.solid && !boxesoverlap(this.goalentity.absmin, this.goalentity.absmax, this.absmin, this.absmax))
728  {
729  if(checkpvs(this.origin, this.goalentity))
730  {
732  }
733  }
734  }
735  if (this.goalcurrent == this.goalentity && this.goalentity_lock_timeout > time)
736  locked_goal = true;
737 
738  if (navigation_shortenpath(this))
739  {
740  if (vdist(this.origin - this.goalcurrent_prev.origin, <, 50)
742  {
744  }
745  }
746 
747  bool goalcurrent_can_be_removed = false;
748  if (IS_PLAYER(this.goalcurrent) || IS_MONSTER(this.goalcurrent))
749  {
750  bool freeze_state_changed = (boolean(STAT(FROZEN, this.goalentity)) != this.goalentity_shouldbefrozen);
751  if (IS_DEAD(this.goalcurrent) || (this.goalentity == this.goalcurrent && freeze_state_changed))
752  {
753  goalcurrent_can_be_removed = true;
754  // don't remove if not visible
755  if (checkpvs(this.origin + this.view_ofs, this.goalcurrent))
756  {
757  if (IS_DEAD(this.goalcurrent))
758  {
759  IL_EACH(g_items, it.enemy == this.goalcurrent && Item_IsLoot(it),
760  {
761  if (vdist(it.origin - this.goalcurrent.death_origin, <, 50))
762  {
763  navigation_clearroute(this);
764  navigation_pushroute(this, it);
765  // loot can't be immediately rated since it isn't on ground yet
766  // it will be rated after a second when on ground, meanwhile head to it
767  navigation_goalrating_timeout_expire(this, 1);
768  return;
769  }
770  });
771  }
772  if (!Item_IsLoot(this.goalcurrent))
773  {
775  return;
776  }
777  }
778  }
779  else if (!(STAT(FROZEN, this.goalentity)) && this.bot_tracewalk_time < time)
780  {
781  set_tracewalk_dest(this.goalcurrent, this.origin, true);
782  if (!(trace_ent == this || tracewalk(this, this.origin, this.mins, this.maxs,
784  {
786  return;
787  }
788  this.bot_tracewalk_time = max(time, this.bot_tracewalk_time) + 0.25;
789  }
790  }
791 
792  if(!locked_goal)
793  {
794  // optimize path finding by anticipating goalrating when bot is near a waypoint;
795  // in this case path finding can start directly from a waypoint instead of
796  // looking for all the reachable waypoints up to a certain distance
797  if (navigation_poptouchedgoals(this))
798  {
799  if (this.goalcurrent)
800  {
801  if (goalcurrent_can_be_removed)
802  {
803  // remove even if not visible
805  return;
806  }
809  }
810  else
811  {
812  entity old_goal = this.goalcurrent_prev;
813  if (old_goal.item_group && this.item_group != old_goal.item_group)
814  {
815  // Avoid multiple costly calls of path finding code that selects one of the closest
816  // item of the group by telling the bot to head directly to the farthest item.
817  // Next time we let the bot select a goal as usual which can be another item
818  // of this group (the closest one) and so on
819  this.item_group = old_goal.item_group;
820  entity new_goal = havocbot_select_an_item_of_group(this, old_goal.item_group);
821  if (new_goal)
822  navigation_pushroute(this, new_goal);
823  }
824  }
825  }
826  }
827 
828  // if ran out of goals try to use an alternative goal or get a new strategy asap
829  if(this.goalcurrent == NULL)
830  {
832  return;
833  }
834 
835 
837  debuggoalstack(this);
838 
839  bool bunnyhop_forbidden = false;
840  vector destorg = get_closer_dest(this.goalcurrent, this.origin);
841  if (this.jumppadcount && this.goalcurrent.wpflags & WAYPOINTFLAG_TELEPORT)
842  {
843  // if bot used the jumppad, push towards jumppad origin until jumppad waypoint gets removed
844  destorg = this.goalcurrent.origin;
845  }
846  else if (this.goalcurrent.wpisbox)
847  {
848  // if bot is inside the teleport waypoint, head to teleport origin until teleport gets used
849  // do it even if bot is on a ledge above a teleport/jumppad so it doesn't get stuck
850  if (boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin + eZ * this.mins.z, this.origin + eZ * this.maxs.z)
851  || (this.absmin.z > destorg.z && destorg.x == this.origin.x && destorg.y == this.origin.y))
852  {
853  bunnyhop_forbidden = true;
854  destorg = this.goalcurrent.origin;
855  if(destorg.z > this.origin.z)
856  PHYS_INPUT_BUTTON_JUMP(this) = true;
857  }
858  }
859 
860  diff = destorg - this.origin;
861 
862  if (time < this.bot_stop_moving_timeout
863  || (this.goalcurrent == this.goalentity && time < this.goalentity_lock_timeout && vdist(diff, <, 10)))
864  {
865  // stop if the locked goal has been reached
866  destorg = this.origin;
867  diff = dir = '0 0 0';
868  }
869  else if (IS_PLAYER(this.goalcurrent) || IS_MONSTER(this.goalcurrent))
870  {
871  if (vdist(diff, <, 80))
872  {
873  // stop if too close to target player (even if frozen)
874  destorg = this.origin;
875  diff = dir = '0 0 0';
876  }
877  else
878  {
879  // move destorg out of target players, otherwise bot will consider them
880  // an obstacle that needs to be jumped (especially if frozen)
881  dir = normalize(diff);
882  destorg -= dir * PL_MAX_CONST.x * M_SQRT2;
883  diff = destorg - this.origin;
884  }
885  }
886  else
887  dir = normalize(diff);
888  flatdir = (diff.z == 0) ? dir : normalize(vec2(diff));
889 
890  bool danger_detected = false;
891  vector do_break = '0 0 0';
892 
893  //if (this.bot_dodgevector_time < time)
894  {
895  //this.bot_dodgevector_time = time + cvar("bot_ai_dodgeupdateinterval");
896  //this.bot_dodgevector_jumpbutton = 1;
897 
899  makevectors(this.v_angle.y * '0 1 0');
900  if (this.waterlevel > WATERLEVEL_WETFEET)
901  {
902  if (this.waterlevel > WATERLEVEL_SWIMMING)
903  {
904  if(!this.goalcurrent)
906  else if(destorg.z > this.origin.z)
907  PHYS_INPUT_BUTTON_JUMP(this) = true;
908  }
909  else
910  {
911  if(this.velocity.z >= 0 && !(this.watertype == CONTENT_WATER && destorg.z < this.origin.z) &&
912  (this.aistatus & AI_STATUS_OUT_WATER))
913  {
914  PHYS_INPUT_BUTTON_JUMP(this) = true;
915  dir = flatdir;
916  }
917  else
918  {
919  if (destorg.z > this.origin.z)
920  dir = flatdir;
921  }
922  }
923  }
924  else
925  {
926  float s;
927  vector offset;
928  if(this.aistatus & AI_STATUS_OUT_WATER)
929  this.aistatus &= ~AI_STATUS_OUT_WATER;
930 
931  // jump if going toward an obstacle that doesn't look like stairs we
932  // can walk up directly
933  vector deviation = '0 0 0';
934  float current_speed = vlen(vec2(this.velocity));
935  if (current_speed < maxspeed * 0.2)
936  current_speed = maxspeed * 0.2;
937  else
938  {
939  deviation = vectoangles(diff) - vectoangles(this.velocity);
940  while (deviation.y < -180) deviation.y += 360;
941  while (deviation.y > 180) deviation.y -= 360;
942  }
943  float turning = false;
944  vector flat_diff = vec2(diff);
945  offset = max(32, current_speed * cos(deviation.y * DEG2RAD) * 0.3) * flatdir;
946  vector actual_destorg = this.origin + offset;
947  if (this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP))
948  {
949  if (time > this.bot_stop_moving_timeout
950  && fabs(deviation.y) > 20 && current_speed > maxspeed * 0.4
951  && vdist(vec2(this.origin - this.goalcurrent_prev.origin), <, 50))
952  {
953  this.bot_stop_moving_timeout = time + 0.1;
954  }
955  if (current_speed > autocvar_sv_maxspeed * 0.9
956  && vlen2(flat_diff) < vlen2(vec2(this.goalcurrent_prev.origin - destorg))
957  && vdist(vec2(this.origin - this.goalcurrent_prev.origin), >, 50)
958  && vdist(vec2(this.origin - this.goalcurrent_prev.origin), <, 150)
959  )
960  {
961  PHYS_INPUT_BUTTON_JUMP(this) = true;
962  this.bot_jump_time = time;
963  }
964  }
965  else if (!this.goalstack01 || (this.goalcurrent.wpflags & (WAYPOINTFLAG_TELEPORT | WAYPOINTFLAG_LADDER)))
966  {
967  if (vlen2(flat_diff) < vlen2(offset))
968  {
969  if ((this.goalcurrent.wpflags & WAYPOINTFLAG_JUMP) && this.goalstack01)
970  {
971  // oblique warpzones need a jump otherwise bots gets stuck
972  PHYS_INPUT_BUTTON_JUMP(this) = true;
973  }
974  else
975  {
976  actual_destorg.x = destorg.x;
977  actual_destorg.y = destorg.y;
978  }
979  }
980  }
981  else if (vdist(flat_diff, <, 32) && diff.z < -16) // destination is under the bot
982  {
983  actual_destorg.x = destorg.x;
984  actual_destorg.y = destorg.y;
985  }
986  else if (vlen2(flat_diff) < vlen2(offset))
987  {
988  vector next_goal_org = (this.goalstack01.absmin + this.goalstack01.absmax) * 0.5;
989  vector next_dir = normalize(vec2(next_goal_org - destorg));
990  float dist = vlen(vec2(this.origin + offset - destorg));
991  // if current and next goal are close to each other make sure
992  // actual_destorg isn't set beyond next_goal_org
993  if (dist ** 2 > vlen2(vec2(next_goal_org - destorg)))
994  actual_destorg = next_goal_org;
995  else
996  actual_destorg = vec2(destorg) + dist * next_dir;
997  actual_destorg.z = this.origin.z;
998  turning = true;
999  }
1000 
1001  LABEL(jumpobstacle_check);
1002  dir = flatdir = normalize(actual_destorg - this.origin);
1003 
1004  bool jump_forbidden = false;
1005  if (!turning && fabs(deviation.y) > 50)
1006  jump_forbidden = true;
1007  else if (IS_DUCKED(this))
1008  {
1009  tracebox(this.origin, PL_MIN_CONST, PL_MAX_CONST, this.origin, false, this);
1010  if (trace_startsolid)
1011  jump_forbidden = true;
1012  }
1013 
1014  if (!jump_forbidden)
1015  {
1016  tracebox(this.origin, this.mins, this.maxs, actual_destorg, false, this);
1017  if (trace_fraction < 1 && trace_plane_normal.z < 0.7)
1018  {
1019  s = trace_fraction;
1020  tracebox(this.origin + stepheightvec, this.mins, this.maxs, actual_destorg + stepheightvec, false, this);
1021  if (trace_fraction < s + 0.01 && trace_plane_normal.z < 0.7)
1022  {
1023  // found an obstacle
1024  if (turning && fabs(deviation.y) > 5)
1025  {
1026  // check if the obstacle is still there without turning
1027  actual_destorg = destorg;
1028  turning = false;
1029  this.bot_tracewalk_time = time + 0.25;
1030  goto jumpobstacle_check;
1031  }
1032  s = trace_fraction;
1033  // don't artificially reduce max jump height in real-time
1034  // (jumpstepheightvec is reduced a bit to make the jumps easy in tracewalk)
1035  vector jump_height = (IS_ONGROUND(this)) ? stepheightvec + jumpheight_vec : jumpstepheightvec;
1036  tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this);
1037  if (trace_fraction > s)
1038  {
1039  PHYS_INPUT_BUTTON_JUMP(this) = true;
1040  this.bot_jump_time = time;
1041  }
1042  else
1043  {
1044  jump_height = stepheightvec + jumpheight_vec / 2;
1045  tracebox(this.origin + jump_height, this.mins, this.maxs, actual_destorg + jump_height, false, this);
1046  if (trace_fraction > s)
1047  {
1048  PHYS_INPUT_BUTTON_JUMP(this) = true;
1049  this.bot_jump_time = time;
1050  }
1051  }
1052  }
1053  }
1054  }
1055 
1056  // if bot for some reason doesn't get close to the current goal find another one
1057  if(!this.jumppadcount && !IS_PLAYER(this.goalcurrent))
1058  if(!(locked_goal && this.goalcurrent_distance_z < 50 && this.goalcurrent_distance_2d < 50))
1059  if(havocbot_checkgoaldistance(this, destorg))
1060  {
1061  if(this.goalcurrent_distance_time < 0) // can't get close for the second time
1062  {
1063  navigation_clearroute(this);
1065  return;
1066  }
1067 
1068  set_tracewalk_dest(this.goalcurrent, this.origin, false);
1069  if (!tracewalk(this, this.origin, this.mins, this.maxs,
1071  {
1072  navigation_clearroute(this);
1074  return;
1075  }
1076 
1077  // give bot only another chance to prevent bot getting stuck
1078  // in case it thinks it can walk but actually can't
1081  this.goalcurrent_distance_time = -time; // mark second try
1082  }
1083 
1084  if (skill + this.bot_moveskill <= 3 && time > this.bot_stop_moving_timeout
1085  && current_speed > maxspeed * 0.9 && fabs(deviation.y) > 70)
1086  {
1087  this.bot_stop_moving_timeout = time + 0.4 + random() * 0.2;
1088  }
1089 
1090  // Check for water/slime/lava and dangerous edges
1091  // (only when the bot is on the ground or jumping intentionally)
1092 
1093  offset = (vdist(this.velocity, >, 32) ? this.velocity * 0.2 : flatdir * 32);
1094  vector dst_ahead = this.origin + this.view_ofs + offset;
1095  vector dst_down = dst_ahead - '0 0 3000';
1096  traceline(this.origin + this.view_ofs, dst_ahead, true, NULL);
1097 
1098  bool unreachable = false;
1099  s = CONTENT_SOLID;
1100  if (trace_fraction == 1 && !this.jumppadcount
1102  && !(this.goalcurrent_prev && (this.goalcurrent_prev.wpflags & WAYPOINTFLAG_JUMP)))
1103  if((IS_ONGROUND(this)) || (this.aistatus & AI_STATUS_RUNNING) || (this.aistatus & AI_STATUS_ROAMING) || PHYS_INPUT_BUTTON_JUMP(this))
1104  {
1105  // Look downwards
1106  traceline(dst_ahead , dst_down, true, NULL);
1107  //te_lightning2(NULL, this.origin + this.view_ofs, dst_ahead); // Draw "ahead" look
1108  //te_lightning2(NULL, dst_ahead, trace_endpos); // Draw "downwards" look
1109  if(trace_endpos.z < this.origin.z + this.mins.z)
1110  {
1112  danger_detected = true;
1113  else if (trace_endpos.z < min(this.origin.z + this.mins.z, this.goalcurrent.origin.z) - 100)
1114  danger_detected = true;
1115  else
1116  {
1117  s = pointcontents(trace_endpos + '0 0 1');
1118  if (s != CONTENT_SOLID)
1119  {
1120  if (s == CONTENT_LAVA || s == CONTENT_SLIME)
1121  danger_detected = true;
1122  else if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
1123  {
1124  // the traceline check isn't enough but is good as optimization,
1125  // when not true (most of the time) this tracebox call is avoided
1126  tracebox(dst_ahead, this.mins, this.maxs, dst_down, true, this);
1127  if (tracebox_hits_trigger_hurt(dst_ahead, this.mins, this.maxs, trace_endpos))
1128  {
1129  if (destorg.z > this.origin.z + jumpstepheightvec.z)
1130  {
1131  // the goal is probably on an upper platform, assume bot can't get there
1132  unreachable = true;
1133  }
1134  else
1135  danger_detected = true;
1136  }
1137  }
1138  }
1139  }
1140  }
1141  }
1142 
1143  dir = flatdir;
1144  makevectors(this.v_angle.y * '0 1 0');
1145 
1146  if (danger_detected || (s == CONTENT_WATER))
1147  {
1149  if(IS_PLAYER(this.goalcurrent))
1150  unreachable = true;
1151  }
1152 
1153  // slow down if bot is in the air and goal is under it
1155  && vdist(flat_diff, <, 250) && this.origin.z - destorg.z > 120
1156  && (!IS_ONGROUND(this) || vdist(vec2(this.velocity), >, maxspeed * 0.3)))
1157  {
1158  // tracebox wouldn't work when bot is still on the ledge
1159  traceline(this.origin, this.origin - '0 0 200', true, this);
1160  if (this.origin.z - trace_endpos.z > 120)
1161  do_break = normalize(this.velocity) * -1;
1162  }
1163 
1164  if(unreachable)
1165  {
1166  navigation_clearroute(this);
1168  this.ignoregoal = this.goalcurrent;
1170  }
1171  }
1172 
1173  dodge = havocbot_dodge(this);
1174  if (dodge)
1175  dodge *= bound(0, 0.5 + (skill + this.bot_dodgeskill) * 0.1, 1);
1176  if (this.enemy)
1177  {
1178  traceline(this.origin, (this.enemy.absmin + this.enemy.absmax) * 0.5, true, NULL);
1179  if (IS_PLAYER(trace_ent))
1180  dodge_enemy_factor = bound(0, (skill + this.bot_dodgeskill) / 7, 1);
1181  }
1182  // this.bot_dodgevector = dir;
1183  // this.bot_dodgevector_jumpbutton = PHYS_INPUT_BUTTON_JUMP(this);
1184  }
1185 
1186  float ladder_zdir = 0;
1187  if(this.ladder_entity)
1188  {
1189  if(this.goalcurrent.origin.z + this.goalcurrent.mins.z > this.origin.z + this.mins.z)
1190  {
1191  if(this.origin.z + this.mins.z < this.ladder_entity.origin.z + this.ladder_entity.maxs.z)
1192  ladder_zdir = 1;
1193  }
1194  else
1195  {
1196  if(this.origin.z + this.mins.z > this.ladder_entity.origin.z + this.ladder_entity.mins.z)
1197  ladder_zdir = -1;
1198  }
1199  if (ladder_zdir)
1200  {
1201  if (vdist(vec2(diff), <, 40))
1202  dir.z = ladder_zdir * 4;
1203  else
1204  dir.z = ladder_zdir * 2;
1205  dir = normalize(dir);
1206  }
1207  }
1208 
1209  if (this.goalcurrent.wpisbox
1210  && boxesoverlap(this.goalcurrent.absmin, this.goalcurrent.absmax, this.origin, this.origin))
1211  {
1212  // bot is inside teleport waypoint but hasn't touched the real teleport yet
1213  // head to teleport origin
1214  dir = (this.goalcurrent.origin - this.origin);
1215  dir.z = 0;
1216  dir = normalize(dir);
1217  }
1218 
1219  // already executed when bot targets an enemy
1220  if (!this.bot_aimdir_executed)
1221  {
1222  if (time < this.bot_stop_moving_timeout)
1223  bot_aimdir(this, normalize(this.goalcurrent.origin - this.origin), 0);
1224  else
1225  bot_aimdir(this, dir, 0);
1226  }
1227 
1228  vector evadedanger = '0 0 0';
1229  if (!ladder_zdir)
1230  {
1231  dir *= dodge_enemy_factor;
1232  if (danger_detected && vdist(this.velocity, >, maxspeed * 0.8) && this.goalcurrent_prev
1233  && this.goalcurrent.classname == "waypoint")
1234  {
1235  vector p = this.origin + this.velocity * 0.2;
1236  vector evadedanger = point_line_vec(p, vec2(this.goalcurrent_prev.origin) + eZ * p.z,
1237  vec2(destorg - this.goalcurrent_prev.origin));
1238  if (vdist(evadedanger, >, 20))
1239  {
1240  if (vdist(evadedanger, >, 40))
1241  do_break = normalize(this.velocity) * -1;
1242  evadedanger = normalize(evadedanger);
1243  evadedanger *= bound(1, 3 - (skill + this.bot_dodgeskill), 3); // Noobs fear dangers a lot and take more distance from them
1244  }
1245  else
1246  evadedanger = '0 0 0';
1247  }
1248  dir = normalize(dir + dodge + do_break + evadedanger);
1249  }
1250 
1251  makevectors(this.v_angle);
1252  //dir = this.bot_dodgevector;
1253  //if (this.bot_dodgevector_jumpbutton)
1254  // PHYS_INPUT_BUTTON_JUMP(this) = true;
1255  CS(this).movement_x = dir * v_forward * maxspeed;
1256  CS(this).movement_y = dir * v_right * maxspeed;
1257  CS(this).movement_z = dir * v_up * maxspeed;
1258 
1259  // Emulate keyboard interface
1260  if (skill < 10)
1261  havocbot_keyboard_movement(this, destorg);
1262 
1263  // Bunnyhop!
1264  if (!bunnyhop_forbidden && !evadedanger && !do_break && skill + this.bot_moveskill >= autocvar_bot_ai_bunnyhop_skilloffset)
1265  havocbot_bunnyhop(this, dir);
1266 
1267  if (dir * v_up >= autocvar_sv_jumpvelocity * 0.5 && IS_ONGROUND(this))
1268  PHYS_INPUT_BUTTON_JUMP(this) = true;
1269  if (dodge)
1270  {
1271  if (dodge * v_up > 0 && random() * frametime >= 0.2 * bound(0, (10 - skill - this.bot_dodgeskill) * 0.1, 1))
1272  PHYS_INPUT_BUTTON_JUMP(this) = true;
1273  if (dodge * v_up < 0 && random() * frametime >= 0.5 * bound(0, (10 - skill - this.bot_dodgeskill) * 0.1, 1))
1274  this.havocbot_ducktime = time + 0.3 / bound(0.1, skill + this.bot_dodgeskill, 10);
1275  }
1276 }
1277 
1278 entity havocbot_gettarget(entity this, bool secondary)
1279 {
1280  entity best = NULL;
1281  vector eye = CENTER_OR_VIEWOFS(this);
1282  IL_EACH(g_bot_targets, boolean((secondary) ? it.classname == "misc_breakablemodel" : it.classname != "misc_breakablemodel"),
1283  {
1284  vector v = CENTER_OR_VIEWOFS(it);
1285  if(vdist(v - eye, <, autocvar_bot_ai_enemydetectionradius))
1286  if(!best || vlen2(CENTER_OR_VIEWOFS(best) - eye) > vlen2(v - eye))
1287  if(bot_shouldattack(this, it))
1288  {
1289  traceline(eye, v, true, this);
1290  if (trace_ent == it || trace_fraction >= 1)
1291  best = it;
1292  }
1293  });
1294 
1295  return best;
1296 }
1297 
1299 {
1301  {
1302  this.enemy = NULL;
1303  return;
1304  }
1305 
1306  if (this.enemy)
1307  {
1308  if (!bot_shouldattack(this, this.enemy))
1309  {
1310  // enemy died or something, find a new target
1311  this.enemy = NULL;
1313  }
1314  else if (this.havocbot_stickenemy_time && time < this.havocbot_stickenemy_time)
1315  {
1316  // tracking last chosen enemy
1317  vector targ_pos = (this.enemy.absmin + this.enemy.absmax) * 0.5;
1318  traceline(this.origin + this.view_ofs, targ_pos, false, NULL);
1319  if (trace_ent == this.enemy || trace_fraction == 1)
1320  if (vdist(targ_pos - this.origin, <, 1000))
1321  {
1322  // remain tracking him for a shot while (case he went after a small corner or pilar
1323  this.havocbot_chooseenemy_finished = time + 0.5;
1324  return;
1325  }
1326 
1327  // stop preferring this enemy
1328  this.havocbot_stickenemy_time = 0;
1329  }
1330  }
1332  return;
1334  vector eye = this.origin + this.view_ofs;
1335  entity best = NULL;
1336  float bestrating = autocvar_bot_ai_enemydetectionradius ** 2;
1337 
1338  // Backup hit flags
1339  int hf = this.dphitcontentsmask;
1340 
1341  // Search for enemies, if no enemy can be seen directly try to look through transparent objects
1342 
1344 
1345  bool scan_transparent = false;
1346  bool scan_secondary_targets = false;
1347  bool have_secondary_targets = false;
1348  while(true)
1349  {
1350  scan_secondary_targets = false;
1351 LABEL(scan_targets)
1352  IL_EACH(g_bot_targets, it.bot_attack,
1353  {
1354  if(!scan_secondary_targets)
1355  {
1356  if(it.classname == "misc_breakablemodel")
1357  {
1358  have_secondary_targets = true;
1359  continue;
1360  }
1361  }
1362  else if(it.classname != "misc_breakablemodel")
1363  continue;
1364 
1365  vector v = (it.absmin + it.absmax) * 0.5;
1366  float rating = vlen2(v - eye);
1367  if (rating < bestrating && bot_shouldattack(this, it))
1368  {
1369  traceline(eye, v, true, this);
1370  if (trace_ent == it || trace_fraction >= 1)
1371  {
1372  best = it;
1373  bestrating = rating;
1374  }
1375  }
1376  });
1377 
1378  if(!best && have_secondary_targets && !scan_secondary_targets)
1379  {
1380  scan_secondary_targets = true;
1381  // restart the loop
1382  bestrating = autocvar_bot_ai_enemydetectionradius ** 2;
1383  goto scan_targets;
1384  }
1385 
1386  // I want to do a second scan if no enemy was found or I don't have weapons
1387  // TODO: Perform the scan when using the rifle (requires changes on the rifle code)
1388  if(best || STAT(WEAPONS, this)) // || this.weapon == WEP_RIFLE.m_id
1389  break;
1390  if(scan_transparent)
1391  break;
1392 
1393  // Set flags to see through transparent objects
1395 
1396  scan_transparent = true;
1397  }
1398 
1399  // Restore hit flags
1400  this.dphitcontentsmask = hf;
1401 
1402  this.enemy = best;
1404  if(best && best.classname == "misc_breakablemodel")
1405  this.havocbot_stickenemy_time = 0;
1406 }
1407 
1408 float havocbot_chooseweapon_checkreload(entity this, .entity weaponentity, int new_weapon)
1409 {
1410  // bots under this skill cannot find unloaded weapons to reload idly when not in combat,
1411  // so skip this for them, or they'll never get to reload their weapons at all.
1412  // this also allows bots under this skill to be more stupid, and reload more often during combat :)
1413  if(skill < 5)
1414  return false;
1415 
1416  // if this weapon is scheduled for reloading, don't switch to it during combat
1417  if (this.(weaponentity).weapon_load[new_weapon] < 0)
1418  {
1419  FOREACH(Weapons, it != WEP_Null, {
1420  if(it.wr_checkammo1(it, this, weaponentity) + it.wr_checkammo2(it, this, weaponentity))
1421  return true; // other weapon available
1422  });
1423  }
1424 
1425  return false;
1426 }
1427 
1428 void havocbot_chooseweapon(entity this, .entity weaponentity)
1429 {
1430  int i;
1431 
1432  // ;)
1433  if(g_weaponarena_weapons == WEPSET(TUBA))
1434  {
1435  this.(weaponentity).m_switchweapon = WEP_TUBA;
1436  return;
1437  }
1438 
1439  // TODO: clean this up by moving it to weapon code
1440  if(this.enemy==NULL)
1441  {
1442  // If no weapon was chosen get the first available weapon
1443  if(this.(weaponentity).m_weapon==WEP_Null)
1444  FOREACH(Weapons, it != WEP_Null, {
1445  if(client_hasweapon(this, it, weaponentity, true, false))
1446  {
1447  this.(weaponentity).m_switchweapon = it;
1448  return;
1449  }
1450  });
1451  return;
1452  }
1453 
1454  // Do not change weapon during the next second after a combo
1455  float f = time - this.lastcombotime;
1456  if(f < 1)
1457  return;
1458 
1459  float w;
1460  float distance; distance=bound(10,vlen(this.origin-this.enemy.origin)-200,10000);
1461 
1462  // Should it do a weapon combo?
1463  float af, ct, combo_time, combo;
1464 
1465  af = ATTACK_FINISHED(this, weaponentity);
1467 
1468  // Bots with no skill will be 4 times more slower than "godlike" bots when doing weapon combos
1469  // Ideally this 4 should be calculated as longest_weapon_refire / bot_ai_weapon_combo_threshold
1470  combo_time = time + ct + (ct * ((-0.3*(skill+this.bot_weaponskill))+3));
1471 
1472  combo = false;
1473 
1475  if(this.(weaponentity).m_weapon.m_id == this.(weaponentity).lastfiredweapon)
1476  if(af > combo_time)
1477  {
1478  combo = true;
1479  this.lastcombotime = time;
1480  }
1481 
1482  distance *= (2 ** this.bot_rangepreference);
1483 
1484  // Custom weapon list based on distance to the enemy
1485  if(bot_custom_weapon){
1486 
1487  // Choose weapons for far distance
1488  if ( distance > bot_distance_far ) {
1489  for(i=0; i < REGISTRY_COUNT(Weapons) && bot_weapons_far[i] != -1 ; ++i){
1490  w = bot_weapons_far[i];
1491  if ( client_hasweapon(this, REGISTRY_GET(Weapons, w), weaponentity, true, false) )
1492  {
1493  if ((this.(weaponentity).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
1494  continue;
1495  this.(weaponentity).m_switchweapon = REGISTRY_GET(Weapons, w);
1496  return;
1497  }
1498  }
1499  }
1500 
1501  // Choose weapons for mid distance
1502  if ( distance > bot_distance_close) {
1503  for(i=0; i < REGISTRY_COUNT(Weapons) && bot_weapons_mid[i] != -1 ; ++i){
1504  w = bot_weapons_mid[i];
1505  if ( client_hasweapon(this, REGISTRY_GET(Weapons, w), weaponentity, true, false) )
1506  {
1507  if ((this.(weaponentity).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
1508  continue;
1509  this.(weaponentity).m_switchweapon = REGISTRY_GET(Weapons, w);
1510  return;
1511  }
1512  }
1513  }
1514 
1515  // Choose weapons for close distance
1516  for(i=0; i < REGISTRY_COUNT(Weapons) && bot_weapons_close[i] != -1 ; ++i){
1517  w = bot_weapons_close[i];
1518  if ( client_hasweapon(this, REGISTRY_GET(Weapons, w), weaponentity, true, false) )
1519  {
1520  if ((this.(weaponentity).m_weapon.m_id == w && combo) || havocbot_chooseweapon_checkreload(this, weaponentity, w))
1521  continue;
1522  this.(weaponentity).m_switchweapon = REGISTRY_GET(Weapons, w);
1523  return;
1524  }
1525  }
1526  }
1527 }
1528 
1530 {
1531  if (time < this.nextaim)
1532  return;
1533  this.nextaim = time + 0.1;
1534  vector myvel = this.velocity;
1535  if (!this.waterlevel)
1536  myvel.z = 0;
1537  if(MUTATOR_CALLHOOK(HavocBot_Aim, this)) { /* do nothing */ }
1538  else if (this.enemy)
1539  {
1540  vector enemyvel = this.enemy.velocity;
1541  if (!this.enemy.waterlevel)
1542  enemyvel.z = 0;
1543  lag_additem(this, time + CS(this).ping, 0, 0, this.enemy, this.origin, myvel, (this.enemy.absmin + this.enemy.absmax) * 0.5, enemyvel);
1544  }
1545  else
1546  lag_additem(this, time + CS(this).ping, 0, 0, NULL, this.origin, myvel, ( this.goalcurrent.absmin + this.goalcurrent.absmax ) * 0.5, '0 0 0');
1547 }
1548 
1550 {
1551  // Refresh path to goal if necessary
1552  entity wp;
1553  wp = this.havocbot_personal_waypoint;
1555  navigation_routerating(this, wp, 10000, 10000);
1557  return (this.goalentity != NULL);
1558 }
1559 
1561 {
1562  entity wp;
1563 
1565  {
1566  // Step 4: Move to waypoint
1568  {
1569  LOG_TRACE("Error: ", this.netname, " trying to walk to a non existent personal waypoint");
1570  this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_GOING;
1571  return CMD_STATUS_ERROR;
1572  }
1573 
1576  {
1577  bot_strategytoken_taken = true;
1579  {
1580  LOG_TRACE(this.netname, " walking to its personal waypoint (after ", ftos(this.havocbot_personal_waypoint_failcounter), " failed attempts)");
1583  }
1584  else
1585  {
1589  {
1590  LOG_TRACE("Warning: can't walk to the personal waypoint located at ", vtos(this.havocbot_personal_waypoint.origin));
1592  delete(this.havocbot_personal_waypoint);
1593  return CMD_STATUS_ERROR;
1594  }
1595  else
1596  LOG_TRACE(this.netname, " can't walk to its personal waypoint (after ", ftos(this.havocbot_personal_waypoint_failcounter), " failed attempts), trying later");
1597  }
1598  }
1599 
1601  debuggoalstack(this);
1602 
1603 
1604  // Go!
1605  havocbot_movetogoal(this);
1606 
1607  if (!this.bot_aimdir_executed && this.goalcurrent)
1608  {
1609  // Heading
1611  dir -= this.origin + this.view_ofs;
1612  dir.z = 0;
1613  bot_aimdir(this, dir, 0);
1614  }
1615 
1617  {
1618  // Step 5: Waypoint reached
1619  LOG_TRACE(this.netname, "'s personal waypoint reached");
1621  this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_REACHED;
1622  return CMD_STATUS_FINISHED;
1623  }
1624 
1625  return CMD_STATUS_EXECUTING;
1626  }
1627 
1628  // Step 2: Linking waypoint
1630  {
1631  // Wait until it is linked
1632  if(!this.havocbot_personal_waypoint.wplinked)
1633  {
1634  LOG_TRACE(this.netname, " waiting for personal waypoint to be linked");
1635  return CMD_STATUS_EXECUTING;
1636  }
1637 
1638  this.havocbot_personal_waypoint_searchtime = time; // so we set the route next frame
1639  this.aistatus &= ~AI_STATUS_WAYPOINT_PERSONAL_LINKING;
1641 
1642  // Step 3: Route to waypoint
1643  LOG_TRACE(this.netname, " walking to its personal waypoint");
1644 
1645  return CMD_STATUS_EXECUTING;
1646  }
1647 
1648  // Step 1: Spawning waypoint
1649  wp = waypoint_spawnpersonal(this, pos);
1650  if(wp==NULL)
1651  {
1652  LOG_TRACE("Error: Can't spawn personal waypoint at ",vtos(pos));
1653  return CMD_STATUS_ERROR;
1654  }
1655 
1656  this.havocbot_personal_waypoint = wp;
1659 
1660  // if pos is inside a teleport, then let's mark it as teleport waypoint
1662  {
1663  wp.wpflags |= WAYPOINTFLAG_TELEPORT;
1664  this.lastteleporttime = 0;
1665  });
1666 
1667 /*
1668  if(wp.wpflags & WAYPOINTFLAG_TELEPORT)
1669  print("routing to a teleporter\n");
1670  else
1671  print("routing to a non-teleporter\n");
1672 */
1673 
1674  return CMD_STATUS_EXECUTING;
1675 }
1676 
1678 {
1679  navigation_clearroute(this);
1680  return CMD_STATUS_FINISHED;
1681 }
1682 
1684 {
1685  this.bot_ai = havocbot_ai;
1686  this.cmd_moveto = havocbot_moveto;
1687  this.cmd_resetgoal = havocbot_resetgoal;
1688 
1689  // NOTE: bot is not player yet
1690  havocbot_chooserole(this);
1691 }
1692 
1694 {
1695  // LordHavoc: disabled because this is too expensive
1696  return '0 0 0';
1697 #if 0
1698  entity head;
1699  vector dodge, v, n;
1700  float danger, bestdanger, vl, d;
1701  dodge = '0 0 0';
1702  bestdanger = -20;
1703  // check for dangerous objects near bot or approaching bot
1704  head = findchainfloat(bot_dodge, true);
1705  while(head)
1706  {
1707  if (head.owner != this)
1708  {
1709  vl = vlen(head.velocity);
1710  if (vl > autocvar_sv_maxspeed * 0.3)
1711  {
1712  n = normalize(head.velocity);
1713  v = this.origin - head.origin;
1714  d = v * n;
1715  if (d > (0 - head.bot_dodgerating))
1716  if (d < (vl * 0.2 + head.bot_dodgerating))
1717  {
1718  // calculate direction and distance from the flight path, by removing the forward axis
1719  v = v - (n * (v * n));
1720  danger = head.bot_dodgerating - vlen(v);
1721  if (bestdanger < danger)
1722  {
1723  bestdanger = danger;
1724  // dodge to the side of the object
1725  dodge = normalize(v);
1726  }
1727  }
1728  }
1729  else
1730  {
1731  danger = head.bot_dodgerating - vlen(head.origin - this.origin);
1732  if (bestdanger < danger)
1733  {
1734  bestdanger = danger;
1735  dodge = normalize(this.origin - head.origin);
1736  }
1737  }
1738  }
1739  head = head.chain;
1740  }
1741  return dodge;
1742 #endif
1743 }
#define PHYS_INPUT_BUTTON_ATCK2(s)
Definition: player.qh:148
#define WEPSET(id)
Definition: all.qh:37
float havocbot_stickenemy_time
Definition: havocbot.qh:21
#define PHYS_INPUT_BUTTON_JUMP(s)
Definition: player.qh:147
const int WAYPOINTFLAG_CROUCH
Definition: api.qh:22
#define IL_EACH(this, cond, body)
float autocvar_bot_ai_bunnyhop_turn_angle_min
Definition: cvars.qh:25
#define PHYS_INPUT_BUTTON_CROUCH(s)
Definition: player.qh:150
float havocbot_moveto(entity this, vector pos)
Definition: havocbot.qc:1560
float ignoregoaltime
Definition: api.qh:98
float havocbot_ducktime
Definition: havocbot.qh:14
float weapon_load[REGISTRY_MAX(Weapons)]
Definition: weaponsystem.qh:29
entity ignoregoal
Definition: api.qh:99
void navigation_goalrating_start(entity this)
Definition: navigation.qc:1830
float DEG2RAD
Definition: csprogsdefs.qc:961
const float CONTENT_LAVA
Definition: csprogsdefs.qc:240
const int WEP_FLAG_RELOADABLE
Definition: weapon.qh:201
bool autocvar_bot_ai_weapon_combo
Definition: cvars.qh:45
void waypoint_remove(entity wp)
Definition: waypoints.qc:821
float lastcombotime
Definition: havocbot.qh:10
vector view_ofs
Definition: progsdefs.qc:151
float bot_dodgeskill
Definition: bot.qh:27
float bot_weaponskill
Definition: bot.qh:31
void havocbot_chooserole(entity this)
Definition: roles.qc:241
float autocvar_bot_ai_enemydetectionradius
Definition: cvars.qh:37
float trace_dphitq3surfaceflags
void navigation_goalrating_end(entity this)
Definition: navigation.qc:1845
float havocbot_resetgoal(entity this)
Definition: havocbot.qc:1677
float waterlevel
Definition: progsdefs.qc:181
vector get_closer_dest(entity ent, vector org)
Definition: navigation.qc:103
#define IS_DUCKED(s)
Definition: player.qh:206
#define IS_INDEPENDENT_PLAYER(e)
Definition: client.qh:314
float nextaim
Definition: havocbot.qh:17
int bot_execute_commands(entity this)
Definition: scripting.qc:1350
IntrusiveList g_teleporters
Definition: teleporters.qh:10
const int AI_STATUS_ROAMING
Definition: bot.qh:6
entity havocbot_gettarget(entity this, bool secondary)
Definition: havocbot.qc:1278
int aistatus
Definition: bot.qh:20
float lastfiredweapon
Definition: havocbot.qh:9
const int WAYPOINTFLAG_LADDER
Definition: api.qh:19
float goalentity_lock_timeout
Definition: api.qh:97
entity() spawn
#define REGISTRY_GET(id, i)
Definition: registry.qh:43
float autocvar_bot_ai_bunnyhop_turn_angle_max
Definition: cvars.qh:26
float autocvar_bot_ai_ignoregoal_timeout
Definition: cvars.qh:39
ClientState CS(Client this)
Definition: state.qh:47
void bot_aimdir(entity this, vector v, float maxfiredeviation)
Definition: aim.qc:186
float autocvar_bot_ai_keyboard_threshold
Definition: cvars.qh:41
const int WAYPOINTFLAG_JUMP
Definition: api.qh:20
vector v_angle
Definition: progsdefs.qc:161
#define IS_ONGROUND(s)
Definition: movetypes.qh:16
float skill
Definition: api.qh:35
vector maxs
Definition: csprogsdefs.qc:113
float autocvar_bot_ai_enemydetectioninterval_stickingtoenemy
Definition: cvars.qh:36
const float CONTENT_SOLID
Definition: csprogsdefs.qc:237
const int AI_STATUS_JETPACK_LANDING
Definition: bot.qh:16
#define ROCKETJUMP_DAMAGE()
float checkpvs(vector viewpos, entity viewee)
string netname
Definition: powerups.qc:20
float bot_distance_far
Definition: bot.qh:45
#define WEP_CVAR(wepname, name)
Definition: all.qh:299
float bot_weapons_close[REGISTRY_MAX(Weapons)]
Definition: api.qh:32
const int AI_STATUS_WAYPOINT_PERSONAL_GOING
Definition: bot.qh:13
#define IS_MONSTER(v)
Definition: utils.qh:21
float jumppadcount
Definition: jumppads.qh:15
#define CMD_STATUS_ERROR
Definition: scripting.qh:9
float havocbot_blockhead
Definition: havocbot.qh:11
const int AI_STATUS_OUT_JUMPPAD
Definition: bot.qh:10
entity bot_aimtarg
Definition: aim.qh:83
origin
Definition: ent_cs.qc:114
float rocketjumptime
Definition: havocbot.qh:16
float ping
Definition: main.qh:138
void havocbot_aim(entity this)
Definition: havocbot.qc:1529
bool tracewalk(entity e, vector start, vector m1, vector m2, vector end, float end_height, float movemode)
Definition: navigation.qc:273
float bot_stop_moving_timeout
Definition: havocbot.qh:24
void navigation_goalrating_timeout_force(entity this)
Definition: navigation.qc:28
int clip_load
Definition: wepent.qh:14
ERASEABLE vector point_line_vec(vector p, vector l0, vector ldir)
Definition: vector.qh:119
float autocvar_bot_ai_chooseweaponinterval
Definition: cvars.qh:28
void navigation_routerating(entity this, entity e, float f, float rangebias)
Definition: navigation.qc:1220
IntrusiveList g_bot_targets
Definition: api.qh:149
float bot_chooseweapontime
Definition: havocbot.qh:15
entity trace_ent
Definition: csprogsdefs.qc:40
int clip_size
Definition: wepent.qh:15
float autocvar_bot_ai_bunnyhop_skilloffset
Definition: cvars.qh:22
float bot_tracewalk_time
Definition: api.qh:37
int item_group
Definition: items.qh:16
float autocvar_bot_ai_bunnyhop_dir_deviation_max
Definition: cvars.qh:23
float bot_moveskill
Definition: api.qh:42
#define REGISTRY_COUNT(id)
Definition: registry.qh:18
RES_HEALTH
Definition: ent_cs.qc:126
const int AI_STATUS_WAYPOINT_PERSONAL_LINKING
Definition: bot.qh:12
#define CMD_STATUS_FINISHED
Definition: scripting.qh:8
float havocbot_chooseenemy_finished
Definition: havocbot.qh:20
bool navigation_goalrating_timeout_can_be_anticipated(entity this)
Definition: navigation.qc:55
float bot_rangepreference
Definition: bot.qh:33
float bot_distance_close
Definition: bot.qh:46
entity enemy
Definition: sv_ctf.qh:143
float Q3SURFACEFLAG_SKY
const float MOVE_NOMONSTERS
Definition: csprogsdefs.qc:253
vector mins
Definition: csprogsdefs.qc:113
#define vlen2(v)
Definition: vector.qh:4
void lag_update(entity this)
Definition: aim.qc:92
const float CONTENT_SLIME
Definition: csprogsdefs.qc:239
vector v_up
Definition: csprogsdefs.qc:31
const int WAYPOINTFLAG_TELEPORT
Definition: api.qh:13
void havocbot_keyboard_movement(entity this, vector destorg)
Definition: havocbot.qc:274
ERASEABLE float boxesoverlap(vector m1, vector m2, vector m3, vector m4)
requires that m2>m1 in all coordinates, and that m4>m3
Definition: vector.qh:73
const int MAX_WEAPONSLOTS
Definition: weapon.qh:13
float WarpZoneLib_BoxTouchesBrush(vector mi, vector ma, entity e, entity ig)
Definition: common.qc:138
const float CONTENT_WATER
Definition: csprogsdefs.qc:238
IntrusiveList g_items
Definition: items.qh:126
entity goalentity
Definition: progsdefs.qc:189
entity havocbot_personal_waypoint
Definition: havocbot.qh:28
#define NULL
Definition: post.qh:17
float frametime
Definition: csprogsdefs.qc:17
float DPCONTENTS_SOLID
#define CMD_STATUS_EXECUTING
Definition: scripting.qh:7
void havocbot_chooseenemy(entity this)
Definition: havocbot.qc:1298
const float M_SQRT2
Definition: mathlib.qh:114
float lastteleporttime
Definition: api.qh:50
vector trace_endpos
Definition: csprogsdefs.qc:37
#define PHYS_INPUT_BUTTON_ATCK(s)
Definition: player.qh:146
float autocvar_bot_ai_bunnyhop_downward_pitch_max
Definition: cvars.qh:24
const int AI_STATUS_WAYPOINT_PERSONAL_REACHED
Definition: bot.qh:14
float autocvar_bot_ai_bunnyhop_turn_angle_reduction
Definition: cvars.qh:27
entity draggedby
Definition: sv_monsters.qc:708
const int WATERLEVEL_WETFEET
Definition: movetypes.qh:12
void havocbot_movetogoal(entity this)
Definition: havocbot.qc:399
#define IS_DEAD(s)
Definition: utils.qh:26
float bot_weapons_far[REGISTRY_MAX(Weapons)]
Definition: api.qh:33
IntrusiveList g_waypoints
Definition: api.qh:148
entity waypoint_spawnpersonal(entity this, vector position)
Definition: waypoints.qc:2075
void navigation_goalrating_timeout_expire(entity this, float seconds)
Definition: navigation.qc:35
float bot_custom_weapon
Definition: api.qh:31
const int AI_STATUS_OUT_WATER
Definition: bot.qh:11
float bot_jump_time
Definition: bot.qh:70
entity ladder_entity
Definition: ladder.qh:11
#define CENTER_OR_VIEWOFS(ent)
Definition: utils.qh:28
vector havocbot_keyboard
Definition: havocbot.qh:30
vector(float skel, float bonenum) _skel_get_boneabs_hidden
void havocbot_bunnyhop(entity this, vector dir)
Definition: havocbot.qc:217
float autocvar_bot_ai_weapon_combo_threshold
Definition: cvars.qh:46
float DPCONTENTS_OPAQUE
bool bot_aimdir_executed
Definition: aim.qh:62
const int AI_STATUS_DANGER_AHEAD
Definition: bot.qh:9
bool autocvar_bot_nofire
Definition: cvars.qh:54
void set_tracewalk_dest(entity ent, vector org, bool fix_player_dest)
Definition: navigation.qc:119
vector v
Definition: ent_cs.qc:116
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
Definition: cl_resources.qc:10
float DPCONTENTS_BODY
void havocbot_chooseweapon(entity this,.entity weaponentity)
Definition: havocbot.qc:1428
vector havocbot_dodge(entity this)
Definition: havocbot.qc:1693
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition: vector.qh:8
void havocbot_ai(entity this)
Definition: havocbot.qc:35
const vector eZ
Definition: vector.qh:46
#define LOG_TRACE(...)
Definition: log.qh:81
bool waypoint_is_hardwiredlink(entity wp_from, entity wp_to)
Definition: waypoints.qc:281
#define PHYS_INPUT_BUTTON_JETPACK(s)
Definition: player.qh:158
float items
Definition: progsdefs.qc:145
float havocbot_chooseweapon_checkreload(entity this,.entity weaponentity, int new_weapon)
Definition: havocbot.qc:1408
void havocbot_setupbot(entity this)
Definition: havocbot.qc:1683
float autocvar_bot_ai_enemydetectioninterval
Definition: cvars.qh:35
float autocvar_bot_ai_keyboard_distance
Definition: cvars.qh:40
bool autocvar_bot_debug_goalstack
Definition: cvars.qh:59
vector v_right
Definition: csprogsdefs.qc:31
float havocbot_personal_waypoint_failcounter
Definition: havocbot.qh:19
const int WATERLEVEL_SWIMMING
Definition: movetypes.qh:13
bool havocbot_checkgoaldistance(entity this, vector gco)
Definition: havocbot.qc:345
#define MUTATOR_CALLHOOK(id,...)
Definition: base.qh:140
float bot_dodge
Definition: api.qh:40
#define vec2(...)
Definition: vector.qh:90
#define LABEL(id)
Definition: compiler.qh:36
entity weaponentities[MAX_WEAPONSLOTS]
Definition: weapon.qh:14
bool bot_shouldattack(entity this, entity e)
Definition: aim.qc:112
bool Item_IsLoot(entity item)
Returns whether the item is loot.
Definition: spawning.qc:121
float dphitcontentsmask
entity havocbot_select_an_item_of_group(entity this, int gr)
Definition: havocbot.qc:371
float havocbot_keyboardskill
Definition: havocbot.qh:7
float bot_weapons_mid[REGISTRY_MAX(Weapons)]
Definition: api.qh:34
vector trace_plane_normal
Definition: csprogsdefs.qc:38
#define ATTACK_FINISHED(ent, w)
Definition: weaponsystem.qh:42
float trace_startsolid
Definition: csprogsdefs.qc:35
best
Definition: all.qh:77
float havocbot_personal_waypoint_searchtime
Definition: havocbot.qh:18
bool bot_ispaused(entity this)
Definition: scripting.qc:1003
float havocbot_keyboardtime
Definition: havocbot.qh:13
entity bot_strategytoken
Definition: bot.qh:76
const int AI_STATUS_ATTACKING
Definition: bot.qh:7
fields which are explicitly/manually set are marked with "M", fields set automatically are marked wit...
Definition: weapon.qh:41
bool client_hasweapon(entity this, Weapon wpn,.entity weaponentity, float andammo, bool complain)
Definition: selection.qc:48
float time
Definition: csprogsdefs.qc:16
const int AI_STATUS_JETPACK_FLYING
Definition: bot.qh:15
vector velocity
Definition: csprogsdefs.qc:103
float lag_additem(entity this, float t, float f1, float f2, entity e1, vector v1, vector v2, vector v3, vector v4)
Definition: aim.qc:101
int dir
Definition: impulse.qc:89
#define makevectors
Definition: post.qh:21
float trace_fraction
Definition: csprogsdefs.qc:36
#define FOREACH(list, cond, body)
Definition: iter.qh:19
#define boolean(value)
Definition: bool.qh:9
const int AI_STATUS_RUNNING
Definition: bot.qh:8
WepSet g_weaponarena_weapons
Definition: world.qh:77
#define IS_PLAYER(v)
Definition: utils.qh:9
bool havocbot_moveto_refresh_route(entity this)
Definition: havocbot.qc:1549
const float FLOAT_MAX
Definition: float.qh:3
float bot_strategytoken_taken
Definition: bot.qh:75
float DPCONTENTS_CORPSE
vector v_forward
Definition: csprogsdefs.qc:31