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