1 /********************************
2 * B_Think.c                     *
3 * Description:                  *
4 * Functions for the different   *
5 * states that the bot           *
6 * uses. These functions are     *
7 * the main AI                   *
8 *                               *
9 *********************************/
10 
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 "stats.h"
18 #include "a_pickups.h"
19 #include "statnums.h"
20 #include "d_net.h"
21 #include "d_event.h"
22 
23 static FRandom pr_botmove ("BotMove");
24 
25 //This function is called each tic for each bot,
26 //so this is what the bot does.
Think()27 void DBot::Think ()
28 {
29 	ticcmd_t *cmd = &netcmds[player - players][((gametic + 1)/ticdup)%BACKUPTICS];
30 
31 	memset (cmd, 0, sizeof(*cmd));
32 
33 	if (enemy && enemy->health <= 0)
34 		enemy = NULL;
35 
36 	if (player->mo->health > 0) //Still alive
37 	{
38 		if (teamplay || !deathmatch)
39 			mate = Choose_Mate ();
40 
41 		angle_t oldyaw = player->mo->angle;
42 		int oldpitch = player->mo->pitch;
43 
44 		Set_enemy ();
45 		ThinkForMove (cmd);
46 		TurnToAng ();
47 
48 		cmd->ucmd.yaw = (short)((player->mo->angle - oldyaw) >> 16) / ticdup;
49 		cmd->ucmd.pitch = (short)((oldpitch - player->mo->pitch) >> 16);
50 		if (cmd->ucmd.pitch == -32768)
51 			cmd->ucmd.pitch = -32767;
52 		cmd->ucmd.pitch /= ticdup;
53 		player->mo->angle = oldyaw + (cmd->ucmd.yaw << 16) * ticdup;
54 		player->mo->pitch = oldpitch - (cmd->ucmd.pitch << 16) * ticdup;
55 	}
56 
57 	if (t_active)	t_active--;
58 	if (t_strafe)	t_strafe--;
59 	if (t_react)	t_react--;
60 	if (t_fight)	t_fight--;
61 	if (t_rocket)	t_rocket--;
62 	if (t_roam)		t_roam--;
63 
64 	//Respawn ticker
65 	if (t_respawn)
66 	{
67 		t_respawn--;
68 	}
69 	else if (player->mo->health <= 0)
70 	{ // Time to respawn
71 		cmd->ucmd.buttons |= BT_USE;
72 	}
73 }
74 
75 //how the bot moves.
76 //MAIN movement function.
ThinkForMove(ticcmd_t * cmd)77 void DBot::ThinkForMove (ticcmd_t *cmd)
78 {
79 	fixed_t dist;
80 	bool stuck;
81 	int r;
82 
83 	stuck = false;
84 	dist = dest ? player->mo->AproxDistance(dest) : 0;
85 
86 	if (missile &&
87 		((!missile->velx || !missile->vely) || !Check_LOS(missile, SHOOTFOV*3/2)))
88 	{
89 		sleft = !sleft;
90 		missile = NULL; //Probably ended its travel.
91 	}
92 
93 	if (player->mo->pitch > 0)
94 		player->mo->pitch -= 80;
95 	else if (player->mo->pitch <= -60)
96 		player->mo->pitch += 80;
97 
98 	//HOW TO MOVE:
99 	if (missile && (player->mo->AproxDistance(missile)<AVOID_DIST)) //try avoid missile got from P_Mobj.c thinking part.
100 	{
101 		Pitch (missile);
102 		angle = player->mo->AngleTo(missile);
103 		cmd->ucmd.sidemove = sleft ? -SIDERUN : SIDERUN;
104 		cmd->ucmd.forwardmove = -FORWARDRUN; //Back IS best.
105 
106 		if ((player->mo->AproxDistance(oldx, oldy)<50000)
107 			&& t_strafe<=0)
108 		{
109 			t_strafe = 5;
110 			sleft = !sleft;
111 		}
112 
113 		//If able to see enemy while avoiding missile, still fire at enemy.
114 		if (enemy && Check_LOS (enemy, SHOOTFOV))
115 			Dofire (cmd); //Order bot to fire current weapon
116 	}
117 	else if (enemy && P_CheckSight (player->mo, enemy, 0)) //Fight!
118 	{
119 		Pitch (enemy);
120 
121 		//Check if it's more important to get an item than fight.
122 		if (dest && (dest->flags&MF_SPECIAL)) //Must be an item, that is close enough.
123 		{
124 #define is(x) dest->IsKindOf (PClass::FindClass (#x))
125 			if (
126 				(
127 				 (player->mo->health < skill.isp &&
128 				  (is (Medikit) ||
129 				   is (Stimpack) ||
130 				   is (Soulsphere) ||
131 				   is (Megasphere) ||
132 				   is (CrystalVial)
133 				  )
134 				 ) || (
135 				  is (Invulnerability) ||
136 				  is (Invisibility) ||
137 				  is (Megasphere)
138 				 ) ||
139 				 dist < (GETINCOMBAT/4) ||
140 				 (player->ReadyWeapon == NULL || player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON)
141 				)
142 				&& (dist < GETINCOMBAT || (player->ReadyWeapon == NULL || player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON))
143 				&& Reachable (dest))
144 #undef is
145 			{
146 				goto roam; //Pick it up, no matter the situation. All bonuses are nice close up.
147 			}
148 		}
149 
150 		dest = NULL; //To let bot turn right
151 
152 		if (player->ReadyWeapon != NULL && !(player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON))
153 			player->mo->flags &= ~MF_DROPOFF; //Don't jump off any ledges when fighting.
154 
155 		if (!(enemy->flags3 & MF3_ISMONSTER))
156 			t_fight = AFTERTICS;
157 
158 		if (t_strafe <= 0 &&
159 			(player->mo->AproxDistance(oldx, oldy)<50000
160 			|| ((pr_botmove()%30)==10))
161 			)
162 		{
163 			stuck = true;
164 			t_strafe = 5;
165 			sleft = !sleft;
166 		}
167 
168 		angle = player->mo->AngleTo(enemy);
169 
170 		if (player->ReadyWeapon == NULL ||
171 			player->mo->AproxDistance(enemy) >
172 			player->ReadyWeapon->MoveCombatDist)
173 		{
174 			// If a monster, use lower speed (just for cooler apperance while strafing down doomed monster)
175 			cmd->ucmd.forwardmove = (enemy->flags3 & MF3_ISMONSTER) ? FORWARDWALK : FORWARDRUN;
176 		}
177 		else if (!stuck) //Too close, so move away.
178 		{
179 			// If a monster, use lower speed (just for cooler apperance while strafing down doomed monster)
180 			cmd->ucmd.forwardmove = (enemy->flags3 & MF3_ISMONSTER) ? -FORWARDWALK : -FORWARDRUN;
181 		}
182 
183 		//Strafing.
184 		if (enemy->flags3 & MF3_ISMONSTER) //It's just a monster so take it down cool.
185 		{
186 			cmd->ucmd.sidemove = sleft ? -SIDEWALK : SIDEWALK;
187 		}
188 		else
189 		{
190 			cmd->ucmd.sidemove = sleft ? -SIDERUN : SIDERUN;
191 		}
192 		Dofire (cmd); //Order bot to fire current weapon
193 	}
194 	else if (mate && !enemy && (!dest || dest==mate)) //Follow mate move.
195 	{
196 		fixed_t matedist;
197 
198 		Pitch (mate);
199 
200 		if (!Reachable (mate))
201 		{
202 			if (mate == dest && pr_botmove.Random() < 32)
203 			{ // [RH] If the mate is the dest, pick a new dest sometimes
204 				dest = NULL;
205 			}
206 			goto roam;
207 		}
208 
209 		angle = player->mo->AngleTo(mate);
210 
211 		matedist = player->mo->AproxDistance(mate);
212 		if (matedist > (FRIEND_DIST*2))
213 			cmd->ucmd.forwardmove = FORWARDRUN;
214 		else if (matedist > FRIEND_DIST)
215 			cmd->ucmd.forwardmove = FORWARDWALK; //Walk, when starting to get close.
216 		else if (matedist < FRIEND_DIST-(FRIEND_DIST/3)) //Got too close, so move away.
217 			cmd->ucmd.forwardmove = -FORWARDWALK;
218 	}
219 	else //Roam after something.
220 	{
221 		first_shot = true;
222 
223 	/////
224 	roam:
225 	/////
226 		if (enemy && Check_LOS (enemy, SHOOTFOV*3/2)) //If able to see enemy while avoiding missile , still fire at it.
227 			Dofire (cmd); //Order bot to fire current weapon
228 
229 		if (dest && !(dest->flags&MF_SPECIAL) && dest->health < 0)
230 		{ //Roaming after something dead.
231 			dest = NULL;
232 		}
233 
234 		if (dest == NULL)
235 		{
236 			if (t_fight && enemy) //Enemy/bot has jumped around corner. So what to do?
237 			{
238 				if (enemy->player)
239 				{
240 					if (((enemy->player->ReadyWeapon != NULL && enemy->player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) ||
241 						(pr_botmove()%100)>skill.isp) && player->ReadyWeapon != NULL && !(player->ReadyWeapon->WeaponFlags & WIF_WIMPY_WEAPON))
242 						dest = enemy;//Dont let enemy kill the bot by supressive fire. So charge enemy.
243 					else //hide while t_fight, but keep view at enemy.
244 						angle = player->mo->AngleTo(enemy);
245 				} //Just a monster, so kill it.
246 				else
247 					dest = enemy;
248 
249 				//VerifFavoritWeapon(player); //Dont know why here.., but it must be here, i know the reason, but not why at this spot, uh.
250 			}
251 			else //Choose a distant target. to get things going.
252 			{
253 				r = pr_botmove();
254 				if (r < 128)
255 				{
256 					TThinkerIterator<AInventory> it (STAT_INVENTORY, bglobal.firstthing);
257 					AInventory *item = it.Next();
258 
259 					if (item != NULL || (item = it.Next()) != NULL)
260 					{
261 						r &= 63;	// Only scan up to 64 entries at a time
262 						while (r)
263 						{
264 							--r;
265 							item = it.Next();
266 						}
267 						if (item == NULL)
268 						{
269 							item = it.Next();
270 						}
271 						bglobal.firstthing = item;
272 						dest = item;
273 					}
274 				}
275 				else if (mate && (r < 179 || P_CheckSight(player->mo, mate)))
276 				{
277 					dest = mate;
278 				}
279 				else if ((playeringame[(r&(MAXPLAYERS-1))]) && players[(r&(MAXPLAYERS-1))].mo->health > 0)
280 				{
281 					dest = players[(r&(MAXPLAYERS-1))].mo;
282 				}
283 			}
284 
285 			if (dest)
286 			{
287 				t_roam = MAXROAM;
288 			}
289 		}
290 		if (dest)
291 		{ //Bot has a target so roam after it.
292 			Roam (cmd);
293 		}
294 
295 	} //End of movement main part.
296 
297 	if (!t_roam && dest)
298 	{
299 		prev = dest;
300 		dest = NULL;
301 	}
302 
303 	if (t_fight<(AFTERTICS/2))
304 		player->mo->flags |= MF_DROPOFF;
305 
306 	oldx = player->mo->X();
307 	oldy = player->mo->Y();
308 }
309 
310 //BOT_WhatToGet
311 //
312 //Determines if the bot will roam after an item or not.
WhatToGet(AActor * item)313 void DBot::WhatToGet (AActor *item)
314 {
315 #define typeis(x) item->IsKindOf (PClass::FindClass (#x))
316 	if ((item->renderflags & RF_INVISIBLE) //Under respawn and away.
317 		|| item == prev)
318 	{
319 		return;
320 	}
321 	int weapgiveammo = (alwaysapplydmflags || deathmatch) && !(dmflags & DF_WEAPONS_STAY);
322 
323 	//if(pos && !bglobal.thingvis[pos->id][item->id]) continue;
324 //	if (item->IsKindOf (RUNTIME_CLASS(AArtifact)))
325 //		return;	// don't know how to use artifacts
326 	if (item->IsKindOf (RUNTIME_CLASS(AWeapon)))
327 	{
328 		// FIXME
329 		AWeapon *heldWeapon;
330 
331 		heldWeapon = static_cast<AWeapon *> (player->mo->FindInventory (item->GetClass()));
332 		if (heldWeapon != NULL)
333 		{
334 			if (!weapgiveammo)
335 				return;
336 			if ((heldWeapon->Ammo1 == NULL || heldWeapon->Ammo1->Amount >= heldWeapon->Ammo1->MaxAmount) &&
337 				(heldWeapon->Ammo2 == NULL || heldWeapon->Ammo2->Amount >= heldWeapon->Ammo2->MaxAmount))
338 			{
339 				return;
340 			}
341 		}
342 	}
343 	else if (item->IsKindOf (RUNTIME_CLASS(AAmmo)))
344 	{
345 		AAmmo *ammo = static_cast<AAmmo *> (item);
346 		const PClass *parent = ammo->GetParentAmmo ();
347 		AInventory *holdingammo = player->mo->FindInventory (parent);
348 
349 		if (holdingammo != NULL && holdingammo->Amount >= holdingammo->MaxAmount)
350 		{
351 			return;
352 		}
353 	}
354 	else if ((typeis (Megasphere) || typeis (Soulsphere) || typeis (HealthBonus)) && player->mo->health >= deh.MaxSoulsphere)
355 		return;
356 	else if (item->IsKindOf (RUNTIME_CLASS(AHealth)) && player->mo->health >= deh.MaxHealth /*MAXHEALTH*/)
357 		return;
358 
359 	if ((dest == NULL ||
360 		!(dest->flags & MF_SPECIAL)/* ||
361 		!Reachable (dest)*/)/* &&
362 		Reachable (item)*/)	// Calling Reachable slows this down tremendously
363 	{
364 		prev = dest;
365 		dest = item;
366 		t_roam = MAXROAM;
367 	}
368 }
369 
Set_enemy()370 void DBot::Set_enemy ()
371 {
372 	AActor *oldenemy;
373 
374 	if (enemy
375 		&& enemy->health > 0
376 		&& P_CheckSight (player->mo, enemy))
377 	{
378 		oldenemy = enemy;
379 	}
380 	else
381 	{
382 		oldenemy = NULL;
383 	}
384 
385 	// [RH] Don't even bother looking for a different enemy if this is not deathmatch
386 	// and we already have an existing enemy.
387 	if (deathmatch || !enemy)
388 	{
389 		allround = !!enemy;
390 		enemy = Find_enemy();
391 		if (!enemy)
392 			enemy = oldenemy; //Try go for last (it will be NULL if there wasn't anyone)
393 	}
394 	//Verify that that enemy is really something alive that bot can kill.
395 	if (enemy && ((enemy->health < 0 || !(enemy->flags&MF_SHOOTABLE)) || player->mo->IsFriend(enemy)))
396 		enemy = NULL;
397 }
398