Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
havocbot.qc File Reference
Include dependency graph for havocbot.qc:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Macros

#define ROCKETJUMP_DAMAGE()

Functions

void havocbot_ai (entity this)
void havocbot_aim (entity this)
void havocbot_bunnyhop (entity this, vector dir)
int havocbot_checkdanger (entity this, vector dst_ahead)
bool havocbot_checkgoaldistance (entity this, vector gco)
void havocbot_chooseenemy (entity this)
void havocbot_chooseweapon (entity this,.entity weaponentity)
bool havocbot_chooseweapon_checkreload (entity this,.entity weaponentity, int new_weapon)
vector havocbot_dodge (entity this)
void havocbot_keyboard_movement (entity this, vector destorg)
float havocbot_moveto (entity this, vector pos)
bool havocbot_moveto_refresh_route (entity this)
void havocbot_movetogoal (entity this)
float havocbot_resetgoal (entity this)
entity havocbot_select_an_item_of_group (entity this, int gr)
void havocbot_setupbot (entity this)

Macro Definition Documentation

◆ ROCKETJUMP_DAMAGE

#define ROCKETJUMP_DAMAGE ( )
Value:
WEP_CVAR(WEP_DEVASTATOR, damage) * 0.8 \
* ((StatusEffects_active(STATUSEFFECT_Strength, this)) ? autocvar_g_balance_powerup_strength_selfdamage : 1) \
float autocvar_g_balance_powerup_invincible_takedamage
Definition shield.qh:18
bool StatusEffects_active(StatusEffect this, entity actor)
float autocvar_g_balance_powerup_strength_selfdamage
Definition strength.qh:20
#define WEP_CVAR(wep, name)
Definition all.qh:321

Referenced by havocbot_movetogoal().

Function Documentation

◆ havocbot_ai()

void havocbot_ai ( entity this)

Definition at line 35 of file havocbot.qc.

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}
void bot_aimdir(entity this, vector v, float maxfiredeviation)
Definition aim.qc:150
bool bot_aimdir_executed
Definition aim.qh:13
float lastteleporttime
Definition api.qh:50
void navigation_goalrating_timeout_force(entity this)
Definition navigation.qc:29
vector get_closer_dest(entity ent, vector org)
float skill
Definition api.qh:35
const int WAYPOINTFLAG_JUMP
Definition api.qh:20
IntrusiveList g_waypoints
Definition api.qh:148
const int AI_STATUS_ATTACKING
Definition bot.qh:7
int aistatus
Definition bot.qh:20
entity bot_strategytoken
Definition bot.qh:77
const int AI_STATUS_ROAMING
Definition bot.qh:6
float bot_strategytoken_taken
Definition bot.qh:76
const int AI_STATUS_OUT_WATER
Definition bot.qh:11
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
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
#define IS_DEAD(s)
Definition player.qh:245
float waterlevel
Definition player.qh:226
#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 time
vector origin
bool autocvar_bot_nofire
Definition cvars.qh:51
float autocvar_bot_ai_chooseweaponinterval
Definition cvars.qh:25
Weapons
Definition guide.qh:113
void havocbot_chooseenemy(entity this)
Definition havocbot.qc:1334
void havocbot_movetogoal(entity this)
Definition havocbot.qc:446
void havocbot_chooseweapon(entity this,.entity weaponentity)
Definition havocbot.qc:1495
void havocbot_aim(entity this)
Definition havocbot.qc:1609
float lastfiredweapon
Definition havocbot.qh:9
float havocbot_blockhead
Definition havocbot.qh:11
float bot_chooseweapontime
Definition havocbot.qh:15
#define IL_EACH(this, cond, body)
#define FOREACH(list, cond, body)
Definition iter.qh:19
float jumppadcount
Definition jumppads.qh:28
#define STAT(...)
Definition stats.qh:82
const int WATERLEVEL_SWIMMING
Definition movetypes.qh:13
#define IS_ONGROUND(s)
Definition movetypes.qh:16
void navigation_clearroute(entity this)
void navigation_pushroute(entity this, entity e)
entity goalcurrent
Definition navigation.qh:19
entity goalcurrent_prev
Definition navigation.qh:28
#define NULL
Definition post.qh:14
vector view_ofs
Definition progsdefs.qc:151
bool bot_ispaused(entity this)
int bot_execute_commands(entity this)
vector
Definition self.qh:92
#define IS_INDEPENDENT_PLAYER(e)
Definition client.qh:312
int dir
Definition impulse.qc:89
ClientState CS(Client this)
Definition state.qh:47
entity enemy
Definition sv_ctf.qh:153
entity draggedby
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
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
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

References AI_STATUS_ATTACKING, AI_STATUS_OUT_WATER, AI_STATUS_ROAMING, aistatus, autocvar_bot_ai_chooseweaponinterval, autocvar_bot_nofire, bot_aimdir(), bot_aimdir_executed, bot_chooseweapontime, bot_execute_commands(), bot_ispaused(), bot_strategytoken, bot_strategytoken_taken, clip_load, clip_size, CS(), dir, draggedby, enemy, entity(), FOREACH, g_waypoints, get_closer_dest(), goalcurrent, goalcurrent_prev, havocbot_aim(), havocbot_blockhead, havocbot_chooseenemy(), havocbot_chooseweapon(), havocbot_movetogoal(), IL_EACH, IS_DEAD, IS_INDEPENDENT_PLAYER, IS_ONGROUND, IS_PLAYER, jumppadcount, lastfiredweapon, lastteleporttime, m_switchweapon, m_weapon, MAX_WEAPONSLOTS, navigation_clearroute(), navigation_goalrating_timeout_force(), navigation_pushroute(), NULL, origin, PHYS_INPUT_BUTTON_ATCK, PHYS_INPUT_BUTTON_ATCK2, skill, STAT, StatusEffects_active(), time, vdist, vector, view_ofs, waterlevel, WATERLEVEL_SWIMMING, WAYPOINTFLAG_JUMP, weaponentities, Weapons, WEP_FLAG_RELOADABLE, and Weapon::wr_aim().

Referenced by havocbot_setupbot().

◆ havocbot_aim()

void havocbot_aim ( entity this)

Definition at line 1609 of file havocbot.qc.

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}
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
vector velocity
float nextaim
Definition havocbot.qh:17

References enemy, entity(), MUTATOR_CALLHOOK, nextaim, time, vector, velocity, and waterlevel.

Referenced by havocbot_ai().

◆ havocbot_bunnyhop()

void havocbot_bunnyhop ( entity this,
vector dir )

Definition at line 215 of file havocbot.qc.

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}
const int WAYPOINTFLAG_TELEPORT
Definition api.qh:13
const int AI_STATUS_RUNNING
Definition bot.qh:8
const int AI_STATUS_DANGER_AHEAD
Definition bot.qh:9
float bot_jump_time
Definition bot.qh:71
#define PHYS_INPUT_BUTTON_JUMP(s)
Definition player.qh:151
float autocvar_sv_maxspeed
Definition player.qh:53
#define IS_DUCKED(s)
Definition player.qh:210
float autocvar_bot_ai_bunnyhop_downward_pitch_max
Definition cvars.qh:21
float autocvar_bot_ai_bunnyhop_turn_angle_min
Definition cvars.qh:22
float autocvar_bot_ai_bunnyhop_turn_angle_max
Definition cvars.qh:23
float autocvar_bot_ai_bunnyhop_turn_angle_reduction
Definition cvars.qh:24
float autocvar_bot_ai_bunnyhop_dir_deviation_max
Definition cvars.qh:20
float vlen(vector v)
vector vectoangles(vector v)
float fabs(float f)
float max(float f,...)
const int WATERLEVEL_WETFEET
Definition movetypes.qh:12
entity goalstack01
Definition navigation.qh:19
vector vector ang
Definition self.qh:92
#define vec2(...)
Definition vector.qh:90

References AI_STATUS_ATTACKING, AI_STATUS_DANGER_AHEAD, AI_STATUS_RUNNING, aistatus, ang, autocvar_bot_ai_bunnyhop_dir_deviation_max, autocvar_bot_ai_bunnyhop_downward_pitch_max, autocvar_bot_ai_bunnyhop_turn_angle_max, autocvar_bot_ai_bunnyhop_turn_angle_min, autocvar_bot_ai_bunnyhop_turn_angle_reduction, autocvar_sv_maxspeed, bot_jump_time, dir, entity(), fabs(), get_closer_dest(), goalcurrent, goalcurrent_prev, goalstack01, IS_DUCKED, IS_ONGROUND, IS_PLAYER, max(), origin, PHYS_INPUT_BUTTON_JUMP, time, vdist, vec2, vectoangles(), vector, velocity, vlen(), waterlevel, WATERLEVEL_WETFEET, WAYPOINTFLAG_JUMP, and WAYPOINTFLAG_TELEPORT.

Referenced by havocbot_movetogoal().

◆ havocbot_checkdanger()

int havocbot_checkdanger ( entity this,
vector dst_ahead )

Definition at line 401 of file havocbot.qc.

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}
float Q3SURFACEFLAG_SKY
const float CONTENT_SOLID
vector mins
vector trace_endpos
vector maxs
float trace_dphitq3surfaceflags
float trace_fraction
const float CONTENT_LAVA
const float CONTENT_SLIME
bool tracebox_hits_trigger_hurt(vector start, vector e_min, vector e_max, vector end)
Definition hurt.qc:79
float min(float f,...)
bool waypoint_is_hardwiredlink(entity wp_from, entity wp_to)
Definition waypoints.qc:283

References AI_STATUS_ROAMING, AI_STATUS_RUNNING, aistatus, CONTENT_LAVA, CONTENT_SLIME, CONTENT_SOLID, entity(), goalcurrent, goalcurrent_prev, IS_ONGROUND, jumppadcount, maxs, min(), mins, NULL, origin, PHYS_INPUT_BUTTON_JUMP, Q3SURFACEFLAG_SKY, trace_dphitq3surfaceflags, trace_endpos, trace_fraction, tracebox_hits_trigger_hurt(), vector, view_ofs, waypoint_is_hardwiredlink(), and WAYPOINTFLAG_JUMP.

Referenced by havocbot_movetogoal().

◆ havocbot_checkgoaldistance()

bool havocbot_checkgoaldistance ( entity this,
vector gco )

Definition at line 344 of file havocbot.qc.

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}
float bot_stop_moving_timeout
Definition havocbot.qh:24
float goalcurrent_distance_2d
Definition navigation.qh:30
float goalcurrent_distance_time
Definition navigation.qh:31
float goalcurrent_distance_z
Definition navigation.qh:29

References bot_stop_moving_timeout, entity(), fabs(), goalcurrent_distance_2d, goalcurrent_distance_time, goalcurrent_distance_z, max(), origin, time, vec2, vector, and vlen().

Referenced by havocbot_movetogoal().

◆ havocbot_chooseenemy()

void havocbot_chooseenemy ( entity this)

Definition at line 1334 of file havocbot.qc.

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}
IntrusiveList g_bot_targets
Definition api.qh:149
bool bot_shouldattack(entity this, entity e)
Definition aim.qc:97
#define SUPERBOT
Definition bot.qh:23
float GetResource(entity e, Resource res_type)
Returns the current amount of resource the given entity has.
#define LABEL(id)
Definition compiler.qh:34
entity trace_ent
float DPCONTENTS_SOLID
float DPCONTENTS_CORPSE
float DPCONTENTS_BODY
float DPCONTENTS_OPAQUE
float dphitcontentsmask
float autocvar_bot_ai_enemydetectioninterval
Definition cvars.qh:32
float autocvar_bot_ai_enemydetectioninterval_stickingtoenemy
Definition cvars.qh:33
float autocvar_bot_ai_enemydetectionradius
Definition cvars.qh:34
RES_ARMOR
Definition ent_cs.qc:130
float havocbot_stickenemy_time
Definition havocbot.qh:21
float havocbot_chooseenemy_finished
Definition havocbot.qh:20
best
Definition all.qh:82
float bound(float min, float value, float max)
float health
Legacy fields for the resources. To be removed.
Definition resources.qh:9
#define vlen2(v)
Definition vector.qh:4

References autocvar_bot_ai_enemydetectioninterval, autocvar_bot_ai_enemydetectionradius, autocvar_bot_nofire, best, bot_shouldattack(), bound(), DPCONTENTS_BODY, DPCONTENTS_CORPSE, DPCONTENTS_OPAQUE, DPCONTENTS_SOLID, dphitcontentsmask, enemy, entity(), g_bot_targets, GetResource(), havocbot_chooseenemy_finished, havocbot_stickenemy_time, health, IL_EACH, IS_INDEPENDENT_PLAYER, LABEL, NULL, origin, RES_ARMOR, STAT, SUPERBOT, time, trace_ent, trace_fraction, vdist, vector, view_ofs, and vlen2.

Referenced by havocbot_ai().

◆ havocbot_chooseweapon()

void havocbot_chooseweapon ( entity this,
.entity weaponentity )

Definition at line 1495 of file havocbot.qc.

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}
float bot_weapons_close[REGISTRY_MAX(Weapons)]
Definition api.qh:32
float bot_custom_weapon
Definition api.qh:31
float bot_weapons_mid[REGISTRY_MAX(Weapons)]
Definition api.qh:34
float bot_weapons_far[REGISTRY_MAX(Weapons)]
Definition api.qh:33
float bot_weaponskill
Definition bot.qh:33
float bot_rangepreference
Definition bot.qh:35
float bot_distance_far
Definition bot.qh:47
float bot_distance_close
Definition bot.qh:48
bool autocvar_bot_ai_weapon_combo
Definition cvars.qh:42
float autocvar_bot_ai_weapon_combo_threshold
Definition cvars.qh:43
bool havocbot_chooseweapon_checkreload(entity this,.entity weaponentity, int new_weapon)
Definition havocbot.qc:1473
float lastcombotime
Definition havocbot.qh:10
#define REGISTRY_COUNT(id)
Definition registry.qh:18
#define REGISTRY_GET(id, i)
Definition registry.qh:43
bool client_hasweapon(entity this, Weapon wpn,.entity weaponentity, float andammo, bool complain)
Definition selection.qc:48
#define WEPSET(id)
Definition all.qh:45
#define ATTACK_FINISHED(ent, w)
WepSet g_weaponarena_weapons
Definition world.qh:76

References ATTACK_FINISHED, autocvar_bot_ai_weapon_combo, autocvar_bot_ai_weapon_combo_threshold, bot_custom_weapon, bot_distance_close, bot_distance_far, bot_rangepreference, bot_weapons_close, bot_weapons_far, bot_weapons_mid, bot_weaponskill, bound(), client_hasweapon(), enemy, entity(), FOREACH, g_weaponarena_weapons, havocbot_chooseweapon_checkreload(), lastcombotime, m_switchweapon, m_weapon, NULL, origin, REGISTRY_COUNT, REGISTRY_GET, skill, time, vlen(), Weapons, and WEPSET.

Referenced by havocbot_ai().

◆ havocbot_chooseweapon_checkreload()

bool havocbot_chooseweapon_checkreload ( entity this,
.entity weaponentity,
int new_weapon )

Definition at line 1473 of file havocbot.qc.

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}
const int IT_UNLIMITED_AMMO
Definition item.qh:23
int items
Definition player.qh:227
float weapon_load[REGISTRY_MAX(Weapons)]

References entity(), FOREACH, IT_UNLIMITED_AMMO, items, skill, weapon_load, and Weapons.

Referenced by havocbot_chooseweapon().

◆ havocbot_dodge()

vector havocbot_dodge ( entity this)

Definition at line 1774 of file havocbot.qc.

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}
float bot_dodge
Definition api.qh:40
entity findchainfloat(.float field, float match)
vector normalize(vector v)

References autocvar_sv_maxspeed, bot_dodge, entity(), findchainfloat(), normalize(), origin, SUPERBOT, vector, and vlen().

Referenced by havocbot_movetogoal().

◆ havocbot_keyboard_movement()

void havocbot_keyboard_movement ( entity this,
vector destorg )

Definition at line 272 of file havocbot.qc.

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}
float bot_moveskill
Definition api.qh:42
#define PHYS_INPUT_BUTTON_CROUCH(s)
Definition player.qh:154
float autocvar_bot_ai_keyboard_distance
Definition cvars.qh:37
float autocvar_bot_ai_keyboard_threshold
Definition cvars.qh:38
float havocbot_keyboardtime
Definition havocbot.qh:13
float havocbot_keyboardskill
Definition havocbot.qh:7
float havocbot_ducktime
Definition havocbot.qh:14
vector havocbot_keyboard
Definition havocbot.qh:30
float random(void)

References autocvar_bot_ai_keyboard_distance, autocvar_bot_ai_keyboard_threshold, autocvar_sv_maxspeed, bot_moveskill, bound(), CS(), entity(), havocbot_ducktime, havocbot_keyboard, havocbot_keyboardskill, havocbot_keyboardtime, max(), min(), origin, PHYS_INPUT_BUTTON_CROUCH, random(), skill, time, vector, and vlen().

Referenced by havocbot_movetogoal().

◆ havocbot_moveto()

float havocbot_moveto ( entity this,
vector pos )

Definition at line 1641 of file havocbot.qc.

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}
void waypoint_remove(entity wp)
Definition waypoints.qc:819
const int AI_STATUS_WAYPOINT_PERSONAL_LINKING
Definition bot.qh:12
const int AI_STATUS_WAYPOINT_PERSONAL_REACHED
Definition bot.qh:14
const int AI_STATUS_WAYPOINT_PERSONAL_GOING
Definition bot.qh:13
string netname
Definition powerups.qc:20
bool autocvar_bot_debug_goalstack
Definition cvars.qh:56
bool havocbot_moveto_refresh_route(entity this)
Definition havocbot.qc:1630
float havocbot_personal_waypoint_searchtime
Definition havocbot.qh:18
entity havocbot_personal_waypoint
Definition havocbot.qh:28
float havocbot_personal_waypoint_failcounter
Definition havocbot.qh:19
float WarpZoneLib_BoxTouchesBrush(vector mi, vector ma, entity e, entity ig)
Definition common.qc:133
#define LOG_TRACE(...)
Definition log.qh:76
string vtos(vector v)
string ftos(float f)
void debuggoalstack(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
IntrusiveList g_teleporters
entity waypoint_spawnpersonal(entity this, vector position)

References AI_STATUS_WAYPOINT_PERSONAL_GOING, AI_STATUS_WAYPOINT_PERSONAL_LINKING, AI_STATUS_WAYPOINT_PERSONAL_REACHED, aistatus, autocvar_bot_debug_goalstack, bot_aimdir(), bot_aimdir_executed, bot_strategytoken_taken, CMD_STATUS_ERROR, CMD_STATUS_EXECUTING, CMD_STATUS_FINISHED, debuggoalstack(), dir, entity(), ftos(), g_teleporters, get_closer_dest(), goalcurrent, havocbot_moveto_refresh_route(), havocbot_movetogoal(), havocbot_personal_waypoint, havocbot_personal_waypoint_failcounter, havocbot_personal_waypoint_searchtime, IL_EACH, lastteleporttime, LOG_TRACE, netname, NULL, origin, time, vector, view_ofs, vtos(), WarpZoneLib_BoxTouchesBrush(), waypoint_remove(), waypoint_spawnpersonal(), and WAYPOINTFLAG_TELEPORT.

Referenced by havocbot_setupbot().

◆ havocbot_moveto_refresh_route()

bool havocbot_moveto_refresh_route ( entity this)

Definition at line 1630 of file havocbot.qc.

1631{
1632 // Refresh path to goal if necessary
1633 entity wp;
1636 navigation_routerating(this, wp, 10000, 10000);
1638 return (this.goalentity != NULL);
1639}
void navigation_goalrating_start(entity this)
void navigation_routerating(entity this, entity e, float f, float rangebias)
void navigation_goalrating_end(entity this)
entity goalentity
Definition viewloc.qh:16

References entity(), goalentity, havocbot_personal_waypoint, navigation_goalrating_end(), navigation_goalrating_start(), navigation_routerating(), and NULL.

Referenced by havocbot_moveto().

◆ havocbot_movetogoal()

void havocbot_movetogoal ( entity this)

Definition at line 446 of file havocbot.qc.

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}
const int WAYPOINTFLAG_CROUCH
Definition api.qh:22
const int WAYPOINTFLAG_LADDER
Definition api.qh:19
entity ignoregoal
Definition api.qh:99
bool navigation_goalrating_timeout_can_be_anticipated(entity this)
Definition navigation.qc:56
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 goalentity_lock_timeout
Definition api.qh:97
void navigation_goalrating_timeout_expire(entity this, float seconds)
Definition navigation.qc:36
float ignoregoaltime
Definition api.qh:98
#define boolean(value)
Definition bool.qh:9
const int AI_STATUS_JETPACK_LANDING
Definition bot.qh:16
const int AI_STATUS_OUT_JUMPPAD
Definition bot.qh:10
const int AI_STATUS_JETPACK_FLYING
Definition bot.qh:15
float bot_dodgeskill
Definition bot.qh:29
const int IT_JETPACK
Definition item.qh:26
float autocvar_sv_jumpvelocity
Definition player.qh:50
vector v_angle
Definition player.qh:237
#define PHYS_INPUT_BUTTON_JETPACK(s)
Definition player.qh:162
float autocvar_g_jetpack_acceleration_side
cvar loopback
Definition stats.qh:295
const vector PL_MIN_CONST
Definition constants.qh:56
const vector PL_MAX_CONST
Definition constants.qh:55
vector v_up
const float MOVE_NOMONSTERS
float DEG2RAD
float frametime
const float CONTENT_WATER
vector v_right
float checkpvs(vector viewpos, entity viewee)
float trace_startsolid
vector v_forward
vector trace_plane_normal
float autocvar_bot_ai_ignoregoal_timeout
Definition cvars.qh:36
float autocvar_bot_ai_bunnyhop_skilloffset
Definition cvars.qh:19
const float FLOAT_MAX
Definition float.qh:3
entity havocbot_select_an_item_of_group(entity this, int gr)
Definition havocbot.qc:370
int havocbot_checkdanger(entity this, vector dst_ahead)
Definition havocbot.qc:401
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_bunnyhop(entity this, vector dir)
Definition havocbot.qc:215
bool havocbot_checkgoaldistance(entity this, vector gco)
Definition havocbot.qc:344
float randomdirectiontime
Definition havocbot.qh:32
float rocketjumptime
Definition havocbot.qh:16
vector randomdirection
Definition havocbot.qh:33
#define ITEM_IS_LOOT(item)
Returns whether the item is loot.
Definition spawning.qh:39
entity ladder_entity
Definition ladder.qh:11
const float M_SQRT2
Definition mathlib.qh:114
float cos(float f)
bool navigation_routetogoal(entity this, entity e, vector startposition)
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
vector navigation_jetpack_point
Definition navigation.qh:78
vector jumpstepheightvec
Definition navigation.qh:10
vector stepheightvec
Definition navigation.qh:11
entity navigation_jetpack_goal
Definition navigation.qh:77
vector jumpheight_vec
Definition navigation.qh:12
vector tracewalk_dest
Definition navigation.qh:66
float tracewalk_dest_height
Definition navigation.qh:67
#define makevectors
Definition post.qh:21
#define crandom()
Returns a random number between -1.0 and 1.0.
Definition random.qh:32
IntrusiveList g_items
Definition items.qh:125
int item_group
Definition items.qh:94
if(frag_attacker.flagcarried)
Definition sv_ctf.qc:2325
#define IS_MONSTER(v)
Definition utils.qh:21
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

References AI_STATUS_ATTACKING, AI_STATUS_DANGER_AHEAD, AI_STATUS_JETPACK_FLYING, AI_STATUS_JETPACK_LANDING, AI_STATUS_OUT_JUMPPAD, AI_STATUS_OUT_WATER, aistatus, autocvar_bot_ai_bunnyhop_skilloffset, autocvar_bot_ai_ignoregoal_timeout, autocvar_bot_debug_goalstack, autocvar_g_jetpack_acceleration_side, autocvar_sv_jumpvelocity, autocvar_sv_maxspeed, boolean, bot_aimdir(), bot_aimdir_executed, bot_dodgeskill, bot_jump_time, bot_moveskill, bot_navigation_movemode, bot_stop_moving_timeout, bot_tracewalk_time, bound(), boxesoverlap(), checkpvs(), client_hasweapon(), CONTENT_WATER, cos(), crandom, CS(), debuggoalstack(), DEG2RAD, dir, enemy, entity(), eZ, fabs(), FLOAT_MAX, frametime, g_items, g_waypoints, get_closer_dest(), GetResource(), goalcurrent, goalcurrent_distance_2d, goalcurrent_distance_time, goalcurrent_distance_z, goalcurrent_prev, goalentity, goalentity_lock_timeout, goalentity_shouldbefrozen, goalstack01, havocbot_blockhead, havocbot_bunnyhop(), havocbot_checkdanger(), havocbot_checkgoaldistance(), havocbot_dodge(), havocbot_ducktime, havocbot_keyboard_movement(), havocbot_select_an_item_of_group(), ignoregoal, ignoregoaltime, IL_EACH, IS_DEAD, IS_DUCKED, IS_MONSTER, IS_ONGROUND, IS_PLAYER, IT_JETPACK, IT_UNLIMITED_AMMO, item_group, ITEM_IS_LOOT, items, jumpheight_vec, jumppadcount, jumpstepheightvec, LABEL, ladder_entity, LOG_TRACE, M_SQRT2, m_switchweapon, m_weapon, makevectors, max(), MAX_WEAPONSLOTS, maxs, mins, MOVE_NOMONSTERS, navigation_clearroute(), navigation_goalrating_timeout_can_be_anticipated(), navigation_goalrating_timeout_expire(), navigation_goalrating_timeout_force(), navigation_jetpack_goal, navigation_jetpack_point, navigation_poptouchedgoals(), navigation_pushroute(), navigation_routetogoal(), navigation_shortenpath(), netname, normalize(), NULL, origin, PHYS_INPUT_BUTTON_ATCK, PHYS_INPUT_BUTTON_ATCK2, PHYS_INPUT_BUTTON_CROUCH, PHYS_INPUT_BUTTON_JETPACK, PHYS_INPUT_BUTTON_JUMP, PL_MAX_CONST, PL_MIN_CONST, point_line_vec(), random(), randomdirection, randomdirectiontime, RES_ARMOR, ROCKETJUMP_DAMAGE, rocketjumptime, set_tracewalk_dest(), skill, STAT, stepheightvec, SUPERBOT, time, trace_endpos, trace_ent, trace_fraction, trace_plane_normal, trace_startsolid, tracebox_hits_trigger_hurt(), tracewalk(), tracewalk_dest, tracewalk_dest_height, v_angle, v_forward, v_right, v_up, vdist, vec2, vectoangles(), vector, velocity, view_ofs, vlen(), vlen2, vtos(), waterlevel, WATERLEVEL_SWIMMING, WATERLEVEL_WETFEET, waypoint_is_hardwiredlink(), WAYPOINTFLAG_CROUCH, WAYPOINTFLAG_JUMP, WAYPOINTFLAG_LADDER, WAYPOINTFLAG_TELEPORT, weaponentities, and WEP_CVAR.

Referenced by havocbot_ai(), and havocbot_moveto().

◆ havocbot_resetgoal()

float havocbot_resetgoal ( entity this)

Definition at line 1758 of file havocbot.qc.

1759{
1761 return CMD_STATUS_FINISHED;
1762}

References CMD_STATUS_FINISHED, entity(), and navigation_clearroute().

Referenced by havocbot_setupbot().

◆ havocbot_select_an_item_of_group()

entity havocbot_select_an_item_of_group ( entity this,
int gr )

Definition at line 370 of file havocbot.qc.

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}

References entity(), g_items, IL_EACH, and NULL.

Referenced by havocbot_movetogoal().

◆ havocbot_setupbot()

void havocbot_setupbot ( entity this)

Definition at line 1764 of file havocbot.qc.

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}
float havocbot_resetgoal(entity this)
Definition havocbot.qc:1758
float havocbot_moveto(entity this, vector pos)
Definition havocbot.qc:1641
void havocbot_ai(entity this)
Definition havocbot.qc:35
void havocbot_chooserole(entity this)
Definition roles.qc:241

References entity(), havocbot_ai(), havocbot_chooserole(), havocbot_moveto(), and havocbot_resetgoal().

Referenced by bot_clientconnect(), and float().