Merge branch 'addplayer' into 'next'

Multiplayer Bot Features (aka AddPlayer)

See merge request STJr/SRB2!1383
This commit is contained in:
Shane Ellis 2021-08-25 21:38:37 +00:00
commit ccc71ddafc
15 changed files with 506 additions and 219 deletions

View file

@ -18,29 +18,38 @@
#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 boolean jump_last = false;
static boolean spin_last = false;
static UINT8 anxiety = 0;
static boolean panic = false;
static UINT8 flymode = 0;
static boolean spinmode = false;
static boolean thinkfly = false;
static inline void B_ResetAI(void)
void B_UpdateBotleader(player_t *player)
{
jump_last = false;
spin_last = false;
anxiety = 0;
panic = false;
flymode = 0;
spinmode = false;
thinkfly = false;
UINT32 i;
fixed_t dist;
fixed_t neardist = INT32_MAX;
player_t *nearplayer = NULL;
//Find new botleader
for (i = 0; i < MAXPLAYERS; i++)
{
if (players[i].bot || players[i].playerstate != PST_LIVE || players[i].spectator || !players[i].mo)
continue;
if (!player->mo) //Can't do distance calculations if there's no player object, so we'll just take the first we find
{
player->botleader = &players[i];
return;
}
//Update best candidate based on nearest distance
dist = R_PointToDist2(player->mo->x, player->mo->y, players[i].mo->x, players[i].mo->y);
if (neardist > dist)
{
neardist = dist;
nearplayer = &players[i];
}
}
//Set botleader to best candidate (or null if none available)
player->botleader = nearplayer;
}
static inline void B_ResetAI(botmem_t *mem)
{
mem->thinkstate = AI_FOLLOW;
mem->catchup_tics = 0;
}
static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
@ -49,39 +58,47 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
player_t *player = sonic->player, *bot = tails->player;
ticcmd_t *pcmd = &player->cmd;
boolean water = tails->eflags & MFE_UNDERWATER;
botmem_t *mem = &bot->botmem;
boolean water = (tails->eflags & MFE_UNDERWATER);
SINT8 flip = P_MobjFlip(tails);
boolean _2d = (tails->flags2 & MF2_TWOD) || twodlevel;
fixed_t scale = tails->scale;
boolean jump_last = (bot->lastbuttons & BT_JUMP);
boolean spin_last = (bot->lastbuttons & BT_SPIN);
fixed_t dist = P_AproxDistance(sonic->x - tails->x, sonic->y - tails->y);
fixed_t zdist = flip * (sonic->z - tails->z);
angle_t ang = sonic->angle;
fixed_t pmom = P_AproxDistance(sonic->momx, sonic->momy);
fixed_t bmom = P_AproxDistance(tails->momx, tails->momy);
fixed_t followmax = 128 * 8 * scale; // Max follow distance before AI begins to enter "panic" state
fixed_t followmax = 128 * 8 * scale; // Max follow distance before AI begins to enter catchup state
fixed_t followthres = 92 * scale; // Distance that AI will try to reach
fixed_t followmin = 32 * scale;
fixed_t comfortheight = 96 * scale;
fixed_t touchdist = 24 * scale;
boolean stalled = (bmom < scale >> 1) && dist > followthres; // Helps to see if the AI is having trouble catching up
boolean samepos = (sonic->x == tails->x && sonic->y == tails->y);
boolean blocked = bot->blocked;
if (!samepos)
ang = R_PointToAngle2(tails->x, tails->y, sonic->x, sonic->y);
// We can't follow Sonic if he's not around!
if (!sonic || sonic->health <= 0)
return;
// Lua can handle it!
if (LUA_HookBotAI(sonic, tails, cmd))
return;
// We can't follow Sonic if he's not around!
if (!sonic || sonic->health <= 0)
{
mem->thinkstate = AI_STANDBY;
return;
}
else if (mem->thinkstate == AI_STANDBY)
mem->thinkstate = AI_FOLLOW;
if (tails->player->powers[pw_carry] == CR_MACESPIN || tails->player->powers[pw_carry] == CR_GENERIC)
{
boolean isrelevant = (sonic->player->powers[pw_carry] == CR_MACESPIN || sonic->player->powers[pw_carry] == CR_GENERIC);
dist = P_AproxDistance(tails->x-sonic->x, tails->y-sonic->y);
if (sonic->player->cmd.buttons & BT_JUMP && (sonic->player->pflags & PF_JUMPED) && isrelevant)
cmd->buttons |= BT_JUMP;
if (isrelevant)
@ -103,56 +120,57 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
followmin = 0;
followthres = 16*scale;
followmax >>= 1;
thinkfly = false;
if (mem->thinkstate == AI_THINKFLY)
mem->thinkstate = AI_FOLLOW;
}
// Check anxiety
if (spinmode)
// Update catchup_tics
if (mem->thinkstate == AI_SPINFOLLOW)
{
anxiety = 0;
panic = false;
mem-> catchup_tics = 0;
}
else if (dist > followmax || zdist > comfortheight || stalled)
{
anxiety = min(anxiety + 2, 70);
if (anxiety >= 70)
panic = true;
mem-> catchup_tics = min(mem-> catchup_tics + 2, 70);
if (mem-> catchup_tics >= 70)
mem->thinkstate = AI_CATCHUP;
}
else
{
anxiety = max(anxiety - 1, 0);
panic = false;
mem-> catchup_tics = max(mem-> catchup_tics - 1, 0);
if (mem->thinkstate == AI_CATCHUP)
mem->thinkstate = AI_FOLLOW;
}
// Orientation
// cmd->angleturn won't be relative to player angle, since we're not going through G_BuildTiccmd.
if (bot->pflags & (PF_SPINNING|PF_STARTDASH))
{
cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
cmd->angleturn = (sonic->angle) >> 16; // NOT FRACBITS DAMNIT
}
else if (flymode == 2)
else if (mem->thinkstate == AI_FLYCARRY)
{
cmd->angleturn = sonic->player->cmd.angleturn - (tails->angle >> 16);
cmd->angleturn = sonic->player->cmd.angleturn;
}
else
{
cmd->angleturn = (ang - tails->angle) >> 16; // NOT FRACBITS DAMNIT
cmd->angleturn = (ang) >> 16; // NOT FRACBITS DAMNIT
}
// ********
// FLY MODE
// spinmode check
if (spinmode || player->exiting)
thinkfly = false;
// exiting check
if (player->exiting && mem->thinkstate == AI_THINKFLY)
mem->thinkstate = AI_FOLLOW;
else
{
// Activate co-op flight
if (thinkfly && player->pflags & PF_JUMPED)
if (mem->thinkstate == AI_THINKFLY && player->pflags & PF_JUMPED)
{
if (!jump_last)
{
jump = true;
flymode = 1;
thinkfly = false;
mem->thinkstate = AI_FLYSTANDBY;
bot->pflags |= PF_CANCARRY;
}
}
@ -165,20 +183,19 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
&& P_IsObjectOnGround(sonic) && P_IsObjectOnGround(tails)
&& !(player->pflags & PF_STASIS)
&& bot->charability == CA_FLY)
thinkfly = true;
else
thinkfly = false;
mem->thinkstate = AI_THINKFLY;
else if (mem->thinkstate == AI_THINKFLY)
mem->thinkstate = AI_FOLLOW;
// Set carried state
if (player->powers[pw_carry] == CR_PLAYER && sonic->tracer == tails)
{
flymode = 2;
mem->thinkstate = AI_FLYCARRY;
}
// Ready for takeoff
if (flymode == 1)
if (mem->thinkstate == AI_FLYSTANDBY)
{
thinkfly = false;
if (zdist < -64*scale || (flip * tails->momz) > scale) // Make sure we're not too high up
spin = true;
else if (!jump_last)
@ -186,10 +203,10 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
// Abort if the player moves away or spins
if (dist > followthres || player->dashspeed)
flymode = 0;
mem->thinkstate = AI_FOLLOW;
}
// Read player inputs while carrying
else if (flymode == 2)
else if (mem->thinkstate == AI_FLYCARRY)
{
cmd->forwardmove = pcmd->forwardmove;
cmd->sidemove = pcmd->sidemove;
@ -203,19 +220,19 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
// End flymode
if (player->powers[pw_carry] != CR_PLAYER)
{
flymode = 0;
mem->thinkstate = AI_FOLLOW;
}
}
}
if (flymode && P_IsObjectOnGround(tails) && !(pcmd->buttons & BT_JUMP))
flymode = 0;
if (P_IsObjectOnGround(tails) && !(pcmd->buttons & BT_JUMP) && (mem->thinkstate == AI_FLYSTANDBY || mem->thinkstate == AI_FLYCARRY))
mem->thinkstate = AI_FOLLOW;
// ********
// SPINNING
if (panic || flymode || !(player->pflags & PF_SPINNING) || (player->pflags & PF_JUMPED))
spinmode = false;
else
if (!(player->pflags & (PF_SPINNING|PF_STARTDASH)) && mem->thinkstate == AI_SPINFOLLOW)
mem->thinkstate = AI_FOLLOW;
else if (mem->thinkstate == AI_FOLLOW || mem->thinkstate == AI_SPINFOLLOW)
{
if (!_2d)
{
@ -224,21 +241,21 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
{
if (dist < followthres && dist > touchdist) // Do positioning
{
cmd->angleturn = (ang - tails->angle) >> 16; // NOT FRACBITS DAMNIT
cmd->angleturn = (ang) >> 16; // NOT FRACBITS DAMNIT
cmd->forwardmove = 50;
spinmode = true;
mem->thinkstate = AI_SPINFOLLOW;
}
else if (dist < touchdist)
{
if (!bmom && (!(bot->pflags & PF_SPINNING) || (bot->dashspeed && bot->pflags & PF_SPINNING)))
{
cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
cmd->angleturn = (sonic->angle) >> 16; // NOT FRACBITS DAMNIT
spin = true;
}
spinmode = true;
mem->thinkstate = AI_SPINFOLLOW;
}
else
spinmode = false;
mem->thinkstate = AI_FOLLOW;
}
// Spin
else if (player->dashspeed == bot->dashspeed && player->pflags & PF_SPINNING)
@ -246,12 +263,12 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
if (bot->pflags & PF_SPINNING || !spin_last)
{
spin = true;
cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
cmd->angleturn = (sonic->angle) >> 16; // NOT FRACBITS DAMNIT
cmd->forwardmove = MAXPLMOVE;
spinmode = true;
mem->thinkstate = AI_SPINFOLLOW;
}
else
spinmode = false;
mem->thinkstate = AI_FOLLOW;
}
}
// 2D mode
@ -261,17 +278,19 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
&& ((bot->pflags & PF_SPINNING) || !spin_last))
{
spin = true;
spinmode = true;
mem->thinkstate = AI_SPINFOLLOW;
}
else
mem->thinkstate = AI_FOLLOW;
}
}
// ********
// FOLLOW
if (!(flymode || spinmode))
if (mem->thinkstate == AI_FOLLOW || mem->thinkstate == AI_CATCHUP)
{
// Too far
if (panic || dist > followthres)
if (mem->thinkstate == AI_CATCHUP || dist > followthres)
{
if (!_2d)
cmd->forwardmove = MAXPLMOVE;
@ -281,7 +300,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
cmd->sidemove = -MAXPLMOVE;
}
// Within threshold
else if (!panic && dist > followmin && abs(zdist) < 192*scale)
else if (dist > followmin && abs(zdist) < 192*scale)
{
if (!_2d)
cmd->forwardmove = FixedHypot(pcmd->forwardmove, pcmd->sidemove);
@ -292,7 +311,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
else if (dist < followmin)
{
// Copy inputs
cmd->angleturn = (sonic->angle - tails->angle) >> 16; // NOT FRACBITS DAMNIT
cmd->angleturn = (sonic->angle) >> 16; // NOT FRACBITS DAMNIT
bot->drawangle = ang;
cmd->forwardmove = 8 * pcmd->forwardmove / 10;
cmd->sidemove = 8 * pcmd->sidemove / 10;
@ -301,7 +320,7 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
// ********
// JUMP
if (!(flymode || spinmode))
if (mem->thinkstate == AI_FOLLOW || mem->thinkstate == AI_CATCHUP || (mem->thinkstate == AI_SPINFOLLOW && player->pflags & PF_JUMPED))
{
// Flying catch-up
if (bot->pflags & PF_THOKKED)
@ -319,31 +338,30 @@ static void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd)
// Start jump
else if (!jump_last && !(bot->pflags & PF_JUMPED) //&& !(player->pflags & PF_SPINNING)
&& ((zdist > 32*scale && player->pflags & PF_JUMPED) // Following
|| (zdist > 64*scale && panic) // Vertical catch-up
|| (stalled && anxiety > 20 && bot->powers[pw_carry] == CR_NONE)
|| (zdist > 64*scale && mem->thinkstate == AI_CATCHUP) // Vertical catch-up
|| (stalled && mem-> catchup_tics > 20 && bot->powers[pw_carry] == CR_NONE)
//|| (bmom < scale>>3 && dist > followthres && !(bot->powers[pw_carry])) // Stopped & not in carry state
|| (bot->pflags & PF_SPINNING && !(bot->pflags & PF_JUMPED)))) // Spinning
jump = true;
// Hold jump
else if (bot->pflags & PF_JUMPED && jump_last && tails->momz*flip > 0 && (zdist > 0 || panic))
else if (bot->pflags & PF_JUMPED && jump_last && tails->momz*flip > 0 && (zdist > 0 || mem->thinkstate == AI_CATCHUP))
jump = true;
// Start flying
else if (bot->pflags & PF_JUMPED && panic && !jump_last && bot->charability == CA_FLY)
else if (bot->pflags & PF_JUMPED && mem->thinkstate == AI_CATCHUP && !jump_last && bot->charability == CA_FLY)
jump = true;
}
// ********
// HISTORY
jump_last = jump;
spin_last = spin;
//jump_last = jump;
//spin_last = spin;
// 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;
mem->lastForward = forward;
mem->lastBlocked = blocked;
}
void B_BuildTiccmd(player_t *player, ticcmd_t *cmd)
@ -366,22 +384,25 @@ void B_BuildTiccmd(player_t *player, ticcmd_t *cmd)
if (LUA_HookTiccmd(player, cmd, HOOK(BotTiccmd)))
return;
// We don't have any main character AI, sorry. D:
if (player-players == consoleplayer)
// Make sure we have a valid main character to follow
B_UpdateBotleader(player);
if (!player->botleader)
return;
// Basic Tails AI
B_BuildTailsTiccmd(players[consoleplayer].mo, player->mo, cmd);
// Single Player Tails AI
//B_BuildTailsTiccmd(players[consoleplayer].mo, player->mo, cmd);
B_BuildTailsTiccmd(player->botleader->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)
{
player_t *player = mo->player;
// don't try to do stuff if your sonic is in a minecart or something
if (players[consoleplayer].powers[pw_carry] && players[consoleplayer].powers[pw_carry] != CR_PLAYER)
if (&player->botleader && player->botleader->powers[pw_carry] && player->botleader->powers[pw_carry] != CR_PLAYER)
return;
// Turn the virtual keypresses into ticcmd_t.
if (twodlevel || mo->flags2 & MF2_TWOD) {
if (players[consoleplayer].climbing
if (player->botleader->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.
@ -420,10 +441,10 @@ void B_KeysToTiccmd(mobj_t *mo, ticcmd_t *cmd, boolean forward, boolean backward
cmd->forwardmove += MAXPLMOVE<<FRACBITS>>16;
if (backward)
cmd->forwardmove -= MAXPLMOVE<<FRACBITS>>16;
if (left)
if (left)
cmd->angleturn += 1280;
if (right)
cmd->angleturn -= 1280;
cmd->angleturn -= 1280;
if (strafeleft)
cmd->sidemove -= MAXPLMOVE<<FRACBITS>>16;
if (straferight)
@ -447,14 +468,19 @@ void B_KeysToTiccmd(mobj_t *mo, ticcmd_t *cmd, boolean forward, boolean backward
void B_MoveBlocked(player_t *player)
{
(void)player;
blocked = true;
player->blocked = true;
}
boolean B_CheckRespawn(player_t *player)
{
mobj_t *sonic = players[consoleplayer].mo;
mobj_t *sonic;
mobj_t *tails = player->mo;
//We don't have a main player to spawn to!
if (!player->botleader)
return false;
sonic = player->botleader->mo;
// We can't follow Sonic if he's not around!
if (!sonic || sonic->health <= 0)
return false;
@ -505,15 +531,19 @@ void B_RespawnBot(INT32 playernum)
{
player_t *player = &players[playernum];
fixed_t x,y,z;
mobj_t *sonic = players[consoleplayer].mo;
mobj_t *sonic;
mobj_t *tails;
if (!player->botleader)
return;
sonic = player->botleader->mo;
if (!sonic || sonic->health <= 0)
return;
B_ResetAI();
B_ResetAI(&player->botmem);
player->bot = 1;
player->bot = BOT_2PAI;
P_SpawnPlayer(playernum);
tails = player->mo;
@ -540,10 +570,6 @@ void B_RespawnBot(INT32 playernum)
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];
player->acceleration = sonic->player->acceleration;
player->accelstart = sonic->player->accelstart;
player->thrustfactor = sonic->player->thrustfactor;
player->normalspeed = sonic->player->normalspeed;
player->pflags |= PF_AUTOBRAKE|(sonic->player->pflags & PF_DIRECTIONCHAR);
P_TeleportMove(tails, x, y, z);
@ -561,11 +587,11 @@ void B_RespawnBot(INT32 playernum)
void B_HandleFlightIndicator(player_t *player)
{
mobj_t *tails = player->mo;
botmem_t *mem = &player->botmem;
if (!tails)
return;
if (thinkfly && player->bot == 1 && tails->health)
if (mem->thinkstate == AI_THINKFLY && player->bot == BOT_2PAI && tails->health)
{
if (!tails->hnext)
{

View file

@ -10,6 +10,7 @@
/// \file b_bot.h
/// \brief Basic bot handling
void B_UpdateBotleader(player_t *player);
void B_BuildTiccmd(player_t *player, ticcmd_t *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);
boolean B_CheckRespawn(player_t *player);

View file

@ -2537,7 +2537,7 @@ void CL_ClearPlayer(INT32 playernum)
//
// Removes a player from the current game
//
static void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
void CL_RemovePlayer(INT32 playernum, kickreason_t reason)
{
// Sanity check: exceptional cases (i.e. c-fails) can cause multiple
// kick commands to be issued for the same player.

View file

@ -405,6 +405,7 @@ void CL_Reset(void);
void CL_ClearPlayer(INT32 playernum);
void CL_QueryServerList(msg_server_t *list);
void CL_UpdateServerList(boolean internetsearch, INT32 room);
void CL_RemovePlayer(INT32 playernum, kickreason_t reason);
// Is there a game running
boolean Playing(void);

View file

@ -313,9 +313,43 @@ typedef enum
RW_RAIL = 32
} ringweapons_t;
//Bot types
typedef enum
{
BOT_NONE = 0,
BOT_2PAI,
BOT_2PHUMAN,
BOT_MPAI
} bottype_t;
//AI states
typedef enum
{
AI_STANDBY = 0,
AI_FOLLOW,
AI_CATCHUP,
AI_THINKFLY,
AI_FLYSTANDBY,
AI_FLYCARRY,
AI_SPINFOLLOW
} aistatetype_t;
// ========================================================================
// PLAYER STRUCTURE
// ========================================================================
//Bot memory struct
typedef struct botmem_s
{
boolean lastForward;
boolean lastBlocked;
boolean blocked;
UINT8 catchup_tics;
UINT8 thinkstate;
} botmem_t;
//Main struct
typedef struct player_s
{
mobj_t *mo;
@ -525,8 +559,13 @@ typedef struct player_s
boolean spectator;
boolean outofcoop;
boolean removing;
UINT8 bot;
struct player_s *botleader;
UINT16 lastbuttons;
botmem_t botmem;
boolean blocked;
tic_t jointime; // Timer when player joins game to change skin/color
tic_t quittime; // Time elapsed since user disconnected, zero if connected
#ifdef HWRENDER

View file

@ -5170,6 +5170,12 @@ struct int_const_s const INT_CONST[] = {
{"GF_REDFLAG",GF_REDFLAG},
{"GF_BLUEFLAG",GF_BLUEFLAG},
// Bot types
{"BOT_NONE",BOT_NONE},
{"BOT_2PAI",BOT_2PAI},
{"BOT_2PHUMAN",BOT_2PHUMAN},
{"BOT_MPAI",BOT_MPAI},
// Customisable sounds for Skins, from sounds.h
{"SKSSPIN",SKSSPIN},
{"SKSPUTPUT",SKSPUTPUT},

View file

@ -1071,7 +1071,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
boolean turnleft, turnright, strafelkey, straferkey, movefkey, movebkey, mouseaiming, analogjoystickmove, gamepadjoystickmove, thisjoyaiming;
boolean strafeisturn; // Simple controls only
player_t *player = &players[ssplayer == 2 ? secondarydisplayplayer : consoleplayer];
camera_t *thiscam = ((ssplayer == 1 || player->bot == 2) ? &camera : &camera2);
camera_t *thiscam = ((ssplayer == 1 || player->bot == BOT_2PHUMAN) ? &camera : &camera2);
angle_t *myangle = (ssplayer == 1 ? &localangle : &localangle2);
INT32 *myaiming = (ssplayer == 1 ? &localaiming : &localaiming2);
@ -1545,23 +1545,14 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
cmd->forwardmove = (SINT8)(cmd->forwardmove + forward);
cmd->sidemove = (SINT8)(cmd->sidemove + side);
if (player->bot == 1) { // Tailsbot for P2
if (!player->powers[pw_tailsfly] && (cmd->forwardmove || cmd->sidemove || cmd->buttons))
{
player->bot = 2; // A player-controlled bot. Returns to AI when it respawns.
CV_SetValue(&cv_analog[1], true);
}
else
{
G_CopyTiccmd(cmd, I_BaseTiccmd2(), 1); // empty, or external driver
B_BuildTiccmd(player, cmd);
}
B_HandleFlightIndicator(player);
}
else if (player->bot == 2)
// Note: Majority of botstuffs are handled in G_Ticker now.
if (player->bot == BOT_2PHUMAN) //Player-controlled bot
{
G_CopyTiccmd(cmd, I_BaseTiccmd2(), 1); // empty, or external driver
// Fix offset angle for P2-controlled Tailsbot when P2's controls are set to non-Legacy
cmd->angleturn = (INT16)((localangle - *myangle) >> 16);
}
*myangle += (cmd->angleturn<<16);
if (controlstyle == CS_LMAOGALOG) {
@ -2207,6 +2198,23 @@ void G_Ticker(boolean run)
UINT32 i;
INT32 buf;
// Bot players queued for removal
for (i = MAXPLAYERS-1; i != UINT32_MAX; i--)
{
if (playeringame[i] && players[i].removing)
{
CL_RemovePlayer(i, i);
if (netgame)
{
char kickmsg[256];
strcpy(kickmsg, M_GetText("\x82*Bot %s has been removed"));
strcpy(kickmsg, va(kickmsg, player_names[i], i));
HU_AddChatText(kickmsg, false);
}
}
}
// see also SCR_DisplayMarathonInfo
if ((marathonmode & (MA_INIT|MA_INGAME)) == MA_INGAME && gamestate == GS_LEVEL)
marathontime++;
@ -2292,23 +2300,58 @@ void G_Ticker(boolean run)
if (playeringame[i])
{
INT16 received;
// Save last frame's button readings
players[i].lastbuttons = players[i].cmd.buttons;
G_CopyTiccmd(&players[i].cmd, &netcmds[buf][i], 1);
received = (players[i].cmd.angleturn & TICCMD_RECEIVED);
players[i].angleturn += players[i].cmd.angleturn - players[i].oldrelangleturn;
players[i].oldrelangleturn = players[i].cmd.angleturn;
if (P_ControlStyle(&players[i]) == CS_LMAOGALOG)
P_ForceLocalAngle(&players[i], players[i].angleturn << 16);
else
players[i].cmd.angleturn = players[i].angleturn;
players[i].cmd.angleturn &= ~TICCMD_RECEIVED;
// Bot ticcmd handling
// Yes, ordinarily this would be handled in G_BuildTiccmd...
// ...however, bot players won't have a corresponding consoleplayer or splitscreen player 2 to send that information.
// Therefore, this has to be done after ticcmd sends are received.
if (players[i].bot == BOT_2PAI) { // Tailsbot for P2
if (!players[i].powers[pw_tailsfly] && (players[i].cmd.forwardmove || players[i].cmd.sidemove || players[i].cmd.buttons))
{
players[i].bot = BOT_2PHUMAN; // A player-controlled bot. Returns to AI when it respawns.
CV_SetValue(&cv_analog[1], true);
}
else
{
B_BuildTiccmd(&players[i], &players[i].cmd);
}
B_HandleFlightIndicator(&players[i]);
}
else if (players[i].bot == BOT_MPAI) {
B_BuildTiccmd(&players[i], &players[i].cmd);
}
// Do angle adjustments.
if (players[i].bot == BOT_NONE || players[i].bot == BOT_2PHUMAN)
{
received = (players[i].cmd.angleturn & TICCMD_RECEIVED);
players[i].angleturn += players[i].cmd.angleturn - players[i].oldrelangleturn;
players[i].oldrelangleturn = players[i].cmd.angleturn;
if (P_ControlStyle(&players[i]) == CS_LMAOGALOG)
P_ForceLocalAngle(&players[i], players[i].angleturn << 16);
else
players[i].cmd.angleturn = players[i].angleturn;
if (P_ControlStyle(&players[i]) == CS_LMAOGALOG)
P_ForceLocalAngle(&players[i], players[i].angleturn << 16);
else
players[i].cmd.angleturn = players[i].angleturn;
players[i].cmd.angleturn &= ~TICCMD_RECEIVED;
// Use the leveltime sent in the player's ticcmd to determine control lag
players[i].cmd.latency = min(((leveltime & 0xFF) - players[i].cmd.latency) & 0xFF, MAXPREDICTTICS-1);
}
else // Less work is required if we're building a bot ticcmd.
{
// Since bot TicCmd is pre-determined for both the client and server, the latency and packet checks are simplified.
received = 1;
players[i].cmd.latency = 0;
players[i].angleturn = players[i].cmd.angleturn;
players[i].oldrelangleturn = players[i].cmd.angleturn;
}
players[i].cmd.angleturn |= received;
// Use the leveltime sent in the player's ticcmd to determine control lag
players[i].cmd.latency = min(((leveltime & 0xFF) - players[i].cmd.latency) & 0xFF, MAXPREDICTTICS-1);
}
}
@ -2494,6 +2537,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
tic_t quittime;
boolean spectator;
boolean outofcoop;
boolean removing;
INT16 bot;
SINT8 pity;
INT16 rings;
@ -2510,6 +2554,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
quittime = players[player].quittime;
spectator = players[player].spectator;
outofcoop = players[player].outofcoop;
removing = players[player].removing;
pflags = (players[player].pflags & (PF_FLIPCAM|PF_ANALOGMODE|PF_DIRECTIONCHAR|PF_AUTOBRAKE|PF_TAGIT|PF_GAMETYPEOVER));
playerangleturn = players[player].angleturn;
oldrelangleturn = players[player].oldrelangleturn;
@ -2586,6 +2631,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->quittime = quittime;
p->spectator = spectator;
p->outofcoop = outofcoop;
p->removing = removing;
p->angleturn = playerangleturn;
p->oldrelangleturn = oldrelangleturn;
@ -2630,8 +2676,10 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->totalring = totalring;
p->mare = mare;
if (bot)
p->bot = 1; // reset to AI-controlled
if (bot == BOT_2PHUMAN)
p->bot = BOT_2PAI; // reset to AI-controlled
else
p->bot = bot;
p->pity = pity;
p->rings = rings;
p->spheres = spheres;
@ -2977,7 +3025,8 @@ void G_DoReborn(INT32 playernum)
// Make sure objectplace is OFF when you first start the level!
OP_ResetObjectplace();
if (player->bot && playernum != consoleplayer)
// Tailsbot
if (player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN)
{ // Bots respawn next to their master.
mobj_t *oldmo = NULL;
@ -2995,6 +3044,28 @@ void G_DoReborn(INT32 playernum)
return;
}
// Additional players (e.g. independent bots) in Single Player
if (playernum != consoleplayer && !(netgame || multiplayer))
{
mobj_t *oldmo = NULL;
// Do nothing if out of lives
if (player->lives <= 0)
return;
// Otherwise do respawn, starting by removing the player object
if (player->mo)
{
oldmo = player->mo;
P_RemoveMobj(player->mo);
}
// Do spawning
G_SpawnPlayer(playernum);
if (oldmo)
G_ChangePlayerReferences(oldmo, players[playernum].mo);
return; //Exit function to avoid proccing other SP related mechanics
}
if (countdowntimeup || (!(netgame || multiplayer) && (gametyperules & GTR_CAMPAIGN)))
resetlevel = true;
@ -3176,7 +3247,7 @@ void G_AddPlayer(INT32 playernum)
if (!playeringame[i])
continue;
if (players[i].bot) // ignore dumb, stupid tails
if (players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN) // ignore dumb, stupid tails
continue;
countplayers++;
@ -3217,7 +3288,7 @@ boolean G_EnoughPlayersFinished(void)
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || players[i].bot)
if (!playeringame[i] || players[i].spectator || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN)
continue;
if (players[i].quittime > 30 * TICRATE)
continue;

View file

@ -29,6 +29,8 @@
#include "d_netcmd.h" // IsPlayerAdmin
#include "m_menu.h" // Player Setup menu color stuff
#include "m_misc.h" // M_MapNumber
#include "b_bot.h" // B_UpdateBotleader
#include "d_clisrv.h" // CL_RemovePlayer
#include "lua_script.h"
#include "lua_libs.h"
@ -3397,6 +3399,111 @@ static int lib_gAddGametype(lua_State *L)
return 0;
}
// Bot adding function!
// Partly lifted from Got_AddPlayer
static int lib_gAddPlayer(lua_State *L)
{
INT16 i, newplayernum, botcount = 1;
player_t *newplayer;
INT8 skinnum = 0, bot;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
break;
if (players[i].bot)
botcount++; // How many of us are there already?
}
if (i >= MAXPLAYERS)
{
lua_pushnil(L);
return 1;
}
newplayernum = i;
CL_ClearPlayer(newplayernum);
playeringame[newplayernum] = true;
G_AddPlayer(newplayernum);
newplayer = &players[newplayernum];
newplayer->jointime = 0;
newplayer->quittime = 0;
// Set the bot name (defaults to Bot #)
strcpy(player_names[newplayernum], va("Bot %d", botcount));
// Read the skin argument (defaults to Sonic)
if (!lua_isnoneornil(L, 1))
{
skinnum = R_SkinAvailable(luaL_checkstring(L, 1));
skinnum = skinnum < 0 ? 0 : skinnum;
}
// Read the color (defaults to skin prefcolor)
if (!lua_isnoneornil(L, 2))
newplayer->skincolor = R_GetColorByName(luaL_checkstring(L, 2));
else
newplayer->skincolor = skins[newplayer->skin].prefcolor;
// Read the bot name, if given
if (!lua_isnoneornil(L, 3))
strcpy(player_names[newplayernum], luaL_checkstring(L, 3));
bot = luaL_optinteger(L, 4, 3);
newplayer->bot = (bot >= BOT_NONE && bot <= BOT_MPAI) ? bot : BOT_MPAI;
// If our bot is a 2P type, we'll need to set its leader so it can spawn
if (newplayer->bot == BOT_2PAI || newplayer->bot == BOT_2PHUMAN)
B_UpdateBotleader(newplayer);
// Set the skin (can't do this until AFTER bot type is set!)
SetPlayerSkinByNum(newplayernum, skinnum);
if (netgame)
{
char joinmsg[256];
strcpy(joinmsg, M_GetText("\x82*Bot %s has joined the game (player %d)"));
strcpy(joinmsg, va(joinmsg, player_names[newplayernum], newplayernum));
HU_AddChatText(joinmsg, false);
}
LUA_PushUserdata(L, newplayer, META_PLAYER);
return 1;
}
// Bot removing function
static int lib_gRemovePlayer(lua_State *L)
{
UINT8 pnum = -1;
if (!lua_isnoneornil(L, 1))
pnum = luaL_checkinteger(L, 1);
else // No argument
return luaL_error(L, "argument #1 not given (expected number)");
if (pnum >= MAXPLAYERS) // Out of range
return luaL_error(L, "playernum %d out of range (0 - %d)", pnum, MAXPLAYERS-1);
if (playeringame[pnum]) // Found player
{
if (players[pnum].bot == BOT_NONE) // Can't remove clients.
return luaL_error(L, "G_RemovePlayer can only be used on players with a bot value other than BOT_NONE.");
else
{
players[pnum].removing = true;
lua_pushboolean(L, true);
return 1;
}
}
// Fell through. Invalid player
return LUA_ErrInvalid(L, "player_t");
}
static int Lcheckmapnumber (lua_State *L, int idx, const char *fun)
{
if (ISINLEVEL)
@ -3983,6 +4090,8 @@ static luaL_Reg lib[] = {
// g_game
{"G_AddGametype", lib_gAddGametype},
{"G_AddPlayer", lib_gAddPlayer},
{"G_RemovePlayer", lib_gRemovePlayer},
{"G_BuildMapName",lib_gBuildMapName},
{"G_BuildMapTitle",lib_gBuildMapTitle},
{"G_FindMap",lib_gFindMap},

View file

@ -370,6 +370,12 @@ static int player_get(lua_State *L)
lua_pushboolean(L, plr->outofcoop);
else if (fastcmp(field,"bot"))
lua_pushinteger(L, plr->bot);
else if (fastcmp(field,"botleader"))
LUA_PushUserdata(L, plr->botleader, META_PLAYER);
else if (fastcmp(field,"lastbuttons"))
lua_pushinteger(L, plr->lastbuttons);
else if (fastcmp(field,"blocked"))
lua_pushboolean(L, plr->blocked);
else if (fastcmp(field,"jointime"))
lua_pushinteger(L, plr->jointime);
else if (fastcmp(field,"quittime"))
@ -719,6 +725,17 @@ static int player_set(lua_State *L)
plr->outofcoop = lua_toboolean(L, 3);
else if (fastcmp(field,"bot"))
return NOSET;
else if (fastcmp(field,"botleader"))
{
player_t *player = NULL;
if (!lua_isnil(L, 3))
player = *((player_t **)luaL_checkudata(L, 3, META_PLAYER));
plr->botleader = player;
}
else if (fastcmp(field,"lastbuttons"))
plr->lastbuttons = (UINT16)luaL_checkinteger(L, 3);
else if (fastcmp(field,"blocked"))
plr->blocked = (UINT8)luaL_checkinteger(L, 3);
else if (fastcmp(field,"jointime"))
plr->jointime = (tic_t)luaL_checkinteger(L, 3);
else if (fastcmp(field,"quittime"))

View file

@ -744,8 +744,8 @@ boolean P_LookForPlayers(mobj_t *actor, boolean allaround, boolean tracer, fixed
if (player->mo->health <= 0)
continue; // dead
if (player->bot)
continue; // ignore bots
if (player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN)
continue; // ignore followbots
if (player->quittime)
continue; // Ignore uncontrolled bodies
@ -3591,7 +3591,7 @@ void A_1upThinker(mobj_t *actor)
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].bot || players[i].spectator)
if (!playeringame[i] || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN || players[i].spectator)
continue;
if (!players[i].mo)

View file

@ -151,7 +151,7 @@ boolean P_CanPickupItem(player_t *player, boolean weapon)
if (!player->mo || player->mo->health <= 0)
return false;
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
{
if (weapon)
return false;
@ -178,7 +178,7 @@ void P_DoNightsScore(player_t *player)
return; // Don't do any fancy shit for failures.
dummymo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z+player->mo->height/2, MT_NIGHTSCORE);
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
player = &players[consoleplayer];
if (G_IsSpecialStage(gamemap)) // Global link count? Maybe not a good idea...
@ -470,14 +470,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
{
fixed_t setmomz = -toucher->momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
if (elementalpierce == 2) // Reset bubblewrap, part 1
P_DoBubbleBounce(player);
toucher->momz = setmomz;
if (elementalpierce == 2) // Reset bubblewrap, part 2
{
boolean underwater = toucher->eflags & MFE_UNDERWATER;
if (underwater)
toucher->momz /= 2;
toucher->momz -= (toucher->momz/(underwater ? 8 : 4)); // Cap the height!
@ -630,7 +630,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
// ***************************** //
// Special Stage Token
case MT_TOKEN:
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
P_AddPlayerScore(player, 1000);
@ -670,7 +670,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
// Emerald Hunt
case MT_EMERHUNT:
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
if (hunt1 == special)
@ -701,7 +701,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
case MT_EMERALD5:
case MT_EMERALD6:
case MT_EMERALD7:
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
if (special->threshold)
@ -738,7 +738,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
// Secret emblem thingy
case MT_EMBLEM:
{
if (demoplayback || player->bot)
if (demoplayback || (player->bot && player->bot != BOT_MPAI))
return;
emblemlocations[special->health-1].collected = true;
@ -751,7 +751,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
// CTF Flags
case MT_REDFLAG:
case MT_BLUEFLAG:
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
if (player->powers[pw_flashing] || player->tossdelay)
return;
@ -826,7 +826,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
{
boolean spec = G_IsSpecialStage(gamemap);
boolean cangiveemmy = false;
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
if (player->exiting)
return;
@ -1072,7 +1072,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
}
return;
case MT_EGGCAPSULE:
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
// make sure everything is as it should be, THEN take rings from players in special stages
@ -1164,7 +1164,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
}
return;
case MT_NIGHTSSUPERLOOP:
if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
if ((player->bot && player->bot != BOT_MPAI) || !(player->powers[pw_carry] == CR_NIGHTSMODE))
return;
if (!G_IsSpecialStage(gamemap))
player->powers[pw_nights_superloop] = (UINT16)special->info->speed;
@ -1186,7 +1186,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
}
break;
case MT_NIGHTSDRILLREFILL:
if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
if ((player->bot && player->bot != BOT_MPAI) || !(player->powers[pw_carry] == CR_NIGHTSMODE))
return;
if (!G_IsSpecialStage(gamemap))
player->drillmeter = special->info->speed;
@ -1208,7 +1208,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
}
break;
case MT_NIGHTSHELPER:
if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
if ((player->bot && player->bot != BOT_MPAI) || !(player->powers[pw_carry] == CR_NIGHTSMODE))
return;
if (!G_IsSpecialStage(gamemap))
{
@ -1240,7 +1240,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
}
break;
case MT_NIGHTSEXTRATIME:
if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
if ((player->bot && player->bot != BOT_MPAI) || !(player->powers[pw_carry] == CR_NIGHTSMODE))
return;
if (!G_IsSpecialStage(gamemap))
{
@ -1272,7 +1272,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
}
break;
case MT_NIGHTSLINKFREEZE:
if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
if ((player->bot && player->bot != BOT_MPAI) || !(player->powers[pw_carry] == CR_NIGHTSMODE))
return;
if (!G_IsSpecialStage(gamemap))
{
@ -1332,7 +1332,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
players[i].drillmeter += TICRATE/2;
}
else if (player->bot)
else if (player->bot && player->bot != BOT_MPAI)
players[consoleplayer].drillmeter += TICRATE/2;
else
player->drillmeter += TICRATE/2;
@ -1385,9 +1385,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
thinker_t *th;
mobj_t *mo2;
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
// Initialize my junk
junk.tags.tags = NULL;
junk.tags.count = 0;
@ -1423,7 +1423,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
return;
}
case MT_FIREFLOWER:
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
S_StartSound(toucher, sfx_mario3);
@ -1617,7 +1617,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
if (special->tracer && !(special->tracer->flags2 & MF2_STRONGBOX))
macespin = true;
if (macespin ? (player->powers[pw_ignorelatch] & (1<<15)) : (player->powers[pw_ignorelatch]))
return;
@ -1685,7 +1685,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
return; // Only go in the mouth
// Eaten by player!
if ((!player->bot) && (player->powers[pw_underwater] && player->powers[pw_underwater] <= 12*TICRATE + 1))
if ((!player->bot || player->bot == BOT_MPAI) && (player->powers[pw_underwater] && player->powers[pw_underwater] <= 12*TICRATE + 1))
{
player->powers[pw_underwater] = underwatertics + 1;
P_RestoreMusic(player);
@ -1696,7 +1696,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
if (!player->climbing)
{
if (player->bot && toucher->state-states != S_PLAY_GASP)
if (player->bot && player->bot != BOT_MPAI && toucher->state-states != S_PLAY_GASP)
S_StartSound(toucher, special->info->deathsound); // Force it to play a sound for bots
P_SetPlayerMobjState(toucher, S_PLAY_GASP);
P_ResetPlayer(player);
@ -1704,7 +1704,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
toucher->momx = toucher->momy = toucher->momz = 0;
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
else
break;
@ -1736,7 +1736,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
return;
case MT_MINECARTSPAWNER:
if (!player->bot && special->fuse <= TICRATE && player->powers[pw_carry] != CR_MINECART && !(player->powers[pw_ignorelatch] & (1<<15)))
if (!player->bot && player->bot != BOT_MPAI && special->fuse <= TICRATE && player->powers[pw_carry] != CR_MINECART && !(player->powers[pw_ignorelatch] & (1<<15)))
{
mobj_t *mcart = P_SpawnMobj(special->x, special->y, special->z, MT_MINECART);
P_SetTarget(&mcart->target, toucher);
@ -1789,7 +1789,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
}
return;
default: // SOC or script pickup
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
P_SetTarget(&special->target, toucher);
break;
@ -1813,7 +1813,7 @@ void P_TouchStarPost(mobj_t *post, player_t *player, boolean snaptopost)
mobj_t *toucher = player->mo;
mobj_t *checkbase = snaptopost ? post : toucher;
if (player->bot)
if (player->bot && player->bot != BOT_MPAI)
return;
// In circuit, player must have touched all previous starposts
if (circuitmap
@ -2555,7 +2555,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
if ((target->player->lives <= 1) && (netgame || multiplayer) && G_GametypeUsesCoopLives() && (cv_cooplives.value == 0))
;
else if (!target->player->bot && !target->player->spectator && (target->player->lives != INFLIVES)
else if ((!target->player->bot || target->player->bot == BOT_MPAI) && !target->player->spectator && (target->player->lives != INFLIVES)
&& G_GametypeUsesLives())
{
if (!(target->player->pflags & PF_FINISHED))
@ -3475,7 +3475,7 @@ void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
if (inflictor && inflictor->type == MT_LHRT)
return;
if (player->powers[pw_shield] || player->bot) //If One-Hit Shield
if (player->powers[pw_shield] || (player->bot && player->bot != BOT_MPAI)) //If One-Hit Shield
{
P_RemoveShield(player);
S_StartSound(player->mo, sfx_shldls); // Ba-Dum! Shield loss.
@ -3566,7 +3566,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
return false;
// Make sure that boxes cannot be popped by enemies, red rings, etc.
if (target->flags & MF_MONITOR && ((!source || !source->player || source->player->bot)
if (target->flags & MF_MONITOR && ((!source || !source->player || (source->player->bot && source->player->bot != BOT_MPAI))
|| (inflictor && (inflictor->type == MT_REDRING || (inflictor->type >= MT_THROWNBOUNCE && inflictor->type <= MT_THROWNGRENADE)))))
return false;
}
@ -3701,7 +3701,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
}
else if (LUA_HookMobjDamage(target, inflictor, source, damage, damagetype))
return true;
else if (player->powers[pw_shield] || (player->bot && !ultimatemode)) //If One-Hit Shield
else if (player->powers[pw_shield] || (player->bot && player->bot != BOT_MPAI && !ultimatemode)) //If One-Hit Shield
{
P_ShieldDamage(player, inflictor, source, damage, damagetype);
damage = 0;

View file

@ -1839,10 +1839,8 @@ void P_XYMovement(mobj_t *mo)
// blocked move
moved = false;
if (player) {
if (player->bot)
B_MoveBlocked(player);
}
if (player)
B_MoveBlocked(player);
if (LUA_HookMobj(mo, MOBJ_HOOK(MobjMoveBlocked)))
{
@ -4140,7 +4138,7 @@ boolean P_BossTargetPlayer(mobj_t *actor, boolean closest)
player = &players[actor->lastlook];
if (player->pflags & PF_INVIS || player->bot || player->spectator)
if (player->pflags & PF_INVIS || player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN || player->spectator)
continue; // ignore notarget
if (!player->mo || P_MobjWasRemoved(player->mo))
@ -4181,7 +4179,7 @@ boolean P_SupermanLook4Players(mobj_t *actor)
if (players[c].pflags & PF_INVIS)
continue; // ignore notarget
if (!players[c].mo || players[c].bot)
if (!players[c].mo || players[c].bot == BOT_2PAI || players[c].bot == BOT_2PHUMAN)
continue;
if (players[c].mo->health <= 0)
@ -7310,7 +7308,7 @@ static void P_RosySceneryThink(mobj_t *mobj)
continue;
if (!players[i].mo)
continue;
if (players[i].bot)
if (players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN)
continue;
if (!players[i].mo->health)
continue;

View file

@ -193,6 +193,19 @@ static void P_NetArchivePlayers(void)
WRITEUINT32(save_p, players[i].dashmode);
WRITEUINT32(save_p, players[i].skidtime);
//////////
// Bots //
//////////
WRITEUINT8(save_p, players[i].bot);
WRITEUINT8(save_p, players[i].botmem.lastForward);
WRITEUINT8(save_p, players[i].botmem.lastBlocked);
WRITEUINT8(save_p, players[i].botmem.catchup_tics);
WRITEUINT8(save_p, players[i].botmem.thinkstate);
WRITEUINT8(save_p, players[i].removing);
WRITEUINT8(save_p, players[i].blocked);
WRITEUINT16(save_p, players[i].lastbuttons);
////////////////////////////
// Conveyor Belt Movement //
////////////////////////////
@ -407,6 +420,20 @@ static void P_NetUnArchivePlayers(void)
players[i].dashmode = READUINT32(save_p); // counter for dashmode ability
players[i].skidtime = READUINT32(save_p); // Skid timer
//////////
// Bots //
//////////
players[i].bot = READUINT8(save_p);
players[i].botmem.lastForward = READUINT8(save_p);
players[i].botmem.lastBlocked = READUINT8(save_p);
players[i].botmem.catchup_tics = READUINT8(save_p);
players[i].botmem.thinkstate = READUINT8(save_p);
players[i].removing = READUINT8(save_p);
players[i].blocked = READUINT8(save_p);
players[i].lastbuttons = READUINT16(save_p);
////////////////////////////
// Conveyor Belt Movement //
////////////////////////////

View file

@ -777,7 +777,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime)
UINT8 oldmare, oldmarelap, oldmarebonuslap;
// Bots can't be NiGHTSerized, silly!1 :P
if (player->bot)
if (player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN)
return;
if (player->powers[pw_carry] != CR_NIGHTSMODE)
@ -1188,9 +1188,9 @@ void P_GivePlayerRings(player_t *player, INT32 num_rings)
{
if (!player)
return;
if (player->bot)
player = &players[consoleplayer];
if ((player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN) && player->botleader)
player = player->botleader;
if (!player->mo)
return;
@ -1234,8 +1234,8 @@ void P_GivePlayerSpheres(player_t *player, INT32 num_spheres)
if (!player)
return;
if (player->bot)
player = &players[consoleplayer];
if ((player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN) && player->botleader)
player = player->botleader;
if (!player->mo)
return;
@ -1261,8 +1261,8 @@ void P_GivePlayerLives(player_t *player, INT32 numlives)
if (!player)
return;
if (player->bot)
player = &players[consoleplayer];
if ((player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN) && player->botleader)
player = player->botleader;
if (gamestate == GS_LEVEL)
{
@ -1367,8 +1367,8 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
{
UINT32 oldscore;
if (player->bot)
player = &players[consoleplayer];
if ((player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN) && player->botleader)
player = player->botleader;
// NiGHTS does it different!
if (gamestate == GS_LEVEL && mapheaderinfo[gamemap-1]->typeoflevel & TOL_NIGHTS)
@ -5378,7 +5378,7 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
player->powers[pw_tailsfly] = tailsflytics + 1; // Set the fly timer
player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE|PF_SPINNING|PF_STARTDASH);
if (player->bot == 1)
if (player->bot == BOT_2PAI)
player->pflags |= PF_THOKKED;
else
player->pflags |= (PF_THOKKED|PF_CANCARRY);
@ -5965,22 +5965,6 @@ static void P_3dMovement(player_t *player)
acceleration = 96 + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * 40;
topspeed = normalspd;
}
else if (player->bot)
{ // Bot steals player 1's stats
normalspd = FixedMul(players[consoleplayer].normalspeed, player->mo->scale);
thrustfactor = players[consoleplayer].thrustfactor;
acceleration = players[consoleplayer].accelstart + (FixedDiv(player->speed, player->mo->scale)>>FRACBITS) * players[consoleplayer].acceleration;
if (player->powers[pw_tailsfly])
topspeed = normalspd/2;
else if (player->mo->eflags & (MFE_UNDERWATER|MFE_GOOWATER))
{
topspeed = normalspd/2;
acceleration = 2*acceleration/3;
}
else
topspeed = normalspd;
}
else
{
if (player->powers[pw_super] || player->powers[pw_sneakers])
@ -9510,11 +9494,11 @@ static void P_DeathThink(player_t *player)
if (player->deadtimer < INT32_MAX)
player->deadtimer++;
if (player->bot) // don't allow bots to do any of the below, B_CheckRespawn does all they need for respawning already
if (player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN) // don't allow followbots to do any of the below, B_CheckRespawn does all they need for respawning already
goto notrealplayer;
// continue logic
if (!(netgame || multiplayer) && player->lives <= 0)
if (!(netgame || multiplayer) && player->lives <= 0 && player == &players[consoleplayer]) //Extra players in SP can't be allowed to continue or end game
{
if (player->deadtimer > (3*TICRATE) && (cmd->buttons & BT_SPIN || cmd->buttons & BT_JUMP) && (!continuesInSession || player->continues > 0))
G_UseContinue();
@ -11487,6 +11471,9 @@ void P_PlayerThink(player_t *player)
I_Error("p_playerthink: players[%s].mo == NULL", sizeu1(playeri));
#endif
// Reset terrain blocked status for this frame
player->blocked = false;
// todo: Figure out what is actually causing these problems in the first place...
if (player->mo->health <= 0 && player->playerstate == PST_LIVE) //you should be DEAD!
{
@ -11494,7 +11481,7 @@ void P_PlayerThink(player_t *player)
player->playerstate = PST_DEAD;
}
if (player->bot)
if (player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN)
{
if (player->playerstate == PST_LIVE || player->playerstate == PST_DEAD)
{
@ -11638,7 +11625,7 @@ void P_PlayerThink(player_t *player)
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || players[i].bot)
if (!playeringame[i] || players[i].spectator || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN)
continue;
if (players[i].lives <= 0)
continue;
@ -11669,8 +11656,8 @@ void P_PlayerThink(player_t *player)
INT32 i, total = 0, exiting = 0;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || players[i].bot)
{
if (!playeringame[i] || players[i].spectator || players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN)
continue;
if (players[i].quittime > 30 * TICRATE)
continue;
@ -12610,8 +12597,8 @@ void P_PlayerAfterThink(player_t *player)
player->mo->momy = tails->momy;
player->mo->momz = tails->momz;
}
if (G_CoopGametype() && tails->player && tails->player->bot != 1)
if (G_CoopGametype() && tails->player && tails->player->bot != BOT_2PAI)
{
player->mo->angle = tails->angle;

View file

@ -242,6 +242,11 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
// Force 3.
return true;
}
if (playernum != -1 && players[playernum].bot)
{
//Force 4.
return true;
}
// We will now check if this skin is supposed to be locked or not.