1//**************************************************************************
2//**
3//**    ##   ##    ##    ##   ##   ####     ####   ###     ###
4//**    ##   ##  ##  ##  ##   ##  ##  ##   ##  ##  ####   ####
5//**     ## ##  ##    ##  ## ##  ##    ## ##    ## ## ## ## ##
6//**     ## ##  ########  ## ##  ##    ## ##    ## ##  ###  ##
7//**      ###   ##    ##   ###    ##  ##   ##  ##  ##       ##
8//**       #    ##    ##    #      ####     ####   ##       ##
9//**
10//**    $Id: BotPlayer.vc 4111 2009-11-14 23:46:45Z dj_jl $
11//**
12//**    Copyright (C) 1999-2006 Jānis Legzdiņš
13//**
14//**    This program is free software; you can redistribute it and/or
15//**  modify it under the terms of the GNU General Public License
16//**  as published by the Free Software Foundation; either version 2
17//**  of the License, or (at your option) any later version.
18//**
19//**    This program is distributed in the hope that it will be useful,
20//**  but WITHOUT ANY WARRANTY; without even the implied warranty of
21//**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22//**  GNU General Public License for more details.
23//**
24//**************************************************************************
25//
26//  This section contains the main bot AI. The
27//  main bot AI loop, B_Think, is called every tic.
28//  Also included are various bot decision-making
29//  procedures, such as B_CheckStuff and B_SetEnemy.
30//
31//**************************************************************************
32
33// Main bot class
34class BotPlayer : GameObject;
35
36const float FORWARDWALK		= 200.0;
37const float FORWARDRUN		= 400.0;
38const float SIDEWALK		= 192.0;
39const float SIDERUN			= 320.0;
40
41/*const float MAX_TRAVERSE_DIST			(1024*FRACUNIT)	//10 meters, used within b_func.c*/
42const float AVOID_DIST					= 512.0;	//Try avoid incoming missiles once they reached this close
43const float SAFE_SELF_MISDIST			= 128.0;	//Distance from self to target where it's safe to pull a rocket.
44/*const float FRIEND_DIST					(128*FRACUNIT)	//To friend.
45const float DARK_DIST					(256*FRACUNIT)	//Distance that bot can see enemies in the dark from.
46const float WHATS_DARK					64				//light value thats classed as dark.
47const float MAX_MONSTER_TARGET_DIST		(1024*FRACUNIT)	//Too high can slow down the performance, see P_mobj.c*/
48const float ENEMY_SCAN_FOV				= 120.0;
49/*const float MAXMOVEHEIGHT				(32*FRACUNIT)	//MAXSTEPMOVE but with jumping counted in.
50const float GETINCOMBAT					(512*FRACUNIT)	//Max distance to item. if it's due to be icked up in a combat situation.*/
51const float SHOOTFOV					= 60.0;
52/*const float MAXROAM						(5*TICRATE)		//When this time is elapsed the bot will roam after something else.*/
53
54/*
55
56  Class definitions for botinfo, chatinfo, and
57  other various bot information thingamabobers.
58*/
59
60enum
61{
62	bsk_verypoor,
63	bsk_poor,
64	bsk_low,
65	bsk_medium,
66	bsk_high,
67	bsk_excellent,
68	bsk_supreme
69};
70
71struct botinfo_t
72{
73	string Name;		// Bot's name*/
74	int accuracy;		// Accuracy with "instant" weapons (this includes "leading")
75	int intelect;		// Accuracy with "missile" weapons (rocket launcher, etc.)
76	int evade;			// Ability to dodge incoming missiles
77	int anticip;		// Ability to anticipate "instant" shots
78	int reaction;		// Overall reaction time (lower is "better")
79/*	int pisschance;		// Chance the bot will get pissed when his threshold is reached
80	int threshold;		// How much it takes to frustrate/piss off the bot
81	int dangerlevel;	// When health is below this, we need some health
82	int wpfav;			// Favorite weapon
83	int chatinfo;		// Bot's chat strings
84	int chattime;		// How long it takes us to type a line
85	int chatty;			// How talkative the bot is
86	char *colour;		// Colour (in form of a string)
87	char *gender;		// Male/female/it :)
88	char *skin;			// Skin
89	int railcolour;		// Railgun trail colour
90	bool revealed;		// Hidden bots must be revealed*/
91	string userinfo;
92};
93
94/*struct chatline_t
95{
96	char		*string;
97	chattype_t  bot;
98	chatline_t	*line;
99};
100
101struct chatinfo_t
102{
103	chatline_t		intro[5];
104	chatline_t		inter[5];
105	chatline_t		rare[5];
106	chatline_t		frag[15];
107	chatline_t		died[10];
108	chatline_t		roam[10];
109	chatline_t		pissed[5];
110	chatline_t		frustrated[5];
111	chatline_t		special[20];
112} chatinfo_t;*/
113
114// The things the bot knows about their enemy
115//struct enemyinfo_t
116//{
117//	int				health;
118//	weapontype_t	weap;
119//};
120
121//
122// Bot states
123//
124enum
125{
126	// Deciding what to do
127	BST_NOTHING,
128	// Bot is "fetching" something (skull, whatever)
129	BST_OFFENSE,
130	// Bot is defending something
131	BST_DEFENSE,
132	// Bot is heading to scoring place
133	BST_RETURN
134};
135
136Player player;		// Points to reference player
137Actor botmo;
138
139// Destinations
140Actor item;			// Item (roam towards weapon, etc)
141bool bItemIsWeapon;
142bool bItemIsPowerup;
143Actor goal;			// Teamgame goal spot
144Actor node;			// Node we're heading towards
145Actor prev;			// Previous node we were at
146TVec posdest;			// Position of our destination (doesn't have to be an actor)
147bool posdest_valid;
148Actor enemy;			// The dead meat.
149TVec lastpos;			// Last place we saw our enemy
150bool lastpos_valid;
151Actor missile;			// A threathing missile that got to be avoided.
152
153Actor ally;			// Ally to tag along with
154float t_strafe;
155float t_react;
156float t_fire;			// Tics left until our gun will actually fire again
157float t_anticip;
158
159float forwardmove;	// For building ucmd
160float sidemove;
161
162// Misc booleans
163bool bAllRound;
164bool bNewItemIsWeapon;
165bool bNewItemIsPowerup;
166
167botinfo_t info;			// Aiming, name, perfection, yadda yadda
168
169int angerlevel;
170
171float angle;			// The wanted angle that the bot tries to get every tic.
172
173//==========================================================================
174//
175//	BotTick
176//
177//  Main bot function
178//
179//==========================================================================
180
181void BotTick(float deltaTime)
182{
183	botmo = Actor(player.MO);
184
185	player.ForwardMove = 0.0;
186	player.SideMove = 0.0;
187	player.FlyMove = 0.0;
188	player.Buttons = 0;
189
190	// We're dead, so hit space to respawn
191	if (!player.Health)
192	{
193		player.Buttons |= BT_USE;
194		return;
195	}
196
197	// Weed out any bad destinations/enemies
198	CheckStuff();
199
200	// Destination setting...
201	SetEnemy();
202	Scan();
203
204	// Turning towards destination...
205	BotAim();
206	Turn();
207
208	// Moving towards destination
209	Attack();
210	Move(deltaTime);
211
212	Pitch();
213
214	// Only walk if we're on skill 0
215	if (!MainGameInfo(player.Level.Game).botskill)
216	{
217		if (forwardmove == FORWARDRUN)
218			forwardmove = FORWARDWALK;
219		if (forwardmove == -FORWARDRUN)
220			forwardmove = -FORWARDWALK;
221		if (sidemove == SIDERUN)
222			sidemove = SIDEWALK;
223		if (sidemove == -SIDERUN)
224			sidemove = -SIDEWALK;
225	}
226
227	player.SideMove = sidemove;
228	player.ForwardMove = forwardmove;
229
230	t_react -= deltaTime;
231	if (t_react <= 0.0)
232	{
233		t_react = 0.0;
234	}
235	t_anticip -= deltaTime;
236	if (t_anticip <= 0.0)
237	{
238		t_anticip = 0.0;
239	}
240}
241
242//==========================================================================
243//
244//	AngleTo
245//
246//==========================================================================
247
248float AngleTo(TVec dest)
249{
250	TVec dir;
251	TAVec ang;
252
253	dir = dest - botmo.Origin;
254	VectorAngles(&dir, &ang);
255	return ang.yaw;
256}
257
258//==========================================================================
259//
260//	CheckItem
261//
262//	Determines if we should bother picking up an item or not
263//
264//==========================================================================
265
266bool CheckItem(Actor item)
267{
268	bNewItemIsWeapon = false;
269	bNewItemIsPowerup = false;
270//?????
271    if (!item)
272		return false;
273	if (!item.bSpecial)
274		return false;
275
276	Weapon Wpn = Weapon(item);
277	if (Wpn)
278	{
279		bNewItemIsWeapon = true;
280
281		// If we don't have the weapon, pick it up
282		if (!botmo.FindInventory(class<Inventory>(Wpn.Class)))
283			return true;
284
285		// If we have no more room for the ammo it gives
286		if (botmo.FindInventory(Wpn.AmmoType1).Amount ==
287			botmo.FindInventory(Wpn.AmmoType1).MaxAmount)
288			return false;
289
290		// Can't pick it up because we have it and it's not a dropped weapon
291		if (!item.bDropped)
292			return false;
293	}
294
295	if (Ammo(item))
296	{
297		// If we have no more room for the ammo it gives
298		Inventory AmmoItem = botmo.FindInventory(Ammo(item).GetParentAmmo());
299		if (AmmoItem && AmmoItem.Amount >= AmmoItem.MaxAmount)
300			return false;
301	}
302
303	if (Health(item) && (botmo.Health >= Player::MAXHEALTH))
304		return false;
305	BasicArmor Armor = BasicArmor(botmo.FindInventory(BasicArmor));
306	if (BasicArmorPickup(item) && (Armor.Amount >= BasicArmorPickup(item).SaveAmount))
307		return false;
308
309	// Guess we're okay
310	return true;
311}
312
313//==========================================================================
314//
315//	SetEnemy
316//
317//==========================================================================
318
319void SetEnemy()
320{
321	if (enemy && enemy.Health > 0 && player.MO &&
322		player.MO.CanSee(enemy))
323	{
324		return;
325	}
326
327	bAllRound = !!enemy;
328	enemy = FindEnemy();
329
330	if (!enemy)
331		return;
332
333	// Double check the validity of the enemy
334	if (!enemy.bShootable)
335		enemy = none;
336}
337
338//==========================================================================
339//
340//	CheckTo
341//
342//	Checks if an location is reachable
343//
344//==========================================================================
345
346bool CheckTo(TVec pos)
347{
348	float dist;
349	float an;
350
351	dist = Length(botmo.Origin - pos);
352	an = GetAngle();
353
354	if (!CheckPath(an, dist))
355		return false;
356
357	return true;
358}
359
360//==========================================================================
361//
362//	CheckStuff
363//
364//	Make sure that our destinations/enemies and everything are valid.
365//
366//==========================================================================
367
368void CheckStuff()
369{
370	if (item)
371	{
372		if (!item.bSpecial || item.IsDestroyed() || // somebody picked
373				!CheckTo(item.Origin)) // Can't reach
374			item = none;
375	}
376
377	if (missile)
378	{
379		if (!missile.bMissile || missile.IsDestroyed())
380			missile = none;
381	}
382
383	if (node)
384	{
385		if (!CheckTo(node.Origin) ||
386			(botmo.DistTo2(node) < botmo.Radius))
387		{
388			prev = node;
389			node = none;
390		}
391	}
392
393	if (posdest_valid)
394	{
395		TVec dir;
396		dir = posdest - botmo.Origin;
397		dir.z = 0.0;
398		if ((Length(dir) < botmo.Radius) || !CheckTo(posdest))
399		{
400			posdest_valid = false;
401		}
402	}
403
404	if (lastpos_valid)
405	{
406		TVec dir;
407		dir = lastpos - botmo.Origin;
408		dir.z = 0.0;
409		if ((Length(dir) < botmo.Radius) || !CheckTo(lastpos))
410		{
411			lastpos_valid = false;
412		}
413	}
414
415	if (enemy)
416	{
417		if (enemy.Health <= 0 || !enemy.bShootable)
418			enemy = none;
419	}
420}
421
422//==========================================================================
423//
424//	Scan
425//
426//	Scan all mobj's visible to the bot for incoming missiles, enemies, and
427// various items to pick up.
428//
429//==========================================================================
430
431void Scan()
432{
433	Actor actor;
434
435	foreach botmo.AllThinkers(Actor, actor)
436	{
437		if (!actor.bSpecial && !actor.bMissile)
438		{
439			// Not interested in this one
440			continue;
441		}
442		if (Check_LOS(actor, 90.0))
443		{
444			// Look for special items
445			if (!item && actor.bSpecial)
446			{
447				if (CheckItem(actor))
448				{
449					item = actor;
450					bItemIsWeapon = bNewItemIsWeapon;
451					bItemIsPowerup = bNewItemIsPowerup;
452				}
453			}
454			else if (!missile && actor.bMissile &&
455				(botmo.DistTo(actor) < AVOID_DIST))
456			{
457				missile = actor;
458			}
459		}
460	}
461}
462
463//==========================================================================
464//
465//	SkillLower
466//
467//==========================================================================
468
469int SkillLower(int skill, int num)
470{
471	if (num <= 0)
472		return skill;
473
474	skill -= num;
475	if (skill < 0)
476		skill = 0;
477
478	return skill;
479}
480
481//==========================================================================
482//
483//	SetAngle
484//
485//==========================================================================
486
487void SetAngle(float an)
488{
489	angle = AngleMod360(an);
490}
491
492//==========================================================================
493//
494//	GetAngle
495//
496//==========================================================================
497
498float GetAngle()
499{
500	return angle;
501}
502
503//==========================================================================
504//
505//	IsDangerous
506//
507//	Checks if a sector is dangerous.
508//
509//==========================================================================
510
511bool IsDangerous(sector_t *sec)
512{
513	switch (sec->special & ~SECSPEC_SECRET_MASK)
514	{
515	case 71:	// Damage_Sludge
516	case 82:	// Damage_LavaWimpy
517	case 83:	// Damage_LavaHefty
518	case 84:	// Scroll_EastLavaDamage
519		return true;
520	}
521	return false;
522}
523
524//==========================================================================
525//
526//	CheckPath
527//
528//	Checks for obstructions at a certain angle and distance. Returns true if
529// the path is clear, and false is the path is blocked.
530//
531//==========================================================================
532
533bool CheckPath(float ang, float dist)
534{
535	float x1, y1, x2, y2;
536	intercept_t* in;
537	float bottracerange;
538	TVec bottracedir;
539
540	bottracerange = dist;
541	bottracedir.x = cos(ang);
542	bottracedir.y = sin(ang);
543	bottracedir.z = 0.0;
544	x1 = botmo.Origin.x;
545	y1 = botmo.Origin.y;
546 	x2 = x1 + dist * bottracedir.x;
547 	y2 = y1 + dist * bottracedir.y;
548
549	EntityEx(player.MO).UseLines(Player::USERANGE, Player::USETHINGRANGE, '*usefail');
550
551	foreach botmo.PathTraverse(in, x1, y1, x2, y2, PT_ADDLINES)
552	{
553		Actor th;
554		line_t *ld;
555		TVec hit_point;
556
557		if (in->bIsALine)
558		{
559			sector_t *back;
560			sector_t *front;
561			opening_t *open;
562			float diffheight;
563
564			ld = in->line;					// This linedef
565			hit_point = botmo.Origin + (bottracerange * in->frac) * bottracedir;
566
567			// Line is impassible
568			if (!(ld->flags & ML_TWOSIDED) || (ld->flags & (ML_BLOCKING | ML_BLOCKPLAYERS | ML_BLOCKEVERYTHING)))
569				return false;
570
571			// Line isn't two sided
572			if (!ld->backsector)
573				return false;
574
575			if (!PointOnPlaneSide(botmo.Origin, ld))
576			{
577				back = ld->backsector;
578				front = ld->frontsector;
579			}
580			else
581			{
582				back = ld->frontsector;
583				front = ld->backsector;
584			}
585
586			// Sector is dangerous
587			if (IsDangerous(back))
588				return false;
589
590			// crosses a two sided line
591			open = LineOpenings(ld, hit_point);
592			open = FindOpening(open, hit_point.z, hit_point.z + botmo.Height);
593			// No valid openings
594			if (!open)
595			{
596				return false;
597			}
598
599			diffheight = GetPlanePointZ(&back->floor, hit_point) -
600				GetPlanePointZ(&front->floor, hit_point);
601
602			// No cliff jumping unless we're going after something
603			if (-diffheight > 32.0 && !enemy)
604			{
605				return false;
606			}
607
608			if (diffheight > 0.0)
609			{
610				if (diffheight > 48.0)
611				{
612/*					if (front->SSpecial == ThrustThingZ || front->springpadzone)
613					{
614						return true;
615					}
616					else*/
617						return false;
618				}
619				else if (diffheight <= 48.0 && diffheight >= 24.0)
620				{
621					player.Buttons |= BT_JUMP;
622				}
623			}
624		}
625		else
626		{
627			th = Actor(in->Thing);
628
629			if (th == botmo)
630				continue;
631
632			if (th.bSolid)
633				return false;
634
635/*			fixed_t diffheight = (th->z + th->Height) - shootthing->z;
636
637			if (diffheight > 0)
638			{
639				if (diffheight <= 48*FRACUNIT && diffheight >= 24*FRACUNIT)
640				{
641					bot->player.cmd.ucmd.buttons |= BT_JUMP;
642					return true;
643				}
644				else if (diffheight <= 24*FRACUNIT)
645					return true;
646				else
647					return false;
648
649			}
650*/
651		}
652	}
653	return true;
654}
655
656//==========================================================================
657//
658//	Check_LOS
659//
660//	Doesnt check LOS, checks visibility with a set view angle.
661//	B_Checksight checks LOS (straight line)
662//
663//	Check if mo1 has free line to mo2 and if mo2 is within mo1 viewangle
664// (vangle) given with normal degrees. If these conditions are true, the
665// function returns true. GOOD TO KNOW is that the players view angle in
666// doom is 90 degrees infront.
667//
668//==========================================================================
669
670bool Check_LOS(Actor to, float vangle)
671{
672	if (!botmo.CanSee(to))
673		return false; // out of sight
674	if (vangle == 360.0)
675		return true;
676	if (vangle == 0.0)
677		return false; //Looker seems to be blind.
678
679	return fabs(AngleMod180(AngleTo(to.Origin) - botmo.Angles.yaw)) <=
680		vangle / 2.0;
681}
682
683//==========================================================================
684//
685//	BotAim
686//
687//==========================================================================
688
689void BotAim()
690{
691	float dist;
692	bool right;
693	float an;
694
695	if (!enemy)
696		return;
697
698	if (t_react)
699		return;
700
701	// Distance to enemy.
702	dist = botmo.DistTo2(enemy);
703
704	right = !!(P_Random() & 1);
705	an = AngleTo(enemy.Origin);
706
707	// [BC] Cajun prediction... maybe use this somewhere
708	/*
709			{
710			//Here goes the prediction.
711			dist = P_AproxDistance(MO->x - enemy->x, MO->y - enemy->y);
712			fixed_t m = (dist/FRACUNIT) / mobjinfo[MT_PLASMA].speed;
713			bot->SetAngle(R_PointToAngle2(MO->x,
714											MO->y,
715											enemy->x + FixedMul (enemy->momx, (m*2*FRACUNIT)),
716											enemy->y + FixedMul (enemy->momy, (m*2*FRACUNIT))));
717			}
718	*/
719
720	// Fix me: Implement botskill, accuracy, and intelligence
721	if (player.ReadyWeapon.bBotProjectile)
722	{
723		// Splash weapons
724//		bot->SetAngle(R_PointToAngle2(MO->x, MO->y, enemy->x, enemy->y));
725
726		// Projectile weapons
727		switch (info.intelect)
728		{
729		case bsk_verypoor:
730		case bsk_poor:
731		case bsk_low:
732			// Aim right at the enemy
733			SetAngle(an);
734			break;
735		case bsk_medium:
736		case bsk_high:
737		case bsk_excellent:
738		case bsk_supreme:
739			if (right)
740				SetAngle(an + Random() * 20.0);
741			else
742				SetAngle(an - Random() * 20.0);
743			break;
744
745		default:
746			Error("Unknown bot skill level: %d", info.accuracy);
747			return;
748		}
749	}
750	else
751	{
752		// Instant weapons
753		switch (info.accuracy)
754		{
755		case bsk_verypoor:
756			if (right)
757				SetAngle(an + Random() * 60.0);
758			else
759				SetAngle(an - Random() * 60.0);
760			break;
761		case bsk_poor:
762			if (right)
763				SetAngle(an + Random() * 45.0);
764			else
765				SetAngle(an - Random() * 45.0);
766			break;
767		case bsk_low:
768			if (right)
769				SetAngle(an + Random() * 30.0);
770			else
771				SetAngle(an - Random() * 30.0);
772			break;
773		case bsk_medium:
774			if (right)
775				SetAngle(an + Random() * 15.0);
776			else
777				SetAngle(an - Random() * 15.0);
778			break;
779		case bsk_high:
780			SetAngle(an);
781			break;
782		case bsk_excellent:
783			SetAngle(AngleTo(enemy.Origin + enemy.Velocity * 0.1));
784			break;
785		case bsk_supreme://FIXME
786			SetAngle(AngleTo(enemy.Origin + enemy.Velocity * 0.1));
787			break;
788		default:
789			Error("Unknown bot skill level: %d", info.accuracy);
790			return;
791		}
792	}
793}
794
795//==========================================================================
796//
797//	FindEnemy
798//
799//==========================================================================
800
801Actor FindEnemy()
802{
803	float closest_dist, temp;
804	Actor target;
805	float vangle;
806	int i;
807
808	//Note: It's hard to ambush a bot who is not alone
809	if (bAllRound || ally)
810		vangle = 360.0;
811	else
812		vangle = ENEMY_SCAN_FOV;
813	bAllRound = false;
814
815	target = none;
816	closest_dist = 99999.0;
817
818	// Search for player enemies
819	for (i = 0; i < MAXPLAYERS; i++)
820	{
821		if (player.Level.Game.Players[i] &&
822			player.Level.Game.Players[i].MO.Health > 0 && botmo != player.Level.Game.Players[i].MO)
823		{
824			 //Here's a strange one, when bot is standing still, the CanSee within Check_LOS almost always returns false. tought it should be the same checksight as below but.. (below works) something must be fuckin wierd screded up.
825			if (Check_LOS(Actor(player.Level.Game.Players[i].MO), vangle))
826			{
827				if (botmo.CanSee(player.Level.Game.Players[i].MO))
828				{
829					temp = player.Level.Game.Players[i].MO.DistTo(botmo);
830					if (temp < closest_dist)
831					{
832						closest_dist = temp;
833						target = Actor(player.Level.Game.Players[i].MO);
834					}
835				}
836			}
837		}
838	}
839
840	return target;
841}
842
843//==========================================================================
844//
845//	Roam
846//
847//	Handle non-attack/dodging movement
848//
849//==========================================================================
850
851void Roam()
852{
853	TVec dest;
854
855	if (lastpos_valid)
856	{
857		TVec dir;
858
859		dir = lastpos - botmo.Origin;
860		dir.z = 0.0;
861		if (Length(dir) <= 32.0)
862		{
863			lastpos_valid = false;
864		}
865	}
866
867	// Order of item response precedence:
868	if (goal)
869	{
870		dest = goal.Origin;
871	}
872	else if (lastpos_valid)
873	{
874		dest = lastpos;
875	}
876	else if (item)
877	{
878		dest = item.Origin;
879	}
880	else if (node)
881	{
882		dest = node.Origin;
883	}
884	else if (posdest_valid)
885	{
886		dest = posdest;
887	}
888	else
889	{
890		// No target, so just run around until we find something
891		int r = P_Random();
892		float an = GetAngle();
893		float dist;
894
895		for (dist = 256.0; dist >= 64.0; dist -= 64.0)
896		{
897			if (CheckPath(an, dist))
898			{
899				posdest_valid = true;
900				posdest.x = botmo.Origin.x + dist * cos(an);
901				posdest.y = botmo.Origin.y + dist * sin(an);
902				posdest.z = botmo.Origin.z;
903				SetAngle(an);
904				break;
905			}
906
907			if (CheckPath(an + 45.0, dist))
908			{
909				posdest_valid = true;
910				posdest.x = botmo.Origin.x + dist * cos(an + 45.0);
911				posdest.y = botmo.Origin.y + dist * sin(an + 45.0);
912				posdest.z = botmo.Origin.z;
913				SetAngle(an + 45.0);
914				break;
915			}
916
917			// Left is no good, try right
918			if (CheckPath(an - 45.0, dist))
919			{
920				posdest_valid = true;
921				posdest.x = botmo.Origin.x + dist * cos(an - 45.0);
922				posdest.y = botmo.Origin.y + dist * sin(an - 45.0);
923				posdest.z = botmo.Origin.z;
924				SetAngle(an - 45.0);
925				break;
926			}
927		}
928		if (posdest_valid)
929		{
930			dest = posdest;
931		}
932		else
933		{
934			SetAngle(GetAngle() + 45.0 / 3.0);
935			forwardmove = -FORWARDWALK;
936			return;
937		}
938	}
939
940	forwardmove = FORWARDRUN;
941	SetAngle(AngleTo(dest));
942}
943
944//==========================================================================
945//
946//	Move
947//
948//	Main bot movement function. Dodging/attacking movement is also handled
949// here
950//
951//==========================================================================
952
953void Move(float deltaTime)
954{
955	float dist;
956
957	// Worry about missiles above all else
958	if (missile)
959	{
960		if (t_strafe)
961		{
962			t_strafe -= deltaTime;
963			if (!t_strafe)
964			{
965				// Don't change direction while dodging missiles (that could be bad)
966				//bot->sidemove = -bot->sidemove;
967				t_strafe = 2.0;
968			}
969		}
970
971		// Look at the missle and sidestep it
972		SetAngle(AngleTo(missile.Origin));
973		forwardmove = -FORWARDRUN;
974		return;
975	}
976
977	// Anticipate a shot: time to dodge!
978	if (enemy && t_anticip <= 0.25)
979	{
980		if (!sidemove)
981			sidemove = SIDERUN;
982
983		switch (info.anticip)
984		{
985		case bsk_verypoor:
986			// Deer caught in the headlights
987			sidemove = 0.0;
988			return;
989
990		case bsk_poor:
991			// Always walk right
992			sidemove = SIDEWALK;
993			break;
994
995		case bsk_low:
996			// Always run right
997			sidemove = SIDERUN;
998			break;
999
1000		case bsk_medium:
1001			// Just switch directions every couple seconds
1002			if (t_strafe)
1003			{
1004				t_strafe -= deltaTime;
1005				if (t_strafe <= 0.0)
1006				{
1007					sidemove = -sidemove;
1008					t_strafe = 2.0;
1009				}
1010			}
1011			break;
1012
1013		case bsk_high:
1014			// Switch directions when we think our opponent will fire
1015			if (!t_anticip)
1016				sidemove = -sidemove;
1017			break;
1018
1019		case bsk_excellent:
1020			// Move in a somewhat random direction when we think our opponent will fire
1021			if (!t_anticip)
1022			{
1023				sidemove = Random() < 0.5 ? SIDERUN : -SIDERUN;
1024
1025				if (Random() < 0.5)
1026				{
1027					forwardmove = -FORWARDWALK;
1028				}
1029			}
1030			break;
1031
1032		case bsk_supreme:
1033			// What a squirmy little fucker!
1034			if (!t_anticip)
1035			{
1036				switch (P_Random() & 3)
1037				{
1038				case 0:
1039					sidemove = SIDERUN;
1040					break;
1041				case 1:
1042					sidemove = SIDEWALK;
1043					break;
1044				case 2:
1045					sidemove = -SIDERUN;
1046					break;
1047				case 3:
1048					sidemove = -SIDEWALK;
1049					break;
1050				}
1051
1052				switch (P_Random() & 3)
1053				{
1054				case 0:
1055					forwardmove = FORWARDRUN;
1056					break;
1057				case 1:
1058					forwardmove = FORWARDWALK;
1059					break;
1060				case 2:
1061					forwardmove = -FORWARDRUN;
1062					break;
1063				case 3:
1064					forwardmove = -FORWARDWALK;
1065					break;
1066				}
1067			}
1068			break;
1069
1070		default:
1071			Error("Unknown bot skill level: %d", info.anticip);
1072			return;
1073		}
1074	}
1075
1076	// Now handle attack movement
1077	if (enemy)
1078	{
1079		bool noforward;
1080		bool noside;
1081		float an;
1082
1083		noforward = false;
1084		noside = false;
1085		if (!sidemove )
1086			sidemove = SIDERUN;
1087
1088		if (t_strafe)
1089		{
1090			t_strafe -= deltaTime;
1091			if (t_strafe <= 0.0)
1092			{
1093				sidemove = -sidemove;
1094				t_strafe = 2.0;
1095			}
1096		}
1097
1098		dist = botmo.DistTo(enemy);
1099
1100		// Remember where we saw him last in case he gets away
1101		lastpos = enemy.Origin;
1102		lastpos_valid = true;
1103
1104		// Check if we'd rather pick up something than fight
1105		if (item)
1106		{
1107			if (bItemIsPowerup ||
1108				(bItemIsWeapon && player.ReadyWeapon.bWimpyWeapon))
1109			{
1110				Roam();
1111				return;
1112			}
1113		}
1114
1115		an = botmo.Angles.yaw;
1116		if (sidemove < 0.0)
1117			an -= 90.0;
1118		else
1119			an += 90.0;
1120
1121		if (!CheckPath(an, 48.0)) // We're blocked, so go the other way!
1122			sidemove = -sidemove;
1123
1124		if (CheckTo(enemy.Origin) && dist > player.ReadyWeapon.BotCombatDist)
1125			forwardmove = FORWARDRUN;
1126		else
1127			forwardmove = -FORWARDRUN;
1128
1129		return;
1130	}
1131
1132	if (t_strafe)
1133	{
1134		t_strafe -= deltaTime;
1135		if (t_strafe <= 0.0)
1136		{
1137			sidemove = -sidemove;
1138			t_strafe = 2.0;
1139		}
1140	}
1141
1142	// Roam after an item
1143	Roam();
1144}
1145
1146//==========================================================================
1147//
1148//	Attack
1149//
1150//==========================================================================
1151
1152void Attack()
1153{
1154	// Still reacting to something or we don't have an enemy to fight
1155	if (t_react || !enemy)
1156		return;
1157
1158	// No point in firing if we won't hit them
1159	if (!Check_LOS(enemy, SHOOTFOV))
1160		return;
1161
1162	player.Buttons |= BT_ATTACK;
1163}
1164
1165//==========================================================================
1166//
1167//	Turn
1168//
1169//	[BC] Ahh, the new and improved turning...
1170//
1171//==========================================================================
1172
1173void Turn()
1174{
1175	float distance;
1176
1177	distance = GetAngle() - botmo.Angles.yaw;
1178
1179	if (!enemy)
1180	{
1181		player.ViewAngles.yaw = GetAngle();
1182		return;
1183	}
1184
1185	// [BC] Don't act crazy while trying to aim
1186	switch (info.accuracy)
1187	{
1188	case bsk_verypoor:
1189	case bsk_poor:
1190	case bsk_low:
1191		if (distance > 7.5)
1192			distance = 7.5;
1193		if (distance < -7.5)
1194			distance = -7.5;
1195		break;
1196	case bsk_medium:
1197		if (distance > 15.0)
1198			distance = 15.0;
1199		if (distance < -15.0)
1200			distance = -15.0;
1201		break;
1202	case bsk_high:
1203		if (distance > 22.5)
1204			distance = 22.5;
1205		if (distance < -22.5)
1206			distance = -22.5;
1207		break;
1208	case bsk_excellent:
1209		if (distance > 30.0)
1210			distance = 30.0;
1211		if (distance < -30.0)
1212			distance = -30.0;
1213		break;
1214	case bsk_supreme:
1215		if (distance > 37.5)
1216			distance = 37.5;
1217		if (distance < -37.5)
1218			distance = -37.5;
1219		break;
1220	}
1221	player.ViewAngles.yaw = AngleMod360(botmo.Angles.yaw + distance);
1222}
1223
1224//==========================================================================
1225//
1226//	Pitch
1227//
1228//==========================================================================
1229
1230void Pitch()
1231{
1232	if (enemy)
1233	{
1234		TVec dir;
1235		TAVec ang;
1236
1237		dir = enemy.Origin - botmo.Origin;
1238		VectorAngles(&dir, &ang);
1239		botmo.Angles.pitch = ang.pitch;
1240	}
1241	else
1242	{
1243		botmo.Angles.pitch = 0.0;
1244	}
1245}
1246
1247//==========================================================================
1248//
1249//	Killed
1250//
1251//==========================================================================
1252
1253void Killed(EntityEx victim)
1254{
1255	// [BC] Let some anger out
1256	angerlevel -= 5;
1257	enemy = none;
1258
1259	// [BC] Don't need to worry about following him anymore
1260	lastpos_valid = false;
1261}
1262
1263//==========================================================================
1264//
1265//	Died
1266//
1267//==========================================================================
1268
1269void Died(EntityEx killer)
1270{
1271}
1272
1273//==========================================================================
1274//
1275//	OnBeginPlay
1276//
1277//==========================================================================
1278
1279void OnBeginPlay()
1280{
1281	int botskill = 2;
1282	int bottype = 0;
1283	botinfo_t *binfo;
1284	int i;
1285
1286	if (strcmp(player.PlayerName, ""))
1287	{
1288		for (i = 0; i < MainGameInfo::NUMTOTALBOTS; i++)
1289		{
1290			if (!stricmp(MainGameInfo(player.Level.Game).botinfo[i].Name, player.PlayerName))
1291			{
1292				bottype = i;
1293				break;
1294			}
1295		}
1296		// We've already handled the "what if there's no match" exception
1297	}
1298	else
1299	{
1300		// If the user doesn't input a name, don't
1301		// spawn one of the "special" bots, only one of the
1302		// normal ones.
1303		bottype = P_Random() % MainGameInfo::NUMBOTTYPES;
1304	}
1305
1306	binfo = &MainGameInfo(player.Level.Game).botinfo[bottype];
1307
1308	t_strafe = 1.0;
1309
1310	if (botskill > 4)
1311		botskill = 4;
1312	if (botskill < 0)
1313		botskill = 0;
1314
1315	// Implement skill settings
1316	info.accuracy = SkillLower(binfo->accuracy, 4 - botskill);
1317	info.intelect = SkillLower(binfo->intelect, 4 - botskill);
1318	info.evade = SkillLower(binfo->evade, 4 - botskill);
1319	info.anticip = SkillLower(binfo->anticip, 4 - botskill);
1320	info.reaction = SkillLower(binfo->reaction, 4 - botskill);
1321	player.UserInfo = binfo->userinfo;
1322}
1323
1324//==========================================================================
1325//
1326//	OnSpawn
1327//
1328//==========================================================================
1329
1330void OnSpawn()
1331{
1332}
1333
1334defaultproperties
1335{
1336}
1337