]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/havocbot_ctf.qc
CTF ai: Improved defense role. Switch to offense if there is only one bot on the...
[divverent/nexuiz.git] / data / qcsrc / server / havocbot_ctf.qc
1 \r
2 #define HAVOCBOT_ROLE_NONE              0\r
3 #define HAVOCBOT_CTF_ROLE_DEFENSE       2\r
4 #define HAVOCBOT_CTF_ROLE_MIDDLE        4\r
5 #define HAVOCBOT_CTF_ROLE_OFFENSE       8\r
6 #define HAVOCBOT_CTF_ROLE_CARRIER       16\r
7 #define HAVOCBOT_CTF_ROLE_RETRIEVER     32\r
8 #define HAVOCBOT_CTF_ROLE_ESCORT        64\r
9
10 .void() havocbot_role;
11 .void() havocbot_previous_role;
12
13 void() havocbot_role_ctf_middle;
14 void() havocbot_role_ctf_defense;
15 void() havocbot_role_ctf_offense;
16 void() havocbot_role_ctf_carrier;
17 void() havocbot_role_ctf_retriever;
18 void() havocbot_role_ctf_escort;
19
20 void(entity bot) havocbot_ctf_reset_role;
21 void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
22 void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
23
24 .float havocbot_cantfindflag;
25 .float havocbot_role_timeout;
26 .entity ctf_worldflagnext;
27
28 vector havocbot_ctf_middlepoint;
29 float havocbot_ctf_middlepoint_radius;
30 entity ctf_worldflaglist;
31
32 entity havocbot_ctf_find_flag(entity bot)
33 {
34         entity f;
35         f = ctf_worldflaglist;
36         while (f)
37         {
38                 if (bot.team == f.team)
39                         return f;
40                 f = f.ctf_worldflagnext;
41         }
42         return world;
43 };
44
45 entity havocbot_ctf_find_enemy_flag(entity bot)
46 {
47         entity f;
48         f = ctf_worldflaglist;
49         while (f)
50         {
51                 if (bot.team != f.team)
52                         return f;
53                 f = f.ctf_worldflagnext;
54         }
55         return world;
56 };
57 \r
58 float havocbot_ctf_teamcount(entity bot, vector org, float radius)\r
59 {\r
60         if not(teams_matter)\r
61                 return 0;\r
62 \r
63         float c;\r
64         entity head;\r
65 \r
66         FOR_EACH_PLAYER(head)\r
67         {\r
68                 if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)\r
69                         continue;\r
70 \r
71                 if(vlen(head.origin - org) < radius)\r
72                         ++c;\r
73         }\r
74 \r
75         return c;\r
76 };\r
77
78 void havocbot_goalrating_ctf_ourflag(float ratingscale)
79 {
80         local entity head;
81         head = ctf_worldflaglist;
82         while (head)
83         {
84                 if (self.team == head.team)
85                         break;
86                 head = head.ctf_worldflagnext;
87         }
88         if (head)
89                 navigation_routerating(head, ratingscale, 10000);
90 };
91
92 void havocbot_goalrating_ctf_ourbase(float ratingscale)
93 {
94         if not(bot_waypoints_for_items)
95         {
96                 havocbot_goalrating_ctf_ourflag(ratingscale);
97                 return;
98         }
99
100         local entity head;
101         head = ctf_worldflaglist;
102         while (head)
103         {
104                 if (self.team == head.team)
105                         break;
106                 head = head.ctf_worldflagnext;
107         }
108         if not(head)
109                 return;
110
111         // dropped_origin is set by ctf code whenever the flag is picked up
112         head = findradius(head.dropped_origin, 100);
113         while(head)
114         {
115                 if(head.classname=="waypoint")
116                 {
117                         navigation_routerating(head, ratingscale, 10000);
118                         return;
119                 }
120                 head=head.chain;
121         }
122 };
123
124 void havocbot_goalrating_ctf_enemyflag(float ratingscale)
125 {
126         local entity head;
127         head = ctf_worldflaglist;
128         while (head)
129         {
130                 if (self.team != head.team)
131                         break;
132                 head = head.ctf_worldflagnext;
133         }
134         if (head)
135                 navigation_routerating(head, ratingscale, 10000);
136 };
137
138 void havocbot_goalrating_ctf_enemybase(float ratingscale)
139 {
140         if not(bot_waypoints_for_items)
141         {
142                 havocbot_goalrating_ctf_enemyflag(ratingscale);
143                 return;
144         }
145
146         local entity head;
147
148         head = havocbot_ctf_find_enemy_flag(self);
149
150         if not(head)
151                 return;
152
153         head = findradius(head.dropped_origin, 100);
154         while(head)
155         {
156                 if(head.classname=="waypoint")
157                 {
158                         navigation_routerating(head, ratingscale, 10000);
159                         return;
160                 }
161                 head=head.chain;
162         }
163 };
164
165 void havocbot_goalrating_ctf_ourstolenflag(float ratingscale)
166 {
167         local entity mf;
168
169         mf = havocbot_ctf_find_flag(self);
170
171         if(mf.cnt == FLAG_BASE)
172                 return;
173
174         navigation_routerating(mf, ratingscale, 10000);
175 };
176
177 void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float radius)
178 {
179         local entity head;
180         head = ctf_worldflaglist;
181         while (head)
182         {
183                 // flag is out in the field
184                 if(head.cnt != FLAG_BASE)
185                 if(head.tag_entity==world)      // dropped
186                 {
187                         if(radius)
188                         {
189                                 if(vlen(org-head.origin)<radius)
190                                         navigation_routerating(head, ratingscale, 10000);
191                         }
192                         else
193                                 navigation_routerating(head, ratingscale, 10000);
194                 }
195
196                 head = head.ctf_worldflagnext;
197         }
198 };
199
200 void havocbot_goalrating_ctf_carrieritems(float ratingscale, vector org, float sradius)
201 {
202         local entity head;
203         local float t;
204         head = findchainfloat(bot_pickup, TRUE);
205         while (head)
206         {
207                 // gather health and armor only
208                 if (head.solid)
209                 if (head.health || head.armorvalue)
210                 if (vlen(head.origin - org) < sradius)
211                 {
212                         // get the value of the item
213                         t = head.bot_pickupevalfunc(self, head) * 0.0001;
214                         if (t > 0)
215                                 navigation_routerating(head, t * ratingscale, 500);
216                 }
217                 head = head.chain;
218         }
219 };
220 \r
221 void havocbot_role_ctf_setrole(entity bot, float role)\r
222 {\r
223         dprint(strcat(bot.netname," switched to "));\r
224         switch(role)\r
225         {\r
226                 case HAVOCBOT_CTF_ROLE_CARRIER:\r
227                         dprint("carrier");\r
228                         bot.havocbot_role = havocbot_role_ctf_carrier;\r
229                         bot.havocbot_role_timeout = 0;\r
230                         bot.havocbot_cantfindflag = time + 10;\r
231                         break;\r
232                 case HAVOCBOT_CTF_ROLE_DEFENSE:\r
233                         dprint("defense");\r
234                         bot.havocbot_role = havocbot_role_ctf_defense;\r
235                         bot.havocbot_role_timeout = 0;\r
236                         break;\r
237                 case HAVOCBOT_CTF_ROLE_MIDDLE:\r
238                         dprint("middle");\r
239                         bot.havocbot_role = havocbot_role_ctf_middle;\r
240                         bot.havocbot_role_timeout = 0;\r
241                         break;\r
242                 case HAVOCBOT_CTF_ROLE_OFFENSE:\r
243                         dprint("offense");\r
244                         bot.havocbot_role = havocbot_role_ctf_offense;\r
245                         bot.havocbot_role_timeout = 0;\r
246                         break;\r
247                 case HAVOCBOT_CTF_ROLE_RETRIEVER:\r
248                         dprint("retriever");
249                         bot.havocbot_previous_role = bot.havocbot_role;\r
250                         bot.havocbot_role = havocbot_role_ctf_retriever;\r
251                         bot.havocbot_role_timeout = time + 10;\r
252                         break;\r
253                 case HAVOCBOT_CTF_ROLE_ESCORT:\r
254                         dprint("escort");
255                         bot.havocbot_previous_role = bot.havocbot_role;\r
256                         bot.havocbot_role = havocbot_role_ctf_escort;\r
257                         bot.havocbot_role_timeout = time + 30;\r
258                         break;\r
259         }\r
260         dprint("\n");\r
261 };\r
262 \r
263 void havocbot_role_ctf_carrier()\r
264 {\r
265         if(self.deadflag != DEAD_NO)
266         {\r
267                 havocbot_ctf_reset_role(self);\r
268                 return;\r
269         }\r
270 \r
271         if (self.flagcarried == world)\r
272         {\r
273                 havocbot_ctf_reset_role(self);\r
274                 return;\r
275         }\r
276 \r
277         if (self.bot_strategytime < time)\r
278         {\r
279                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");\r
280 \r
281                 navigation_goalrating_start();\r
282                 havocbot_goalrating_ctf_ourbase(50000);\r
283 \r
284                 if(self.health<100)\r
285                         havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);\r
286 \r
287                 navigation_goalrating_end();\r
288 \r
289                 if (self.navigation_hasgoals)\r
290                         self.havocbot_cantfindflag = time + 10;\r
291                 else if (time > self.havocbot_cantfindflag)\r
292                 {\r
293                         // Can't navigate to my own base, suicide!\r
294                         // TODO: drop it and wander around\r
295                         Damage(self, self, self, 100000, DEATH_KILL, self.origin, '0 0 0');\r
296                         return;\r
297                 }\r
298         }\r
299 };\r
300 \r
301 void havocbot_role_ctf_escort()\r
302 {\r
303         local entity mf, ef;\r
304 \r
305         if(self.deadflag != DEAD_NO)
306         {\r
307                 havocbot_ctf_reset_role(self);\r
308                 return;\r
309         }\r
310 \r
311         if (self.flagcarried)\r
312         {\r
313                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);\r
314                 return;\r
315         }\r
316 \r
317         // If enemy flag is back on the base switch to previous role\r
318         ef = havocbot_ctf_find_enemy_flag(self);\r
319         if(ef.cnt==FLAG_BASE)\r
320         {\r
321                 self.havocbot_role = self.havocbot_previous_role;\r
322                 self.havocbot_role_timeout = 0;\r
323                 return;\r
324         }\r
325 \r
326         // If the flag carrier reached the base switch to defense\r
327         mf = havocbot_ctf_find_flag(self);\r
328         if(mf.cnt!=FLAG_BASE)\r
329         if(vlen(ef.origin - mf.dropped_origin) < 300)\r
330         {\r
331                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);\r
332                 return;\r
333         }\r
334 \r
335         // Set the role timeout if necessary\r
336         if (!self.havocbot_role_timeout)
337         {\r
338                 self.havocbot_role_timeout = time + random() * 30 + 60;
339         }\r
340 \r
341         // If nothing happened just switch to previous role\r
342         if (time > self.havocbot_role_timeout)\r
343         {\r
344                 self.havocbot_role = self.havocbot_previous_role;\r
345                 self.havocbot_role_timeout = 0;\r
346                 return;\r
347         }\r
348 \r
349         // Chase the flag carrier\r
350         if (self.bot_strategytime < time)\r
351         {\r
352                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");\r
353                 navigation_goalrating_start();\r
354                 havocbot_goalrating_ctf_enemyflag(30000);\r
355                 havocbot_goalrating_ctf_ourstolenflag(40000);\r
356                 havocbot_goalrating_items(10000, self.origin, 10000);\r
357                 navigation_goalrating_end();\r
358         }\r
359 };\r
360 \r
361 void havocbot_role_ctf_offense()\r
362 {\r
363         local entity mf, ef;
364         local vector pos;\r
365 \r
366         if(self.deadflag != DEAD_NO)
367         {\r
368                 havocbot_ctf_reset_role(self);\r
369                 return;\r
370         }\r
371 \r
372         if (self.flagcarried)\r
373         {\r
374                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);\r
375                 return;\r
376         }\r
377 \r
378         // Check flags\r
379         mf = havocbot_ctf_find_flag(self);\r
380         ef = havocbot_ctf_find_enemy_flag(self);\r
381
382         // Own flag stolen
383         if(mf.cnt!=FLAG_BASE)\r
384         {\r
385                 if(mf.tag_entity)
386                         pos = mf.tag_entity.origin;
387                 else
388                         pos = mf.origin;
389 \r
390                 // Try to get it if closer than the enemy base
391                 if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))\r
392                 {\r
393                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);\r
394                         return;\r
395                 }\r
396         }\r
397
398         // Escort flag carrier
399         if(ef.cnt!=FLAG_BASE)
400         {
401                 if(ef.tag_entity)
402                         pos = ef.tag_entity.origin;
403                 else
404                         pos = ef.origin;
405
406                 if(vlen(pos-mf.dropped_origin)>700)\r
407                 {\r
408                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_ESCORT);\r
409                         return;\r
410                 }\r
411         }
412 \r
413         // About to fail, switch to middlefield\r
414         if(self.health<50)\r
415         {\r
416                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_MIDDLE);\r
417                 return;\r
418         }\r
419 \r
420         // Set the role timeout if necessary\r
421         if (!self.havocbot_role_timeout)\r
422                 self.havocbot_role_timeout = time + 120;\r
423 \r
424         if (time > self.havocbot_role_timeout)\r
425         {\r
426                 havocbot_ctf_reset_role(self);\r
427                 return;\r
428         }\r
429 \r
430         if (self.bot_strategytime < time)\r
431         {\r
432                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");\r
433                 navigation_goalrating_start();\r
434                 havocbot_goalrating_ctf_ourstolenflag(50000);\r
435                 havocbot_goalrating_ctf_enemybase(20000);\r
436                 havocbot_goalrating_items(5000, self.origin, 1000);
437                 havocbot_goalrating_items(1000, self.origin, 10000);\r
438                 navigation_goalrating_end();\r
439         }\r
440 };\r
441 \r
442 // Retriever (temporary role):\r
443 void havocbot_role_ctf_retriever()\r
444 {\r
445         local entity mf;\r
446 \r
447         if(self.deadflag != DEAD_NO)
448         {\r
449                 havocbot_ctf_reset_role(self);\r
450                 return;\r
451         }\r
452 \r
453         if (self.flagcarried)\r
454         {\r
455                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);\r
456                 return;\r
457         }\r
458 \r
459         // If flag is back on the base switch to previous role\r
460         mf = havocbot_ctf_find_flag(self);
461         if(mf.cnt==FLAG_BASE)\r
462         {\r
463                 havocbot_ctf_reset_role(self);\r
464                 return;\r
465         }\r
466
467         if (!self.havocbot_role_timeout)
468                 self.havocbot_role_timeout = time + 20;
469
470         if (time > self.havocbot_role_timeout)
471         {
472                 havocbot_ctf_reset_role(self);
473                 return;
474         }
475 \r
476         if (self.bot_strategytime < time)\r
477         {\r
478                 local float radius;\r
479                 radius = 10000;\r
480 \r
481                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");
482                 navigation_goalrating_start();
483                 havocbot_goalrating_ctf_ourstolenflag(50000);
484                 havocbot_goalrating_ctf_droppedflags(40000, self.origin, radius);
485                 havocbot_goalrating_ctf_enemybase(30000);
486                 havocbot_goalrating_items(500, self.origin, radius);\r
487                 navigation_goalrating_end();
488         }\r
489 };\r
490 \r
491 void havocbot_role_ctf_middle()\r
492 {\r
493         local entity mf;\r
494 \r
495         if(self.deadflag != DEAD_NO)
496         {\r
497                 havocbot_ctf_reset_role(self);\r
498                 return;\r
499         }\r
500 \r
501         if (self.flagcarried)\r
502         {\r
503                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);\r
504                 return;\r
505         }\r
506 \r
507         mf = havocbot_ctf_find_flag(self);\r
508         if(mf.cnt!=FLAG_BASE)\r
509         {\r
510                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);\r
511                 return;\r
512         }\r
513 \r
514         if (!self.havocbot_role_timeout)\r
515                 self.havocbot_role_timeout = time + 10;\r
516 \r
517         if (time > self.havocbot_role_timeout)\r
518         {\r
519                 havocbot_ctf_reset_role(self);\r
520                 return;\r
521         }\r
522 \r
523         if (self.bot_strategytime < time)\r
524         {
525                 local vector org;
526
527                 org = havocbot_ctf_middlepoint;
528                 org_z = self.origin_z;
529 \r
530                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");\r
531                 navigation_goalrating_start();\r
532                 havocbot_goalrating_ctf_ourstolenflag(50000);\r
533                 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);\r
534                 havocbot_goalrating_enemyplayers(10000, org, havocbot_ctf_middlepoint_radius * 0.5);\r
535                 havocbot_goalrating_items(5000, org, havocbot_ctf_middlepoint_radius * 0.5);
536                 havocbot_goalrating_items(2500, self.origin, 10000);
537                 havocbot_goalrating_ctf_enemybase(2500);
538                 navigation_goalrating_end();\r
539         }\r
540 };\r
541 \r
542 void havocbot_role_ctf_defense()\r
543 {\r
544         local entity mf;\r
545 \r
546         if(self.deadflag != DEAD_NO)
547         {\r
548                 havocbot_ctf_reset_role(self);\r
549                 return;\r
550         }\r
551 \r
552         if (self.flagcarried)\r
553         {\r
554                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);\r
555                 return;\r
556         }\r
557 \r
558         // If own flag was captured\r
559         mf = havocbot_ctf_find_flag(self);\r
560         if(mf.cnt!=FLAG_BASE)\r
561         {\r
562                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);\r
563                 return;\r
564         }\r
565 \r
566         if (!self.havocbot_role_timeout)\r
567                 self.havocbot_role_timeout = time + 30;\r
568 \r
569         if (time > self.havocbot_role_timeout)\r
570         {\r
571                 havocbot_ctf_reset_role(self);\r
572                 return;\r
573         }\r
574         if (self.bot_strategytime < time)\r
575         {\r
576                 local float radius;
577                 local vector org;\r
578 \r
579                 org = mf.dropped_origin;
580                 radius = havocbot_ctf_middlepoint_radius;\r
581 \r
582                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");\r
583                 navigation_goalrating_start();
584
585                 // if enemies are closer to our base, go there
586                 local entity head, closestplayer;
587                 local float distance, bestdistance;
588                 distance = 10000;
589                 FOR_EACH_PLAYER(head)
590                 {
591                         if(head.deadflag!=DEAD_NO)
592                                 continue;
593
594                         distance = vlen(org - head.origin);
595                         if(distance<bestdistance)
596                         {
597                                 closestplayer = head;
598                                 bestdistance = distance;
599                         }
600                 }
601
602                 if(closestplayer)
603                 if(closestplayer.team!=self.team)
604                 if(vlen(org - self.origin)>1000)
605                 if(checkpvs(self.origin,closestplayer)||random()<0.5)
606                         havocbot_goalrating_ctf_ourbase(30000);
607 \r
608                 havocbot_goalrating_ctf_ourstolenflag(20000);\r
609                 havocbot_goalrating_ctf_droppedflags(20000, org, radius);\r
610                 havocbot_goalrating_enemyplayers(15000, org, radius);
611                 havocbot_goalrating_items(10000, org, radius);
612                 havocbot_goalrating_items(5000, self.origin, 10000);\r
613                 navigation_goalrating_end();\r
614         }\r
615 };\r
616 \r
617 void havocbot_calculate_middlepoint()\r
618 {\r
619         entity f;\r
620         vector p1, p2;\r
621 \r
622         f = ctf_worldflaglist;\r
623         while (f)\r
624         {\r
625                 if(p1)\r
626                         p2 = f.origin;\r
627                 else\r
628                         p1 = f.origin;\r
629
630                 f = f.ctf_worldflagnext;\r
631         }\r
632         havocbot_ctf_middlepoint = p1 + ((p2-p1) * 0.5);
633         havocbot_ctf_middlepoint_radius  = vlen(p2-p1) * 0.5;\r
634 };\r
635 \r
636 void havocbot_ctf_reset_role(entity bot)\r
637 {\r
638         local float cdefense, cmiddle, coffense;\r
639         local entity mf, ef, head;
640         local float c;\r
641
642         if(self.deadflag != DEAD_NO)
643                 return;
644
645         if(vlen(havocbot_ctf_middlepoint)==0)
646                 havocbot_calculate_middlepoint();
647
648         // Check ctf flags\r
649         if (self.flagcarried)
650         {
651                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
652                 return;
653         }
654
655         mf = havocbot_ctf_find_flag(bot);\r
656         ef = havocbot_ctf_find_enemy_flag(bot);\r
657 \r
658         // Retrieve stolen flag\r
659         if(mf.cnt!=FLAG_BASE)\r
660         {\r
661                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);\r
662                 return;\r
663         }\r
664 \r
665         // If enemy flag is taken go to the middle to intercept pursuers\r
666         if(ef.cnt!=FLAG_BASE)\r
667         {\r
668                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);\r
669                 return;\r
670         }
671
672         // if there is only me on the team switch to offense
673         c = 0;
674         FOR_EACH_PLAYER(head)
675         if(head.team==self.team)
676                 ++c;
677
678         if(c==1)
679         {
680                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);
681                 return;
682         }
683 \r
684         // Evaluate best position to take\r
685         // Count mates on middle position\r
686         cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);\r
687 \r
688         // Count mates on defense position\r
689         cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);\r
690 \r
691         // Count mates on offense position\r
692         coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);\r
693 \r
694         if(cdefense<=coffense)\r
695                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);\r
696         else if(coffense<=cmiddle)\r
697                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);\r
698         else\r
699                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);\r
700 };\r
701 \r
702 void havocbot_chooserole_ctf()\r
703 {\r
704         havocbot_ctf_reset_role(self);\r
705 };