1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: b_game.c 1512 2020-04-04 08:51:13Z wesleyjohnson $
5 //
6 // Copyright (C) 2002 by DooM Legacy Team.
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; either version 2
11 // of the License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
17 //
18 //
19 // $Log: b_game.c,v $
20 // Revision 1.5  2004/07/27 08:19:34  exl
21 // New fmod, fs functions, bugfix or 2, patrol nodes
22 //
23 // Revision 1.4  2003/06/11 04:04:50  ssntails
24 // Rellik's Bot Code!
25 //
26 // Revision 1.3  2002/09/28 06:53:11  tonyd
27 // fixed CR problem, fixed game options crash
28 //
29 // Revision 1.2  2002/09/27 16:40:07  tonyd
30 // First commit of acbot
31 //
32 //-----------------------------------------------------------------------------
33 
34 // Bot include
35 #include "b_bot.h"
36 #include "b_game.h"
37 #include "b_look.h"
38 #include "b_node.h"
39 
40 // Doom include
41 #include "doomincl.h"
42 #include "doomstat.h"
43 //#include "r_defs.h"
44 #include "m_random.h"
45 #include "p_local.h"
46 #include "z_zone.h"
47 
48 #include "command.h"
49 #include "r_state.h"
50 #include "v_video.h"
51 #include "m_argv.h"
52 #include "p_setup.h"
53 #include "r_main.h"
54 #include "r_things.h"
55 #include "g_game.h"
56 #include "d_net.h"
57 #include "byteptr.h"
58 
59 
60 // [WDJ] New Bot code is not compatible with old versions, is not old demo compatible.
61 // #define BOT_VERSION_DETECT
62 
63 // Persistant random number, that changes after each use.  Only used to initialize.
64 // Only CV_NETVAR in case someone uses the B_Gen_Random outside of initializing names.
65 consvar_t  cv_bot_random = { "botrandom", "1333", CV_NETVAR | CV_SAVE, CV_Unsigned };
66 
67 // User set random seed.  Only used to initialize.
68 // Only CV_NETVAR in case someone uses the B_Gen_Random outside of initializing names.
69 static void CV_botrandom_OnChange( void );
70 consvar_t  cv_bot_randseed = { "botrandseed", "0", CV_NETVAR | CV_SAVE | CV_CALL, CV_Unsigned, CV_botrandom_OnChange };
71 
72 CV_PossibleValue_t botgen_cons_t[]={ {0,"Plain"}, {1,"Seed"}, {2,"Seed Random"}, {3,"Cfg Random"}, {4,"Sys Random"}, {0,NULL}};
73 consvar_t  cv_bot_gen = { "botgen", "0", CV_NETVAR | CV_SAVE | CV_CALL, botgen_cons_t, CV_botrandom_OnChange };
74 
75 CV_PossibleValue_t botskin_cons_t[]={ {0,"Color"}, {1,"Skin"}, {0,NULL}};
76 consvar_t  cv_bot_skin = { "botskin", "0", CV_NETVAR | CV_SAVE, botskin_cons_t };
77 
78 CV_PossibleValue_t botrespawn_cons_t[]={
79   {5,"MIN"},
80   {255,"MAX"},
81   {0,NULL}};
82 consvar_t  cv_bot_respawn_time = { "botrespawntime", "8", CV_NETVAR | CV_SAVE, botrespawn_cons_t };
83 
84 CV_PossibleValue_t botskill_cons_t[]={
85   {0,"crippled"},
86   {1,"baby"},
87   {2,"easy"},
88   {3,"medium"},
89   {4,"hard"},
90   {5,"nightmare"},
91   {6,"randmed"},
92   {7,"randgame"},
93   {8,"gamemed"},
94   {9,"gameskill"},
95   {0,NULL}};
96 consvar_t  cv_bot_skill = { "botskill", "gamemed", CV_NETVAR | CV_SAVE, botskill_cons_t };
97 
98 static void CV_botspeed_OnChange( void );
99 
100 CV_PossibleValue_t botspeed_cons_t[]={
101   {0,"walk"},
102   {1,"trainer"},
103   {2,"slow"},
104   {3,"medium"},
105   {4,"fast"},
106   {5,"run"},
107   {8,"gamemed"},
108   {9,"gameskill"},
109   {10,"botskill"},
110   {0,NULL}};
111 consvar_t  cv_bot_speed = { "botspeed", "botskill", CV_NETVAR | CV_SAVE | CV_CALL, botspeed_cons_t, CV_botspeed_OnChange };
112 
113 
114 // [WDJ] Tables just happened to be this way for now, they may change later.
115 static byte bot_botskill_to_speed[ 6 ] = { 0, 1, 2, 3, 4, 5 };
116 static byte bot_gameskill_to_speed[ 5 ] = { 1, 2, 3, 4, 5 };  // lowest value must be >= 1 (see gamemed)
117 static byte bot_speed_frac_table[ 6 ] = { 110, 90, 102, 112, 122, 128 };  // 128=full
118 static uint32_t bot_run_tics_table[ 6 ] = { TICRATE/4, 4*TICRATE, 6*TICRATE, 12*TICRATE, 24*TICRATE, 128*TICRATE };  // tics
119 static byte bot_speed_frac;
120 static byte bot_run_tics;
121 
122 // A function of gameskill and cv_bot_speed.
123 // Must be called when either changes.
CV_botspeed_OnChange(void)124 static void CV_botspeed_OnChange( void )
125 {
126     byte bot_speed_index;
127     switch( cv_bot_speed.EV )
128     {
129       case 8:  // gamemed
130         // One step slower than gameskill.
131         bot_speed_index = bot_gameskill_to_speed[ gameskill ] - 1;
132         break;
133       case 10: // botskill (temp value, deferred determination)
134       case 9:  // gameskill
135         bot_speed_index = bot_gameskill_to_speed[ gameskill ];
136         break;
137       default:
138         bot_speed_index = cv_bot_speed.EV;  // 0..5
139         break;
140     }
141 #ifdef BOT_VERSION_DETECT
142     if( demoversion < 148 )  bot_speed_index = 5; // always run
143 #endif
144 
145     bot_speed_frac = bot_speed_frac_table[ bot_speed_index ];
146     bot_run_tics = bot_run_tics_table[ bot_speed_index ];
147 }
148 
149 
150 #ifndef M_PI
151 #define M_PI		3.14159265358979323846	// matches value in gcc v2 math.h
152 #endif
153 
154 boolean B_FindNextNode(player_t* p);
155 
156 bot_info_t  botinfo[MAXPLAYERS];
157 fixed_t botforwardmove[2] = {25/NEWTICRATERATIO, 50/NEWTICRATERATIO};
158 fixed_t botsidemove[2]    = {24/NEWTICRATERATIO, 40/NEWTICRATERATIO};
159 angle_t botangleturn[4]   = {500, 1000, 2000, 4000};
160 
161 extern consvar_t cv_skill;
162 extern thinker_t thinkercap;
163 extern mobj_t*	tmthing;
164 
165 // Player name that is seen for each bot.
166 #define NUM_BOT_NAMES 40
167 char* botnames[NUM_BOT_NAMES] = {
168   "Frag-God",
169   "TF-Master",
170   "FragMaster",
171   "Thresh",
172   "Reptile",
173   "Archer",
174   "Freak",
175   "Reeker",
176   "Dranger",
177   "Enrage",
178   "Franciot",
179   "Grimknot",
180   "Fodder",
181   "Rumble",
182   "Crusher",
183   "Crash",
184   "Krunch",
185   "Wreaker",
186   "Punisher",
187   "Quaker",
188   "Reaper",
189   "Slasher",
190   "Tormenot",
191   "Drat",
192   "Labrat",
193   "Nestor",
194   "Akira",
195   "Meikot",
196   "Aliciot",
197   "Leonardot",
198   "Ralphat",
199   "Xoleras",
200   "Zetat",
201   "Carmack",  // Hon
202   "Romero",  // Hon
203   "Hurdlerbot",  // Team member
204   "Meisterbot",  // Team member
205   "Borisbot",  // Team member
206   "Tailsbot",  // Team member
207   "TonyD-bot", // Team member
208 };
209 
210 int botcolors[NUMSKINCOLORS] =
211 {
212    0, // = Green
213    1, // = Indigo
214    2, // = Blue
215    3, // = Deep Red
216    4, // = White
217    5, // = Bright Brown
218    6, // = Red
219    7, // = Blue
220    8, // = Dark Blue
221    9, // = Yellow
222    10 // = Bleached Bone
223 };
224 
225 #if 1
226 // By Server, for bots.
227 //  skinrand : the botinfo skinrand value
228 static
bot_skinname(int skinrand)229 char * bot_skinname( int skinrand )
230 {
231     if( cv_bot_skin.EV && (numskins > 1) )
232     {
233         skinrand = (skinrand % (numskins-1)) + 1;
234     }
235     else
236     {
237         skinrand = 0; // marine
238     }
239     return skins[skinrand]->name;
240 }
241 #endif
242 
243 
244 // Random number source for bot generation.
B_Gen_Random(void)245 uint32_t  B_Gen_Random( void )
246 {
247     uint32_t r;
248     switch( cv_bot_gen.EV )
249     {
250      case 0:  // Plain
251         r = B_Random();
252         break;
253      default:
254      case 1:  // Seed
255         r = B_Random() + cv_bot_randseed.value;
256         break;
257      case 2:  // Seed Random
258         r = E_Random() + cv_bot_randseed.value;
259         break;
260      case 3:  // Cfg Random
261         r = E_Random() + cv_bot_random.value;
262         break;
263      case 4:  // Sys Random
264         r = rand();
265         break;
266     }
267     return r;
268 }
269 
B_Init_Names()270 void B_Init_Names()
271 {
272     int br, i, j;
273     uint16_t color_used = 0;
274     byte   botindex = B_Rand_GetIndex();
275 
276     CV_ValueIncDec( &cv_bot_random, 7237 ); // add a prime
277 
278     // Initialize botinfo.
279     for (i=0; i< MAXPLAYERS; i++)
280     {
281         // Give each prospective bot a unique name.
282         do
283         {
284             br = B_Gen_Random() % NUM_BOT_NAMES;
285             for( j = 0; j < i; j++ )
286             {
287                 if( botinfo[j].name_index == br )  break;
288             }
289         } while ( j < i );  // when j==i then did not find any duplicates
290         botinfo[i].name_index = br;
291 
292         // Assign a skin color.  Make them unique until have used all colors.
293         j = NUMSKINCOLORS;
294         br = B_Gen_Random();
295         for(;;)
296         {
297             br = br % NUMSKINCOLORS;
298             if( ((1<<br) & color_used) == 0 )  break;
299             br++;
300             if( --j < 0 )  color_used = 0;
301         }
302         botinfo[i].colour = br;
303         color_used |= (1<<br);
304 
305         botinfo[i].skinrand = B_Gen_Random();
306     }
307 
308 #if 1
309     // Restore to keep reproducible bot gen in spite of multiple calls during setup,
310     // and it minimizes effects of network races.
311     if( cv_bot_gen.EV < 2 )
312         B_Rand_SetIndex( botindex );
313 #endif
314 
315     // Existing bots keep their existing names and colors.
316     // This only changes the tables used to create new bots, by the server.
317     // Off-server clients will keep the original bot names that the server sent them.
318 }
319 
320 
321 static byte bot_init_done = 0;
322 
CV_botrandom_OnChange(void)323 static void CV_botrandom_OnChange( void )
324 {
325 #ifdef BOT_VERSION_DETECT
326     if( demoversion < 148 )  return;
327 #endif
328 
329     // With so many NETVAR update paths, only let the server do this.
330     // The bot names will be passed with the NetXCmd, so clients do not need this.
331     // The random number generatators will be updated.
332     if( ! server )
333         return;
334 
335     // [WDJ] Updating the random number generators in the middle of a game, ugh.
336     if( netgame )
337         SV_network_wait_timer( 35 );  // pause everybody
338 
339     if( cv_bot_randseed.state & CS_MODIFIED )
340         B_Rand_SetIndex( cv_bot_randseed.value );
341 
342     // Only change names after initial loading of config.
343     if( bot_init_done )
344         B_Init_Names();
345 
346     // Let network un-pause naturally.  Better timing.
347 }
348 
349 
DemoAdapt_bots(void)350 void DemoAdapt_bots( void )
351 {
352     CV_botspeed_OnChange();
353 }
354 
355 
356 // may be called multiple times
B_Init_Bots()357 void B_Init_Bots()
358 {
359     DemoAdapt_bots();
360 
361     B_Init_Names();
362 
363     botNodeArray = NULL;
364 
365     bot_init_done = 1;
366 }
367 
368 //
369 // bot commands
370 //
371 
372 // By Server
B_Send_bot_NameColor(byte pn)373 void B_Send_bot_NameColor( byte pn )
374 {
375     // From botinfo sources
376     bot_info_t * bip = & botinfo[pn];
377     Send_NameColor_pn( pn, botnames[bip->name_index], bip->colour, bot_skinname(bip->skinrand), 2 );  // server channel
378 }
379 
380 // By Server.
381 // Send name, color, skin of all bots.
B_Send_all_bots_NameColor(void)382 void B_Send_all_bots_NameColor( void )
383 {
384     byte pn;
385 
386     for( pn=0; pn<MAXPLAYERS; pn++ )
387     {
388         if( playeringame[pn] && players[pn].bot )
389         {
390 #if 1
391             B_Send_bot_NameColor( pn );
392 #else
393             // From player, which requires that bot have set the namecolor of player already.
394             Send_NameColor_player( pn, 2 );  // server channel
395 #endif
396         }
397     }
398 }
399 
400 
Command_AddBot(void)401 void Command_AddBot(void)
402 {
403     byte buf[10];
404     byte pn = 0;
405 
406     if( !server )
407     {
408         CONS_Printf("Only the server can add a bot\n");
409         return;
410     }
411 
412     pn = SV_get_player_num();
413 
414     if( pn >= MAXPLAYERS )
415     {
416         CONS_Printf ("You can only have %d players.\n", MAXPLAYERS);
417         return;
418     }
419 
420     bot_info_t * bip = & botinfo[pn];
421     byte * b = &buf[0];
422     // AddBot format: pn, color, name:string0
423     WRITEBYTE( b, pn );
424     WRITEBYTE( b, bip->colour );
425     b = write_stringn(b, botnames[bip->name_index], MAXPLAYERNAME);
426     SV_Send_NetXCmd(XD_ADDBOT, buf, (b - buf));  // as server
427 
428     // Cannot send NameColor XCmd before the bot exists.
429 }
430 
431 // Only call after console player and splitscreen players have grabbed their player slots.
B_Regulate_Bots(int req_numbots)432 void B_Regulate_Bots( int req_numbots )
433 {
434     byte pn;
435     for( pn = 0; pn < MAXPLAYERS; pn++ )
436     {
437         if( playeringame[pn] && players[pn].bot )  req_numbots--;
438     }
439 
440     if( req_numbots > (MAXPLAYERS - num_game_players) )
441         req_numbots = (MAXPLAYERS - num_game_players);
442 
443     while( req_numbots > 0 )
444     {
445         Command_AddBot();
446         req_numbots --;
447     }
448 }
449 
B_Register_Commands(void)450 void B_Register_Commands( void )
451 {
452     COM_AddCommand ("addbot", Command_AddBot, CC_command);
453 }
454 
455 static
B_AvoidMissile(player_t * p,mobj_t * missile)456 void B_AvoidMissile(player_t* p, mobj_t* missile)
457 {
458     angle_t  missile_angle = R_PointToAngle2 (p->mo->x, p->mo->y, missile->x, missile->y);
459     signed_angle_t  delta = p->mo->angle - missile_angle;
460 
461     if( delta >= 0)
462         p->cmd.sidemove = -botsidemove[1];
463     else if( delta < 0)
464         p->cmd.sidemove = botsidemove[1];
465 }
466 
467 static
B_ChangeWeapon(player_t * p)468 void B_ChangeWeapon (player_t* p)
469 {
470     bot_t * pbot = p->bot;
471     boolean  usable_weapon[NUMWEAPONS]; // weapons with ammo
472     byte  num_weapons = 0;
473     byte  weaponChance;
474     byte  i;
475 
476     for (i=0; i<NUMWEAPONS; i++)
477     {
478         byte hw = false;
479         switch (i)
480         {
481          case wp_fist:
482             hw = false;//true;
483             break;
484          case wp_pistol:
485             hw = p->ammo[am_clip];
486             break;
487          case wp_shotgun:
488             hw = (p->weaponowned[i] && p->ammo[am_shell]);
489             break;
490          case wp_chaingun:
491             hw = (p->weaponowned[i] && p->ammo[am_clip]);
492             break;
493          case wp_missile:
494             hw = (p->weaponowned[i] && p->ammo[am_misl]);
495             break;
496          case wp_plasma:
497             hw = (p->weaponowned[i] && p->ammo[am_cell]);
498             break;
499          case wp_bfg:
500             hw = (p->weaponowned[i] && (p->ammo[am_cell] >= 40));
501             break;
502          case wp_chainsaw:
503             hw = p->weaponowned[i];
504             break;
505          case wp_supershotgun:
506             hw = (p->weaponowned[i] && (p->ammo[am_shell] >= 2));
507         }
508         usable_weapon[i] = hw;
509         if( hw ) // || ((i == wp_fist) && p->powers[pw_strength]))
510             num_weapons++;
511     }
512 
513     //or I have just picked up a new weapon
514     if( !pbot->weaponchangetimer || !usable_weapon[p->readyweapon]
515         || (num_weapons != pbot->lastNumWeapons))
516     {
517         if( (usable_weapon[wp_shotgun] && (p->readyweapon != wp_shotgun))
518             || (usable_weapon[wp_chaingun] && (p->readyweapon != wp_chaingun))
519             || (usable_weapon[wp_missile] && (p->readyweapon != wp_missile))
520             || (usable_weapon[wp_plasma] && (p->readyweapon != wp_plasma))
521             || (usable_weapon[wp_bfg] && (p->readyweapon != wp_bfg))
522             || (usable_weapon[wp_supershotgun] && (p->readyweapon != wp_supershotgun)))
523         {
524             p->cmd.buttons &= ~BT_ATTACK;	//stop rocket from jamming;
525             do
526             {
527                 weaponChance = B_Random();
528                 if( (weaponChance < 30) && usable_weapon[wp_shotgun]
529                      && (p->readyweapon != wp_shotgun))//has shotgun and shells
530                     p->cmd.buttons |= (BT_CHANGE | (wp_shotgun<<BT_WEAPONSHIFT));
531                 else if( (weaponChance < 80) && usable_weapon[wp_chaingun]
532                      && (p->readyweapon != wp_chaingun))//has chaingun and bullets
533                     p->cmd.buttons |= (BT_CHANGE | (wp_chaingun<<BT_WEAPONSHIFT));
534                 else if( (weaponChance < 130) && usable_weapon[wp_missile]
535                      && (p->readyweapon != wp_missile))//has rlauncher and rocket
536                     p->cmd.buttons |= (BT_CHANGE | (wp_missile<<BT_WEAPONSHIFT));
537                 else if( (weaponChance < 180) && usable_weapon[wp_plasma]
538                      && (p->readyweapon != wp_plasma))//has plasma and cells
539                     p->cmd.buttons |= (BT_CHANGE | (wp_plasma<<BT_WEAPONSHIFT));
540                 else if( (weaponChance < 200) && usable_weapon[wp_bfg]
541                      && (p->readyweapon != wp_bfg))//has bfg and cells
542                     p->cmd.buttons |= (BT_CHANGE | (wp_bfg<<BT_WEAPONSHIFT));
543                 else if( usable_weapon[wp_supershotgun]
544                      && (p->readyweapon != wp_supershotgun))
545                     p->cmd.buttons |= (BT_CHANGE | BT_EXTRAWEAPON | (wp_shotgun<<BT_WEAPONSHIFT));
546             } while (!(p->cmd.buttons & BT_CHANGE));
547         }
548         else if( usable_weapon[wp_pistol]
549                  && (p->readyweapon != wp_pistol))//has pistol and bullets
550             p->cmd.buttons |= (BT_CHANGE | wp_pistol<<BT_WEAPONSHIFT);
551         else if( p->weaponowned[wp_chainsaw] && !p->powers[pw_strength]
552                  && (p->readyweapon != wp_chainsaw))//has chainsaw, and not powered
553             p->cmd.buttons |= (BT_CHANGE | BT_EXTRAWEAPON | (wp_fist<<BT_WEAPONSHIFT));
554         else	//resort to fists, if have powered fists, better with fists then chainsaw
555             p->cmd.buttons |= (BT_CHANGE | wp_fist<<BT_WEAPONSHIFT);
556 
557         pbot->weaponchangetimer = (B_Random()<<7)+10000;	//how long until I next change my weapon
558     }
559     else if( pbot->weaponchangetimer)
560         pbot->weaponchangetimer--;
561 
562     if( num_weapons != pbot->lastNumWeapons)
563         p->cmd.buttons &= ~BT_ATTACK;	//stop rocket from jamming;
564     pbot->lastNumWeapons = num_weapons;
565 
566     //debug_Printf("pbot->weaponchangetimer is %d\n", pbot->weaponchangetimer);
567 }
568 
569 #define ANG5 (ANG90/18)
570 
571 // returns the difference between the angle mobj is facing,
572 // and the angle from mo to x,y
573 
574 #if 0
575 // Not Used
576 static
577 signed_angle_t  B_AngleDiff(mobj_t* mo, fixed_t x, fixed_t y)
578 {
579     return ((R_PointToAngle2 (mo->x, mo->y, x, y)) - mo->angle);
580 }
581 #endif
582 
583 static
B_TurnTowardsPoint(player_t * p,fixed_t x,fixed_t y)584 void B_TurnTowardsPoint(player_t* p, fixed_t x, fixed_t y)
585 {
586     angle_t  angle = R_PointToAngle2(p->mo->x, p->mo->y, x, y);
587     signed_angle_t  delta =  angle - p->mo->angle;
588     angle_t  abs_delta = abs(delta);
589 
590     if( abs_delta < ANG5 )
591     {
592         p->cmd.angleturn = angle>>FRACBITS;	//perfect aim
593     }
594     else
595     {
596         angle_t  turnspeed = botangleturn[ (abs_delta < (ANG45>>2))? 0 : 1 ];
597 
598         if( delta > 0)
599             p->cmd.angleturn += turnspeed;
600         else
601             p->cmd.angleturn -= turnspeed;
602     }
603 }
604 
605 // Turn away from a danger, or friend.
606 static
B_Turn_Away_Point(player_t * p,fixed_t x,fixed_t y)607 void B_Turn_Away_Point(player_t* p, fixed_t x, fixed_t y)
608 {
609     signed_angle_t  delta = R_PointToAngle2(p->mo->x, p->mo->y, x, y) - p->mo->angle;
610     angle_t  abs_delta = abs(delta);
611 
612     if( abs_delta < (ANG45*3) )
613     {
614         angle_t  turnspeed = botangleturn[ (abs_delta < ANG45)? 1 : 0 ];
615 
616         // No perfect aim
617         if( delta > 0 )
618             p->cmd.angleturn -= turnspeed;
619         else
620             p->cmd.angleturn += turnspeed;
621     }
622 }
623 
624 static
B_AimWeapon(player_t * p)625 void B_AimWeapon(player_t* p)
626 {
627     bot_t  * pbot = p->bot;
628     mobj_t * source = p->mo;
629     mobj_t * dest = pbot->closestEnemy;
630 
631     int  botspeed = 0;
632     int  mtime, t;
633     angle_t angle, perfect_angle;
634     signed_angle_t  delta;
635     angle_t  abs_delta;
636     byte botskill = pbot->skill;
637 
638     fixed_t  px, py, pz;
639     fixed_t  weapon_range;
640     fixed_t  dist, missile_speed;
641     subsector_t	*sec;
642     boolean  canHit;
643 
644     missile_speed = 0;  // default
645     switch (p->readyweapon)	// changed so bot projectiles don't lead targets at lower skills
646     {
647      case wp_fist: case wp_chainsaw:			//must be close to hit with these
648         missile_speed = 0;
649         weapon_range = 20<<FRACBITS;
650         break;
651      case wp_pistol: case wp_shotgun: case wp_chaingun:	//instant hit weapons, aim directly at enemy
652         missile_speed = 0;
653         weapon_range = 512<<FRACBITS;
654         break;
655      case wp_missile:
656         missile_speed = mobjinfo[MT_ROCKET].speed;
657         weapon_range = 1024<<FRACBITS;
658         break;
659      case wp_plasma:
660         missile_speed = mobjinfo[MT_PLASMA].speed;
661         weapon_range = 1024<<FRACBITS;
662         break;
663      case wp_bfg:
664         missile_speed = mobjinfo[MT_BFG].speed;
665         weapon_range = 1024<<FRACBITS;
666         break;
667      default:
668         missile_speed = 0;
669         weapon_range = 4096<<FRACBITS;
670         break;
671     }
672 
673     // botskill 0 to 5
674     if( botskill < 2 )
675     {
676         missile_speed = 0;  // no aim prediction
677     }
678     else if( botskill == 3 )
679     {
680         missile_speed *= 3;  // throw off aiming
681     }
682     else if( botskill == 4 )
683     {
684         missile_speed *= 2;  // throw off aiming
685     }
686 
687     dist = P_AproxDistance (dest->x - source->x, dest->y - source->y);
688 
689     if( (dest->type == MT_BARREL) || (dest->type == MT_POD) || (dest->flags & MF_TOUCHY) )
690     {
691         // [WDJ] Do not attack exploding things with fists.
692         if( weapon_range < (100<<FRACBITS))  goto reject_enemy;
693         if( dist < (100<<FRACBITS))  goto reject_enemy;  // too close, must get distance first.
694     }
695 
696     if( dist > weapon_range )
697     {
698         if( B_Random() < 240 )  return;  // wait till in range, most of the time
699     }
700 
701     if( (p->readyweapon != wp_missile) || (dist > (100<<FRACBITS)))
702     {
703         if( missile_speed)
704         {
705             mtime = dist/missile_speed;
706             mtime = P_AproxDistance ( dest->x + dest->momx*mtime - source->x,
707                                       dest->y + dest->momy*mtime - source->y)
708                             / missile_speed;
709 
710             t = mtime + 4;
711             do
712             {
713                 t-=4;
714                 if( t < 0)
715                     t = 0;
716                 px = dest->x + dest->momx*t;
717                 py = dest->y + dest->momy*t;
718                 pz = dest->z + dest->momz*t;
719                 canHit = P_CheckSight2(source, dest, px, py, pz);
720             } while (!canHit && (t > 0));
721 
722             sec = R_PointInSubsector(px, py);
723             if( !sec)
724                 sec = dest->subsector;
725 
726             if( pz < sec->sector->floorheight)
727                 pz = sec->sector->floorheight;
728             else if( pz > sec->sector->ceilingheight)
729                 pz = sec->sector->ceilingheight - dest->height;
730         }
731         else
732         {
733             px = dest->x;
734             py = dest->y;
735             pz = dest->z;
736         }
737 
738         perfect_angle = angle = R_PointToAngle2 (source->x, source->y, px, py);
739         p->cmd.aiming = ((int)((atan ((pz - source->z + (dest->height - source->height)/2) / (double)dist)) * ANG180/M_PI))>>FRACBITS;
740 
741         // Random aiming imperfections.
742         if( (P_AproxDistance(dest->momx, dest->momy)>>FRACBITS) > 8)	//enemy is moving reasonably fast, so not perfectly acurate
743         {
744             if( dest->flags & MF_SHADOW)
745                 angle += P_SignedRandom()<<23;
746             else if( missile_speed == 0 )
747                 angle += P_SignedRandom()<<22;
748         }
749         else
750         {
751             if( dest->flags & MF_SHADOW)
752                 angle += P_SignedRandom()<<22;
753             else if( missile_speed == 0 )
754                 angle += P_SignedRandom()<<21;
755         }
756 
757         delta = angle - source->angle;
758         abs_delta = abs(delta);
759 
760         if( abs_delta < ANG45 )
761         {
762             // Fire weapon when aim is best.
763             if( abs_delta <= ANG5 )
764             {
765                 // cmd.angleturn is 16 bit angle (angle is not fixed_t)
766                 // lower skill levels have imperfect aim
767                 p->cmd.angleturn = (( botskill < 4 )?  // < hard
768                       angle  // not so perfect aim
769                     : perfect_angle // perfect aim
770                     ) >>16;  // 32 bit to 16 bit angle
771                 p->cmd.buttons |= BT_ATTACK;
772                 return;
773             }
774 
775             // Fire some weapons when aim is just close.
776             if( (p->readyweapon == wp_chaingun) || (p->readyweapon == wp_plasma)
777                 || (p->readyweapon == wp_pistol))
778                  p->cmd.buttons |= BT_ATTACK;
779         }
780 
781         // Still turning to face target.
782         botspeed = ( abs_delta < (ANG45>>1) )? 0
783            : ( abs_delta < ANG45 )? 1
784            : 3;
785 
786         if( delta > 0)
787             p->cmd.angleturn += botangleturn[botspeed];	//turn right
788         else if( delta < 0)
789             p->cmd.angleturn -= botangleturn[botspeed]; //turn left
790     }
791     return;
792 
793 reject_enemy:
794     pbot->closestEnemy = NULL;
795     return;
796 }
797 
798 //
799 // MAIN BOT AI
800 //
801 
802 static fixed_t bot_strafe_dist[6] = {
803    (20<<FRACBITS),  // crippled
804    (32<<FRACBITS),  // baby
805    (150<<FRACBITS), // easy
806    (150<<FRACBITS), // medium
807    (350<<FRACBITS), // hard
808    (650<<FRACBITS)  // nightmare
809 };
810 
B_BuildTiccmd(player_t * p,ticcmd_t * netcmd)811 void B_BuildTiccmd(player_t* p, ticcmd_t* netcmd)
812 {
813     mobj_t * pmo = p->mo;
814     bot_t  * pbot = p->bot;
815     ticcmd_t*  cmd = &p->cmd;
816 
817     int  x, y;
818     fixed_t  cmomx, cmomy;  //what the extra momentum added from this tick will be
819     fixed_t  px, py;  //coord of where I will be next tick
820     fixed_t  forwardmove = 0, sidemove = 0;
821     int      forward_angf, side_angf;
822     fixed_t  target_dist;  //how far away is my enemy, wanted thing
823     byte  botspeed = 1;
824     boolean  blocked, notUsed = true;
825 
826     //needed so bot doesn't hold down use before reaching switch object
827     if( cmd->buttons & BT_USE)
828         notUsed = false;    //wouldn't be able to use switch
829 
830     memset (cmd,0,sizeof(*cmd));
831 
832 
833     // Exit now if locked
834     if( p->locked == true)
835         return;
836 
837     if( p->playerstate == PST_LIVE)
838     {
839         cmd->angleturn = pmo->angle>>16;  // 32 bit angle to 16 bit angle
840         cmd->aiming = 0;//p->aiming>>16;
841 
842         B_LookForThings(p);
843         B_ChangeWeapon(p);
844 
845         if( pbot->avoidtimer)
846         {
847             pbot->avoidtimer--;
848             if( pmo->eflags & MF_UNDERWATER)
849             {
850                 forwardmove = botforwardmove[1];
851                 cmd->buttons |= BT_JUMP;
852             }
853             else
854             {
855                 if( netcmd->forwardmove > 0)
856                     forwardmove = -botforwardmove[1];
857                 else
858                     forwardmove = botforwardmove[1];
859                 sidemove = botsidemove[1];
860             }
861         }
862         else
863         {
864             if( pbot->bestSeenItem )
865             {
866                 // Move towards the item.
867                 target_dist = P_AproxDistance (pmo->x - pbot->bestSeenItem->x, pmo->y - pbot->bestSeenItem->y);
868                 botspeed = (target_dist > (64<<FRACBITS))? 1 : 0;
869                 B_TurnTowardsPoint(p, pbot->bestSeenItem->x, pbot->bestSeenItem->y);
870                 forwardmove = botforwardmove[botspeed];
871                 if( (((pbot->bestSeenItem->floorz - pmo->z)>>FRACBITS) > 24)
872                     && (target_dist <= (100<<FRACBITS)))
873                     cmd->buttons |= BT_JUMP;
874 
875                 pbot->bestItem = NULL;
876             }
877             else if( pbot->closestEnemy && (pbot->closestEnemy->health > 0))
878             {
879                 // Target exists and is still alive.
880                 // Prepare to attack the enemy.
881                 player_t * enemyp = pbot->closestEnemy->player;
882                 weapontype_t  enemy_readyweapon =
883                  ( enemyp )? enemyp->readyweapon
884                  : wp_nochange; // does not match anything
885                 boolean  enemy_linescan_weapon =
886                    (enemy_readyweapon == wp_pistol)
887                    || (enemy_readyweapon == wp_shotgun)
888                    || (enemy_readyweapon == wp_chaingun);
889 
890                 //debug_Printf("heading for an enemy\n");
891                 target_dist = P_AproxDistance (pmo->x - pbot->closestEnemy->x, pmo->y - pbot->closestEnemy->y);
892                 if( (target_dist > (300<<FRACBITS))
893                     || (p->readyweapon == wp_fist)
894                     || (p->readyweapon == wp_chainsaw))
895                     forwardmove = botforwardmove[botspeed];
896                 if( (p->readyweapon == wp_missile) && (target_dist < (400<<FRACBITS)))
897                     forwardmove = -botforwardmove[botspeed];
898 
899                 // bot skill setting determines likelyhood bot will start strafing
900                 if(( target_dist <= bot_strafe_dist[ pbot->skill ])
901                     || ( enemy_linescan_weapon && (pbot->skill >= 3) ))  // >= medium skill
902                 {
903                     sidemove = botsidemove[botspeed];
904                 }
905 
906                 B_AimWeapon(p);
907 
908                 if( pbot->closestEnemy )
909                 {
910                     pbot->lastMobj = pbot->closestEnemy;
911                     pbot->lastMobjX = pbot->closestEnemy->x;
912                     pbot->lastMobjY = pbot->closestEnemy->y;
913                 }
914             }
915             else
916             {
917                 cmd->aiming = 0;
918                 //look for an unactivated switch/door
919                 if( (B_Random() > 190)  // not every time, so it does not obsess
920                     && B_LookForSpecialLine(p, &x, &y)
921                     && B_ReachablePoint(p, pmo->subsector->sector, x, y))
922                 {
923                     //debug_Printf("found a special line\n");
924                     B_TurnTowardsPoint(p, x, y);
925                     if( P_AproxDistance (pmo->x - x, pmo->y - y) <= USERANGE)
926                     {
927                         if( notUsed )
928                             cmd->buttons |= BT_USE;
929                     }
930                     else
931                         forwardmove = botforwardmove[1];
932                 }
933                 else if( pbot->teammate)
934                 {
935                     mobj_t * tmate = pbot->teammate;
936                     target_dist =
937                         P_AproxDistance (pmo->x - tmate->x, pmo->y - tmate->y);
938 
939                     // [WDJ]: Like MBF, Move away from friends when too close, except
940                     // in certain situations.
941 
942                     // assume BOTH_FRIEND( p, tmate )
943                     if( target_dist < EV_mbf_distfriend )
944                     {
945                         // Allowed to bump bot away, even in crusher.
946                         if(( !P_IsOnLift( tmate )
947                               && !P_IsUnderDamage( pmo ) )
948                             || (target_dist <= (pmo->info->radius + tmate->info->radius + (FRACUNIT*35/16))) )  // bump
949                         {
950                             B_Turn_Away_Point(p, tmate->x, tmate->y);
951                             forwardmove = botforwardmove[0];
952                         }
953                     }
954                     else if( target_dist > (EV_mbf_distfriend + (8<<FRACBITS)) )
955                     {
956                         B_TurnTowardsPoint(p, tmate->x, tmate->y);
957                         forwardmove = botforwardmove[botspeed];
958                     }
959 
960                     pbot->lastMobj = tmate;
961                     pbot->lastMobjX = tmate->x;
962                     pbot->lastMobjY = tmate->y;
963                 }
964                 //since nothing else to do, go where last enemy/teammate was seen
965                 else if( pbot->lastMobj && (pbot->lastMobj->health > 0))
966                   // && B_ReachablePoint(p, R_PointInSubsector(pbot->lastMobjX, pbot->lastMobjY)->sector, pbot->lastMobjX, pbot->lastMobjY))
967                 {
968                     if( (pmo->momx == 0 && pmo->momy == 0)
969                         || !B_NodeReachable(NULL, pmo->x, pmo->y, pbot->lastMobjX, pbot->lastMobjY))
970                         pbot->lastMobj = NULL;	//just went through teleporter
971                     else
972                     {
973                         //debug_Printf("heading towards last mobj\n");
974                         B_TurnTowardsPoint(p, pbot->lastMobjX, pbot->lastMobjY);
975                         forwardmove = botforwardmove[botspeed];
976                     }
977                 }
978                 else
979                 {
980                     byte br = B_Random();
981                     pbot->lastMobj = NULL;
982 
983                     if( pbot->bestItem && (br & 0x01) )  // do not obsess if cannot get to it
984                     {
985                         SearchNode_t* temp =
986                             B_GetNodeAt(pbot->bestItem->x, pbot->bestItem->y);
987                         //debug_Printf("found a best item at x:%d, y:%d\n", pbot->bestItem->x>>FRACBITS, pbot->bestItem->y>>FRACBITS);
988                         if( pbot->destNode != temp)
989                             B_LLClear(pbot->path);
990                         pbot->destNode = temp;
991                     }
992                     else if( pbot->closestUnseenTeammate && (br & 0x02) )  // do not obsess if cannot get to it
993                     {
994                         SearchNode_t* temp =
995                             B_GetNodeAt(pbot->closestUnseenTeammate->x, pbot->closestUnseenTeammate->y);
996                         if( pbot->destNode != temp)
997                             B_LLClear(pbot->path);
998                         pbot->destNode = temp;
999                     }
1000                     else if( pbot->closestUnseenEnemy && (br & 0x04) )  // do not obsess if cannot get to it
1001                     {
1002                         SearchNode_t* temp =
1003                             B_GetNodeAt(pbot->closestUnseenEnemy->x, pbot->closestUnseenEnemy->y);
1004                         if( pbot->destNode != temp)
1005                             B_LLClear(pbot->path);
1006                         pbot->destNode = temp;
1007                     }
1008                     else
1009                         pbot->destNode = NULL;
1010 
1011                     if( pbot->destNode)
1012                     {
1013                         if( !B_LLIsEmpty(pbot->path)
1014                             && P_AproxDistance(pmo->x - posX2x(pbot->path->first->x), pmo->y - posY2y(pbot->path->first->y)) < (BOTNODEGRIDSIZE<<1))//BOTNODEGRIDSIZE>>1))
1015                         {
1016 #ifdef SHOWBOTPATH
1017                             SearchNode_t* temp = B_LLRemoveFirstNode(pbot->path);
1018                             P_RemoveMobj(temp->mo);
1019                             Z_Free(temp);
1020 #else
1021                             Z_Free(B_LLRemoveFirstNode(pbot->path));
1022 #endif
1023                         }
1024 
1025 
1026                         //debug_Printf("at x%d, y%d\n", pbot->wantedItemNode->x>>FRACBITS, pbot->wantedItemNode->y>>FRACBITS);
1027                         if( B_LLIsEmpty(pbot->path)
1028                             || !B_NodeReachable(NULL, pmo->x, pmo->y,
1029                                                 posX2x(pbot->path->first->x), posY2y(pbot->path->first->y) )
1030                                // > (BOTNODEGRIDSIZE<<2))
1031                             )
1032                         {
1033                             if( !B_FindNextNode(p))	//search for next node
1034                             {
1035                                 //debug_Printf("Bot stuck at x:%d y:%d could not find a path to x:%d y:%d\n",pmo->x>>FRACBITS, pmo->y>>FRACBITS, posX2x(pbot->destNode->x)>>FRACBITS, posY2y(pbot->destNode->y)>>FRACBITS);
1036 
1037                                 pbot->destNode = NULL;	//can't get to it
1038                             }
1039                         }
1040 
1041                         if( !B_LLIsEmpty(pbot->path))
1042                         {
1043                             //debug_Printf("turning towards node at x%d, y%d\n", (pbot->nextItemNode->x>>FRACBITS), (pbot->nextItemNode->y>>FRACBITS));
1044                             //debug_Printf("it has a distance %d\n", (P_AproxDistance(pmo->x - pbot->nextItemNode->x, pmo->y - pbot->nextItemNode->y)>>FRACBITS));
1045                             B_TurnTowardsPoint(p, posX2x(pbot->path->first->x), posY2y(pbot->path->first->y));
1046                             forwardmove = botforwardmove[1];//botspeed];
1047                         }
1048                     }
1049                 }
1050             }
1051 
1052             // proportional forward and side movement
1053             forward_angf = ANGLE_TO_FINE(pmo->angle);
1054             side_angf = ANGLE_TO_FINE(pmo->angle - ANG90);
1055             cmomx = FixedMul(forwardmove*2048, finecosine[forward_angf]) + FixedMul(sidemove*2048, finecosine[side_angf]);
1056             cmomy = FixedMul(forwardmove*2048, finesine[forward_angf]) + FixedMul(sidemove*2048, finesine[side_angf]);
1057             px = pmo->x + pmo->momx + cmomx;
1058             py = pmo->y + pmo->momy + cmomy;
1059 
1060             // tmr_floorz, tmr_ceilingz returned by P_CheckPosition
1061             blocked = !P_CheckPosition (pmo, px, py)
1062                  || (((tmr_floorz - pmo->z)>>FRACBITS) > 24)
1063                  || ((tmr_ceilingz - tmr_floorz) < pmo->height);
1064 
1065             //if its time to change strafe directions,
1066             if( sidemove && ((pmo->flags & MF_JUSTHIT) || blocked))
1067             {
1068                 pbot->straferight = !pbot->straferight;
1069                 pmo->flags &= ~MF_JUSTHIT;
1070             }
1071 
1072             if( blocked)
1073             {
1074                 // tm_thing is global var of P_CheckPosition
1075                 if( (++pbot->blockedcount > 20)
1076                     && ((P_AproxDistance(pmo->momx, pmo->momy) < (4<<FRACBITS))
1077                         || (tm_thing && (tm_thing->flags & MF_SOLID)))
1078                     )
1079                     pbot->avoidtimer = 20;
1080 
1081                 if( (((tmr_floorz - pmo->z)>>FRACBITS) > 24)
1082                     && ((((tmr_floorz - pmo->z)>>FRACBITS) <= 37)
1083                         || ((((tmr_floorz - pmo->z)>>FRACBITS) <= 45)
1084                             && (pmo->subsector->sector->floortype != FLOOR_WATER))))
1085                     cmd->buttons |= BT_JUMP;
1086 
1087                 for (x=0; x<numspechit; x++)
1088                 {
1089                     if( lines[spechit[x]].backsector)
1090                     {
1091                         if( !lines[spechit[x]].backsector->ceilingdata && !lines[spechit[x]].backsector->floordata && (lines[spechit[x]].special != 11))	//not the exit switch
1092                             cmd->buttons |= BT_USE;
1093                     }
1094                 }
1095             }
1096             else
1097                 pbot->blockedcount = 0;
1098         }
1099 
1100         if( sidemove )
1101         {
1102             if( pbot->strafetimer )
1103                 pbot->strafetimer--;
1104             else
1105             {
1106                 pbot->straferight = !pbot->straferight;
1107                 pbot->strafetimer = B_Random()/3;
1108             }
1109         }
1110         if( pbot->weaponchangetimer)
1111             pbot->weaponchangetimer--;
1112 
1113         if( cv_bot_speed.EV == 10 )  // botskill dependent speed
1114         {
1115             byte spd = bot_botskill_to_speed[ pbot->skill ];
1116             bot_speed_frac = bot_speed_frac_table[ spd ];
1117             bot_run_tics = bot_run_tics_table[ spd ];
1118         }
1119 
1120         // [WDJ] Limit the running.
1121         if( abs(forwardmove) > botforwardmove[0] )
1122         {
1123             // Running
1124             if( pbot->runtimer < bot_run_tics )
1125             {
1126                 pbot->runtimer++;
1127                 goto cmd_move;
1128             }
1129 
1130             // Tired, must walk.
1131             forwardmove = forwardmove>>1;  // walk
1132             if( pbot->runtimer == bot_run_tics )
1133             {
1134                 pbot->runtimer += 10 * TICRATE;  // rest time
1135                 goto cmd_move;
1136             }
1137         }
1138 
1139         if( pbot->runtimer > 0 )
1140         {
1141             pbot->runtimer--;
1142             // Run time needs to be proportional to bot_run_tics,
1143             // so reset timer to constant value.
1144             if( pbot->runtimer == bot_run_tics )
1145            {
1146                 pbot->runtimer = (4 * TICRATE) - 2;  // reset hysterisis
1147            }
1148         }
1149 
1150     cmd_move:
1151         // [WDJ] Regulate the run speed.
1152         p->cmd.forwardmove = (forwardmove * bot_speed_frac) >> 7;
1153         p->cmd.sidemove = pbot->straferight ? sidemove : -sidemove;
1154         if( pbot->closestMissile )
1155             B_AvoidMissile(p, pbot->closestMissile);
1156     }
1157     else
1158     {
1159         // Dead
1160 #ifdef BOT_VERSION_DETECT
1161         if( demoversion < 146 )
1162         {
1163             cmd->buttons |= BT_USE;	//I want to respawn
1164         }
1165         else
1166 #endif
1167         {
1168             // Version 1.46
1169             // [WDJ] Slow down bot respawn, so they are not so overwhelming.
1170             cmd->buttons = 0;
1171             if( p->damagecount )
1172             {
1173                 p->damagecount = 0;
1174                 pbot->avoidtimer = cv_bot_respawn_time.EV * TICRATE; // wait
1175             }
1176             if( --pbot->avoidtimer <= 0 )
1177                 cmd->buttons |= BT_USE;	//I want to respawn
1178         }
1179     }
1180 
1181     memcpy (netcmd, cmd, sizeof(*cmd));
1182 } // end of BOT_Thinker
1183 
1184 
1185 // Forget what cannot be saved.  To sync client and server bots.
B_forget_stuff(bot_t * bot)1186 void  B_forget_stuff( bot_t * bot )
1187 {
1188     bot->destNode = NULL;
1189     B_LLClear( bot->path );
1190 }
1191 
B_Destroy_Bot(player_t * player)1192 void  B_Destroy_Bot( player_t * player )
1193 {
1194     bot_t * bot = player->bot;
1195 
1196     if( bot )
1197     {
1198         B_LLClear( bot->path );
1199 
1200         Z_Free( bot );
1201 
1202         player->bot = NULL;
1203     }
1204 }
1205 
B_Create_Bot(player_t * player)1206 void  B_Create_Bot( player_t * player )
1207 {
1208     bot_t * bot = player->bot;
1209     if( bot )
1210     {
1211         B_LLClear( bot->path );
1212         B_LLDelete( bot->path );
1213     }
1214     else
1215     {
1216         bot = Z_Malloc (sizeof(*bot), PU_STATIC, 0);
1217         player->bot = bot;
1218     }
1219     memset( bot, 0, sizeof(bot_t));
1220     bot->path = B_LLCreate();
1221 }
1222 
1223 
B_SpawnBot(bot_t * bot)1224 void B_SpawnBot(bot_t* bot)
1225 {
1226     byte sk;
1227 
1228     bot->avoidtimer = 0;
1229     bot->blockedcount = 0;
1230     bot->weaponchangetimer = 0;
1231     bot->runtimer = 0;
1232 
1233     bot->bestItem = NULL;
1234     bot->lastMobj = NULL;
1235     bot->destNode = NULL;
1236 
1237     // [WDJ] Bot skill = 0..5, Game skill = 0..4.
1238     switch( cv_bot_skill.EV )
1239     {
1240       case 6: // randmed
1241          sk = E_Random() % gameskill;  // random bots, 0 to gameskill-1
1242          break;
1243       case 7: // randgame
1244          sk = (E_Random() % gameskill) + 1;  // random bots, 1 to gameskill
1245          break;
1246       case 8: // gamemed
1247          sk = gameskill;  // bot lower skill levels
1248          break;
1249       case 9: // gameskill
1250          sk = gameskill + 1;  // bot upper skill levels
1251          break;
1252       default:  // fixed bot skill selection
1253          sk = cv_bot_skill.EV;  // 0..5
1254          break;
1255     }
1256     bot->skill = sk; // 0=crippled, 1=baby .. 5=nightmare
1257 
1258     B_LLClear(bot->path);
1259 }
1260