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