]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/gamec/urrebot_ai_main.c
First working version of the (hopefully) optimised navigation.
[divverent/nexuiz.git] / data / qcsrc / server / gamec / urrebot_ai_main.c
1 /* --- UrreBotInfront ---\r
2 I could've used id's infront, but as it wasn't in LordHavoc's multiplayer\r
3 only mod, I had to add a new one, named something else to not mess with people's\r
4 possible/eventual plugin-attempts*/\r
5 \r
6 float(entity targ) UrreBotInfront =\r
7 {\r
8         local float dot;\r
9         local vector vec;\r
10 \r
11         makevectors (self.angles);\r
12         vec = normalize (targ.origin - self.origin);\r
13         dot = vec * v_forward;\r
14 \r
15         if (dot > 0.3)\r
16                 return TRUE;\r
17         return FALSE;\r
18 };\r
19 \r
20 /* --- UrreBotEvalTargets ---\r
21 Enemies are found and lost using this function\r
22 If the bot can't see his enemy for 3 seconds, it is dropped*/\r
23 \r
24 void() UrreBotEvalTargets =\r
25 {\r
26         local float old, new;\r
27         local vector v1, v2;\r
28         local entity e;\r
29 \r
30         v1 = self.origin + self.view_ofs;\r
31 \r
32         if (self.enemy)\r
33         {\r
34                 if (self.enemy.health >= 1 && !self.enemy.deadflag)\r
35                 {\r
36                         if (self.evaltime <= time)\r
37                         {\r
38                                 self.evaltime = time + 3;\r
39                                 v2 = (self.enemy.absmin + self.enemy.absmax) * 0.5;\r
40                                 traceline(v1, v2, TRUE, self);\r
41                                 if (trace_fraction < 1)\r
42                                         self.enemy = world;\r
43                         }\r
44                 }\r
45                 else\r
46                         self.enemy = world;\r
47         }\r
48         e = findradius(v1, 1500);\r
49         while (e)\r
50         {\r
51                 if (!(e.flags & FL_NOTARGET))\r
52                 if (!(cvar("teamplay") && self.team == e.team))      // don't target teammates\r
53                 if (e.flags & FL_CLIENT)\r
54                 if (e != self)\r
55                 if (!e.deadflag)\r
56                 if (UrreBotInfront(e))\r
57                 if (e.health >= 1)\r
58                 {\r
59                         v2 = (e.absmin + e.absmax) * 0.5;\r
60                         traceline(v1, v2, TRUE, self);\r
61                         if (trace_fraction == 1 || trace_ent == e)\r
62                         {\r
63                                 if (self.enemy)\r
64                                 {\r
65                                         old = vlen(self.origin - (self.enemy.absmin + self.enemy.absmax)*0.5);\r
66                                         new = vlen(self.origin - v2);\r
67                                         if (new < old)\r
68                                                 self.enemy = e;\r
69                                 }\r
70                                 else\r
71                                         self.enemy = e;\r
72                         }\r
73                 }\r
74                 e = e.chain;\r
75         }\r
76 \r
77         e = world;\r
78         if (self.goalcurrent.sflags & S_DOOR)\r
79                 e = self.goalcurrent.goalentity;\r
80         else if (self.link0.sflags & S_DOOR)\r
81                 e = self.link0.goalentity;\r
82         if (e.health >= 1)\r
83                 self.enemy = e;\r
84 };\r
85 \r
86 /* --- UrreBotAim ---\r
87 Very crude and simple aiming, with some leading capability*/\r
88 \r
89 void() UrreBotAim =\r
90 {\r
91         local float dist, skeel;\r
92         local vector v, desiredang, testang, diffang;\r
93 \r
94         skeel = bound(1, skill, 10);\r
95 \r
96         // get the desired angles to aim at\r
97         if (self.enemy)\r
98         {\r
99                 v = (self.enemy.absmin + self.enemy.absmax) * 0.5;\r
100                 if (self.enemy.classname == "door")\r
101                         self.aimpoint = v;\r
102                 else if (self.aimtime <= time)\r
103                 {\r
104                         self.aimtime = time + 0.3;\r
105                         traceline(self.origin + self.view_ofs, (self.enemy.absmin + self.enemy.absmax) * 0.5, TRUE, self);\r
106                         if (trace_fraction == 1)\r
107                         {\r
108                                 self.aimpoint = v + self.enemy.velocity*vlen(self.origin - v)*self.lead;\r
109                                 self.aimpoint = self.aimpoint + randomvec()*max(0, 120 - skeel*12);\r
110                         }\r
111                 }\r
112                 desiredang = vectoangles(self.aimpoint - (self.origin + self.view_ofs));\r
113         }\r
114         else\r
115                 desiredang = vectoangles(self.velocity);\r
116         desiredang_x = 0-desiredang_x;\r
117 \r
118         if (desiredang_y <= -180)\r
119                 desiredang_y = desiredang_y + 360;\r
120         else if (desiredang_y >= 180)\r
121                 desiredang_y = desiredang_y - 360;\r
122         if (desiredang_x <= -180)\r
123                 desiredang_x = desiredang_x + 360;\r
124         else if (desiredang_x >= 180)\r
125                 desiredang_x = desiredang_x - 360;\r
126 \r
127         // calculate turn angles\r
128         testang = desiredang - self.v_angle;\r
129         testang_z = 0;\r
130 \r
131         // turn\r
132         dist = vlen(testang * ((skeel + 1) * frametime));\r
133         if (vlen(normalize(testang) * skeel) > dist)\r
134         {\r
135                 diffang = normalize(testang) * skeel;\r
136                 dist = vlen(normalize(testang) * skeel);\r
137         }\r
138         else\r
139                 diffang = testang * ((skeel + 1) * frametime);\r
140         if (dist > vlen(testang))\r
141                 diffang = testang;\r
142 \r
143         self.v_angle = self.v_angle + diffang;\r
144         self.angles_y = self.v_angle_y;\r
145 };\r
146 \r
147 /* --- UrreBotMove ---\r
148 Moves towards the closest point on the next goal in the bots list,\r
149 which can be a navnode, item or domination point*/\r
150 \r
151 void() UrreBotMove =\r
152 {\r
153         local float f, bad;\r
154         local vector dir, tvec;\r
155         local entity plane, optpoint;\r
156 \r
157         if (self.link0)\r
158         {\r
159                 if (boxesoverlap(self.origin + self.mins, self.origin + self.maxs, self.link0.origin + self.link0.mins, self.link0.origin + self.link0.maxs))\r
160                 {\r
161                         plane = self.link0.plane_chain;\r
162                         while (plane)\r
163                         {\r
164                                 tvec = self.maxs;\r
165                                 if (plane.mangle_x < 0)\r
166                                         tvec_x = self.mins_x;\r
167                                 if (plane.mangle_y < 0)\r
168                                         tvec_y = self.mins_y;\r
169                                 if (plane.mangle_z < 0)\r
170                                         tvec_z = self.mins_z;\r
171                                 tvec += self.origin;\r
172                                 f = tvec*plane.mangle - self.link0.origin*plane.mangle-plane.delay;\r
173                                 if (f > 0)\r
174                                         bad = TRUE;\r
175                                 plane = plane.list;\r
176                         }\r
177                         if (!bad)\r
178                         {\r
179                                 PopRoute();\r
180                                 if (self.goalcurrent.sflags & S_TELEPORT)\r
181                                         self.movepoint = self.goalcurrent.origin;\r
182                                 else\r
183                                 {\r
184                                         optpoint = MatchOptPoint(self.goalcurrent, self.goallist, self.link0);\r
185                                         if (optpoint)\r
186                                         {\r
187                                                 // bprint("^3LIST^7 succeeded!!!\n");\r
188                                                 self.movepoint = optpoint.origin;\r
189                                         }\r
190                                         else\r
191                                         {\r
192                                                 // bprint("No ^3LIST^7 success\n");\r
193                                                 self.movepoint = ClampPointToSpace(self.origin, self.goalcurrent, self.link0);\r
194                                         }\r
195                                 }\r
196                         }\r
197                 }\r
198                 else if (((self.goalcurrent.sflags & S_TOUCH) && boxesoverlap(self.origin + self.mins, self.origin + self.maxs, self.goalcurrent.origin + self.goalcurrent.mins, self.goalcurrent.origin + self.goalcurrent.maxs)) || boxenclosed(self.origin + self.mins, self.origin + self.maxs, self.goalcurrent.origin + self.goalcurrent.mins, self.goalcurrent.origin + self.goalcurrent.maxs))\r
199                 {\r
200                         plane = self.goalcurrent.plane_chain;\r
201                         while (plane)\r
202                         {\r
203                                 tvec = self.maxs;\r
204                                 if (plane.mangle_x < 0)\r
205                                         tvec_x = self.mins_x;\r
206                                 if (plane.mangle_y < 0)\r
207                                         tvec_y = self.mins_y;\r
208                                 if (plane.mangle_z < 0)\r
209                                         tvec_z = self.mins_z;\r
210                                 tvec += self.origin;\r
211                                 f = tvec*plane.mangle - self.goalcurrent.origin*plane.mangle-plane.delay;\r
212                                 if (f > 0)\r
213                                         bad = TRUE;\r
214                                 plane = plane.list;\r
215                         }\r
216                         if (!bad)\r
217                         {\r
218                                 PopRoute();\r
219                                 if (self.goalcurrent.sflags & S_TELEPORT)\r
220                                         self.movepoint = self.goalcurrent.origin;\r
221                                 else\r
222                                 {\r
223                                         optpoint = MatchOptPoint(self.goalcurrent, self.goallist, self.goalcurrent);\r
224                                         if (optpoint)\r
225                                         {\r
226                                                 // bprint("^2SINGLE^7 succeeded!!!\n");\r
227                                                 self.movepoint = optpoint.origin;\r
228                                         }\r
229                                         else\r
230                                         {\r
231                                                 // bprint("No ^2SINGLE^7 success\n");\r
232                                                 self.movepoint = ClampPointToSpace(self.origin, self.goalcurrent, self.goalcurrent);                                    \r
233                                         }\r
234                                 }\r
235                                 if (self.movepoint == '0 0 0')\r
236                                 {\r
237                                         self.strat_me = TRUE;\r
238                                         UrreBotPath(minisearch_distance);\r
239                                 }\r
240                         }\r
241                 }\r
242         }\r
243         else\r
244         {\r
245                 if (!self.goalcurrent || ((self.goalcurrent.flags & FL_ITEM) && !self.goalcurrent.solid) || (self.goalcurrent.classname == "dom_controlpoint" && self.goalcurrent.enemy.team == self.team))\r
246                 {\r
247                         self.strat_me = TRUE;\r
248                         UrreBotPath(minisearch_distance);\r
249                 }\r
250                 optpoint = MatchOptPoint(self.goalcurrent, self.goallist, self.goalcurrent);\r
251                 if (optpoint)\r
252                 {\r
253                         // bprint("^2SINGLE^7 succeeded!!!\n");\r
254                         self.movepoint = optpoint.origin;\r
255                 }\r
256                 else\r
257                 {\r
258                         // bprint("No ^2SINGLE^7 success\n");\r
259                         self.movepoint = ClampPointToSpace(self.origin, self.goalcurrent, self.goalcurrent);                                    \r
260                 }\r
261         }\r
262         dir = normalize(ToPointInSpace(self.goalcurrent, self.movepoint));\r
263         dir = dir * sv_maxspeed;\r
264         makevectors(self.v_angle);\r
265         self.movement_x = dir * v_forward;\r
266         self.movement_y = dir * v_right;\r
267         self.movement_z = dir * v_up;\r
268 };\r
269 \r
270 /* --- UrreBotImpulses ---\r
271 Returns the impulse for the best weapon in the given situation*/\r
272 \r
273 float() UrreBotImpulses =\r
274 {\r
275         local float dist;\r
276         local float cells, rockets, nails, shells;\r
277         local vector v;\r
278 \r
279         if (random() < 0.5)\r
280                 return 0;\r
281 \r
282         dist = 400; // we like nex and mortar\r
283         if (self.enemy)\r
284         {\r
285                 v = (self.enemy.absmin + self.enemy.absmax) * 0.5;\r
286                 dist = vlen(v - self.origin);\r
287         }\r
288 \r
289         cells = self.ammo_cells;\r
290         rockets = self.ammo_rockets;\r
291         nails = self.ammo_nails;\r
292         shells = self.ammo_shells;\r
293 \r
294         if (dist > 300 && cells && (self.items & IT_NEX))\r
295         {\r
296                 self.lead = 0;\r
297                 return WEP_NEX;\r
298         }\r
299         else if (rockets)\r
300         {\r
301                 if (dist < 500)\r
302                 if (self.items & IT_GRENADE_LAUNCHER)\r
303                 {\r
304                         self.lead = 1 / cvar("g_balance_grenadelauncher_speed");\r
305                         return WEP_GRENADE_LAUNCHER;\r
306                 }\r
307                 if (self.items & IT_HAGAR)\r
308                 {\r
309                         self.lead = 1 / cvar("g_balance_hagar_speed");\r
310                         return WEP_HAGAR;\r
311                 }\r
312                 else if (self.items & IT_ROCKET_LAUNCHER)\r
313                 {\r
314                         self.lead = 1 / cvar("g_balance_rocketlauncher_speed");\r
315                         return WEP_ROCKET_LAUNCHER;\r
316                 }\r
317         }\r
318         else if (cells)\r
319         {\r
320                 if (self.items & IT_ELECTRO)\r
321                 {\r
322                         self.lead = 1 / cvar("g_balance_electro_speed");\r
323                         return WEP_ELECTRO;\r
324                 }\r
325                 else if (self.items & IT_CRYLINK)\r
326                 {\r
327                         self.lead = 1 / cvar("g_balance_crylink_speed");\r
328                         return WEP_CRYLINK;\r
329                 }\r
330         }\r
331         else if (nails)\r
332         {\r
333                 if (self.items & IT_UZI)\r
334                 {\r
335                         self.lead = 0;\r
336                         return WEP_UZI;\r
337                 }\r
338         }\r
339         else if (shells)\r
340         {\r
341                 if (self.items & IT_SHOTGUN)\r
342                 {\r
343                         self.lead = 0;\r
344                         return WEP_SHOTGUN;\r
345                 }\r
346         }\r
347         self.lead = 1 / cvar("g_balance_laser_speed");\r
348         return WEP_LASER;\r
349 };\r
350 \r
351 /* --- BeamBox ---\r
352 Used for some debugging, occasionally*/\r
353 \r
354 float BT_LIGHTNING = 0;\r
355 float BT_BEAM = 1;\r
356 void(float beamtype, vector bmins, vector bmaxs) BeamBox =\r
357 {\r
358         local vector v1, v2;\r
359 \r
360         v1 = bmaxs;\r
361         v2 = bmaxs;\r
362         v2_x = bmins_x;\r
363         if (beamtype == BT_LIGHTNING)\r
364                 te_lightning1(world, v1, v2);\r
365         else\r
366                 te_beam(world, v1, v2);\r
367         v1 = bmaxs;\r
368         v2 = bmaxs;\r
369         v2_y = bmins_y;\r
370         if (beamtype == BT_LIGHTNING)\r
371                 te_lightning1(world, v1, v2);\r
372         else\r
373                 te_beam(world, v1, v2);\r
374         v1 = bmaxs;\r
375         v2 = bmaxs;\r
376         v2_z = bmins_z;\r
377         if (beamtype == BT_LIGHTNING)\r
378                 te_lightning1(world, v1, v2);\r
379         else\r
380                 te_beam(world, v1, v2);\r
381         v1 = bmins;\r
382         v2 = bmins;\r
383         v2_x = bmaxs_x;\r
384         if (beamtype == BT_LIGHTNING)\r
385                 te_lightning1(world, v1, v2);\r
386         else\r
387                 te_beam(world, v1, v2);\r
388         v1 = bmins;\r
389         v2 = bmins;\r
390         v2_y = bmaxs_y;\r
391         if (beamtype == BT_LIGHTNING)\r
392                 te_lightning1(world, v1, v2);\r
393         else\r
394                 te_beam(world, v1, v2);\r
395         v1 = bmins;\r
396         v2 = bmins;\r
397         v2_z = bmaxs_z;\r
398         if (beamtype == BT_LIGHTNING)\r
399                 te_lightning1(world, v1, v2);\r
400         else\r
401                 te_beam(world, v1, v2);\r
402         v1 = bmins;\r
403         v1_z = bmaxs_z;\r
404         v2 = bmins;\r
405         v2_x = bmaxs_x;\r
406         v2_z = bmaxs_z;\r
407         if (beamtype == BT_LIGHTNING)\r
408                 te_lightning1(world, v1, v2);\r
409         else\r
410                 te_beam(world, v1, v2);\r
411         v1 = bmins;\r
412         v1_z = bmaxs_z;\r
413         v2 = bmins;\r
414         v2_y = bmaxs_y;\r
415         v2_z = bmaxs_z;\r
416         if (beamtype == BT_LIGHTNING)\r
417                 te_lightning1(world, v1, v2);\r
418         else\r
419                 te_beam(world, v1, v2);\r
420         v1 = bmaxs;\r
421         v1_z = bmins_z;\r
422         v2 = bmaxs;\r
423         v2_x = bmins_x;\r
424         v2_z = bmins_z;\r
425         if (beamtype == BT_LIGHTNING)\r
426                 te_lightning1(world, v1, v2);\r
427         else\r
428                 te_beam(world, v1, v2);\r
429         v1 = bmaxs;\r
430         v1_z = bmins_z;\r
431         v2 = bmaxs;\r
432         v2_y = bmins_y;\r
433         v2_z = bmins_z;\r
434         if (beamtype == BT_LIGHTNING)\r
435                 te_lightning1(world, v1, v2);\r
436         else\r
437                 te_beam(world, v1, v2);\r
438         v1 = bmins;\r
439         v1_x = bmaxs_x;\r
440         v2 = bmaxs;\r
441         v2_y = bmins_y;\r
442         if (beamtype == BT_LIGHTNING)\r
443                 te_lightning1(world, v1, v2);\r
444         else\r
445                 te_beam(world, v1, v2);\r
446         v1 = bmins;\r
447         v1_y = bmaxs_y;\r
448         v2 = bmaxs;\r
449         v2_x = bmins_x;\r
450         if (beamtype == BT_LIGHTNING)\r
451                 te_lightning1(world, v1, v2);\r
452         else\r
453                 te_beam(world, v1, v2);\r
454 };\r
455 \r
456 /* --- UrreBotPath ---\r
457 Marks route and determines which goal is the most useful to head for*/\r
458 \r
459 void(float sdist) UrreBotPath =\r
460 {\r
461         local float f, f2, f3;\r
462         local entity e, best;\r
463 \r
464         ClearRoute();\r
465         MarkRoute(sdist);\r
466         DistEvalItems();\r
467         f2 = 10000000;\r
468         e = findchainflags(flags, FL_ITEM);\r
469         while (e)\r
470         {\r
471                 f = 0;\r
472                 if (e.evalfunc)\r
473                         f = e.evalfunc(e);\r
474                 if (f > 0)\r
475                 if (e.goallist)\r
476                 {\r
477                         f = f + e.costl;\r
478                         if (f < f2)\r
479                         if (e.solid)\r
480                         {\r
481                                 best = e;\r
482                                 f2 = e.costl;\r
483                         }\r
484                 }\r
485                 e = e.chain;\r
486         }\r
487         if (best)\r
488         {\r
489                 while (best)\r
490                 {\r
491                         PushRoute(best);\r
492                         best = best.goallist;\r
493                 }\r
494         }\r
495         else\r
496         {\r
497                 f3 = 0;\r
498                 while (f3 < 3)\r
499                 {\r
500                         f = 0;\r
501                         f2 = 0;\r
502                         f = ceil(random() * navnodes);\r
503                         e = navnode_chain;\r
504                         while (e)\r
505                         {\r
506                                 if (f == f2)\r
507                                 if (e.goallist)\r
508                                 {\r
509                                         best = e;\r
510                                         f3 = 3;\r
511                                 }\r
512                                 f2 = f2 + 1;\r
513                                 e = e.list;\r
514                         }\r
515                         f3 = f3 + 1;\r
516                 }\r
517                 while (best)\r
518                 {\r
519                         PushRoute(best);\r
520                         best = best.goallist;\r
521                 }\r
522         }\r
523 };\r
524 \r
525 /* --- UrreBotThink ---\r
526 In this version, UrreBot does a path search based on timer and turn\r
527 Then it aims, moves, checks if it's stuck, finds/loses stuff to shoot at,\r
528 picks best weapon, shoots, and optionally displays debug stuff, in that\r
529 order\r
530 Aiming must happen before movement, because movement depends on angles\r
531 He does not yet have any combat movement*/\r
532 \r
533 void() UrreBotThink =\r
534 {\r
535         local float f;\r
536         local vector v;\r
537 \r
538         self.movement = '0 0 0';\r
539         self.button0 = 0;\r
540         self.button2 = 0;\r
541         self.impulse = 0;\r
542 \r
543         if (cvar("teamplay") && self.team == self.enemy.team) // don't return fire if hit by a teammate\r
544                 self.enemy = world;\r
545 \r
546         if (self.deadflag)\r
547         {\r
548                 ClearRoute();\r
549                 if (random() < 0.2)\r
550                         self.button0 = 1;\r
551                 return;\r
552         }\r
553 \r
554         if (strategytime <= time)\r
555         if (strategytoken == self)\r
556         {\r
557                 strategytime = time + urrebots_strategytime;\r
558                 strategytoken = self.list;\r
559                 if (!strategytoken)\r
560                         strategytoken = urrebot_chain;\r
561                 if (self.strat_me)\r
562                 {\r
563                         self.strat_me = FALSE;\r
564                         UrreBotPath(stratsearch_distance);\r
565                 }\r
566         }\r
567 \r
568         UrreBotAim();\r
569         UrreBotMove();\r
570 \r
571         if (self.camptime <= time)\r
572         {\r
573                 if (vlen(self.origin - self.campcheck) < 200) // stuckage avoidage\r
574                 {\r
575                         self.camptime = time + urrebots;\r
576                         self.strat_me = TRUE;\r
577                         UrreBotPath(minisearch_distance);\r
578                 }\r
579                 else\r
580                 {\r
581                         self.campcheck = self.origin;\r
582                         self.camptime = time + 2;\r
583                 }\r
584         }\r
585 \r
586         if (self.combattime <= time)\r
587         {\r
588                 self.combattime = time + urrebots_combattime;\r
589                 UrreBotEvalTargets();\r
590                 self.impulse = UrreBotImpulses();\r
591         }\r
592 \r
593         if (self.enemy)\r
594         {\r
595                 makevectors (self.v_angle);\r
596                 v = self.origin + '0 0 16';\r
597                 f = vlen(v - self.aimpoint);\r
598                 traceline (v, v + v_forward*f, FALSE, self);\r
599                 if (vlen(trace_endpos - self.aimpoint) < 150)\r
600                 {\r
601                         if (skill < 5)\r
602                         {\r
603                                 f = skill*0.1;\r
604                                 if (random() < f)\r
605                                         self.button0 = 1;\r
606                         }\r
607                         else\r
608                                 self.button0 = 1;\r
609                 }\r
610         }\r
611         if (cvar("urrebots_debug"))\r
612         {\r
613                 te_lightning1(self, self.origin, self.movepoint);\r
614                 if (!self.goalcurrent)\r
615                 {\r
616                         bprint(self.netname);\r
617                         bprint(" has no goalcurrent\n");\r
618                 }\r
619         }\r
620 };\r