1 /*******************************
2 * B_spawn.c                    *
3 * Description:                 *
4 * various procedures that the  *
5 * bot need to work             *
6 *******************************/
7 
8 #include <stdlib.h>
9 
10 #include "doomtype.h"
11 #include "doomdef.h"
12 #include "doomstat.h"
13 #include "p_local.h"
14 #include "b_bot.h"
15 #include "g_game.h"
16 #include "m_random.h"
17 #include "r_sky.h"
18 #include "st_stuff.h"
19 #include "stats.h"
20 #include "i_system.h"
21 #include "s_sound.h"
22 #include "d_event.h"
23 
24 static FRandom pr_botdofire ("BotDoFire");
25 
26 
27 //Checks TRUE reachability from bot to a looker.
Reachable(AActor * rtarget)28 bool DBot::Reachable (AActor *rtarget)
29 {
30 	if (player->mo == rtarget)
31 		return false;
32 
33 	if ((rtarget->Sector->ceilingplane.ZatPoint (rtarget) -
34 		 rtarget->Sector->floorplane.ZatPoint (rtarget))
35 		< player->mo->height) //Where rtarget is, player->mo can't be.
36 		return false;
37 
38 	sector_t *last_s = player->mo->Sector;
39 	fixed_t last_z = last_s->floorplane.ZatPoint (player->mo);
40 	fixed_t estimated_dist = player->mo->AproxDistance(rtarget);
41 	bool reachable = true;
42 
43 	FPathTraverse it(player->mo->X()+player->mo->velx, player->mo->Y()+player->mo->vely, rtarget->X(), rtarget->Y(), PT_ADDLINES|PT_ADDTHINGS);
44 	intercept_t *in;
45 	while ((in = it.Next()))
46 	{
47 		fixed_t hitx, hity;
48 		fixed_t frac;
49 		line_t *line;
50 		AActor *thing;
51 		fixed_t dist;
52 		sector_t *s;
53 
54 		frac = in->frac - FixedDiv (4*FRACUNIT, MAX_TRAVERSE_DIST);
55 		dist = FixedMul (frac, MAX_TRAVERSE_DIST);
56 
57 		hitx = it.Trace().x + FixedMul (player->mo->velx, frac);
58 		hity = it.Trace().y + FixedMul (player->mo->vely, frac);
59 
60 		if (in->isaline)
61 		{
62 			line = in->d.line;
63 
64 			if (!(line->flags & ML_TWOSIDED) || (line->flags & (ML_BLOCKING|ML_BLOCKEVERYTHING|ML_BLOCK_PLAYERS)))
65 			{
66 				return false; //Cannot continue.
67 			}
68 			else
69 			{
70 				//Determine if going to use backsector/frontsector.
71 				s = (line->backsector == last_s) ? line->frontsector : line->backsector;
72 				fixed_t ceilingheight = s->ceilingplane.ZatPoint (hitx, hity);
73 				fixed_t floorheight = s->floorplane.ZatPoint (hitx, hity);
74 
75 				if (!bglobal.IsDangerous (s) &&		//Any nukage/lava?
76 					(floorheight <= (last_z+MAXMOVEHEIGHT)
77 					&& ((ceilingheight == floorheight && line->special)
78 						|| (ceilingheight - floorheight) >= player->mo->height))) //Does it fit?
79 				{
80 					last_z = floorheight;
81 					last_s = s;
82 					continue;
83 				}
84 				else
85 				{
86 					return false;
87 				}
88 			}
89 		}
90 
91 		if (dist > estimated_dist)
92 		{
93 			return true;
94 		}
95 
96 		thing = in->d.thing;
97 		if (thing == player->mo) //Can't reach self in this case.
98 			continue;
99 		if (thing == rtarget && (rtarget->Sector->floorplane.ZatPoint (rtarget) <= (last_z+MAXMOVEHEIGHT)))
100 		{
101 			return true;
102 		}
103 
104 		reachable = false;
105 	}
106 	return reachable;
107 }
108 
109 //doesnt check LOS, checks visibility with a set view angle.
110 //B_Checksight checks LOS (straight line)
111 //----------------------------------------------------------------------
112 //Check if mo1 has free line to mo2
113 //and if mo2 is within mo1 viewangle (vangle) given with normal degrees.
114 //if these conditions are true, the function returns true.
115 //GOOD TO KNOW is that the player's view angle
116 //in doom is 90 degrees infront.
Check_LOS(AActor * to,angle_t vangle)117 bool DBot::Check_LOS (AActor *to, angle_t vangle)
118 {
119 	if (!P_CheckSight (player->mo, to, SF_SEEPASTBLOCKEVERYTHING))
120 		return false; // out of sight
121 	if (vangle == ANGLE_MAX)
122 		return true;
123 	if (vangle == 0)
124 		return false; //Looker seems to be blind.
125 
126 	return absangle(player->mo->AngleTo(to) - player->mo->angle) <= vangle/2;
127 }
128 
129 //-------------------------------------
130 //Bot_Dofire()
131 //-------------------------------------
132 //The bot will check if it's time to fire
133 //and do so if that is the case.
Dofire(ticcmd_t * cmd)134 void DBot::Dofire (ticcmd_t *cmd)
135 {
136 	bool no_fire; //used to prevent bot from pumping rockets into nearby walls.
137 	int aiming_penalty=0; //For shooting at shading target, if screen is red, MAKEME: When screen red.
138 	int aiming_value; //The final aiming value.
139 	fixed_t dist;
140 	angle_t an;
141 	int m;
142 
143 	if (!enemy || !(enemy->flags & MF_SHOOTABLE) || enemy->health <= 0)
144 		return;
145 
146 	if (player->ReadyWeapon == NULL)
147 		return;
148 
149 	if (player->damagecount > skill.isp)
150 	{
151 		first_shot = true;
152 		return;
153 	}
154 
155 	//Reaction skill thing.
156 	if (first_shot &&
157 		!(player->ReadyWeapon->WeaponFlags & WIF_BOT_REACTION_SKILL_THING))
158 	{
159 		t_react = (100-skill.reaction+1)/((pr_botdofire()%3)+3);
160 	}
161 	first_shot = false;
162 	if (t_react)
163 		return;
164 
165 	//MAKEME: Decrease the rocket suicides even more.
166 
167 	no_fire = true;
168 	//Distance to enemy.
169 	dist = player->mo->AproxDistance(enemy, player->mo->velx - enemy->velx, player->mo->vely - enemy->vely);
170 
171 	//FIRE EACH TYPE OF WEAPON DIFFERENT: Here should all the different weapons go.
172 	if (player->ReadyWeapon->WeaponFlags & WIF_MELEEWEAPON)
173 	{
174 		if ((player->ReadyWeapon->ProjectileType != NULL))
175 		{
176 			if (player->ReadyWeapon->CheckAmmo (AWeapon::PrimaryFire, false, true))
177 			{
178 				// This weapon can fire a projectile and has enough ammo to do so
179 				goto shootmissile;
180 			}
181 			else if (!(player->ReadyWeapon->WeaponFlags & WIF_AMMO_OPTIONAL))
182 			{
183 				// Ammo is required, so don't shoot. This is for weapons that shoot
184 				// missiles that die at close range, such as the powered-up Phoneix Rod.
185 				return;
186 			}
187 		}
188 		else
189 		{
190 			//*4 is for atmosphere,  the chainsaws sounding and all..
191 			no_fire = (dist > (MELEERANGE*4));
192 		}
193 	}
194 	else if (player->ReadyWeapon->WeaponFlags & WIF_BOT_BFG)
195 	{
196 		//MAKEME: This should be smarter.
197 		if ((pr_botdofire()%200)<=skill.reaction)
198 			if(Check_LOS(enemy, SHOOTFOV))
199 				no_fire = false;
200 	}
201 	else if (player->ReadyWeapon->ProjectileType != NULL)
202 	{
203 		if (player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE)
204 		{
205 			//Special rules for RL
206 			an = FireRox (enemy, cmd);
207 			if(an)
208 			{
209 				angle = an;
210 				//have to be somewhat precise. to avoid suicide.
211 				if (absangle(angle - player->mo->angle) < 12*ANGLE_1)
212 				{
213 					t_rocket = 9;
214 					no_fire = false;
215 				}
216 			}
217 		}
218 		// prediction aiming
219 shootmissile:
220 		dist = player->mo->AproxDistance (enemy);
221 		m = dist / GetDefaultByType (player->ReadyWeapon->ProjectileType)->Speed;
222 		bglobal.SetBodyAt (enemy->X() + enemy->velx*m*2, enemy->Y() + enemy->vely*m*2, enemy->Z(), 1);
223 		angle = player->mo->AngleTo(bglobal.body1);
224 		if (Check_LOS (enemy, SHOOTFOV))
225 			no_fire = false;
226 	}
227 	else
228 	{
229 		//Other weapons, mostly instant hit stuff.
230 		angle = player->mo->AngleTo(enemy);
231 		aiming_penalty = 0;
232 		if (enemy->flags & MF_SHADOW)
233 			aiming_penalty += (pr_botdofire()%25)+10;
234 		if (enemy->Sector->lightlevel<WHATS_DARK/* && !(player->powers & PW_INFRARED)*/)
235 			aiming_penalty += pr_botdofire()%40;//Dark
236 		if (player->damagecount)
237 			aiming_penalty += player->damagecount; //Blood in face makes it hard to aim
238 		aiming_value = skill.aiming - aiming_penalty;
239 		if (aiming_value <= 0)
240 			aiming_value = 1;
241 		m = ((SHOOTFOV/2)-(aiming_value*SHOOTFOV/200)); //Higher skill is more accurate
242 		if (m <= 0)
243 			m = 1; //Prevents lock.
244 
245 		if (m)
246 		{
247 			if (increase)
248 				angle += m;
249 			else
250 				angle -= m;
251 		}
252 
253 		if (absangle(angle - player->mo->angle) < 4*ANGLE_1)
254 		{
255 			increase = !increase;
256 		}
257 
258 		if (Check_LOS (enemy, (SHOOTFOV/2)))
259 			no_fire = false;
260 	}
261 	if (!no_fire) //If going to fire weapon
262 	{
263 		cmd->ucmd.buttons |= BT_ATTACK;
264 	}
265 	//Prevents bot from jerking, when firing automatic things with low skill.
266 }
267 
IsLeader(player_t * player)268 bool FCajunMaster::IsLeader (player_t *player)
269 {
270 	for (int count = 0; count < MAXPLAYERS; count++)
271 	{
272 		if (players[count].Bot != NULL
273 			&& players[count].Bot->mate == player->mo)
274 		{
275 			return true;
276 		}
277 	}
278 
279 	return false;
280 }
281 
282 //This function is called every
283 //tick (for each bot) to set
284 //the mate (teammate coop mate).
Choose_Mate()285 AActor *DBot::Choose_Mate ()
286 {
287 	int count;
288 	fixed_t closest_dist, test;
289 	AActor *target;
290 	AActor *observer;
291 
292 	//is mate alive?
293 	if (mate)
294 	{
295 		if (mate->health <= 0)
296 			mate = NULL;
297 		else
298 			last_mate = mate;
299 	}
300 	if (mate) //Still is..
301 		return mate;
302 
303 	//Check old_mates status.
304 	if (last_mate)
305 		if (last_mate->health <= 0)
306 			last_mate = NULL;
307 
308 	target = NULL;
309 	closest_dist = FIXED_MAX;
310 	if (bot_observer)
311 		observer = players[consoleplayer].mo;
312 	else
313 		observer = NULL;
314 
315 	//Check for player friends
316 	for (count = 0; count < MAXPLAYERS; count++)
317 	{
318 		player_t *client = &players[count];
319 
320 		if (playeringame[count]
321 			&& client->mo
322 			&& player->mo != client->mo
323 			&& (player->mo->IsTeammate (client->mo) || !deathmatch)
324 			&& client->mo->health > 0
325 			&& client->mo != observer
326 			&& ((player->mo->health/2) <= client->mo->health || !deathmatch)
327 			&& !bglobal.IsLeader(client)) //taken?
328 		{
329 			if (P_CheckSight (player->mo, client->mo, SF_IGNOREVISIBILITY))
330 			{
331 				test = client->mo->AproxDistance(player->mo);
332 
333 				if (test < closest_dist)
334 				{
335 					closest_dist = test;
336 					target = client->mo;
337 				}
338 			}
339 		}
340 	}
341 
342 /*
343 	//Make a introducing to mate.
344 	if(target && target!=last_mate)
345 	{
346 		if((P_Random()%(200*bglobal.botnum))<3)
347 		{
348 			chat = c_teamup;
349 			if(target->bot)
350 					strcpy(c_target, botsingame[target->bot_id]);
351 						else if(target->player)
352 					strcpy(c_target, player_names[target->play_id]);
353 		}
354 	}
355 */
356 
357 	return target;
358 
359 }
360 
361 //MAKEME: Make this a smart decision
Find_enemy()362 AActor *DBot::Find_enemy ()
363 {
364 	int count;
365 	fixed_t closest_dist, temp; //To target.
366 	AActor *target;
367 	angle_t vangle;
368 	AActor *observer;
369 
370 	if (!deathmatch)
371 	{ // [RH] Take advantage of the Heretic/Hexen code to be a little smarter
372 		return P_RoughMonsterSearch (player->mo, 20);
373 	}
374 
375 	//Note: It's hard to ambush a bot who is not alone
376 	if (allround || mate)
377 		vangle = ANGLE_MAX;
378 	else
379 		vangle = ENEMY_SCAN_FOV;
380 	allround = false;
381 
382 	target = NULL;
383 	closest_dist = FIXED_MAX;
384 	if (bot_observer)
385 		observer = players[consoleplayer].mo;
386 	else
387 		observer = NULL;
388 
389 	for (count = 0; count < MAXPLAYERS; count++)
390 	{
391 		player_t *client = &players[count];
392 		if (playeringame[count]
393 			&& !player->mo->IsTeammate (client->mo)
394 			&& client->mo != observer
395 			&& client->mo->health > 0
396 			&& player->mo != client->mo)
397 		{
398 			if (Check_LOS (client->mo, vangle)) //Here's a strange one, when bot is standing still, the P_CheckSight 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.
399 			//if(P_CheckSight(player->mo, players[count].mo))
400 			{
401 				temp = client->mo->AproxDistance(player->mo);
402 
403 				//Too dark?
404 				if (temp > DARK_DIST &&
405 					client->mo->Sector->lightlevel < WHATS_DARK /*&&
406 					player->Powers & PW_INFRARED*/)
407 					continue;
408 
409 				if (temp < closest_dist)
410 				{
411 					closest_dist = temp;
412 					target = client->mo;
413 				}
414 			}
415 		}
416 	}
417 
418 	return target;
419 }
420 
421 
422 
423 //Creates a temporary mobj (invisible) at the given location.
SetBodyAt(fixed_t x,fixed_t y,fixed_t z,int hostnum)424 void FCajunMaster::SetBodyAt (fixed_t x, fixed_t y, fixed_t z, int hostnum)
425 {
426 	if (hostnum == 1)
427 	{
428 		if (body1)
429 		{
430 			body1->SetOrigin (x, y, z, false);
431 		}
432 		else
433 		{
434 			body1 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE);
435 		}
436 	}
437 	else if (hostnum == 2)
438 	{
439 		if (body2)
440 		{
441 			body2->SetOrigin (x, y, z, false);
442 		}
443 		else
444 		{
445 			body2 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE);
446 		}
447 	}
448 }
449 
450 //------------------------------------------
451 //    FireRox()
452 //
453 //Returns NULL if shouldn't fire
454 //else an angle (in degrees) are given
455 //This function assumes actor->player->angle
456 //has been set an is the main aiming angle.
457 
458 
459 //Emulates missile travel. Returns distance travelled.
FakeFire(AActor * source,AActor * dest,ticcmd_t * cmd)460 fixed_t FCajunMaster::FakeFire (AActor *source, AActor *dest, ticcmd_t *cmd)
461 {
462 	AActor *th = Spawn ("CajunTrace", source->PosPlusZ(4*8*FRACUNIT), NO_REPLACE);
463 
464 	th->target = source;		// where it came from
465 
466 	float speed = (float)th->Speed;
467 
468 	fixedvec3 fixvel = source->Vec3To(dest);
469 	TVector3<double> velocity(fixvel.x, fixvel.y, fixvel.z);
470 	velocity.MakeUnit();
471 	th->velx = FLOAT2FIXED(velocity[0] * speed);
472 	th->vely = FLOAT2FIXED(velocity[1] * speed);
473 	th->velz = FLOAT2FIXED(velocity[2] * speed);
474 
475 	fixed_t dist = 0;
476 
477 	while (dist < SAFE_SELF_MISDIST)
478 	{
479 		dist += th->Speed;
480 		th->Move(th->velx, th->vely, th->velz);
481 		if (!CleanAhead (th, th->X(), th->Y(), cmd))
482 			break;
483 	}
484 	th->Destroy ();
485 	return dist;
486 }
487 
FireRox(AActor * enemy,ticcmd_t * cmd)488 angle_t DBot::FireRox (AActor *enemy, ticcmd_t *cmd)
489 {
490 	fixed_t dist;
491 	angle_t ang;
492 	AActor *actor;
493 	int m;
494 
495 	bglobal.SetBodyAt (player->mo->X() + FixedMul(player->mo->velx, 5*FRACUNIT),
496 					   player->mo->Y() + FixedMul(player->mo->vely, 5*FRACUNIT),
497 					   player->mo->Z() + (player->mo->height / 2), 2);
498 
499 	actor = bglobal.body2;
500 
501 	dist = actor->AproxDistance (enemy);
502 	if (dist < SAFE_SELF_MISDIST)
503 		return 0;
504 	//Predict.
505 	m = (((dist+1)/FRACUNIT) / GetDefaultByName("Rocket")->Speed);
506 
507 	bglobal.SetBodyAt (enemy->X() + FixedMul(enemy->velx, (m+2*FRACUNIT)),
508 					   enemy->Y() + FixedMul(enemy->vely, (m+2*FRACUNIT)), ONFLOORZ, 1);
509 
510 	//try the predicted location
511 	if (P_CheckSight (actor, bglobal.body1, SF_IGNOREVISIBILITY)) //See the predicted location, so give a test missile
512 	{
513 		FCheckPosition tm;
514 		if (bglobal.SafeCheckPosition (player->mo, actor->X(), actor->Y(), tm))
515 		{
516 			if (bglobal.FakeFire (actor, bglobal.body1, cmd) >= SAFE_SELF_MISDIST)
517 			{
518 				ang = actor->AngleTo(bglobal.body1);
519 				return ang;
520 			}
521 		}
522 	}
523 	//Try fire straight.
524 	if (P_CheckSight (actor, enemy, 0))
525 	{
526 		if (bglobal.FakeFire (player->mo, enemy, cmd) >= SAFE_SELF_MISDIST)
527 		{
528 			ang = player->mo->AngleTo(enemy);
529 			return ang;
530 		}
531 	}
532 	return 0;
533 }
534 
535 // [RH] We absolutely do not want to pick things up here. The bot code is
536 // executed apart from all the other simulation code, so we don't want it
537 // creating side-effects during gameplay.
SafeCheckPosition(AActor * actor,fixed_t x,fixed_t y,FCheckPosition & tm)538 bool FCajunMaster::SafeCheckPosition (AActor *actor, fixed_t x, fixed_t y, FCheckPosition &tm)
539 {
540 	ActorFlags savedFlags = actor->flags;
541 	actor->flags &= ~MF_PICKUP;
542 	bool res = P_CheckPosition (actor, x, y, tm);
543 	actor->flags = savedFlags;
544 	return res;
545 }
546 
StartTravel()547 void FCajunMaster::StartTravel ()
548 {
549 	for (int i = 0; i < MAXPLAYERS; ++i)
550 	{
551 		if (players[i].Bot != NULL)
552 		{
553 			players[i].Bot->ChangeStatNum (STAT_TRAVELLING);
554 		}
555 	}
556 }
557 
FinishTravel()558 void FCajunMaster::FinishTravel ()
559 {
560 	for (int i = 0; i < MAXPLAYERS; ++i)
561 	{
562 		if (players[i].Bot != NULL)
563 		{
564 			players[i].Bot->ChangeStatNum (STAT_BOT);
565 		}
566 	}
567 }
568