1 /*******************************************
2 * B_game.h *
3 * Description: *
4 * Misc things that has to do with the bot, *
5 * like it's spawning etc. *
6 * Makes the bot fit into game *
7 * *
8 *******************************************/
9 /*The files which are modified for Cajun Purpose
10 D_player.h (v0.85: added some variables)
11 D_netcmd.c (v0.71)
12 D_netcmd.h (v0.71)
13 D_main.c (v0.71)
14 D_ticcmd.h (v0.71)
15 G_game.c (v0.95: Too make demorecording work somewhat)
16 G_input.c (v0.95: added some keycommands)
17 G_input.h (v0.95)
18 P_mobj.c (v0.95: changed much in the P_MobjThinker(), a little in P_SpawnPlayerMissile(), maybee something else )
19 P_mobj.h (v0.95: Removed some unnecessary variables)
20 P_user.c (v0.95: It's only one change maybee it already was there in 0.71)
21 P_inter.c (v0.95: lot of changes)
22 P_pspr.c (v0.71)
23 P_map.c (v0.95: Test missile for bots)
24 P_tick.c (v0.95: Freeze mode things only)
25 P_local.h (v0.95: added> extern int tmsectortype)
26 Info.c (v0.95: maybee same as 0.71)
27 Info.h (v0.95: maybee same as 0.71)
28 M_menu.c (v0.95: an extra menu in the key setup with the new commands)
29 R_main.c (v0.95: Fix for bot's view)
30 wi_stuff.c (v0.97: To remove bots correct)
31
32 (v0.85) Removed all my code from: P_enemy.c
33 New file: b_move.c
34
35 ******************************************
36 What I know has to be done. in near future.
37
38 - Do some hunting/fleeing functions.
39 - Make the roaming 100% flawfree.
40 - Fix all SIGSEVS (Below is known SIGSEVS)
41 -Nada (but they might be there)
42 ******************************************
43 Everything that is changed is marked (maybe commented) with "Added by MC"
44 */
45
46 #include "doomdef.h"
47 #include "p_local.h"
48 #include "b_bot.h"
49 #include "g_game.h"
50 #include "m_random.h"
51 #include "doomstat.h"
52 #include "cmdlib.h"
53 #include "sc_man.h"
54 #include "stats.h"
55 #include "m_misc.h"
56 #include "sbar.h"
57 #include "p_acs.h"
58 #include "teaminfo.h"
59 #include "i_system.h"
60 #include "d_net.h"
61 #include "d_netinf.h"
62
63 static FRandom pr_botspawn ("BotSpawn");
64
65 //Externs
66 FCajunMaster bglobal;
67
68 cycle_t BotThinkCycles, BotSupportCycles;
69 int BotWTG;
70
71 static const char *BotConfigStrings[] =
72 {
73 "name",
74 "aiming",
75 "perfection",
76 "reaction",
77 "isp",
78 "team",
79 NULL
80 };
81
82 enum
83 {
84 BOTCFG_NAME,
85 BOTCFG_AIMING,
86 BOTCFG_PERFECTION,
87 BOTCFG_REACTION,
88 BOTCFG_ISP,
89 BOTCFG_TEAM
90 };
91
~FCajunMaster()92 FCajunMaster::~FCajunMaster()
93 {
94 ForgetBots();
95 }
96
97 //This function is called every tick (from g_game.c).
Main()98 void FCajunMaster::Main ()
99 {
100 BotThinkCycles.Reset();
101
102 if (demoplayback || gamestate != GS_LEVEL || consoleplayer != Net_Arbitrator)
103 return;
104
105 //Add new bots?
106 if (wanted_botnum > botnum && !freeze)
107 {
108 if (t_join == ((wanted_botnum - botnum) * SPAWN_DELAY))
109 {
110 if (!SpawnBot (getspawned[spawn_tries]))
111 wanted_botnum--;
112 spawn_tries++;
113 }
114
115 t_join--;
116 }
117
118 //Check if player should go observer. Or un observe
119 if (bot_observer && !observer && !netgame)
120 {
121 Printf ("%s is now observer\n", players[consoleplayer].userinfo.GetName());
122 observer = true;
123 players[consoleplayer].mo->UnlinkFromWorld ();
124 players[consoleplayer].mo->flags = MF_DROPOFF|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOTDMATCH|MF_NOGRAVITY|MF_FRIENDLY;
125 players[consoleplayer].mo->flags2 |= MF2_FLY;
126 players[consoleplayer].mo->LinkToWorld ();
127 }
128 else if (!bot_observer && observer && !netgame) //Go back
129 {
130 Printf ("%s returned to the fray\n", players[consoleplayer].userinfo.GetName());
131 observer = false;
132 players[consoleplayer].mo->UnlinkFromWorld ();
133 players[consoleplayer].mo->flags = MF_SOLID|MF_SHOOTABLE|MF_DROPOFF|MF_PICKUP|MF_NOTDMATCH|MF_FRIENDLY;
134 players[consoleplayer].mo->flags2 &= ~MF2_FLY;
135 players[consoleplayer].mo->LinkToWorld ();
136 }
137 }
138
Init()139 void FCajunMaster::Init ()
140 {
141 botnum = 0;
142 firstthing = NULL;
143 spawn_tries = 0;
144 freeze = false;
145 observer = false;
146 body1 = NULL;
147 body2 = NULL;
148
149 if (ctf && teamplay == false)
150 teamplay = true; //Need teamplay for ctf. (which is not done yet)
151
152 t_join = (wanted_botnum + 1) * SPAWN_DELAY; //The + is to let player get away before the bots come in.
153
154 if (botinfo == NULL)
155 {
156 LoadBots ();
157 }
158 else
159 {
160 botinfo_t *thebot = botinfo;
161
162 while (thebot != NULL)
163 {
164 thebot->inuse = BOTINUSE_No;
165 thebot = thebot->next;
166 }
167 }
168 }
169
170 //Called on each level exit (from g_game.c).
End()171 void FCajunMaster::End ()
172 {
173 int i;
174
175 //Arrange wanted botnum and their names, so they can be spawned next level.
176 getspawned.Clear();
177 if (deathmatch)
178 {
179 for (i = 0; i < MAXPLAYERS; i++)
180 {
181 if (players[i].Bot != NULL)
182 {
183 getspawned.Push(players[i].userinfo.GetName());
184 }
185 }
186
187 wanted_botnum = botnum;
188 }
189 }
190
191
192
193 //Name can be optional, if = NULL
194 //then a random bot is spawned.
195 //If no bot with name = name found
196 //the function will CONS print an
197 //error message and will not spawn
198 //anything.
199 //The color parameter can be either a
200 //color (range from 0-10), or = NOCOLOR.
201 //The color parameter overides bots
202 //individual colors if not = NOCOLOR.
203
SpawnBot(const char * name,int color)204 bool FCajunMaster::SpawnBot (const char *name, int color)
205 {
206 //COLORS
207 static const char colors[11][17] =
208 {
209 "\\color\\40 cf 00", //0 = Green
210 "\\color\\b0 b0 b0", //1 = Gray
211 "\\color\\50 50 60", //2 = Indigo
212 "\\color\\8f 00 00", //3 = Deep Red
213 "\\color\\ff ff ff", //4 = White
214 "\\color\\ff af 3f", //5 = Bright Brown
215 "\\color\\bf 00 00", //6 = Red
216 "\\color\\00 00 ff", //7 = Blue
217 "\\color\\00 00 7f", //8 = Dark Blue
218 "\\color\\ff ff 00", //9 = Yellow
219 "\\color\\cf df 90" //10 = Bleached Bone
220 };
221
222 botinfo_t *thebot = botinfo;
223 int botshift = 0;
224
225 if (name)
226 {
227 // Check if exist or already in the game.
228 while (thebot && stricmp (name, thebot->name))
229 {
230 botshift++;
231 thebot = thebot->next;
232 }
233
234 if (thebot == NULL)
235 {
236 Printf ("couldn't find %s in %s\n", name, BOTFILENAME);
237 return false;
238 }
239 else if (thebot->inuse == BOTINUSE_Waiting)
240 {
241 return false;
242 }
243 else if (thebot->inuse == BOTINUSE_Yes)
244 {
245 Printf ("%s is already in the thick\n", name);
246 return false;
247 }
248 }
249 else
250 {
251 //Spawn a random bot from bots.cfg if no name given.
252 TArray<botinfo_t *> BotInfoAvailable;
253
254 while (thebot)
255 {
256 if (thebot->inuse == BOTINUSE_No)
257 BotInfoAvailable.Push (thebot);
258
259 thebot = thebot->next;
260 }
261
262 if (BotInfoAvailable.Size () == 0)
263 {
264 Printf ("Couldn't spawn bot; no bot left in %s\n", BOTFILENAME);
265 return false;
266 }
267
268 thebot = BotInfoAvailable[pr_botspawn() % BotInfoAvailable.Size ()];
269
270 botinfo_t *thebot2 = botinfo;
271 while (thebot2)
272 {
273 if (thebot == thebot2)
274 break;
275
276 botshift++;
277 thebot2 = thebot2->next;
278 }
279 }
280
281 thebot->inuse = BOTINUSE_Waiting;
282
283 Net_WriteByte (DEM_ADDBOT);
284 Net_WriteByte (botshift);
285 {
286 //Set color.
287 char concat[512];
288 strcpy (concat, thebot->info);
289 if (color == NOCOLOR && bot_next_color < NOCOLOR && bot_next_color >= 0)
290 {
291 strcat (concat, colors[bot_next_color]);
292 }
293 if (TeamLibrary.IsValidTeam (thebot->lastteam))
294 { // Keep the bot on the same team when switching levels
295 mysnprintf (concat + strlen(concat), countof(concat) - strlen(concat),
296 "\\team\\%d\n", thebot->lastteam);
297 }
298 Net_WriteString (concat);
299 }
300 Net_WriteByte(thebot->skill.aiming);
301 Net_WriteByte(thebot->skill.perfection);
302 Net_WriteByte(thebot->skill.reaction);
303 Net_WriteByte(thebot->skill.isp);
304
305 return true;
306 }
307
TryAddBot(BYTE ** stream,int player)308 void FCajunMaster::TryAddBot (BYTE **stream, int player)
309 {
310 int botshift = ReadByte (stream);
311 char *info = ReadString (stream);
312 botskill_t skill;
313 skill.aiming = ReadByte (stream);
314 skill.perfection = ReadByte (stream);
315 skill.reaction = ReadByte (stream);
316 skill.isp = ReadByte (stream);
317
318 botinfo_t *thebot = NULL;
319
320 if (consoleplayer == player)
321 {
322 thebot = botinfo;
323
324 while (botshift > 0)
325 {
326 thebot = thebot->next;
327 botshift--;
328 }
329 }
330
331 if (DoAddBot ((BYTE *)info, skill))
332 {
333 //Increment this.
334 botnum++;
335
336 if (thebot != NULL)
337 {
338 thebot->inuse = BOTINUSE_Yes;
339 }
340 }
341 else
342 {
343 if (thebot != NULL)
344 {
345 thebot->inuse = BOTINUSE_No;
346 }
347 }
348
349 delete[] info;
350 }
351
DoAddBot(BYTE * info,botskill_t skill)352 bool FCajunMaster::DoAddBot (BYTE *info, botskill_t skill)
353 {
354 int bnum;
355
356 for (bnum = 0; bnum < MAXPLAYERS; bnum++)
357 {
358 if (!playeringame[bnum])
359 {
360 break;
361 }
362 }
363
364 if (bnum == MAXPLAYERS)
365 {
366 Printf ("The maximum of %d players/bots has been reached\n", MAXPLAYERS);
367 return false;
368 }
369
370 D_ReadUserInfoStrings (bnum, &info, false);
371
372 multiplayer = true; //Prevents cheating and so on; emulates real netgame (almost).
373 players[bnum].Bot = new DBot;
374 players[bnum].Bot->player = &players[bnum];
375 players[bnum].Bot->skill = skill;
376 playeringame[bnum] = true;
377 players[bnum].mo = NULL;
378 players[bnum].playerstate = PST_ENTER;
379
380 if (teamplay)
381 Printf ("%s joined the %s team\n", players[bnum].userinfo.GetName(), Teams[players[bnum].userinfo.GetTeam()].GetName());
382 else
383 Printf ("%s joined the game\n", players[bnum].userinfo.GetName());
384
385 G_DoReborn (bnum, true);
386 if (StatusBar != NULL)
387 {
388 StatusBar->MultiplayerChanged ();
389 }
390
391 return true;
392 }
393
RemoveAllBots(bool fromlist)394 void FCajunMaster::RemoveAllBots (bool fromlist)
395 {
396 int i, j;
397
398 for (i = 0; i < MAXPLAYERS; ++i)
399 {
400 if (players[i].Bot != NULL)
401 {
402 // If a player is looking through this bot's eyes, make him
403 // look through his own eyes instead.
404 for (j = 0; j < MAXPLAYERS; ++j)
405 {
406 if (i != j && playeringame[j] && players[j].Bot == NULL)
407 {
408 if (players[j].camera == players[i].mo)
409 {
410 players[j].camera = players[j].mo;
411 if (j == consoleplayer)
412 {
413 StatusBar->AttachToPlayer (players + j);
414 }
415 }
416 }
417 }
418 FBehavior::StaticStartTypedScripts (SCRIPT_Disconnect, players[i].mo, true, i, true);
419 ClearPlayer (i, !fromlist);
420 }
421 }
422
423 if (fromlist)
424 {
425 wanted_botnum = 0;
426 }
427 botnum = 0;
428 }
429
430
431 //------------------
432 //Reads data for bot from
433 //a .bot file.
434 //The skills and other data should
435 //be arranged as follows in the bot file:
436 //
437 //{
438 // Name bot's name
439 // Aiming 0-100
440 // Perfection 0-100
441 // Reaction 0-100
442 // Isp 0-100 (Instincts of Self Preservation)
443 // ??? any other valid userinfo strings can go here
444 //}
445
appendinfo(char * & front,const char * back)446 static void appendinfo (char *&front, const char *back)
447 {
448 char *newstr;
449
450 if (front)
451 {
452 size_t newlen = strlen (front) + strlen (back) + 2;
453 newstr = new char[newlen];
454 strcpy (newstr, front);
455 delete[] front;
456 }
457 else
458 {
459 size_t newlen = strlen (back) + 2;
460 newstr = new char[newlen];
461 newstr[0] = 0;
462 }
463 strcat (newstr, "\\");
464 strcat (newstr, back);
465 front = newstr;
466 }
467
ForgetBots()468 void FCajunMaster::ForgetBots ()
469 {
470 botinfo_t *thebot = botinfo;
471
472 while (thebot)
473 {
474 botinfo_t *next = thebot->next;
475 delete[] thebot->name;
476 delete[] thebot->info;
477 delete thebot;
478 thebot = next;
479 }
480
481 botinfo = NULL;
482 }
483
LoadBots()484 bool FCajunMaster::LoadBots ()
485 {
486 FScanner sc;
487 FString tmp;
488 bool gotteam = false;
489 int loaded_bots = 0;
490
491 bglobal.ForgetBots ();
492 tmp = M_GetCajunPath(BOTFILENAME);
493 if (tmp.IsEmpty())
494 {
495 DPrintf ("No " BOTFILENAME ", so no bots\n");
496 return false;
497 }
498 sc.OpenFile(tmp);
499
500 while (sc.GetString ())
501 {
502 if (!sc.Compare ("{"))
503 {
504 sc.ScriptError ("Unexpected token '%s'\n", sc.String);
505 }
506
507 botinfo_t *newinfo = new botinfo_t;
508 bool gotclass = false;
509
510 memset (newinfo, 0, sizeof(*newinfo));
511
512 newinfo->info = copystring ("\\autoaim\\0\\movebob\\.25");
513
514 for (;;)
515 {
516 sc.MustGetString ();
517 if (sc.Compare ("}"))
518 break;
519
520 switch (sc.MatchString (BotConfigStrings))
521 {
522 case BOTCFG_NAME:
523 sc.MustGetString ();
524 appendinfo (newinfo->info, "name");
525 appendinfo (newinfo->info, sc.String);
526 newinfo->name = copystring (sc.String);
527 break;
528
529 case BOTCFG_AIMING:
530 sc.MustGetNumber ();
531 newinfo->skill.aiming = sc.Number;
532 break;
533
534 case BOTCFG_PERFECTION:
535 sc.MustGetNumber ();
536 newinfo->skill.perfection = sc.Number;
537 break;
538
539 case BOTCFG_REACTION:
540 sc.MustGetNumber ();
541 newinfo->skill.reaction = sc.Number;
542 break;
543
544 case BOTCFG_ISP:
545 sc.MustGetNumber ();
546 newinfo->skill.isp = sc.Number;
547 break;
548
549 case BOTCFG_TEAM:
550 {
551 char teamstr[16];
552 BYTE teamnum;
553
554 sc.MustGetString ();
555 if (IsNum (sc.String))
556 {
557 teamnum = atoi (sc.String);
558 if (!TeamLibrary.IsValidTeam (teamnum))
559 {
560 teamnum = TEAM_NONE;
561 }
562 }
563 else
564 {
565 teamnum = TEAM_NONE;
566 for (unsigned int i = 0; i < Teams.Size(); ++i)
567 {
568 if (stricmp (Teams[i].GetName (), sc.String) == 0)
569 {
570 teamnum = i;
571 break;
572 }
573 }
574 }
575 appendinfo (newinfo->info, "team");
576 mysnprintf (teamstr, countof(teamstr), "%d", teamnum);
577 appendinfo (newinfo->info, teamstr);
578 gotteam = true;
579 break;
580 }
581
582 default:
583 if (stricmp (sc.String, "playerclass") == 0)
584 {
585 gotclass = true;
586 }
587 appendinfo (newinfo->info, sc.String);
588 sc.MustGetString ();
589 appendinfo (newinfo->info, sc.String);
590 break;
591 }
592 }
593 if (!gotclass)
594 { // Bots that don't specify a class get a random one
595 appendinfo (newinfo->info, "playerclass");
596 appendinfo (newinfo->info, "random");
597 }
598 if (!gotteam)
599 { // Same for bot teams
600 appendinfo (newinfo->info, "team");
601 appendinfo (newinfo->info, "255");
602 }
603 newinfo->next = bglobal.botinfo;
604 newinfo->lastteam = TEAM_NONE;
605 bglobal.botinfo = newinfo;
606 loaded_bots++;
607 }
608 Printf ("%d bots read from %s\n", loaded_bots, BOTFILENAME);
609 return true;
610 }
611
ADD_STAT(bots)612 ADD_STAT (bots)
613 {
614 FString out;
615 out.Format ("think = %04.1f ms support = %04.1f ms wtg = %d",
616 BotThinkCycles.TimeMS(), BotSupportCycles.TimeMS(),
617 BotWTG);
618 return out;
619 }
620