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