]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/havocbot.qc
fixed out-of-range g_maplist_position causing runaway loop
[divverent/nexuiz.git] / data / qcsrc / server / havocbot.qc
1
2 .void() havocbot_role;
3 void() havocbot_chooserole;
4 .float havocbot_keyboardskill;
5
6 vector() havocbot_dodge =
7 {
8         local entity head;
9         local vector dodge, v, n;
10         local float danger, bestdanger, vl, d;
11         dodge = '0 0 0';
12         bestdanger = -20;
13         // check for dangerous objects near bot or approaching bot
14         head = findchainfloat(bot_dodge, TRUE);
15         while(head)
16         {
17                 if (head.owner != self)
18                 {
19                         vl = vlen(head.velocity);
20                         if (vl > sv_maxspeed * 0.3)
21                         {
22                                 n = normalize(head.velocity);
23                                 v = self.origin - head.origin;
24                                 d = v * n;
25                                 if (d > (0 - head.bot_dodgerating))
26                                 if (d < (vl * 0.2 + head.bot_dodgerating))
27                                 {
28                                         // calculate direction and distance from the flight path, by removing the forward axis
29                                         v = v - (n * (v * n));
30                                         danger = head.bot_dodgerating - vlen(v);
31                                         if (bestdanger < danger)
32                                         {
33                                                 bestdanger = danger;
34                                                 // dodge to the side of the object
35                                                 dodge = normalize(v);
36                                         }
37                                 }
38                         }
39                         else
40                         {
41                                 danger = head.bot_dodgerating - vlen(head.origin - self.origin);
42                                 if (bestdanger < danger)
43                                 {
44                                         bestdanger = danger;
45                                         dodge = normalize(self.origin - head.origin);
46                                 }
47                         }
48                 }
49                 head = head.chain;
50         }
51         return dodge;
52 };
53
54 //.float havocbotignoretime;
55 .float havocbot_keyboardtime;
56 .float havocbot_ducktime;
57 .vector havocbot_keyboard;
58 void() havocbot_movetogoal =
59 {
60         local vector destorg;
61         local vector diff;
62         local vector dir;
63         local vector flatdir;
64         local vector m1;
65         local vector m2;
66         local vector evadeobstacle;
67         local vector evadelava;
68         local float s;
69         //local float dist;
70         local vector dodge;
71         //if (self.goalentity)
72         //      te_lightning2(self, self.origin, (self.goalentity.absmin + self.goalentity.absmax) * 0.5);
73         self.movement = '0 0 0';
74         if (self.goalcurrent == world)
75                 return;
76         navigation_poptouchedgoals();
77         if (self.goalcurrent == world)
78         {
79                 // ran out of goals, rethink strategy as soon as possible
80                 self.bot_strategytime = 0;
81                 return;
82         }
83         evadeobstacle = '0 0 0';
84         evadelava = '0 0 0';
85         m1 = self.goalcurrent.origin + self.goalcurrent.mins;
86         m2 = self.goalcurrent.origin + self.goalcurrent.maxs;
87         destorg = self.origin;
88         destorg_x = bound(m1_x, destorg_x, m2_x);
89         destorg_y = bound(m1_y, destorg_y, m2_y);
90         destorg_z = bound(m1_z, destorg_z, m2_z);
91         diff = destorg - self.origin;
92         //dist = vlen(diff);
93         dir = normalize(diff);
94         flatdir = diff;flatdir_z = 0;
95         flatdir = normalize(flatdir);
96         if (!self.waterlevel)
97         {
98                 // Since new update in air contol, we can move in air
99                 //if (!(self.flags & FL_ONGROUND))
100                 //{
101                 //      // prevent goal checks when we can't walk
102                 //      if (self.bot_strategytime < time + 0.1)
103                 //              self.bot_strategytime = time + 0.1;
104                 //      return;
105                 //}
106
107                 // jump if going toward an obstacle that doesn't look like stairs we
108                 // can walk up directly
109                 tracebox(self.origin, self.mins, self.maxs, self.origin + self.velocity * 0.2, FALSE, self);
110                 if (trace_fraction < 1)
111                 if (trace_plane_normal_z < 0.7)
112                 {
113                         s = trace_fraction;
114                         tracebox(self.origin + '0 0 16', self.mins, self.maxs, self.origin + self.velocity * 0.2 + '0 0 16', FALSE, self);
115                         if (trace_fraction < s + 0.01)
116                         if (trace_plane_normal_z < 0.7)
117                         {
118                                 s = trace_fraction;
119                                 tracebox(self.origin + '0 0 48', self.mins, self.maxs, self.origin + self.velocity * 0.2 + '0 0 48', FALSE, self);
120                                 if (trace_fraction > s)
121                                         self.button2 = 1;
122                         }
123                 }
124
125                 traceline(self.origin + self.velocity * 0.3, self.origin + self.velocity * 0.3 + '0 0 -1000', TRUE, world);
126                 s = pointcontents(trace_endpos + '0 0 1');
127                 if (s == CONTENT_LAVA || s == CONTENT_SLIME)
128                         evadelava = normalize(self.velocity) * -1;
129
130                 dir = flatdir;
131         }
132         dodge = havocbot_dodge();
133         dodge = dodge * bound(0,3+skill*0.1,1);
134         evadelava = evadelava * bound(1,3-skill,3); //Noobs fear lava a lot and take more distance from it
135         traceline(self.origin, self.enemy.origin, TRUE, world);
136         if(trace_ent.classname == "player")
137                 dir = dir * bound(0,skill/7,1);
138
139         dir = normalize(dir + dodge + evadeobstacle + evadelava);
140
141
142
143         // Emulate keyboard interface;
144         local vector keyboard,flatangle;
145         local float blend;
146         keyboard = self.havocbot_keyboard;
147         if (time >= self.havocbot_keyboardtime)
148         {
149                 flatdir=dir; flatdir_z = 0;
150                 self.havocbot_keyboardtime =
151                         max(
152                                 self.havocbot_keyboardtime
153                                         + bound(0,0.05/(skill+self.havocbot_keyboardskill),0.05)
154                                         +random()*bound(0,0.025/(skill+self.havocbot_keyboardskill),100)
155                         , time);
156                 keyboard = '0 0 0';
157                 
158                 flatangle = self.v_angle; flatangle_z=0;
159                 makevectors(flatangle);
160                 
161                 local float trigger;
162                 local vector v_forward_right;
163                 local vector v_forward_left;
164                 blend = bound(0,skill*0.1,1);
165                 trigger = cvar("bot_ai_keyboard_treshold");
166                 v_forward_right = normalize(v_forward + v_right);
167                 v_forward_left  = normalize(v_forward - v_right);
168                 // Place in reverse order !! least important direction FIRST
169                 
170                 if (skill > 4.5)
171                 {
172                         if (flatdir * v_forward_right * -1 > trigger) keyboard = v_forward_right * -1;
173                         if (flatdir * v_forward_left  * -1 > trigger) keyboard = v_forward_left  * -1;
174                 }
175                 if (skill > 2.5)
176                 {
177                         if (flatdir * v_forward_right      > trigger) keyboard = v_forward_right;
178                         if (flatdir * v_forward_left       > trigger) keyboard = v_forward_left;
179                         if (flatdir * v_forward       *  1 > trigger) keyboard = v_forward * -1;
180                 }
181                 if (skill > 1.5)
182                 {
183                         if (flatdir * v_right              > trigger) keyboard = v_right;
184                         if (flatdir * v_right         * -1 > trigger) keyboard = v_right   * -1;
185                 }
186                         if (flatdir * v_forward            > trigger) keyboard = v_forward;
187                 //dprint(ftos(flatdir * v_forward),"\n");
188                 keyboard = normalize(keyboard);
189                 self.havocbot_keyboard = keyboard;
190                 if (self.havocbot_ducktime>time) self.button5=TRUE;
191         }
192         blend = bound(0,vlen(self.goalcurrent.origin-self.origin)/cvar("bot_ai_keyboard_distance"),1); // When getting close move with 360 degree
193         dir = (keyboard * blend + dir * (1-blend))*cvar("sv_maxspeed");
194
195         self.movement_x = dir * v_forward;
196         self.movement_y = dir * v_right;
197         if (self.flags & FL_INWATER) self.movement_z = dir * v_up; else self.movement_z = 0;
198         if ((dir * v_up) >= cvar("g_balance_jumpheight")*0.5 && (self.flags & FL_ONGROUND)) self.button2=1;
199         if (((dodge * v_up) > 0) && random()*frametime >= 0.2*bound(0,(10-skill)*0.1,1)) self.button2=TRUE;
200         if (((dodge * v_up) < 0) && random()*frametime >= 0.5*bound(0,(10-skill)*0.1,1)) self.havocbot_ducktime=time+0.3/bound(0.1,skill,10);
201         
202 };
203
204 .float havocbot_chooseenemy_finished;
205 .float havocbot_stickenemy;
206 void() havocbot_chooseenemy =
207 {
208         local entity head, best;
209         local float rating, bestrating;
210         local vector eye, v;
211         if (cvar("bot_nofire"))
212         {
213                 self.enemy = world;
214                 return;
215         }
216         traceline(self.origin+self.view_ofs, self.enemy.origin+self.enemy.view_ofs*0.5,FALSE,world);
217         if (trace_ent.classname != "player")
218                 self.havocbot_stickenemy =0;
219         else    if ( (trace_ent != self.enemy) || (vlen(self.enemy.origin - self.origin) > 1000) )
220                 {
221                         self.havocbot_stickenemy =0;
222                         if( (self.health < 30) || (self.enemy.health < 0))
223                         self.havocbot_chooseenemy_finished = time;
224                 }
225         //dprint(ftos(self.havocbot_stickenemy));dprint(etos(self.enemy),"\n");
226         //dprint(ftos(time),"-");dprint(ftos(self.havocbot_chooseenemy_finished),"\n");
227         if (self.havocbot_stickenemy == 1)
228         {
229                 // remain tracking him for a shot while (case he went after a small corner or pilar
230                 self.havocbot_chooseenemy_finished = time + bound(0,skill*0.1,1)*1.8;
231                 return;
232         }
233         if (time < self.havocbot_chooseenemy_finished)
234         {
235                 self.havocbot_stickenemy = 1;
236                 return;
237         }
238         self.havocbot_chooseenemy_finished = time + cvar("bot_ai_enemydetectioninterval")*bound(0,(11-skill)*0.1,1);
239         self.havocbot_chooseenemy_finished = time + 0.01;
240         eye = (self.origin + self.view_ofs);
241         best = world;
242         bestrating = 100000000;
243         head = findchainfloat(bot_attack, TRUE);
244         while (head)
245         {
246                 v = (head.absmin + head.absmax) * 0.5;
247                 rating = vlen(v - eye);
248                 if (bestrating > rating)
249                 if (bot_shouldattack(head))
250                 {
251                         traceline(eye, v, TRUE, self);
252                         if (trace_ent == head || trace_fraction >= 1)
253                         {
254                                 best = head;
255                                 bestrating = rating;
256                         }
257                 }
258                 head = head.chain;
259         }
260         self.enemy = best;
261         self.havocbot_stickenemy = 1;
262 };
263
264 float(entity e) w_getbestweapon;
265 .float havocbot_chooseweapon_timer;
266 .float havocbot_chooseweapon_lastbestscore;
267 void() havocbot_chooseweapon =
268 {
269         if(self.enemy.classname!="player")
270         {
271                 self.switchweapon = w_getbestweapon(self);
272                 return;
273         }
274
275         local float rocket  ; rocket   =-1000;
276         local float nex     ; nex      =-1000;
277         local float hagar   ; hagar    =-1000;
278         local float grenade ; grenade  =-1000;
279         local float electro ; electro  =-1000;
280         local float crylink ; crylink  =-1000;
281         local float uzi     ; uzi      =-1000;
282         local float shotgun ; shotgun  =-1000;
283         local float laser   ; laser    =-1000;
284         local float bestscore; bestscore = 0;
285         local float bestweapon; bestweapon=self.switchweapon;
286         local float distance; distance=bound(10,vlen(self.origin-self.enemy.origin)-200,10000);
287         local float maxdelaytime=0.5;
288         local float spreadpenalty=10;
289         local float distancefromfloor;
290         traceline(self.enemy.origin,self.enemy.origin-'0 0 1000',TRUE,world);
291         distancefromfloor = self.enemy.origin_z - trace_endpos_z;
292         // Formula:
293         //      (Damage/Sec * Weapon spefic change to get that damage)
294         //      *(Time to get to target * weapon specfic hitchange bonus) / (in a time of maxdelaytime)
295         //      *(Spread change of hit) // if it applies
296         //      *(Penality for target beeing in air)
297         if (client_hasweapon(self, WEP_ROCKET_LAUNCHER, TRUE, FALSE))
298                 rocket = (cvar("g_balance_rocketlauncher_damage")/cvar("g_balance_rocketlauncher_refire")*0.75)
299                         * bound(0,(cvar("g_balance_rocketlauncher_speed")/distance*maxdelaytime),1)*1.5;
300         if (client_hasweapon(self, WEP_NEX, TRUE, FALSE))
301                 nex = (cvar("g_balance_nex_damage")/cvar("g_balance_nex_refire")*1.0)
302                         * (0.5);
303         if (client_hasweapon(self, WEP_HAGAR, TRUE, FALSE))
304                 hagar = (cvar("g_balance_hagar_primary_damage")/cvar("g_balance_hagar_primary_refire")*1.0)
305                         * bound(0,(cvar("g_balance_hagar_primary_speed")/distance*maxdelaytime),1)*0.2;
306         if (client_hasweapon(self, WEP_GRENADE_LAUNCHER, TRUE, FALSE))
307                 grenade = (cvar("g_balance_grenadelauncher_primary_damage")/cvar("g_balance_grenadelauncher_primary_refire")*1.0)
308                         * bound(0,(cvar("g_balance_grenadelauncher_primary_speed")/distance*maxdelaytime),1)*1.1;
309         if (client_hasweapon(self, WEP_ELECTRO, TRUE, FALSE))
310                 electro = (cvar("g_balance_electro_primary_damage")/cvar("g_balance_electro_primary_refire")*0.75)
311                         * bound(0,(cvar("g_balance_electro_primary_speed")/distance*maxdelaytime),1)*1.0;
312         if (client_hasweapon(self, WEP_CRYLINK, TRUE, FALSE))
313                 crylink = (cvar("g_balance_crylink_primary_damage")/cvar("g_balance_crylink_primary_refire")*1.0)
314                         * bound(0,(cvar("g_balance_crylink_primary_speed")/distance*maxdelaytime),1)*1.0
315                         * bound(0,1/cvar("g_balance_crylink_primary_spread")/distance*spreadpenalty,1);
316         if (client_hasweapon(self, WEP_UZI, TRUE, FALSE))
317                 uzi = (cvar("g_balance_uzi_sustained_damage")/cvar("g_balance_uzi_sustained_refire")*1.0)
318                         * bound(0,1/cvar("g_balance_uzi_sustained_spread")/distance*spreadpenalty,1)*0.5;
319         if (client_hasweapon(self, WEP_SHOTGUN, TRUE, FALSE))
320                 shotgun = (cvar("g_balance_shotgun_primary_damage")*cvar("g_balance_shotgun_primary_bullets")/cvar("g_balance_shotgun_primary_refire")*1.0)
321                         * bound(0,1/cvar("g_balance_shotgun_primary_spread")/distance*spreadpenalty,1);
322         if (client_hasweapon(self, WEP_LASER, FALSE, FALSE))
323                 laser = (cvar("g_balance_laser_damage")/cvar("g_balance_laser_refire")*1.0)
324                         * bound(0,cvar("g_balance_laser_speed")/distance*0.2*maxdelaytime,1);
325         if((self.enemy.flags & FL_ONGROUND)==FALSE){
326                 rocket = rocket   * (1-bound(0, distancefromfloor/cvar("g_balance_rocketlauncher_radius"         ),0.9)); //slight bigger change
327                 grenade = grenade * (1-bound(0,distancefromfloor/cvar("g_balance_grenadelauncher_primary_radius"),0.95));
328                 electro = electro * (1-bound(0,distancefromfloor/cvar("g_balance_electro_primary_radius"        ),0.95));
329                 laser = laser     * (1-bound(0,distancefromfloor/cvar("g_balance_laser_radius"                  ),0.95));
330         }
331 /*
332         dprint("Floor distance: ",ftos(distancefromfloor),"\n");
333         dprint("Rocket: " , ftos(rocket  ), "\n");
334         dprint("Nex: "    , ftos(nex     ), "\n");
335         dprint("Hagar: "  , ftos(hagar   ), "\n");
336         dprint("Grenade: ", ftos(grenade ), "\n");
337         dprint("Electro: ", ftos(electro ), "\n");
338         dprint("Crylink: ", ftos(crylink ), "\n");
339         dprint("Uzi: "    , ftos(uzi     ), "\n");
340         dprint("Shotgun :", ftos(shotgun ), "\n");
341         dprint("Laser   :", ftos(laser   ), "\n\n");
342 */
343         if (rocket  > bestscore){ bestscore = rocket  ; bestweapon = WEP_ROCKET_LAUNCHER  ;}
344         if (nex     > bestscore){ bestscore = nex     ; bestweapon = WEP_NEX              ;}
345         if (hagar   > bestscore){ bestscore = hagar   ; bestweapon = WEP_HAGAR            ;}
346         if (grenade > bestscore){ bestscore = grenade ; bestweapon = WEP_GRENADE_LAUNCHER ;}
347         if (electro > bestscore){ bestscore = electro ; bestweapon = WEP_ELECTRO          ;}
348         if (crylink > bestscore){ bestscore = crylink ; bestweapon = WEP_CRYLINK          ;}
349         if (uzi     > bestscore){ bestscore = uzi     ; bestweapon = WEP_UZI              ;}
350         if (shotgun > bestscore){ bestscore = shotgun ; bestweapon = WEP_SHOTGUN          ;}
351         if (laser   > bestscore){ bestscore = laser   ; bestweapon = WEP_LASER            ;}
352
353         if(time>self.havocbot_chooseweapon_timer || self.havocbot_chooseweapon_lastbestscore<bestscore/10) //Or when the new damage is SOO much larger ! Or my gun runs out of ammo
354         {
355                 self.havocbot_chooseweapon_timer=max(self.havocbot_chooseweapon_timer+cvar("g_balance_weaponswitchdelay")*120*(10-skill)*0.1,time);
356                 if( self.havocbot_chooseweapon_lastbestscore*1.5<bestscore*bound(1,1+(skill*skill)*0.01,2))
357                 {
358                         self.switchweapon = bestweapon; 
359                         self.havocbot_chooseweapon_lastbestscore=bestscore;
360                 }
361         }
362 };
363
364 .float nextaim;
365 void() havocbot_aim =
366 {
367         local vector selfvel, enemyvel;
368         if (time < self.nextaim)
369                 return;
370         self.nextaim = time + 0.1;
371         selfvel = self.velocity;
372         if (!self.waterlevel)
373                 selfvel_z = 0;
374         if (self.enemy)
375         {
376                 enemyvel = self.enemy.velocity;
377                 if (!self.enemy.waterlevel)
378                         enemyvel_z = 0;
379                 lag_additem(time + self.ping, 0, 0, self.enemy, self.origin, selfvel, self.enemy.origin, enemyvel);
380         }
381         else
382                 lag_additem(time + self.ping, 0, 0, world, self.origin, selfvel, self.goalcurrent.origin, '0 0 0');
383 };
384
385 void() havocbot_ai =
386 {
387         if (bot_strategytoken == self)
388                 self.havocbot_role();
389         havocbot_chooseenemy();
390         havocbot_chooseweapon();
391         havocbot_aim();
392         lag_update();
393         if (self.bot_aimtarg)
394                 weapon_action(self.weapon, WR_AIM);
395         else if (self.goalcurrent)
396         {
397                 local vector now,v,next;//,heading;
398                 local float distance,skillblend,distanceblend;
399                 now = self.goalcurrent.origin - self.origin;
400                 distance = vlen(now);
401                 //heading = self.velocity;
402                 //dprint(self.goalstack01.classname,etos(self.goalstack01),"\n");
403                 if(self.goalstack01 != self && self.goalstack01 != world)
404                         next = self.goalstack01.origin - self.origin;
405                 else
406                         next = now;
407                 skillblend=bound(0,(skill-2.5)*0.5,1); //lower skill player can't preturn
408                 distanceblend=bound(0,distance/cvar("bot_ai_keyboard_distance"),1); 
409                 v = (now * (distanceblend) + next * (1-distanceblend)) * (skillblend) + now * (1-skillblend);
410                 //v = now * (distanceblend) + next * (1-distanceblend);
411                 if (self.waterlevel < 2)
412                         v_z = 0;
413                 //dprint("walk at:", vtos(v), "\n");
414                 //te_lightning2(world, self.origin, self.goalcurrent.origin);
415                 bot_aimdir(v, -1);
416         }
417         havocbot_movetogoal();
418 };
419
420 void() havocbot_setupbot =
421 {
422         self.bot_ai = havocbot_ai;
423         // will be updated by think code
424         //Generate some random skill levels
425         self.havocbot_keyboardskill=random()-0.5;
426         havocbot_chooserole();
427 }