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