Purge .weapons field
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / bot / default / havocbot / roles.qc
1 #include "roles.qh"
2
3 #include <server/defs.qh>
4 #include <server/miscfunctions.qh>
5 #include <server/items.qh>
6 #include "havocbot.qh"
7
8 #include "../cvars.qh"
9
10 #include "../bot.qh"
11 #include "../navigation.qh"
12
13 .float bot_ratingscale;
14 .float bot_ratingscale_time;
15 .float max_armorvalue;
16 .float havocbot_role_timeout;
17
18 .void(entity this) havocbot_previous_role;
19 .void(entity this) havocbot_role;
20
21 void havocbot_goalrating_waypoints(entity this, float ratingscale, vector org, float sradius)
22 {
23         // rate waypoints only if there's no alternative goal
24         if(navigation_bestgoal)
25                 return;
26
27         float f;
28         float range = 500;
29         sradius = max(range, (0.5 + random() * 0.5) * sradius);
30         while(sradius > 100)
31         {
32                 IL_EACH(g_waypoints, vdist(it.origin - org, <, sradius)
33                         && vdist(it.origin - org, >, max(100, sradius - range))
34                         && !(it.wpflags & WAYPOINTFLAG_TELEPORT),
35                 {
36                         if(vdist(it.origin - this.wp_goal_prev0.origin, <, range * 1.5))
37                                 f = 0.1;
38                         else if(vdist(it.origin - this.wp_goal_prev1.origin, <, range * 1.5))
39                                 f = 0.1;
40                         else
41                                 f = 0.5 + random() * 0.5;
42                         navigation_routerating(this, it, ratingscale * f, 2000);
43                 });
44                 if(navigation_bestgoal)
45                         break;
46                 sradius -= range;
47         }
48 };
49
50 bool havocbot_goalrating_item_can_be_left_to_teammate(entity this, entity player, entity item)
51 {
52         if (item.health && player.health <= this.health) {return true;}
53         if (item.armorvalue && player.armorvalue <= this.armorvalue) {return true;}
54         if (STAT(WEAPONS, item) && !(STAT(WEAPONS, player) & STAT(WEAPONS, item))) {return true;}
55         if (item.ammo_shells && player.ammo_shells <= this.ammo_shells) {return true;}
56         if (item.ammo_nails && player.ammo_nails <= this.ammo_nails) {return true;}
57         if (item.ammo_rockets && player.ammo_rockets <= this.ammo_rockets) {return true;}
58         if (item.ammo_cells && player.ammo_cells <= this.ammo_cells) {return true;}
59         if (item.ammo_plasma && player.ammo_plasma <= this.ammo_plasma) {return true;}
60         if (item.itemdef.instanceOfPowerup) {return true;}
61
62         return false;
63 };
64
65 bool havocbot_goalrating_item_pickable_check_players(entity this, vector org, entity item, vector item_org)
66 {
67         if(!teamplay)
68                 return true;
69
70         // actually these variables hold the squared distances in order to optimize code
71         float friend_distance = FLOAT_MAX;
72         float enemy_distance = FLOAT_MAX;
73         float dist;
74
75         FOREACH_CLIENT(IS_PLAYER(it) && it != this && !IS_DEAD(it),
76         {
77                 if (it.team == this.team)
78                 {
79                         if (!IS_REAL_CLIENT(it))
80                                 continue;
81
82                         dist = vlen2(it.origin - item_org);
83                         if(dist > friend_distance)
84                                 continue;
85
86                         if(havocbot_goalrating_item_can_be_left_to_teammate(this, it, item))
87                         {
88                                 friend_distance = dist;
89                                 continue;
90                         }
91                 }
92                 else
93                 {
94                         // If enemy only track distances
95                         // TODO: track only if visible ?
96                         dist = vlen2(it.origin - item_org);
97                         if(dist < enemy_distance)
98                                 enemy_distance = dist;
99                 }
100         });
101
102         // Rate the item only if no one needs it, or if an enemy is closer to it
103         dist = vlen2(item_org - org);
104         if ((enemy_distance < friend_distance && dist < enemy_distance) ||
105                 (friend_distance > autocvar_bot_ai_friends_aware_pickup_radius ** 2) ||
106                 (dist < friend_distance && dist < 200 ** 2))
107                 return true;
108         return false;
109 };
110
111 void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius)
112 {
113         float rating;
114         vector o;
115         ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
116
117         IL_EACH(g_items, it.bot_pickup,
118         {
119                 // ignore if bot already rated this item with a higher ratingscale
120                 // NOTE: this code assumes each bot rates items in a different frame
121                 if(it.bot_ratingscale_time == time && ratingscale < it.bot_ratingscale)
122                         continue;
123                 it.bot_ratingscale_time = time;
124                 it.bot_ratingscale = ratingscale;
125
126                 if(!it.solid)
127                 {
128                         if(!autocvar_bot_ai_timeitems)
129                                 continue;
130                         if(!it.scheduledrespawntime)
131                                 continue;
132                         if(it.respawntime < max(11, autocvar_bot_ai_timeitems_minrespawndelay))
133                                 continue;
134                         if(it.respawntimejitter && !it.itemdef.instanceOfPowerup)
135                                 continue;
136
137                         float t = 0;
138                         if(it.itemdef.instanceOfPowerup)
139                                 t = bound(0, skill / 10, 1) * 6;
140                         else if(skill >= 9)
141                                 t = 4;
142
143                         if(time < it.scheduledrespawntime - t)
144                                 continue;
145
146                         it.bot_pickup_respawning = true;
147                 }
148                 o = (it.absmin + it.absmax) * 0.5;
149                 if(vdist(o - org, >, sradius) || (it == this.ignoregoal && time < this.ignoregoaltime) )
150                         continue;
151
152                 // Check if the item can be picked up safely
153                 if(Item_IsLoot(it))
154                 {
155                         if(!IS_ONGROUND(it))
156                                 continue;
157                         traceline(o, o + '0 0 -1500', true, NULL);
158
159                         if(IN_LAVA(trace_endpos + '0 0 1'))
160                                 continue;
161
162                         // this tracebox_hits_trigger_hurt call isn't needed:
163                         // dropped weapons are removed as soon as they fall on a trigger_hurt
164                         // and can't be rated while they are in the air
165                         //if(tracebox_hits_trigger_hurt(it.origin, it.mins, it.maxs, trace_endpos))
166                         //      continue;
167                 }
168                 else
169                 {
170                         if(IN_LAVA(it.origin + (it.mins + it.maxs) * 0.5))
171                                 continue;
172                 }
173
174                 if(!havocbot_goalrating_item_pickable_check_players(this, org, it, o))
175                         continue;
176
177                 rating = it.bot_pickupevalfunc(this, it);
178                 if(rating > 0)
179                         navigation_routerating(this, it, rating * ratingscale, 2000);
180         });
181 }
182
183 #define BOT_RATING_ENEMY 2500
184 void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org, float sradius)
185 {
186         if (autocvar_bot_nofire)
187                 return;
188
189         // don't chase players if we're under water
190         if(this.waterlevel>WATERLEVEL_WETFEET)
191                 return;
192
193         ratingscale = ratingscale * 0.00005; // enemies are rated around 20000 already
194
195         float t;
196         FOREACH_CLIENT(IS_PLAYER(it) && bot_shouldattack(this, it), {
197                 // TODO: Merge this logic with the bot_shouldattack function
198                 if(vdist(it.origin - org, <, 100) || vdist(it.origin - org, >, sradius))
199                         continue;
200                 if(vdist(vec2(it.velocity), >, autocvar_sv_maxspeed * 2))
201                         continue;
202
203                 // rate only visible enemies
204                 /*
205                 traceline(this.origin + this.view_ofs, it.origin, MOVE_NOMONSTERS, this);
206                 if (trace_fraction < 1 || trace_ent != it)
207                         continue;
208                 */
209
210                 t = ((this.health + this.armorvalue) - (it.health + it.armorvalue)) / 150;
211                 t = bound(0, 1 + t, 3);
212                 if (skill > 3)
213                 {
214                         if (time < this.strength_finished - 1) t += 0.5;
215                         if (time < it.strength_finished - 1) t -= 0.5;
216                         if (time < this.invincible_finished - 1) t += 0.2;
217                         if (time < it.invincible_finished - 1) t -= 0.4;
218                 }
219                 t += max(0, 8 - skill) * 0.05; // less skilled bots attack more mindlessly
220                 ratingscale *= t;
221                 if (ratingscale > 0)
222                         navigation_routerating(this, it, ratingscale * BOT_RATING_ENEMY, 2000);
223         });
224 }
225
226 // legacy bot role for standard gamemodes
227 // go to best items
228 void havocbot_role_generic(entity this)
229 {
230         if(IS_DEAD(this))
231                 return;
232
233         if (navigation_goalrating_timeout(this))
234         {
235                 navigation_goalrating_start(this);
236                 havocbot_goalrating_items(this, 10000, this.origin, 10000);
237                 havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
238                 havocbot_goalrating_waypoints(this, 1, this.origin, 3000);
239                 navigation_goalrating_end(this);
240
241                 navigation_goalrating_timeout_set(this);
242         }
243 }
244
245 void havocbot_chooserole_generic(entity this)
246 {
247         this.havocbot_role = havocbot_role_generic;
248 }
249
250 void havocbot_chooserole(entity this)
251 {
252         LOG_TRACE("choosing a role...");
253         navigation_goalrating_timeout_force(this);
254         if(!MUTATOR_CALLHOOK(HavocBot_ChooseRole, this))
255                 havocbot_chooserole_generic(this);
256 }