Merge commit '7b4c329'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / bot / havocbot / role_onslaught.qc
1 #define HAVOCBOT_ONS_ROLE_NONE          0
2 #define HAVOCBOT_ONS_ROLE_DEFENSE       2
3 #define HAVOCBOT_ONS_ROLE_ASSISTANT     4
4 #define HAVOCBOT_ONS_ROLE_OFFENSE       8
5
6 .float havocbot_role_flags;
7 .float havocbot_attack_time;
8
9 .void() havocbot_role;
10 .void() havocbot_previous_role;
11
12 void() havocbot_role_ons_defense;
13 void() havocbot_role_ons_offense;
14 void() havocbot_role_ons_assistant;
15
16 void(entity bot) havocbot_ons_reset_role;
17 void(float ratingscale, vector org, float sradius) havocbot_goalrating_items;
18 void(float ratingscale, vector org, float sradius) havocbot_goalrating_enemyplayers;
19
20 .float isshielded;
21 .float iscaptured;
22 .float islinked;
23 .float isgenneighbor_blue, iscpneighbor_blue;
24 .float isgenneighbor_red, iscpneighbor_red;
25
26 .entity havocbot_ons_target;
27
28 void havocbot_goalrating_ons_offenseitems(float ratingscale, vector org, float sradius)
29 {
30         local entity head;
31         local float t, i, c, needarmor, needweapons;
32
33         // Needs armor/health?
34         if(self.health<100)
35                 needarmor = TRUE;
36
37         // Needs weapons?
38         for(i = WEP_FIRST; i <= WEP_LAST ; ++i)
39         {
40                 // Find weapon
41                 if(power2of(i-1) & self.weapons)
42                 if(++c>=4)
43                         break;
44         }
45
46         if(c<4)
47                 needweapons = TRUE;
48
49         if(!needweapons && !needarmor)
50                 return;
51
52 //      dprint(self.netname, " needs weapons ", ftos(needweapons) , "\n");
53 //      dprint(self.netname, " needs armor ", ftos(needarmor) , "\n");
54
55         // See what is around
56         head = findchainfloat(bot_pickup, TRUE);
57         while (head)
58         {
59                 // gather health and armor only
60                 if (head.solid)
61                 if ( ((head.health || head.armorvalue) && needarmor) || (head.weapons && needweapons ) )
62                 if (vlen(head.origin - org) < sradius)
63                 {
64                         t = head.bot_pickupevalfunc(self, head);
65                         if (t > 0)
66                                 navigation_routerating(head, t * ratingscale, 500);
67                 }
68                 head = head.chain;
69         }
70 };
71
72 void havocbot_role_ons_setrole(entity bot, float role)
73 {
74         dprint(strcat(bot.netname," switched to "));
75         switch(role)
76         {
77                 case HAVOCBOT_ONS_ROLE_DEFENSE:
78                         dprint("defense");
79                         bot.havocbot_role = havocbot_role_ons_defense;
80                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_DEFENSE;
81                         bot.havocbot_role_timeout = 0;
82                         break;
83                 case HAVOCBOT_ONS_ROLE_ASSISTANT:
84                         dprint("assistant");
85                         bot.havocbot_role = havocbot_role_ons_assistant;
86                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_ASSISTANT;
87                         bot.havocbot_role_timeout = 0;
88                         break;
89                 case HAVOCBOT_ONS_ROLE_OFFENSE:
90                         dprint("offense");
91                         bot.havocbot_role = havocbot_role_ons_offense;
92                         bot.havocbot_role_flags = HAVOCBOT_ONS_ROLE_OFFENSE;
93                         bot.havocbot_role_timeout = 0;
94                         break;
95         }
96         dprint("\n");
97 };
98
99 float havocbot_ons_teamcount(entity bot, float role)
100 {
101         local float c;
102         local entity head;
103
104         FOR_EACH_PLAYER(head)
105         if(head.team==self.team)
106         if(head.havocbot_role_flags & role)
107                 ++c;
108
109         return c;
110 };
111
112 void havocbot_goalrating_ons_controlpoints_attack(float ratingscale)
113 {
114         entity cp, cp1, cp2, best, pl, wp;
115         float radius, found, bestvalue, c;
116
117         cp1 = cp2 = findchain(classname, "onslaught_controlpoint");
118
119         // Filter control points
120         for (; cp2; cp2 = cp2.chain)
121         {
122                 cp2.wpcost = c = 0;
123                 cp2.wpconsidered = FALSE;
124
125                 if(cp2.isshielded)
126                         continue;
127
128                 // Ignore owned controlpoints
129                 if(self.team == COLOR_TEAM1)
130                 {
131                         if( (cp2.isgenneighbor_blue || cp2.iscpneighbor_blue) && !(cp2.isgenneighbor_red || cp2.iscpneighbor_red) )
132                                 continue;
133                 }
134                 else if(self.team == COLOR_TEAM2)
135                 {
136                         if( (cp2.isgenneighbor_red || cp2.iscpneighbor_red) && !(cp2.isgenneighbor_blue || cp2.iscpneighbor_blue) )
137                                 continue;
138                 }
139
140                 // Count team mates interested in this control point
141                 // (easier and cleaner than keeping counters per cp and teams)
142                 FOR_EACH_PLAYER(pl)
143                 if(pl.team==self.team)
144                 if(pl.havocbot_role_flags & HAVOCBOT_ONS_ROLE_OFFENSE)
145                 if(pl.havocbot_ons_target==cp2)
146                         ++c;
147
148                 // NOTE: probably decrease the cost of attackable control points
149                 cp2.wpcost = c;
150                 cp2.wpconsidered = TRUE;
151         }
152
153         // We'll consider only the best case
154         bestvalue = 99999999999;
155         for (; cp1; cp1 = cp1.chain)
156         {
157                 if not(cp1.wpconsidered)
158                         continue;
159
160                 if(cp1.wpcost<bestvalue)
161                 {
162                         bestvalue = cp1.wpcost;
163                         cp = cp1;
164                         self.havocbot_ons_target = cp1;
165                 }
166         }
167
168         if not(cp)
169                 return;
170
171 //      dprint(self.netname, " chose cp ranked ", ftos(bestvalue), "\n");
172
173         if(cp.goalentity)
174         {
175                 // Should be attacked
176                 // Rate waypoints near it
177                 found = FALSE;
178                 best = world;
179                 bestvalue = 99999999999;
180                 for(radius=0; radius<1000 && !found; radius+=500)
181                 {
182                         for(wp=findradius(cp.origin,radius); wp; wp=wp.chain)
183                         {
184                                 if(!(wp.wpflags & WAYPOINTFLAG_GENERATED))
185                                 if(wp.classname=="waypoint")
186                                 if(checkpvs(wp.origin,cp))
187                                 {
188                                         found = TRUE;
189                                         if(wp.cnt<bestvalue)
190                                         {
191                                                 best = wp;
192                                                 bestvalue = wp.cnt;
193                                         }
194                                 }
195                         }
196                 }
197
198                 if(best)
199                 {
200                         navigation_routerating(best, ratingscale, 10000);
201                         best.cnt += 1;
202
203                         self.havocbot_attack_time = 0;
204                         if(checkpvs(self.view_ofs,cp))
205                         if(checkpvs(self.view_ofs,best))
206                                 self.havocbot_attack_time = time + 2;
207                 }
208                 else
209                 {
210                         navigation_routerating(cp, ratingscale, 10000);
211                 }
212         //      dprint(self.netname, " found an attackable controlpoint at ", vtos(cp.origin) ,"\n");
213         }
214         else
215         {
216                 // Should be touched
217                 // dprint(self.netname, " found a touchable controlpoint at ", vtos(cp.origin) ,"\n");
218
219                 // Look for auto generated waypoint
220                 if not(bot_waypoints_for_items)
221                 for (wp = findradius(cp.origin,100); wp; wp = wp.chain)
222                 {
223                         if(wp.classname=="waypoint")
224                         {
225                                 navigation_routerating(wp, ratingscale, 10000);
226                                 found = TRUE;
227                         }
228                 }
229
230                 // Nothing found, rate the controlpoint itself
231                 if not(found)
232                         navigation_routerating(cp, ratingscale, 10000);
233         }
234 };
235
236 float havocbot_goalrating_ons_generator_attack(float ratingscale)
237 {
238         local entity g, wp, bestwp;
239         local float found, best;
240
241         for (g = findchain(classname, "onslaught_generator"); g; g = g.chain)
242         {
243                 if(g.team == self.team || g.isshielded)
244                         continue;
245
246                 // Should be attacked
247                 // Rate waypoints near it
248                 found = FALSE;
249                 bestwp = world;
250                 best = 99999999999;
251
252                 for(wp=findradius(g.origin,400); wp; wp=wp.chain)
253                 {
254                         if(wp.classname=="waypoint")
255                         if(checkpvs(wp.origin,g))
256                         {
257                                 found = TRUE;
258                                 if(wp.cnt<best)
259                                 {
260                                         bestwp = wp;
261                                         best = wp.cnt;
262                                 }
263                         }
264                 }
265
266                 if(bestwp)
267                 {
268                 //      dprint("waypoints found around generator\n");
269                         navigation_routerating(bestwp, ratingscale, 10000);
270                         bestwp.cnt += 1;
271
272                         self.havocbot_attack_time = 0;
273                         if(checkpvs(self.view_ofs,g))
274                         if(checkpvs(self.view_ofs,bestwp))
275                                 self.havocbot_attack_time = time + 5;
276
277                         return TRUE;
278                 }
279                 else
280                 {
281                 //      dprint("generator found without waypoints around\n");
282                         // if there aren't waypoints near the generator go straight to it
283                         navigation_routerating(g, ratingscale, 10000);
284                         self.havocbot_attack_time = 0;
285                         return TRUE;
286                 }
287         }
288         return FALSE;
289 };
290
291 void havocbot_role_ons_offense()
292 {
293         if(self.deadflag != DEAD_NO)
294         {
295                 self.havocbot_attack_time = 0;
296                 havocbot_ons_reset_role(self);
297                 return;
298         }
299
300         // Set the role timeout if necessary
301         if (!self.havocbot_role_timeout)
302                 self.havocbot_role_timeout = time + 120;
303
304         if (time > self.havocbot_role_timeout)
305         {
306                 havocbot_ons_reset_role(self);
307                 return;
308         }
309
310         if(self.havocbot_attack_time>time)
311                 return;
312
313         if (self.bot_strategytime < time)
314         {
315                 navigation_goalrating_start();
316                 havocbot_goalrating_enemyplayers(20000, self.origin, 650);
317                 if(!havocbot_goalrating_ons_generator_attack(20000))
318                         havocbot_goalrating_ons_controlpoints_attack(20000);
319                 havocbot_goalrating_ons_offenseitems(10000, self.origin, 10000);
320                 navigation_goalrating_end();
321
322                 self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
323         }
324 };
325
326 void havocbot_role_ons_assistant()
327 {
328         havocbot_ons_reset_role(self);
329 };
330
331 void havocbot_role_ons_defense()
332 {
333         havocbot_ons_reset_role(self);
334 };
335
336 void havocbot_ons_reset_role(entity bot)
337 {
338         local entity head;
339         local float c;
340
341         if(self.deadflag != DEAD_NO)
342                 return;
343
344         bot.havocbot_ons_target = world;
345
346         // TODO: Defend control points or generator if necessary
347
348         // if there is only me on the team switch to offense
349         c = 0;
350         FOR_EACH_PLAYER(head)
351         if(head.team==self.team)
352                 ++c;
353
354         if(c==1)
355         {
356                 havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
357                 return;
358         }
359
360         havocbot_role_ons_setrole(bot, HAVOCBOT_ONS_ROLE_OFFENSE);
361 };
362
363 void havocbot_chooserole_ons()
364 {
365         havocbot_ons_reset_role(self);
366 };