1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 2007-2016 by John "JTE" Muniz.
4 // Copyright (C) 2011-2020 by Sonic Team Junior.
5 //
6 // This program is free software distributed under the
7 // terms of the GNU General Public License, version 2.
8 // See the 'LICENSE' file for more details.
9 //-----------------------------------------------------------------------------
10 /// \file b_bot.c
11 /// \brief Basic bot handling
12
13 #include "doomdef.h"
14 #include "d_player.h"
15 #include "g_game.h"
16 #include "r_main.h"
17 #include "p_local.h"
18 #include "b_bot.h"
19 #include "lua_hook.h"
20
21 // If you want multiple bots, variables like this will
22 // have to be stuffed in something accessible through player_t.
23 static boolean lastForward = false;
24 static boolean lastBlocked = false;
25 static boolean blocked = false;
26
27 static boolean jump_last = false;
28 static boolean spin_last = false;
29 static UINT8 anxiety = 0;
30 static boolean panic = false;
31 static UINT8 flymode = 0;
32 static boolean spinmode = false;
33 static boolean thinkfly = false;
34
B_ResetAI(void)35 static inline void B_ResetAI(void)
36 {
37 jump_last = false;
38 spin_last = false;
39 anxiety = 0;
40 panic = false;
41 flymode = 0;
42 spinmode = false;
43 thinkfly = false;
44 }
45
B_BuildTailsTiccmd(mobj_t * sonic,mobj_t * tails,ticcmd_t * cmd)46 static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
47 {
48 boolean forward=false, backward=false, left=false, right=false, jump=false, spin=false;
49
50 player_t *player = sonic->player, *bot = tails->player;
51 ticcmd_t *pcmd = &player->cmd;
52 boolean water = tails->eflags & MFE_UNDERWATER;
53 SINT8 flip = P_MobjFlip(tails);
54 boolean _2d = (tails->flags2 & MF2_TWOD) || twodlevel;
55 fixed_t scale = tails->scale;
56
57 fixed_t dist = P_AproxDistance(sonic->x - tails->x, sonic->y - tails->y);
58 fixed_t zdist = flip * (sonic->z - tails->z);
59 angle_t ang = sonic->angle;
60 fixed_t pmom = P_AproxDistance(sonic->momx, sonic->momy);
61 fixed_t bmom = P_AproxDistance(tails->momx, tails->momy);
62 fixed_t followmax = 128 * 8 * scale; // Max follow distance before AI begins to enter "panic" state
63 fixed_t followthres = 92 * scale; // Distance that AI will try to reach
64 fixed_t followmin = 32 * scale;
65 fixed_t comfortheight = 96 * scale;
66 fixed_t touchdist = 24 * scale;
67 boolean stalled = (bmom < scale >> 1) && dist > followthres; // Helps to see if the AI is having trouble catching up
68 boolean samepos = (sonic->x == tails->x && sonic->y == tails->y);
69
70 if (!samepos)
71 ang = R_PointToAngle2(tails->x, tails->y, sonic->x, sonic->y);
72
73 // We can't follow Sonic if he's not around!
74 if (!sonic || sonic->health <= 0)
75 return;
76
77 // Lua can handle it!
78 if (LUAh_BotAI(sonic, tails, cmd))
79 return;
80
81 if (tails->player->powers[pw_carry] == CR_MACESPIN || tails->player->powers[pw_carry] == CR_GENERIC)
82 {
83 boolean isrelevant = (sonic->player->powers[pw_carry] == CR_MACESPIN || sonic->player->powers[pw_carry] == CR_GENERIC);
84 dist = P_AproxDistance(tails->x-sonic->x, tails->y-sonic->y);
85 if (sonic->player->cmd.buttons & BT_JUMP && (sonic->player->pflags & PF_JUMPED) && isrelevant)
86 cmd->buttons |= BT_JUMP;
87 if (isrelevant)
88 {
89 cmd->forwardmove = sonic->player->cmd.forwardmove;
90 cmd->angleturn = abs((signed)(tails->angle - sonic->angle))>>16;
91 if (sonic->angle < tails->angle)
92 cmd->angleturn = -cmd->angleturn;
93 } else if (dist > FixedMul(512*FRACUNIT, tails->scale))
94 cmd->buttons |= BT_JUMP;
95 return;
96 }
97
98 // Adapted from CobaltBW's tails_AI.wad
99
100 // Check water
101 if (water)
102 {
103 followmin = 0;
104 followthres = 16*scale;
105 followmax >>= 1;
106 thinkfly = false;
107 }
108
109 // Check anxiety
110 if (spinmode)
111 {
112 anxiety = 0;
113 panic = false;
114 }
115 else if (dist > followmax || zdist > comfortheight || stalled)
116 {
117 anxiety = min(anxiety + 2, 70);
118 if (anxiety >= 70)
119 panic = true;
120 }
121 else
122 {
123 anxiety = max(anxiety - 1, 0);
124 panic = false;
125 }
126
127 // Orientation
128 if (bot->pflags & (PF_SPINNING|PF_STARTDASH))
129 {
130 cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
131 }
132 else if (flymode == 2)
133 {
134 cmd->angleturn = sonic->player->cmd.angleturn - (tails->angle >> 16);
135 }
136 else
137 {
138 cmd->angleturn = (ang - tails->angle) >> 16; // NOT FRACBITS DAMNIT
139 }
140
141 // ********
142 // FLY MODE
143 // spinmode check
144 if (spinmode || player->exiting)
145 thinkfly = false;
146 else
147 {
148 // Activate co-op flight
149 if (thinkfly && player->pflags & PF_JUMPED)
150 {
151 if (!jump_last)
152 {
153 jump = true;
154 flymode = 1;
155 thinkfly = false;
156 bot->pflags |= PF_CANCARRY;
157 }
158 }
159
160 // Check positioning
161 // Thinker for co-op flight
162 if (!(water || pmom || bmom)
163 && (dist < touchdist && !samepos)
164 && !(pcmd->forwardmove || pcmd->sidemove || player->dashspeed)
165 && P_IsObjectOnGround(sonic) && P_IsObjectOnGround(tails)
166 && !(player->pflags & PF_STASIS)
167 && bot->charability == CA_FLY)
168 thinkfly = true;
169 else
170 thinkfly = false;
171
172 // Set carried state
173 if (player->powers[pw_carry] == CR_PLAYER && sonic->tracer == tails)
174 {
175 flymode = 2;
176 }
177
178 // Ready for takeoff
179 if (flymode == 1)
180 {
181 thinkfly = false;
182 if (zdist < -64*scale || (flip * tails->momz) > scale) // Make sure we're not too high up
183 spin = true;
184 else if (!jump_last)
185 jump = true;
186
187 // Abort if the player moves away or spins
188 if (dist > followthres || player->dashspeed)
189 flymode = 0;
190 }
191 // Read player inputs while carrying
192 else if (flymode == 2)
193 {
194 cmd->forwardmove = pcmd->forwardmove;
195 cmd->sidemove = pcmd->sidemove;
196 if (pcmd->buttons & BT_SPIN)
197 {
198 spin = true;
199 jump = false;
200 }
201 else if (!jump_last)
202 jump = true;
203 // End flymode
204 if (player->powers[pw_carry] != CR_PLAYER)
205 {
206 flymode = 0;
207 }
208 }
209 }
210
211 if (flymode && P_IsObjectOnGround(tails) && !(pcmd->buttons & BT_JUMP))
212 flymode = 0;
213
214 // ********
215 // SPINNING
216 if (panic || flymode || !(player->pflags & PF_SPINNING) || (player->pflags & PF_JUMPED))
217 spinmode = false;
218 else
219 {
220 if (!_2d)
221 {
222 // Spindash
223 if (player->dashspeed)
224 {
225 if (dist < followthres && dist > touchdist) // Do positioning
226 {
227 cmd->angleturn = (ang - tails->angle) >> 16; // NOT FRACBITS DAMNIT
228 cmd->forwardmove = 50;
229 spinmode = true;
230 }
231 else if (dist < touchdist)
232 {
233 if (!bmom && (!(bot->pflags & PF_SPINNING) || (bot->dashspeed && bot->pflags & PF_SPINNING)))
234 {
235 cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
236 spin = true;
237 }
238 spinmode = true;
239 }
240 else
241 spinmode = false;
242 }
243 // Spin
244 else if (player->dashspeed == bot->dashspeed && player->pflags & PF_SPINNING)
245 {
246 if (bot->pflags & PF_SPINNING || !spin_last)
247 {
248 spin = true;
249 cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
250 cmd->forwardmove = MAXPLMOVE;
251 spinmode = true;
252 }
253 else
254 spinmode = false;
255 }
256 }
257 // 2D mode
258 else
259 {
260 if (((player->dashspeed && !bmom) || (player->dashspeed == bot->dashspeed && (player->pflags & PF_SPINNING)))
261 && ((bot->pflags & PF_SPINNING) || !spin_last))
262 {
263 spin = true;
264 spinmode = true;
265 }
266 }
267 }
268
269 // ********
270 // FOLLOW
271 if (!(flymode || spinmode))
272 {
273 // Too far
274 if (panic || dist > followthres)
275 {
276 if (!_2d)
277 cmd->forwardmove = MAXPLMOVE;
278 else if (sonic->x > tails->x)
279 cmd->sidemove = MAXPLMOVE;
280 else
281 cmd->sidemove = -MAXPLMOVE;
282 }
283 // Within threshold
284 else if (!panic && dist > followmin && abs(zdist) < 192*scale)
285 {
286 if (!_2d)
287 cmd->forwardmove = FixedHypot(pcmd->forwardmove, pcmd->sidemove);
288 else
289 cmd->sidemove = pcmd->sidemove;
290 }
291 // Below min
292 else if (dist < followmin)
293 {
294 // Copy inputs
295 cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
296 bot->drawangle = ang;
297 cmd->forwardmove = 8 * pcmd->forwardmove / 10;
298 cmd->sidemove = 8 * pcmd->sidemove / 10;
299 }
300 }
301
302 // ********
303 // JUMP
304 if (!(flymode || spinmode))
305 {
306 // Flying catch-up
307 if (bot->pflags & PF_THOKKED)
308 {
309 cmd->forwardmove = min(MAXPLMOVE, (dist/scale)>>3);
310 if (zdist < -64*scale)
311 spin = true;
312 else if (zdist > 0 && !jump_last)
313 jump = true;
314 }
315
316 // Just landed
317 if (tails->eflags & MFE_JUSTHITFLOOR)
318 jump = false;
319 // Start jump
320 else if (!jump_last && !(bot->pflags & PF_JUMPED) //&& !(player->pflags & PF_SPINNING)
321 && ((zdist > 32*scale && player->pflags & PF_JUMPED) // Following
322 || (zdist > 64*scale && panic) // Vertical catch-up
323 || (stalled && anxiety > 20 && bot->powers[pw_carry] == CR_NONE)
324 //|| (bmom < scale>>3 && dist > followthres && !(bot->powers[pw_carry])) // Stopped & not in carry state
325 || (bot->pflags & PF_SPINNING && !(bot->pflags & PF_JUMPED)))) // Spinning
326 jump = true;
327 // Hold jump
328 else if (bot->pflags & PF_JUMPED && jump_last && tails->momz*flip > 0 && (zdist > 0 || panic))
329 jump = true;
330 // Start flying
331 else if (bot->pflags & PF_JUMPED && panic && !jump_last && bot->charability == CA_FLY)
332 jump = true;
333 }
334
335 // ********
336 // HISTORY
337 jump_last = jump;
338 spin_last = spin;
339
340 // Turn the virtual keypresses into ticcmd_t.
341 B_KeysToTiccmd(tails, cmd, forward, backward, left, right, false, false, jump, spin);
342
343 // Update our status
344 lastForward = forward;
345 lastBlocked = blocked;
346 blocked = false;
347 }
348
B_BuildTiccmd(player_t * player,ticcmd_t * cmd)349 void B_BuildTiccmd(player_t *player, ticcmd_t *cmd)
350 {
351 // Can't build a ticcmd if we aren't spawned...
352 if (!player->mo)
353 return;
354
355 if (player->playerstate == PST_DEAD)
356 {
357 if (B_CheckRespawn(player))
358 cmd->buttons |= BT_JUMP;
359 return;
360 }
361
362 // Bot AI isn't programmed in analog.
363 CV_SetValue(&cv_analog[1], false);
364
365 // Let Lua scripts build ticcmds
366 if (LUAh_BotTiccmd(player, cmd))
367 return;
368
369 // We don't have any main character AI, sorry. D:
370 if (player-players == consoleplayer)
371 return;
372
373 // Basic Tails AI
374 B_BuildTailsTiccmd(players[consoleplayer].mo, player->mo, cmd);
375 }
376
B_KeysToTiccmd(mobj_t * mo,ticcmd_t * cmd,boolean forward,boolean backward,boolean left,boolean right,boolean strafeleft,boolean straferight,boolean jump,boolean spin)377 void B_KeysToTiccmd(mobj_t *mo, ticcmd_t *cmd, boolean forward, boolean backward, boolean left, boolean right, boolean strafeleft, boolean straferight, boolean jump, boolean spin)
378 {
379 // don't try to do stuff if your sonic is in a minecart or something
380 if (players[consoleplayer].powers[pw_carry] && players[consoleplayer].powers[pw_carry] != CR_PLAYER)
381 return;
382 // Turn the virtual keypresses into ticcmd_t.
383 if (twodlevel || mo->flags2 & MF2_TWOD) {
384 if (players[consoleplayer].climbing
385 || mo->player->pflags & PF_GLIDING) {
386 // Don't mess with bot inputs during these unhandled movement conditions.
387 // The normal AI doesn't use abilities, so custom AI should be sending us exactly what it wants anyway.
388 if (forward)
389 cmd->forwardmove += MAXPLMOVE<<FRACBITS>>16;
390 if (backward)
391 cmd->forwardmove -= MAXPLMOVE<<FRACBITS>>16;
392 if (left || strafeleft)
393 cmd->sidemove -= MAXPLMOVE<<FRACBITS>>16;
394 if (right || straferight)
395 cmd->sidemove += MAXPLMOVE<<FRACBITS>>16;
396 } else {
397 // In standard 2D mode, interpret "forward" as "the way you're facing" and everything else as "the way you're not facing"
398 if (left || right)
399 backward = true;
400 left = right = false;
401 if (forward) {
402 if (mo->angle < ANGLE_90 || mo->angle > ANGLE_270)
403 right = true;
404 else
405 left = true;
406 } else if (backward) {
407 if (mo->angle < ANGLE_90 || mo->angle > ANGLE_270)
408 left = true;
409 else
410 right = true;
411 }
412 if (left || strafeleft)
413 cmd->sidemove -= MAXPLMOVE<<FRACBITS>>16;
414 if (right || straferight)
415 cmd->sidemove += MAXPLMOVE<<FRACBITS>>16;
416 }
417 } else {
418 angle_t angle;
419 if (forward)
420 cmd->forwardmove += MAXPLMOVE<<FRACBITS>>16;
421 if (backward)
422 cmd->forwardmove -= MAXPLMOVE<<FRACBITS>>16;
423 if (left)
424 cmd->angleturn += 1280;
425 if (right)
426 cmd->angleturn -= 1280;
427 if (strafeleft)
428 cmd->sidemove -= MAXPLMOVE<<FRACBITS>>16;
429 if (straferight)
430 cmd->sidemove += MAXPLMOVE<<FRACBITS>>16;
431
432 // cap inputs so the bot can't accelerate faster diagonally
433 angle = R_PointToAngle2(0, 0, cmd->sidemove << FRACBITS, cmd->forwardmove << FRACBITS);
434 {
435 INT32 maxforward = abs(P_ReturnThrustY(NULL, angle, MAXPLMOVE));
436 INT32 maxside = abs(P_ReturnThrustX(NULL, angle, MAXPLMOVE));
437 cmd->forwardmove = max(min(cmd->forwardmove, maxforward), -maxforward);
438 cmd->sidemove = max(min(cmd->sidemove, maxside), -maxside);
439 }
440 }
441 if (jump)
442 cmd->buttons |= BT_JUMP;
443 if (spin)
444 cmd->buttons |= BT_SPIN;
445 }
446
B_MoveBlocked(player_t * player)447 void B_MoveBlocked(player_t *player)
448 {
449 (void)player;
450 blocked = true;
451 }
452
B_CheckRespawn(player_t * player)453 boolean B_CheckRespawn(player_t *player)
454 {
455 mobj_t *sonic = players[consoleplayer].mo;
456 mobj_t *tails = player->mo;
457
458 // We can't follow Sonic if he's not around!
459 if (!sonic || sonic->health <= 0)
460 return false;
461
462 // B_RespawnBot doesn't do anything if the condition above this isn't met
463 {
464 UINT8 shouldForce = LUAh_BotRespawn(sonic, tails);
465
466 if (P_MobjWasRemoved(sonic) || P_MobjWasRemoved(tails))
467 return (shouldForce == 1); // mobj was removed
468
469 if (shouldForce == 1)
470 return true;
471 else if (shouldForce == 2)
472 return false;
473 }
474
475 // Check if Sonic is busy first.
476 // If he's doing any of these things, he probably doesn't want to see us.
477 if (sonic->player->pflags & (PF_GLIDING|PF_SLIDING|PF_BOUNCING)
478 || (sonic->player->panim != PA_IDLE && sonic->player->panim != PA_WALK)
479 || (sonic->player->powers[pw_carry] && sonic->player->powers[pw_carry] != CR_PLAYER))
480 return false;
481
482 // Low ceiling, do not want!
483 if (sonic->eflags & MFE_VERTICALFLIP)
484 {
485 if (sonic->z - sonic->floorz < (sonic->player->exiting ? 5 : 2)*sonic->height)
486 return false;
487 }
488 else if (sonic->ceilingz - sonic->z < (sonic->player->exiting ? 6 : 3)*sonic->height)
489 return false;
490
491 // If you're dead, wait a few seconds to respawn.
492 if (player->playerstate == PST_DEAD) {
493 if (player->deadtimer > 4*TICRATE)
494 return true;
495 return false;
496 }
497
498 // If you can't see Sonic, I guess we should?
499 if (!P_CheckSight(sonic, tails) && P_AproxDistance(P_AproxDistance(tails->x-sonic->x, tails->y-sonic->y), tails->z-sonic->z) > FixedMul(1024*FRACUNIT, tails->scale))
500 return true;
501 return false;
502 }
503
B_RespawnBot(INT32 playernum)504 void B_RespawnBot(INT32 playernum)
505 {
506 player_t *player = &players[playernum];
507 fixed_t x,y,z;
508 mobj_t *sonic = players[consoleplayer].mo;
509 mobj_t *tails;
510
511 if (!sonic || sonic->health <= 0)
512 return;
513
514 B_ResetAI();
515
516 player->bot = 1;
517 P_SpawnPlayer(playernum);
518 tails = player->mo;
519
520 x = sonic->x;
521 y = sonic->y;
522 if (sonic->eflags & MFE_VERTICALFLIP) {
523 tails->eflags |= MFE_VERTICALFLIP;
524 z = sonic->z - (512*sonic->scale);
525 if (z < sonic->floorz)
526 z = sonic->floorz;
527 } else {
528 z = sonic->z + sonic->height + (512*sonic->scale);
529 if (z > sonic->ceilingz - sonic->height)
530 z = sonic->ceilingz - sonic->height;
531 }
532
533 if (sonic->flags2 & MF2_OBJECTFLIP)
534 tails->flags2 |= MF2_OBJECTFLIP;
535 if (sonic->flags2 & MF2_TWOD)
536 tails->flags2 |= MF2_TWOD;
537 if (sonic->eflags & MFE_UNDERWATER)
538 tails->eflags |= MFE_UNDERWATER;
539 player->powers[pw_underwater] = sonic->player->powers[pw_underwater];
540 player->powers[pw_spacetime] = sonic->player->powers[pw_spacetime];
541 player->powers[pw_gravityboots] = sonic->player->powers[pw_gravityboots];
542 player->powers[pw_nocontrol] = sonic->player->powers[pw_nocontrol];
543 player->acceleration = sonic->player->acceleration;
544 player->accelstart = sonic->player->accelstart;
545 player->thrustfactor = sonic->player->thrustfactor;
546 player->normalspeed = sonic->player->normalspeed;
547 player->pflags |= PF_AUTOBRAKE|(sonic->player->pflags & PF_DIRECTIONCHAR);
548
549 P_TeleportMove(tails, x, y, z);
550 if (player->charability == CA_FLY)
551 {
552 P_SetPlayerMobjState(tails, S_PLAY_FLY);
553 tails->player->powers[pw_tailsfly] = (UINT16)-1;
554 }
555 else
556 P_SetPlayerMobjState(tails, S_PLAY_FALL);
557 P_SetScale(tails, sonic->scale);
558 tails->destscale = sonic->destscale;
559 }
560
B_HandleFlightIndicator(player_t * player)561 void B_HandleFlightIndicator(player_t *player)
562 {
563 mobj_t *tails = player->mo;
564
565 if (!tails)
566 return;
567
568 if (thinkfly && player->bot == 1 && tails->health)
569 {
570 if (!tails->hnext)
571 {
572 P_SetTarget(&tails->hnext, P_SpawnMobjFromMobj(tails, 0, 0, 0, MT_OVERLAY));
573 if (tails->hnext)
574 {
575 P_SetTarget(&tails->hnext->target, tails);
576 P_SetTarget(&tails->hnext->hprev, tails);
577 P_SetMobjState(tails->hnext, S_FLIGHTINDICATOR);
578 }
579 }
580 }
581 else if (tails->hnext && tails->hnext->type == MT_OVERLAY && tails->hnext->state == states+S_FLIGHTINDICATOR)
582 {
583 P_RemoveMobj(tails->hnext);
584 P_SetTarget(&tails->hnext, NULL);
585 }
586 }
587