2014-03-15 16:59:03 +00:00
|
|
|
// SONIC ROBO BLAST 2
|
|
|
|
//-----------------------------------------------------------------------------
|
2016-05-18 00:42:11 +00:00
|
|
|
// Copyright (C) 2007-2016 by John "JTE" Muniz.
|
2018-11-25 12:35:38 +00:00
|
|
|
// Copyright (C) 2011-2018 by Sonic Team Junior.
|
2014-03-15 16:59:03 +00:00
|
|
|
//
|
|
|
|
// This program is free software distributed under the
|
|
|
|
// terms of the GNU General Public License, version 2.
|
|
|
|
// See the 'LICENSE' file for more details.
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/// \file b_bot.c
|
|
|
|
/// \brief Basic bot handling
|
|
|
|
|
|
|
|
#include "doomdef.h"
|
|
|
|
#include "d_player.h"
|
|
|
|
#include "g_game.h"
|
|
|
|
#include "r_main.h"
|
|
|
|
#include "p_local.h"
|
|
|
|
#include "b_bot.h"
|
|
|
|
#include "lua_hook.h"
|
|
|
|
|
|
|
|
// If you want multiple bots, variables like this will
|
|
|
|
// have to be stuffed in something accessible through player_t.
|
|
|
|
static boolean lastForward = false;
|
|
|
|
static boolean lastBlocked = false;
|
|
|
|
static boolean blocked = false;
|
|
|
|
|
|
|
|
static inline void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
|
|
|
|
{
|
|
|
|
boolean forward=false, backward=false, left=false, right=false, jump=false, spin=false;
|
|
|
|
angle_t angle;
|
|
|
|
INT16 rangle;
|
|
|
|
fixed_t dist;
|
|
|
|
|
|
|
|
// We can't follow Sonic if he's not around!
|
|
|
|
if (!sonic || sonic->health <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
#ifdef HAVE_BLUA
|
|
|
|
// Lua can handle it!
|
|
|
|
if (LUAh_BotAI(sonic, tails, cmd))
|
|
|
|
return;
|
|
|
|
#endif
|
|
|
|
|
2016-09-23 22:48:48 +00:00
|
|
|
if (tails->player->powers[pw_carry] == CR_MACESPIN || tails->player->powers[pw_carry] == CR_GENERIC)
|
2014-03-15 16:59:03 +00:00
|
|
|
{
|
2016-09-23 22:48:48 +00:00
|
|
|
boolean isrelevant = (sonic->player->powers[pw_carry] == CR_MACESPIN || sonic->player->powers[pw_carry] == CR_GENERIC);
|
2014-03-15 16:59:03 +00:00
|
|
|
dist = P_AproxDistance(tails->x-sonic->x, tails->y-sonic->y);
|
2016-09-23 22:48:48 +00:00
|
|
|
if (sonic->player->cmd.buttons & BT_JUMP && (sonic->player->pflags & PF_JUMPED) && isrelevant)
|
2014-03-15 16:59:03 +00:00
|
|
|
cmd->buttons |= BT_JUMP;
|
2016-09-23 22:48:48 +00:00
|
|
|
if (isrelevant)
|
2014-03-15 16:59:03 +00:00
|
|
|
{
|
|
|
|
cmd->forwardmove = sonic->player->cmd.forwardmove;
|
2016-03-30 15:47:27 +00:00
|
|
|
cmd->angleturn = abs((signed)(tails->angle - sonic->angle))>>16;
|
2014-03-15 16:59:03 +00:00
|
|
|
if (sonic->angle < tails->angle)
|
|
|
|
cmd->angleturn = -cmd->angleturn;
|
|
|
|
} else if (dist > FixedMul(512*FRACUNIT, tails->scale))
|
|
|
|
cmd->buttons |= BT_JUMP;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gather data about the environment
|
|
|
|
dist = P_AproxDistance(tails->x-sonic->x, tails->y-sonic->y);
|
|
|
|
if (tails->player->pflags & PF_STARTDASH)
|
|
|
|
angle = sonic->angle;
|
|
|
|
else
|
|
|
|
angle = R_PointToAngle2(tails->x, tails->y, sonic->x, sonic->y);
|
|
|
|
|
|
|
|
// Decide which direction to turn
|
|
|
|
angle = (tails->angle - angle);
|
|
|
|
if (angle < ANGLE_180) {
|
|
|
|
right = true; // We need to turn right
|
|
|
|
rangle = AngleFixed(angle)>>FRACBITS;
|
|
|
|
} else {
|
|
|
|
left = true; // We need to turn left
|
|
|
|
rangle = 360-(AngleFixed(angle)>>FRACBITS);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decide to move forward if you're finished turning
|
|
|
|
if (abs(rangle) < 10) { // We're facing the right way?
|
|
|
|
left = right = false; // Stop turning
|
|
|
|
forward = true; // and walk forward instead.
|
|
|
|
}
|
|
|
|
if (dist < (sonic->radius+tails->radius)*3) // We're close enough?
|
|
|
|
forward = false; // Stop walking.
|
|
|
|
|
|
|
|
// Decide when to jump
|
|
|
|
if (!(tails->player->pflags & (PF_JUMPED|PF_JUMPDOWN))) { // We're not jumping yet...
|
|
|
|
if (forward && lastForward && blocked && lastBlocked) // We've been stopped by a wall or something
|
|
|
|
jump = true; // Try to jump up
|
|
|
|
} else if ((tails->player->pflags & (PF_JUMPDOWN|PF_JUMPED)) == (PF_JUMPDOWN|PF_JUMPED)) { // When we're already jumping...
|
|
|
|
if (lastForward && blocked) // We're still stuck on something?
|
|
|
|
jump = true;
|
|
|
|
if (sonic->floorz > tails->floorz) // He's still above us? Jump HIGHER, then!
|
|
|
|
jump = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decide when to spin
|
|
|
|
if (sonic->player->pflags & PF_STARTDASH
|
|
|
|
&& (tails->player->pflags & PF_STARTDASH || (P_AproxDistance(tails->momx, tails->momy) < 2*FRACUNIT && !forward)))
|
|
|
|
spin = true;
|
|
|
|
|
|
|
|
// Turn the virtual keypresses into ticcmd_t.
|
|
|
|
B_KeysToTiccmd(tails, cmd, forward, backward, left, right, false, false, jump, spin);
|
|
|
|
|
|
|
|
// Update our status
|
|
|
|
lastForward = forward;
|
|
|
|
lastBlocked = blocked;
|
|
|
|
blocked = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void B_BuildTiccmd(player_t *player, ticcmd_t *cmd)
|
|
|
|
{
|
|
|
|
// Can't build a ticcmd if we aren't spawned...
|
|
|
|
if (!player->mo)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (player->playerstate == PST_DEAD)
|
|
|
|
{
|
|
|
|
if (B_CheckRespawn(player))
|
|
|
|
cmd->buttons |= BT_JUMP;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bot AI isn't programmed in analog.
|
|
|
|
CV_SetValue(&cv_analog2, false);
|
|
|
|
|
|
|
|
#ifdef HAVE_BLUA
|
|
|
|
// Let Lua scripts build ticcmds
|
|
|
|
if (LUAh_BotTiccmd(player, cmd))
|
|
|
|
return;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// We don't have any main character AI, sorry. D:
|
|
|
|
if (player-players == consoleplayer)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Basic Tails AI
|
|
|
|
B_BuildTailsTiccmd(players[consoleplayer].mo, player->mo, cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2019-10-06 11:13:13 +00:00
|
|
|
// don't try to do stuff if your sonic is in a minecart or something
|
|
|
|
if (players[consoleplayer].powers[pw_carry])
|
|
|
|
return;
|
2014-03-15 16:59:03 +00:00
|
|
|
// Turn the virtual keypresses into ticcmd_t.
|
|
|
|
if (twodlevel || mo->flags2 & MF2_TWOD) {
|
|
|
|
if (players[consoleplayer].climbing
|
|
|
|
|| mo->player->pflags & PF_GLIDING) {
|
|
|
|
// Don't mess with bot inputs during these unhandled movement conditions.
|
|
|
|
// The normal AI doesn't use abilities, so custom AI should be sending us exactly what it wants anyway.
|
|
|
|
if (forward)
|
|
|
|
cmd->forwardmove += MAXPLMOVE<<FRACBITS>>16;
|
|
|
|
if (backward)
|
|
|
|
cmd->forwardmove -= MAXPLMOVE<<FRACBITS>>16;
|
|
|
|
if (left || strafeleft)
|
|
|
|
cmd->sidemove -= MAXPLMOVE<<FRACBITS>>16;
|
|
|
|
if (right || straferight)
|
|
|
|
cmd->sidemove += MAXPLMOVE<<FRACBITS>>16;
|
|
|
|
} else {
|
|
|
|
// In standard 2D mode, interpret "forward" as "the way you're facing" and everything else as "the way you're not facing"
|
|
|
|
if (left || right)
|
|
|
|
backward = true;
|
|
|
|
left = right = false;
|
|
|
|
if (forward) {
|
|
|
|
if (mo->angle < ANGLE_90 || mo->angle > ANGLE_270)
|
|
|
|
right = true;
|
|
|
|
else
|
|
|
|
left = true;
|
|
|
|
} else if (backward) {
|
|
|
|
if (mo->angle < ANGLE_90 || mo->angle > ANGLE_270)
|
|
|
|
left = true;
|
|
|
|
else
|
|
|
|
right = true;
|
|
|
|
}
|
|
|
|
if (left || strafeleft)
|
|
|
|
cmd->sidemove -= MAXPLMOVE<<FRACBITS>>16;
|
|
|
|
if (right || straferight)
|
|
|
|
cmd->sidemove += MAXPLMOVE<<FRACBITS>>16;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (forward)
|
|
|
|
cmd->forwardmove += MAXPLMOVE<<FRACBITS>>16;
|
|
|
|
if (backward)
|
|
|
|
cmd->forwardmove -= MAXPLMOVE<<FRACBITS>>16;
|
|
|
|
if (left)
|
|
|
|
cmd->angleturn += 1280;
|
|
|
|
if (right)
|
|
|
|
cmd->angleturn -= 1280;
|
|
|
|
if (strafeleft)
|
|
|
|
cmd->sidemove -= MAXPLMOVE<<FRACBITS>>16;
|
|
|
|
if (straferight)
|
|
|
|
cmd->sidemove += MAXPLMOVE<<FRACBITS>>16;
|
|
|
|
}
|
|
|
|
if (jump)
|
|
|
|
cmd->buttons |= BT_JUMP;
|
|
|
|
if (spin)
|
|
|
|
cmd->buttons |= BT_USE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void B_MoveBlocked(player_t *player)
|
|
|
|
{
|
|
|
|
(void)player;
|
|
|
|
blocked = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean B_CheckRespawn(player_t *player)
|
|
|
|
{
|
|
|
|
mobj_t *sonic = players[consoleplayer].mo;
|
|
|
|
mobj_t *tails = player->mo;
|
|
|
|
|
|
|
|
// We can't follow Sonic if he's not around!
|
|
|
|
if (!sonic || sonic->health <= 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Check if Sonic is busy first.
|
|
|
|
// If he's doing any of these things, he probably doesn't want to see us.
|
2017-03-14 18:11:17 +00:00
|
|
|
if (sonic->player->pflags & (PF_GLIDING|PF_SLIDING|PF_BOUNCING)
|
2016-09-23 22:48:48 +00:00
|
|
|
|| (sonic->player->panim != PA_IDLE && sonic->player->panim != PA_WALK)
|
|
|
|
|| (sonic->player->powers[pw_carry]))
|
2014-03-15 16:59:03 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// Low ceiling, do not want!
|
2019-10-06 14:11:00 +00:00
|
|
|
if (sonic->eflags & MFE_VERTICALFLIP)
|
|
|
|
{
|
|
|
|
if (sonic->z - sonic->floorz < (sonic->player->exiting ? 5 : 2)*sonic->height)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else if (sonic->ceilingz - sonic->z < (sonic->player->exiting ? 6 : 3)*sonic->height)
|
2014-03-15 16:59:03 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// If you're dead, wait a few seconds to respawn.
|
|
|
|
if (player->playerstate == PST_DEAD) {
|
|
|
|
if (player->deadtimer > 4*TICRATE)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If you can't see Sonic, I guess we should?
|
|
|
|
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))
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void B_RespawnBot(INT32 playernum)
|
|
|
|
{
|
|
|
|
player_t *player = &players[playernum];
|
|
|
|
fixed_t x,y,z;
|
|
|
|
mobj_t *sonic = players[consoleplayer].mo;
|
|
|
|
mobj_t *tails;
|
|
|
|
|
|
|
|
if (!sonic || sonic->health <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
player->bot = 1;
|
|
|
|
P_SpawnPlayer(playernum);
|
|
|
|
tails = player->mo;
|
|
|
|
|
|
|
|
x = sonic->x;
|
|
|
|
y = sonic->y;
|
|
|
|
if (sonic->eflags & MFE_VERTICALFLIP) {
|
|
|
|
tails->eflags |= MFE_VERTICALFLIP;
|
2019-10-06 14:11:00 +00:00
|
|
|
z = sonic->z - (512*sonic->scale);
|
2014-03-15 16:59:03 +00:00
|
|
|
if (z < sonic->floorz)
|
|
|
|
z = sonic->floorz;
|
|
|
|
} else {
|
2019-10-06 14:11:00 +00:00
|
|
|
z = sonic->z + sonic->height + (512*sonic->scale);
|
2014-03-15 16:59:03 +00:00
|
|
|
if (z > sonic->ceilingz - sonic->height)
|
|
|
|
z = sonic->ceilingz - sonic->height;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sonic->flags2 & MF2_OBJECTFLIP)
|
|
|
|
tails->flags2 |= MF2_OBJECTFLIP;
|
|
|
|
if (sonic->flags2 & MF2_TWOD)
|
|
|
|
tails->flags2 |= MF2_TWOD;
|
|
|
|
if (sonic->eflags & MFE_UNDERWATER)
|
|
|
|
tails->eflags |= MFE_UNDERWATER;
|
|
|
|
player->powers[pw_underwater] = sonic->player->powers[pw_underwater];
|
|
|
|
player->powers[pw_spacetime] = sonic->player->powers[pw_spacetime];
|
|
|
|
player->powers[pw_gravityboots] = sonic->player->powers[pw_gravityboots];
|
|
|
|
player->powers[pw_nocontrol] = sonic->player->powers[pw_nocontrol];
|
2017-09-15 19:34:46 +00:00
|
|
|
player->acceleration = sonic->player->acceleration;
|
|
|
|
player->accelstart = sonic->player->accelstart;
|
|
|
|
player->thrustfactor = sonic->player->thrustfactor;
|
|
|
|
player->normalspeed = sonic->player->normalspeed;
|
2018-06-09 17:06:14 +00:00
|
|
|
player->pflags |= PF_AUTOBRAKE|(sonic->player->pflags & PF_DIRECTIONCHAR);
|
2014-03-15 16:59:03 +00:00
|
|
|
|
|
|
|
P_TeleportMove(tails, x, y, z);
|
|
|
|
if (player->charability == CA_FLY)
|
|
|
|
{
|
2015-01-22 15:23:45 +00:00
|
|
|
P_SetPlayerMobjState(tails, S_PLAY_FLY);
|
2014-03-15 16:59:03 +00:00
|
|
|
tails->player->powers[pw_tailsfly] = (UINT16)-1;
|
|
|
|
}
|
|
|
|
else
|
2015-01-22 15:23:45 +00:00
|
|
|
P_SetPlayerMobjState(tails, S_PLAY_FALL);
|
2014-03-15 16:59:03 +00:00
|
|
|
P_SetScale(tails, sonic->scale);
|
|
|
|
tails->destscale = sonic->destscale;
|
|
|
|
}
|