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