Xonotic QuakeC
The free, fast arena FPS with crisp movement and a wide array of weapons
aim.qc
Go to the documentation of this file.
1#include "aim.qh"
2
4#include <common/state.qh>
5#include <common/stats.qh>
11
12// traces multiple trajectories to find one that will impact the target
13// 'end' vector is the place it aims for,
14// returns true only if it hit targ (don't target non-solid entities)
15
16float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore)
17{
18 float c, savesolid, shottime;
19 vector dir, end, v, o;
20 if (shotspeed < 1)
21 return false; // could cause division by zero if calculated
22 if (targ.solid < SOLID_BBOX) // SOLID_NOT and SOLID_TRIGGER
23 return false; // could never hit it
24 if (!tracetossent)
26 tracetossent.owner = ignore;
27 setsize(tracetossent, m1, m2);
28 savesolid = targ.solid;
29 targ.solid = SOLID_NOT;
30 o = (targ.absmin + targ.absmax) * 0.5;
31 shottime = ((vlen(o - org) / shotspeed) + shotdelay);
32 v = targ.velocity * shottime + o;
33 tracebox(o, targ.mins, targ.maxs, v, false, targ);
34 v = trace_endpos;
35 end = v + (targ.mins + targ.maxs) * 0.5;
36 float shotdistance = max(0.001, vlen(end - org));
37 if ((shotdistance / shotspeed + 0.2) > maxtime)
38 {
39 // out of range
40 targ.solid = savesolid;
41 return false;
42 }
43
46 tracetossfaketarget.solid = savesolid;
47 set_movetype(tracetossfaketarget, targ.move_movetype);
48 _setmodel(tracetossfaketarget, targ.model); // no low precision
49 tracetossfaketarget.model = targ.model;
50 tracetossfaketarget.modelindex = targ.modelindex;
51 setsize(tracetossfaketarget, targ.mins, targ.maxs);
52 setorigin(tracetossfaketarget, v);
53
54 c = 0;
55 // TODO: Consider changing this back to `/ shotdistance` after https://github.com/graphitemaster/gmqcc/issues/210.
56 dir = (end - org) * (1 / shotdistance); // same as dir = normalize(end - org); but cheaper
57 vector test_dir = dir;
58 vector test_dir_normalized = dir;
59 while (c < 10) // 10 traces
60 {
61 setorigin(tracetossent, org); // reset
62 tracetossent.velocity = findtrajectory_velocity = test_dir_normalized * shotspeed + shotspeedupward * '0 0 1';
63 tracetoss(tracetossent, ignore); // love builtin functions...
64 if (trace_ent == tracetossfaketarget) // done
65 {
66 targ.solid = savesolid;
67
68 // make it disappear
71 tracetossfaketarget.model = "";
72 tracetossfaketarget.modelindex = 0;
73 // relink to remove it from physics considerations
74 setorigin(tracetossfaketarget, v);
75
76 return true;
77 }
78 test_dir.z += 0.1; // aim up a little more
79 test_dir_normalized = normalize(test_dir);
80 c = c + 1;
81 }
82 targ.solid = savesolid;
83
84 // make it disappear
87 tracetossfaketarget.model = "";
88 tracetossfaketarget.modelindex = 0;
89 // relink to remove it from physics considerations
90 setorigin(tracetossfaketarget, v);
91
92 // leave a valid one even if it won't reach
93 findtrajectory_velocity = dir * shotspeed + shotspeedupward * '0 0 1';
94 return false;
95}
96
98{
99 if (targ.team == this.team)
100 {
101 if (targ == this)
102 return false;
103 if (teamplay)
104 if (targ.team != 0)
105 return false;
106 }
107
108 if(teamplay)
109 {
110 if(targ.team==0)
111 return false;
112 }
113 else if (autocvar_bot_ignore_bots && IS_BOT_CLIENT(targ))
114 return false;
115
116 if (!targ.takedamage)
117 return false;
118 if (IS_DEAD(targ))
119 return false;
121 return false;
122 if(targ.flags & FL_NOTARGET)
123 return false;
124 if(targ.alpha <= 0.1 && targ.alpha != 0)
125 return false; // invisible via alpha
126
127 if(MUTATOR_CALLHOOK(BotShouldAttack, this, targ))
128 return false;
129
130 return true;
131}
132
133// this function should be called after bot_aim so the aim is reset the next frame
135{
136 this.bot_mouseaim = this.v_angle;
137 this.bot_olddesiredang = this.v_angle;
138 this.bot_aimdir_executed = true;
139 this.bot_badaimtime = 0;
140 this.bot_aimthinktime = time;
141 this.bot_prevaimtime = time;
142 this.bot_1st_order_aimfilter = '0 0 0';
143 this.bot_2nd_order_aimfilter = '0 0 0';
144 this.bot_3th_order_aimfilter = '0 0 0';
145 this.bot_4th_order_aimfilter = '0 0 0';
146 this.bot_5th_order_aimfilter = '0 0 0';
147 this.bot_firetimer = 0;
148}
149
150void bot_aimdir(entity this, vector v, float maxfiredeviation)
151{
152 float dist, delta_t, blend;
153 vector desiredang, diffang;
154
155 this.bot_aimdir_executed = true;
156
157 //dprint("aim ", this.netname, ": old:", vtos(this.v_angle));
158 // make sure v_angle is sane first
159 this.v_angle_y = this.v_angle.y - floor(this.v_angle.y / 360) * 360;
160 this.v_angle_z = 0;
161
162 // make work bot_aim_reset even if called before this function
163 if (this.bot_prevaimtime == time)
164 return;
165
166 // if skill is high enough bots will not have any aim smoothing or aim errors
167 if (SUPERBOT)
168 {
169 this.v_angle = vectoangles(normalize(v));
170
171 this.v_angle.x *= -1;
172
173 makevectors(this.v_angle);
174 shotorg = this.origin + this.view_ofs;
176
177 // bot will fire on the next tick
178 this.bot_firetimer = time + 0.001;
179 return;
180 }
181
182 // invalid aim dir (can happen when bot overlaps target)
183 if(!v) return;
184
185 float skill_save = skill;
186 // allow turning in a more natural way when bot is walking
187 if (!this.enemy)
188 skill = max(4, skill);
189
190 // get the desired angles to aim at
191 //dprint(" at:", vtos(v));
192 //v = normalize(v);
193 //te_lightning2(NULL, this.origin + this.view_ofs, this.origin + this.view_ofs + v * 200);
194 if (time >= this.bot_badaimtime)
195 {
196 this.bot_badaimtime = max(this.bot_badaimtime + 0.2 + 0.3 * random(), time);
197 int f = bound(0, 1 - 0.1 * (skill + this.bot_offsetskill), 1);
199 this.bot_badaimoffset.x *= 0.7; // smaller vertical offset
200 }
201 float enemy_factor = ((this.enemy) ? 5 : 2);
202 // apply enemy_factor every frame so that the bigger offset is applied instantly when the bot aims to a new target
203 desiredang = vectoangles(v) + this.bot_badaimoffset * enemy_factor;
204 //dprint(" desired:", vtos(desiredang));
205 if (desiredang.x >= 180)
206 desiredang.x = desiredang.x - 360;
207 desiredang.x = bound(-90, 0 - desiredang.x, 90);
208 desiredang.z = this.v_angle.z;
209 //dprint(" / ", vtos(desiredang));
210
212 //if (this.bot_painintensity)
213 //{
214 // // shake from pain
215 // desiredang = desiredang + randomvec() * this.bot_painintensity * 0.2;
216 //}
217
218 // calculate turn angles
219 diffang = (desiredang - this.bot_olddesiredang);
220 // wrap yaw turn
221 diffang.y = diffang.y - floor(diffang.y / 360) * 360;
222 if (diffang.y >= 180)
223 diffang.y = diffang.y - 360;
224 this.bot_olddesiredang = desiredang;
225 //dprint(" diff:", vtos(diffang));
226
227 delta_t = time-this.bot_prevaimtime;
228 this.bot_prevaimtime = time;
229 // Here we will try to anticipate the comming aiming direction
231 + (diffang * (1 / delta_t) - this.bot_1st_order_aimfilter) * bound(0, autocvar_bot_ai_aimskill_order_filter_1st,1);
240
241 //blend = (bound(0,skill,10)*0.1)*((1-bound(0,skill,10)*0.05) ** 2.5)*5.656854249; //Plot formule before changing !
242 blend = bound(0,skill+this.bot_aimskill,10)*0.1;
243 desiredang = desiredang + blend *
244 (
250 );
251 desiredang.x = bound(-90, desiredang.x, 90);
252
253 // calculate turn angles
254 diffang = desiredang - this.bot_mouseaim;
255 // wrap yaw turn
256 diffang.y = diffang.y - floor(diffang.y / 360) * 360;
257 if (diffang.y >= 180)
258 diffang.y = diffang.y - 360;
259 //dprint(" diff:", vtos(diffang));
260
261 if (time >= this.bot_aimthinktime)
262 {
263 this.bot_aimthinktime = max(this.bot_aimthinktime + 0.5 - 0.05*(skill+this.bot_thinkskill), time);
264 this.bot_mouseaim = this.bot_mouseaim + diffang * (1-random()*0.1*bound(1,10-(skill+this.bot_thinkskill),10));
265 }
266
267 //this.v_angle = this.v_angle + diffang * bound(0, r * frametime * (skill * 0.5 + 2), 1);
268
269 diffang = this.bot_mouseaim - desiredang;
270 // wrap yaw turn
271 diffang.y = diffang.y - floor(diffang.y / 360) * 360;
272 if (diffang.y >= 180)
273 diffang.y = diffang.y - 360;
274 desiredang = desiredang + diffang * bound(0,autocvar_bot_ai_aimskill_think,1);
275
276 // calculate turn angles
277 diffang = desiredang - this.v_angle;
278 // wrap yaw turn
279 diffang.y = diffang.y - floor(diffang.y / 360) * 360;
280 if (diffang.y >= 180)
281 diffang.y = diffang.y - 360;
282 //dprint(" diff:", vtos(diffang));
283
284 // jitter tracking
285 dist = vlen(diffang);
286 //diffang = diffang + randomvec() * (dist * 0.05 * (3.5 - bound(0, skill, 3)));
287
288 // turn
289 float r, fixedrate, blendrate;
290 fixedrate = autocvar_bot_ai_aimskill_fixedrate / bound(1,dist,1000);
292 r = max(fixedrate, blendrate);
293 //this.v_angle = this.v_angle + diffang * bound(frametime, r * frametime * (2+skill*skill*0.05-random()*0.05*(10-skill)), 1);
294 r = bound(delta_t, r * delta_t * (2 + ((skill + this.bot_mouseskill) ** 3) * 0.005 - random()), 1);
295 this.v_angle += diffang * (r + (1 - r) * bound(0, 1 - autocvar_bot_ai_aimskill_mouse, 1));
296 this.v_angle_z = 0;
297 this.v_angle_y = this.v_angle.y - floor(this.v_angle.y / 360) * 360;
298 //dprint(" turn:", vtos(this.v_angle));
299
300 skill = skill_save;
301
302 if (maxfiredeviation <= 0)
303 return;
304
306 {
307 this.bot_firetimer = time + 0.2;
308 return;
309 }
310
311 makevectors(this.v_angle);
312 shotorg = this.origin + this.view_ofs;
314
315 // decide whether to fire this time
316 // v is the calculated trajectory, shotdir is bot view direction
317 // NOTE: checking if (v * shotdir > cos(maxfiredeviation * DEG2RAD)) would be cheaper
318 // but it gets evaluated to true even if v and shotdir have nearly opposite direction
319 vector deviation = vectoangles(v) - vectoangles(shotdir);
320 while (deviation.x < -180) deviation.x += 360; while (deviation.x > 180) deviation.x -= 360;
321 while (deviation.y < -180) deviation.y += 360; while (deviation.y > 180) deviation.y -= 360;
322 if (fabs(deviation.x) < maxfiredeviation && fabs(deviation.y) < maxfiredeviation)
323 {
324 traceline(shotorg, shotorg + shotdir * 1000, false, NULL);
325 if (vdist(trace_endpos - shotorg, <, 500 + 500 * bound(0, skill + this.bot_aggresskill, 10))
326 || random() * random() > bound(0, (skill + this.bot_aggresskill) * 0.05, 1))
327 {
328 this.bot_firetimer = time + bound(0.1, 0.5 - (skill + this.bot_aggresskill) * 0.05, 0.5);
329 }
330 }
331}
332
333vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay)
334{
335 // Try to add code here that predicts gravity effect here, no clue HOW to though ... well not yet atleast...
336 return targorigin + targvelocity * (shotdelay + vlen(targorigin - shotorg) / shotspeed);
337}
338
339bool bot_aim(entity this, .entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity, bool shot_accurate)
340{
341 float hf, distanceratio;
342 vector v;
343 hf = this.dphitcontentsmask;
345
346 float speed_factor = W_WeaponSpeedFactor(this);
347 shotspeed *= speed_factor;
348 shotspeedupward *= speed_factor;
349 if (!shotspeed)
350 {
351 LOG_TRACE("bot_aim: WARNING: weapon ", this.(weaponentity).m_weapon.m_name, " shotspeed is zero!");
352 shotspeed = 1000000;
353 }
354 if (!maxshottime)
355 {
356 LOG_TRACE("bot_aim: WARNING: weapon ", this.(weaponentity).m_weapon.m_name, " maxshottime is zero!");
357 maxshottime = 1;
358 }
359 makevectors(this.v_angle);
360 shotorg = this.origin + this.view_ofs;
362 vector enemy_org = (this.enemy.absmin + this.enemy.absmax) * 0.5;
363 v = bot_shotlead(enemy_org, this.enemy.velocity, shotspeed, this.bot_aimlatency);
364
365 // this formula was created starting from empiric values of distance and max hit angle
366 // with a player as target (32 qu wide) from the center of it right in front of the bot
367 // distance: 32 50 75 100 150 200 300 400 500
368 // max ang: 44 24 15.1 10.5 6.5 4.9 3.1 2.3 1.8
369 float dist = max(10, vlen(v - shotorg));
370 float maxfiredeviation = 1000 / (dist - 9) - 0.35;
371
372 float f = (shot_accurate) ? 1 : 1.6;
373 f += bound(0, (10 - (skill + this.bot_aimskill)) * 0.3, 3);
374 maxfiredeviation = min(90, maxfiredeviation * f);
375
376 if (applygravity && this.enemy)
377 {
378 if (!findtrajectorywithleading(shotorg, '0 0 0', '0 0 0', this.enemy, shotspeed, shotspeedupward, maxshottime, 0, this))
379 {
380 this.dphitcontentsmask = hf;
381 return false;
382 }
383
384 bot_aimdir(this, findtrajectory_velocity - shotspeedupward * '0 0 1', maxfiredeviation);
385 }
386 else
387 {
388 bot_aimdir(this, v - shotorg, maxfiredeviation);
389 //dprint("AIM: ");dprint(vtos(enemy_org));dprint(" + ");dprint(vtos(this.enemy.velocity));dprint(" * ");dprint(ftos(this.bot_aimlatency + vlen(this.enemy.origin - shotorg) / shotspeed));dprint(" = ");dprint(vtos(v));dprint(" : aimdir = ");dprint(vtos(normalize(v - shotorg)));dprint(" : ");dprint(vtos(shotdir));dprint("\n");
390 //traceline(shotorg, shotorg + shotdir * 10000, false, this);
391 //if (trace_ent.takedamage)
392 //if (trace_fraction < 1)
393 //if (!bot_shouldattack(this, trace_ent))
394 // return false;
395 traceline(shotorg, enemy_org, false, this);
396 if (trace_fraction < 1)
397 if (trace_ent != this.enemy)
398 if (!bot_shouldattack(this, trace_ent))
399 {
400 this.dphitcontentsmask = hf;
401 return false;
402 }
403 }
404
405 if (time > this.bot_firetimer)
406 {
407 this.dphitcontentsmask = hf;
408 return false;
409 }
410
411 //if (r > maxshottime * shotspeed)
412 // return false;
413 this.dphitcontentsmask = hf;
414 return true;
415}
bool bot_shouldattack(entity this, entity targ)
Definition aim.qc:97
vector bot_shotlead(vector targorigin, vector targvelocity, float shotspeed, float shotdelay)
Definition aim.qc:333
float findtrajectorywithleading(vector org, vector m1, vector m2, entity targ, float shotspeed, float shotspeedupward, float maxtime, float shotdelay, entity ignore)
Definition aim.qc:16
void bot_aim_reset(entity this)
Definition aim.qc:134
void bot_aimdir(entity this, vector v, float maxfiredeviation)
Definition aim.qc:150
bool bot_aim(entity this,.entity weaponentity, float shotspeed, float shotspeedupward, float maxshottime, bool applygravity, bool shot_accurate)
Definition aim.qc:339
vector bot_4th_order_aimfilter
Definition aim.qh:25
vector bot_mouseaim
Definition aim.qh:20
float bot_prevaimtime
Definition aim.qh:16
vector bot_olddesiredang
Definition aim.qh:27
vector bot_3th_order_aimfilter
Definition aim.qh:24
entity tracetossent
Definition aim.qh:6
float bot_aimthinktime
Definition aim.qh:15
vector bot_1st_order_aimfilter
Definition aim.qh:22
bool bot_aimdir_executed
Definition aim.qh:13
float bot_badaimtime
Definition aim.qh:14
vector bot_badaimoffset
Definition aim.qh:21
vector findtrajectory_velocity
Definition aim.qh:8
vector shotorg
Definition aim.qh:10
float bot_firetimer
Definition aim.qh:17
entity tracetossfaketarget
Definition aim.qh:7
vector bot_5th_order_aimfilter
Definition aim.qh:26
vector shotdir
Definition aim.qh:11
vector bot_2nd_order_aimfilter
Definition aim.qh:23
float skill
Definition api.qh:35
#define MUTATOR_CALLHOOK(id,...)
Definition base.qh:143
float bot_aggresskill
Definition bot.qh:34
float bot_mouseskill
Definition bot.qh:39
#define SUPERBOT
Definition bot.qh:23
float bot_thinkskill
Definition bot.qh:41
float bot_aimskill
Definition bot.qh:37
float bot_offsetskill
Definition bot.qh:38
var entity(vector mins, vector maxs,.entity tofield) findbox_tofield_OrFallback
#define IS_DEAD(s)
Definition player.qh:245
#define PHYS_INPUT_BUTTON_CHAT(s)
Definition player.qh:159
vector v_angle
Definition player.qh:237
const int FL_NOTARGET
Definition constants.qh:76
entity trace_ent
float DPCONTENTS_SOLID
float DPCONTENTS_CORPSE
float DPCONTENTS_BODY
const float SOLID_BBOX
const float SOLID_NOT
float time
vector trace_endpos
vector v_forward
vector origin
float trace_fraction
float dphitcontentsmask
float autocvar_bot_ai_aimskill_order_mix_5th
Definition cvars.qh:17
float autocvar_bot_ai_aimskill_order_mix_1st
Definition cvars.qh:13
int autocvar_bot_ai_aimskill_firetolerance
Definition cvars.qh:4
float autocvar_bot_ai_aimskill_order_filter_5th
Definition cvars.qh:12
float autocvar_bot_ai_aimskill_order_mix_2nd
Definition cvars.qh:14
float autocvar_bot_ai_aimskill_think
Definition cvars.qh:18
float autocvar_bot_ai_aimskill_order_mix_4th
Definition cvars.qh:16
bool autocvar_bot_typefrag
Definition cvars.qh:58
float autocvar_bot_ai_aimskill_order_filter_3th
Definition cvars.qh:10
float autocvar_bot_ai_aimskill_order_filter_4th
Definition cvars.qh:11
float autocvar_bot_ai_aimskill_order_filter_2nd
Definition cvars.qh:9
float autocvar_bot_ai_aimskill_order_filter_1st
Definition cvars.qh:8
float autocvar_bot_ai_aimskill_mouse
Definition cvars.qh:6
float autocvar_bot_ai_aimskill_offset
Definition cvars.qh:7
float autocvar_bot_ai_aimskill_order_mix_3th
Definition cvars.qh:15
bool autocvar_bot_ignore_bots
Definition cvars.qh:48
float autocvar_bot_ai_aimskill_blendrate
Definition cvars.qh:3
float autocvar_bot_ai_aimskill_fixedrate
Definition cvars.qh:5
#define LOG_TRACE(...)
Definition log.qh:76
float bound(float min, float value, float max)
float random(void)
float vlen(vector v)
vector vectoangles(vector v)
vector randomvec(void)
float min(float f,...)
vector normalize(vector v)
float fabs(float f)
float floor(float f)
float max(float f,...)
void set_movetype(entity this, int mt)
Definition movetypes.qc:4
const int MOVETYPE_NONE
Definition movetypes.qh:129
#define NULL
Definition post.qh:14
#define makevectors
Definition post.qh:21
vector view_ofs
Definition progsdefs.qc:151
vector
Definition self.qh:92
vector org
Definition self.qh:92
int dir
Definition impulse.qc:89
entity enemy
Definition sv_ctf.qh:153
bool teamplay
Definition teams.qh:59
#define IS_BOT_CLIENT(v)
want: (IS_CLIENT(v) && !IS_REAL_CLIENT(v))
Definition utils.qh:15
#define vdist(v, cmp, f)
Vector distance comparison, avoids sqrt()
Definition vector.qh:8
float W_WeaponSpeedFactor(entity this)
Weapon m_weapon
Definition wepent.qh:26