]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/bot/havocbot/roles.qc
cloc
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / bot / havocbot / roles.qc
1 #include "roles.qh"
2
3 #include "havocbot.qh"
4
5 #include "../bot.qh"
6 #include "../navigation.qh"
7
8 .float max_armorvalue;
9 .float havocbot_role_timeout;
10
11 .void(entity this) havocbot_previous_role;
12 .void(entity this) havocbot_role;
13
14 void havocbot_goalrating_items(entity this, float ratingscale, vector org, float sradius)
15 {
16         float rating, d, discard, friend_distance, enemy_distance;
17         vector o;
18         ratingscale = ratingscale * 0.0001; // items are rated around 10000 already
19
20         FOREACH_ENTITY_FLOAT(bot_pickup, true,
21         {
22                 o = (it.absmin + it.absmax) * 0.5;
23                 friend_distance = 10000; enemy_distance = 10000;
24                 rating = 0;
25
26                 if(!it.solid || vdist(o - org, >, sradius) || (it == this.ignoregoal && time < this.ignoregoaltime) )
27                         continue;
28
29                 // Check if the item can be picked up safely
30                 if(it.classname == "droppedweapon")
31                 {
32                         traceline(o, o + '0 0 -1500', true, NULL);
33
34                         d = pointcontents(trace_endpos + '0 0 1');
35                         if(d & CONTENT_WATER || d & CONTENT_SLIME || d & CONTENT_LAVA)
36                                 continue;
37                         if(tracebox_hits_trigger_hurt(it.origin, it.mins, it.maxs, trace_endpos))
38                                 continue;
39                 }
40                 else
41                 {
42                         // Ignore items under water
43                         traceline(it.origin + it.maxs, it.origin + it.maxs, MOVE_NORMAL, it);
44                         if(trace_dpstartcontents & DPCONTENTS_LIQUIDSMASK)
45                                 continue;
46                 }
47
48                 if(teamplay)
49                 {
50                         discard = false;
51
52                         entity picker = it;
53                         FOREACH_CLIENT(IS_PLAYER(it) && it != this && !IS_DEAD(it),
54                         {
55                                 d = vlen(it.origin - o); // distance between player and item
56
57                                 if ( it.team == this.team )
58                                 {
59                                         if ( !IS_REAL_CLIENT(it) || discard )
60                                                 continue;
61
62                                         if( d > friend_distance)
63                                                 continue;
64
65                                         friend_distance = d;
66
67                                         discard = true;
68
69                                         if( picker.health && it.health > this.health )
70                                                 continue;
71
72                                         if( picker.armorvalue && it.armorvalue > this.armorvalue)
73                                                 continue;
74
75                                         if( picker.weapons )
76                                         if( picker.weapons & ~it.weapons )
77                                                 continue;
78
79                                         if (picker.ammo_shells && it.ammo_shells > this.ammo_shells)
80                                                 continue;
81
82                                         if (picker.ammo_nails && it.ammo_nails > this.ammo_nails)
83                                                 continue;
84
85                                         if (picker.ammo_rockets && it.ammo_rockets > this.ammo_rockets)
86                                                 continue;
87
88                                         if (picker.ammo_cells && it.ammo_cells > this.ammo_cells)
89                                                 continue;
90
91                                         if (picker.ammo_plasma && it.ammo_plasma > this.ammo_plasma)
92                                                 continue;
93
94                                         discard = false;
95                                 }
96                                 else
97                                 {
98                                         // If enemy only track distances
99                                         // TODO: track only if visible ?
100                                         if( d < enemy_distance )
101                                                 enemy_distance = d;
102                                 }
103                         });
104
105                         // Rate the item only if no one needs it, or if an enemy is closer to it
106                         if ( (enemy_distance < friend_distance && vdist(o - org, <, enemy_distance)) ||
107                                 (friend_distance > autocvar_bot_ai_friends_aware_pickup_radius ) || !discard )
108                                 rating = it.bot_pickupevalfunc(this, it);
109
110                 }
111                 else
112                         rating = it.bot_pickupevalfunc(this, it);
113
114                 if(rating > 0)
115                         navigation_routerating(this, it, rating * ratingscale, 2000);
116         });
117 }
118
119 void havocbot_goalrating_controlpoints(entity this, float ratingscale, vector org, float sradius)
120 {
121         FOREACH_ENTITY_CLASS("dom_controlpoint", vdist((((it.absmin + it.absmax) * 0.5) - org), <, sradius),
122         {
123                 if(it.cnt > -1) // this is just being fought
124                         navigation_routerating(this, it, ratingscale, 5000);
125                 else if(it.goalentity.cnt == 0) // unclaimed
126                         navigation_routerating(this, it, ratingscale * 0.5, 5000);
127                 else if(it.goalentity.team != this.team) // other team's point
128                         navigation_routerating(this, it, ratingscale * 0.2, 5000);
129         });
130 }
131
132 void havocbot_goalrating_enemyplayers(entity this, float ratingscale, vector org, float sradius)
133 {
134         if (autocvar_bot_nofire)
135                 return;
136
137         // don't chase players if we're under water
138         if(this.waterlevel>WATERLEVEL_WETFEET)
139                 return;
140
141         int t;
142
143         FOREACH_CLIENT(IS_PLAYER(it) && bot_shouldattack(this, it), LAMBDA(
144                 // TODO: Merge this logic with the bot_shouldattack function
145                 if(vdist(it.origin - org, <, 100) || vdist(it.origin - org, >, sradius))
146                         continue;
147
148                 // rate only visible enemies
149                 /*
150                 traceline(this.origin + this.view_ofs, it.origin, MOVE_NOMONSTERS, this);
151                 if (trace_fraction < 1 || trace_ent != it)
152                         continue;
153                 */
154
155                 if((it.flags & FL_INWATER) || (it.flags & FL_PARTIALGROUND))
156                         continue;
157
158                 // not falling
159                 if((IS_ONGROUND(it)) == 0)
160                 {
161                         traceline(it.origin, it.origin + '0 0 -1500', true, NULL);
162                         t = pointcontents(trace_endpos + '0 0 1');
163                         if(t != CONTENT_SOLID )
164                         if(t & CONTENT_WATER || t & CONTENT_SLIME || t & CONTENT_LAVA)
165                                 continue;
166                         if(tracebox_hits_trigger_hurt(it.origin, it.mins, it.maxs, trace_endpos))
167                                 continue;
168                 }
169
170                 // TODO: rate waypoints near the targetted player at that moment, instead of the player itthis
171                 //               adding a player as a goal seems to be quite dangerous, especially on space maps
172                 //               remove hack in navigation_poptouchedgoals() after performing this change
173
174                 t = (this.health + this.armorvalue ) / (it.health + it.armorvalue );
175                 navigation_routerating(this, it, t * ratingscale, 2000);
176         ));
177 }
178
179 // legacy bot role for standard gamemodes
180 // go to best items
181 void havocbot_role_generic(entity this)
182 {
183         if(IS_DEAD(this))
184                 return;
185
186         if (this.bot_strategytime < time)
187         {
188                 this.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
189                 navigation_goalrating_start(this);
190                 havocbot_goalrating_items(this, 10000, this.origin, 10000);
191                 havocbot_goalrating_enemyplayers(this, 20000, this.origin, 10000);
192                 //havocbot_goalrating_waypoints(1, this.origin, 1000);
193                 navigation_goalrating_end(this);
194         }
195 }
196
197 void havocbot_chooserole_generic(entity this)
198 {
199         this.havocbot_role = havocbot_role_generic;
200 }
201
202 void havocbot_chooserole(entity this)
203 {
204         LOG_TRACE("choosing a role...\n");
205         this.bot_strategytime = 0;
206         if(!MUTATOR_CALLHOOK(HavocBot_ChooseRole, this))
207                 havocbot_chooserole_generic(this);
208 }