]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/havocbot_ctf.qc
Major rewrite of the CTF ai
[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 + random() * 30 + 60;
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(5000, 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         {\r
525                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");\r
526                 navigation_goalrating_start();\r
527                 havocbot_goalrating_ctf_ourstolenflag(50000);\r
528                 havocbot_goalrating_ctf_droppedflags(30000, self.origin, 10000);\r
529                 havocbot_goalrating_enemyplayers(10000, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);\r
530                 havocbot_goalrating_items(5000, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);
531                 havocbot_goalrating_items(2500, self.origin, 10000);
532                 havocbot_goalrating_ctf_enemybase(2500);
533                 navigation_goalrating_end();\r
534         }\r
535 };\r
536 \r
537 void havocbot_role_ctf_defense()\r
538 {\r
539         local entity mf;\r
540 \r
541         if(self.deadflag != DEAD_NO)
542         {\r
543                 havocbot_ctf_reset_role(self);\r
544                 return;\r
545         }\r
546 \r
547         if (self.flagcarried)\r
548         {\r
549                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);\r
550                 return;\r
551         }\r
552 \r
553         // If own flag was captured\r
554         mf = havocbot_ctf_find_flag(self);\r
555         if(mf.cnt!=FLAG_BASE)\r
556         {\r
557                 // And its located as far as about half the distance between the two bases\r
558                 if(vlen(self.origin-mf.origin)<havocbot_ctf_middlepoint_radius*1.2)\r
559                         havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_RETRIEVER);\r
560                 return;\r
561         }\r
562 \r
563         if (!self.havocbot_role_timeout)\r
564                 self.havocbot_role_timeout = time + 30;\r
565 \r
566         if (time > self.havocbot_role_timeout)\r
567         {\r
568                 havocbot_ctf_reset_role(self);\r
569                 return;\r
570         }\r
571         if (self.bot_strategytime < time)\r
572         {\r
573                 local float radius;
574                 local vector org;\r
575 \r
576                 org = mf.dropped_origin;
577                 radius = havocbot_ctf_middlepoint_radius;\r
578 \r
579                 self.bot_strategytime = time + cvar("bot_ai_strategyinterval");\r
580                 navigation_goalrating_start();\r
581                 havocbot_goalrating_ctf_ourstolenflag(200000);\r
582                 havocbot_goalrating_ctf_droppedflags(50000, org, radius);\r
583                 havocbot_goalrating_enemyplayers(20000, org, radius);
584                 havocbot_goalrating_items(10000, org, radius);\r
585                 navigation_goalrating_end();\r
586         }\r
587 };\r
588 \r
589 void havocbot_calculate_middlepoint()\r
590 {\r
591         entity f;\r
592         vector p1, p2;\r
593 \r
594         f = ctf_worldflaglist;\r
595         while (f)\r
596         {\r
597                 if(p1)\r
598                         p2 = f.origin;\r
599                 else\r
600                         p1 = f.origin;\r
601
602                 f = f.ctf_worldflagnext;\r
603         }\r
604         havocbot_ctf_middlepoint = p1 + ((p2-p1) * 0.5);
605         havocbot_ctf_middlepoint_radius  = vlen(p2-p1) * 0.5;\r
606 };\r
607 \r
608 void havocbot_ctf_reset_role(entity bot)\r
609 {\r
610         local float cdefense, cmiddle, coffense;\r
611         local entity mf, ef;\r
612
613         if(self.deadflag != DEAD_NO)
614                 return;
615
616         if(vlen(havocbot_ctf_middlepoint)==0)
617                 havocbot_calculate_middlepoint();
618
619         // Check ctf flags\r
620         if (self.flagcarried)
621         {
622                 havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_CARRIER);
623                 return;
624         }
625
626         mf = havocbot_ctf_find_flag(bot);\r
627         ef = havocbot_ctf_find_enemy_flag(bot);\r
628 \r
629         // Retrieve stolen flag\r
630         if(mf.cnt!=FLAG_BASE)\r
631         {\r
632                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_RETRIEVER);\r
633                 return;\r
634         }\r
635 \r
636         // Go to the middle to intercept pursuers\r
637         if(ef.cnt!=FLAG_BASE)\r
638         {\r
639                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);\r
640                 return;\r
641         }\r
642 \r
643         // Evaluate best position to take\r
644         // Count mates on middle position\r
645         cmiddle = havocbot_ctf_teamcount(bot, havocbot_ctf_middlepoint, havocbot_ctf_middlepoint_radius * 0.5);\r
646 \r
647         // Count mates on defense position\r
648         cdefense = havocbot_ctf_teamcount(bot, mf.dropped_origin, havocbot_ctf_middlepoint_radius * 0.5);\r
649 \r
650         // Count mates on offense position\r
651         coffense = havocbot_ctf_teamcount(bot, ef.dropped_origin, havocbot_ctf_middlepoint_radius);\r
652 \r
653         if(cdefense<=coffense)\r
654                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_DEFENSE);\r
655         else if(coffense<=cmiddle)\r
656                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_OFFENSE);\r
657         else\r
658                 havocbot_role_ctf_setrole(bot, HAVOCBOT_CTF_ROLE_MIDDLE);\r
659 };\r
660 \r
661 void havocbot_chooserole_ctf()\r
662 {\r
663         havocbot_ctf_reset_role(self);\r
664 };