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