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