diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 18d82515c..f71f96778 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,7 +3,6 @@ # Core sources set(SRB2_CORE_SOURCES am_map.c - b_bot.c command.c comptime.c console.c @@ -50,7 +49,6 @@ set(SRB2_CORE_SOURCES set(SRB2_CORE_HEADERS am_map.h - b_bot.h byteptr.h command.h console.h @@ -165,6 +163,9 @@ set(SRB2_CORE_GAME_SOURCES k_pwrlv.c k_waypoint.c k_color.c + k_bot.c + k_botitem.c + k_botsearch.c k_respawn.c p_local.h @@ -185,6 +186,7 @@ set(SRB2_CORE_GAME_SOURCES k_pwrlv.h k_waypoint.h k_color.h + k_bot.h k_respawn.h ) diff --git a/src/Makefile b/src/Makefile index 60b0ac054..7039a082f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -563,7 +563,9 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/i_tcp.o \ $(OBJDIR)/lzf.o \ $(OBJDIR)/vid_copy.o \ - $(OBJDIR)/b_bot.o \ + $(OBJDIR)/k_bot.o \ + $(OBJDIR)/k_botitem.o \ + $(OBJDIR)/k_botsearch.o \ $(i_cdmus_o) \ $(i_net_o) \ $(i_system_o) \ diff --git a/src/b_bot.c b/src/b_bot.c deleted file mode 100644 index 667d18be5..000000000 --- a/src/b_bot.c +++ /dev/null @@ -1,277 +0,0 @@ -// SONIC ROBO BLAST 2 -//----------------------------------------------------------------------------- -// Copyright (C) 2007-2016 by John "JTE" Muniz. -// Copyright (C) 2011-2018 by Sonic Team Junior. -// -// 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 - - if (tails->player->pflags & (PF_MACESPIN|PF_ITEMHANG)) - { - dist = P_AproxDistance(tails->x-sonic->x, tails->y-sonic->y); - if (sonic->player->cmd.buttons & BT_DRIFT && sonic->player->pflags & (PF_JUMPED|PF_MACESPIN|PF_ITEMHANG)) - cmd->buttons |= BT_DRIFT; - if (sonic->player->pflags & (PF_MACESPIN|PF_ITEMHANG)) - { - cmd->forwardmove = sonic->player->cmd.forwardmove; - cmd->angleturn = abs((signed)(tails->angle - sonic->angle))>>16; - if (sonic->angle < tails->angle) - cmd->angleturn = -cmd->angleturn; - } else if (dist > FixedMul(512*FRACUNIT, tails->scale)) - cmd->buttons |= BT_DRIFT; - 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_DRIFT; - 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) -{ - // 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<>16; - if (backward) - cmd->forwardmove -= MAXPLMOVE<>16; - if (left || strafeleft) - cmd->sidemove -= MAXPLMOVE<>16; - if (right || straferight) - cmd->sidemove += MAXPLMOVE<>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<>16; - if (right || straferight) - cmd->sidemove += MAXPLMOVE<>16; - } - } else { - if (forward) - cmd->forwardmove += MAXPLMOVE<>16; - if (backward) - cmd->forwardmove -= MAXPLMOVE<>16; - if (left) - cmd->angleturn += 1280; - if (right) - cmd->angleturn -= 1280; - if (strafeleft) - cmd->sidemove -= MAXPLMOVE<>16; - if (straferight) - cmd->sidemove += MAXPLMOVE<>16; - } - if (jump) - cmd->buttons |= BT_DRIFT; - if (spin) - cmd->buttons |= BT_BRAKE; -} - -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. - if (sonic->player->pflags & (PF_ROPEHANG|PF_GLIDING|PF_CARRIED|PF_SLIDING|PF_ITEMHANG|PF_MACESPIN|PF_NIGHTSMODE) - || (sonic->player->panim != PA_IDLE && sonic->player->panim != PA_WALK)) - return false; - - // Low ceiling, do not want! - if (sonic->ceilingz - sonic->z < 2*sonic->height) - 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; - z = sonic->z - FixedMul(512*FRACUNIT,sonic->scale); - if (z < sonic->floorz) - z = sonic->floorz; - } else { - z = sonic->z + sonic->height + FixedMul(512*FRACUNIT,sonic->scale); - 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]; - - P_TeleportMove(tails, x, y, z); - P_SetPlayerMobjState(tails, S_KART_STILL1); // SRB2kart - was S_PLAY_FALL1 - P_SetScale(tails, sonic->scale); - tails->destscale = sonic->destscale; -} diff --git a/src/b_bot.h b/src/b_bot.h deleted file mode 100644 index 20b2803b6..000000000 --- a/src/b_bot.h +++ /dev/null @@ -1,17 +0,0 @@ -// SONIC ROBO BLAST 2 -//----------------------------------------------------------------------------- -// Copyright (C) 2007-2016 by John "JTE" Muniz. -// Copyright (C) 2012-2018 by Sonic Team Junior. -// -// 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.h -/// \brief Basic bot handling - -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); -void B_MoveBlocked(player_t *player); -void B_RespawnBot(INT32 playernum); diff --git a/src/d_clisrv.c b/src/d_clisrv.c index f47c4ddff..4192e1384 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -48,6 +48,7 @@ #include "k_kart.h" #include "k_battle.h" #include "k_pwrlv.h" +#include "k_bot.h" #ifdef CLIENT_LOADINGSCREEN // cl loading screen @@ -653,6 +654,13 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i) rsp->respawn_distanceleft = (UINT32)LONG(players[i].respawn.distanceleft); rsp->respawn_dropdash = (tic_t)LONG(players[i].respawn.dropdash); + // botvars_t + rsp->bot = players[i].bot; + rsp->bot_difficulty = players[i].botvars.difficulty; + rsp->bot_itemdelay = players[i].botvars.itemdelay; + rsp->bot_itemconfirm = players[i].botvars.itemconfirm; + rsp->bot_turnconfirm = players[i].botvars.turnconfirm; + rsp->hasmo = false; //Transfer important mo information if the player has a body. //This lets us resync players even if they are dead. @@ -784,6 +792,13 @@ static void resynch_read_player(resynch_pak *rsp) players[i].respawn.distanceleft = (UINT32)LONG(rsp->respawn_distanceleft); players[i].respawn.dropdash = (tic_t)LONG(rsp->respawn_dropdash); + // botvars_t + players[i].bot = rsp->bot; + players[i].botvars.difficulty = rsp->bot_difficulty; + players[i].botvars.itemdelay = rsp->bot_itemdelay; + players[i].botvars.itemconfirm = rsp->bot_itemconfirm; + players[i].botvars.turnconfirm = rsp->bot_turnconfirm; + //We get a packet for each player in game. if (!playeringame[i]) return; @@ -1314,8 +1329,6 @@ static boolean CL_SendJoin(void) if (splitscreen) localplayers += splitscreen; - else if (botingame) - localplayers++; netbuffer->u.clientcfg.localplayers = localplayers; netbuffer->u.clientcfg._255 = 255; @@ -1566,6 +1579,8 @@ static boolean SV_SendServerConfig(INT32 node) netbuffer->u.servercfg.playerskins[i] = (UINT8)players[i].skin; netbuffer->u.servercfg.playercolor[i] = (UINT8)players[i].skincolor; + + netbuffer->u.servercfg.playerisbot[i] = players[i].bot; } memcpy(netbuffer->u.servercfg.server_context, server_context, 8); @@ -2623,8 +2638,7 @@ static void Command_connect(void) splitscreen = cv_splitplayers.value-1; SplitScreen_OnChange(); } - botingame = false; - botskin = 0; + CL_ConnectToServer(viams); } #endif @@ -2675,7 +2689,7 @@ void CL_RemovePlayer(INT32 playernum, INT32 reason) demo_extradata[playernum] |= DXD_PLAYSTATE; - if (server && !demo.playback) + if (server && !demo.playback && !players[playernum].bot) { INT32 node = playernode[playernum]; //playerpernode[node] = 0; // It'd be better to remove them all at once, but ghosting happened, so continue to let CL_RemovePlayer do it one-by-one @@ -3299,6 +3313,7 @@ consvar_t cv_downloadspeed = {"downloadspeed", "16", CV_SAVE, downloadspeed_cons static void Got_AddPlayer(UINT8 **p, INT32 playernum); static void Got_RemovePlayer(UINT8 **p, INT32 playernum); +static void Got_AddBot(UINT8 **p, INT32 playernum); // called one time at init void D_ClientServerInit(void) @@ -3328,6 +3343,7 @@ void D_ClientServerInit(void) RegisterNetXCmd(XD_KICK, Got_KickCmd); RegisterNetXCmd(XD_ADDPLAYER, Got_AddPlayer); RegisterNetXCmd(XD_REMOVEPLAYER, Got_RemovePlayer); + RegisterNetXCmd(XD_ADDBOT, Got_AddBot); #ifndef NONET #ifdef DUMPCONSISTENCY CV_RegisterVar(&cv_dumpconsistency); @@ -3540,8 +3556,6 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) displayplayers[splitscreenplayer] = newplayernum; g_localplayers[splitscreenplayer] = newplayernum; DEBFILE(va("spawning sister # %d\n", splitscreenplayer)); - if (splitscreenplayer == 1 && botingame) - players[newplayernum].bot = 1; } else { @@ -3560,6 +3574,7 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum) } players[newplayernum].splitscreenindex = splitscreenplayer; + players[newplayernum].bot = false; playerconsole[newplayernum] = console; splitscreen_original_party_size[console] = @@ -3613,6 +3628,63 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum) CL_RemovePlayer(pnum, reason); } +// Xcmd XD_ADDBOT +// Compacted version of XD_ADDPLAYER for simplicity +static void Got_AddBot(UINT8 **p, INT32 playernum) +{ + INT16 newplayernum; + UINT8 skinnum = 0; + UINT8 difficulty = MAXBOTDIFFICULTY; + + if (playernum != serverplayer && !IsPlayerAdmin(playernum)) + { + // protect against hacked/buggy client + CONS_Alert(CONS_WARNING, M_GetText("Illegal add player command received from %s\n"), player_names[playernum]); + if (server) + { + XBOXSTATIC UINT8 buf[2]; + + buf[0] = (UINT8)playernum; + buf[1] = KICK_MSG_CON_FAIL; + SendNetXCmd(XD_KICK, &buf, 2); + } + return; + } + + newplayernum = (UINT8)READUINT8(*p); + skinnum = (UINT8)READUINT8(*p); + difficulty = (UINT8)READUINT8(*p); + + CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum); + + // Clear player before joining, lest some things get set incorrectly + CL_ClearPlayer(newplayernum); + + playeringame[newplayernum] = true; + G_AddPlayer(newplayernum); + if (newplayernum+1 > doomcom->numslots) + doomcom->numslots = (INT16)(newplayernum+1); + + playernode[newplayernum] = servernode; + + players[newplayernum].splitscreenindex = 0; + players[newplayernum].bot = true; + players[newplayernum].botvars.difficulty = difficulty; + + players[newplayernum].skincolor = skins[skinnum].prefcolor; + sprintf(player_names[newplayernum], "%s", skins[skinnum].realname); + SetPlayerSkinByNum(newplayernum, skinnum); + + if (netgame) + { + HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false); + } + +#ifdef HAVE_BLUA + LUAh_PlayerJoin(newplayernum); +#endif +} + static boolean SV_AddWaitingPlayers(void) { INT32 node, n, newplayer = false; @@ -3630,6 +3702,7 @@ static boolean SV_AddWaitingPlayers(void) { UINT8 buf[4]; UINT8 *buf_p = buf; + UINT8 nobotoverwrite; newplayer = true; @@ -3647,6 +3720,21 @@ static boolean SV_AddWaitingPlayers(void) break; } + nobotoverwrite = newplayernum; + + while (playeringame[nobotoverwrite] + && players[nobotoverwrite].bot + && nobotoverwrite < MAXPLAYERS) + { + // Only overwrite bots if there are NO other slots available. + nobotoverwrite++; + } + + if (nobotoverwrite < MAXPLAYERS) + { + newplayernum = nobotoverwrite; + } + // should never happen since we check the playernum // before accepting the join I_Assert(newplayernum < MAXPLAYERS); @@ -4146,6 +4234,7 @@ static void HandlePacketFromAwayNode(SINT8 node) playeringame[j] = true; SetPlayerSkinByNum(j, (INT32)netbuffer->u.servercfg.playerskins[j]); players[j].skincolor = netbuffer->u.servercfg.playercolor[j]; + players[j].bot = netbuffer->u.servercfg.playerisbot[j]; } scp = netbuffer->u.servercfg.varlengthinputs; @@ -4981,7 +5070,7 @@ static void CL_SendClientCmd(void) G_MoveTiccmd(&netbuffer->u.clientpak.cmd, &localcmds, 1); netbuffer->u.clientpak.consistancy = SHORT(consistancy[gametic%TICQUEUE]); - if (splitscreen || botingame) // Send a special packet with 2 cmd for splitscreen + if (splitscreen) // Send a special packet with 2 cmd for splitscreen { netbuffer->packettype = (mis ? PT_CLIENT2MIS : PT_CLIENT2CMD); packetsize = sizeof (client2cmd_pak); @@ -5180,7 +5269,7 @@ static void Local_Maketic(INT32 realtics) if (!dedicated) rendergametic = gametic; // translate inputs (keyboard/mouse/joystick) into game controls G_BuildTiccmd(&localcmds, realtics, 1); - if (splitscreen || botingame) + if (splitscreen) { G_BuildTiccmd(&localcmds2, realtics, 2); if (splitscreen > 1) @@ -5653,9 +5742,15 @@ FILESTAMP INT32 D_NumPlayers(void) { INT32 num = 0, ix; + for (ix = 0; ix < MAXPLAYERS; ix++) - if (playeringame[ix]) + { + if (playeringame[ix] && !players[ix].bot) + { num++; + } + } + return num; } diff --git a/src/d_clisrv.h b/src/d_clisrv.h index cb8069f10..a48ddb9c3 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -292,6 +292,13 @@ typedef struct UINT32 respawn_distanceleft; tic_t respawn_dropdash; + // botvars_t + boolean bot; + UINT8 bot_difficulty; + tic_t bot_itemdelay; + tic_t bot_itemconfirm; + SINT8 bot_turnconfirm; + //player->mo stuff UINT8 hasmo; // Boolean @@ -335,6 +342,8 @@ typedef struct UINT8 playerskins[MAXPLAYERS]; UINT8 playercolor[MAXPLAYERS]; + UINT8 playerisbot[MAXPLAYERS]; + UINT8 gametype; UINT8 modifiedgame; SINT8 adminplayers[MAXPLAYERS]; // Needs to be signed diff --git a/src/d_main.c b/src/d_main.c index 54166c21b..c05a81480 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -772,8 +772,7 @@ void D_StartTitle(void) splitscreen = 0; SplitScreen_OnChange(); - botingame = false; - botskin = 0; + cv_debug = 0; emeralds = 0; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 718c6b614..fcb2cf315 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -393,6 +393,21 @@ consvar_t cv_kartspeedometer = {"kartdisplayspeed", "Off", CV_SAVE, kartspeedome static CV_PossibleValue_t kartvoices_cons_t[] = {{0, "Never"}, {1, "Tasteful"}, {2, "Meme"}, {0, NULL}}; consvar_t cv_kartvoices = {"kartvoices", "Tasteful", CV_SAVE, kartvoices_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; +static CV_PossibleValue_t kartbot_cons_t[] = { + {0, "Off"}, + {1, "Lv.1"}, + {2, "Lv.2"}, + {3, "Lv.3"}, + {4, "Lv.4"}, + {5, "Lv.5"}, + {6, "Lv.6"}, + {7, "Lv.7"}, + {8, "Lv.8"}, + {9, "Lv.9"}, + {0, NULL} +}; +consvar_t cv_kartbot = {"kartbot", "0", CV_NETVAR|CV_CHEAT, kartbot_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; + consvar_t cv_karteliminatelast = {"karteliminatelast", "Yes", CV_NETVAR|CV_CHEAT|CV_CALL|CV_NOSHOWHELP, CV_YesNo, KartEliminateLast_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_kartusepwrlv = {"kartusepwrlv", "Yes", CV_NETVAR|CV_CHEAT, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -550,6 +565,7 @@ const char *netxcmdnames[MAXNETXCMD - 1] = "LEAVEPARTY", "CANCELPARTYINVITE", "GIVEITEM", + "ADDBOT", #ifdef HAVE_BLUA "LUACMD", "LUAVAR" @@ -1554,7 +1570,7 @@ static void SendNameAndColor2(void) XBOXSTATIC char buf[MAXPLAYERNAME+2]; char *p; - if (splitscreen < 1 && !botingame) + if (splitscreen < 1) return; // can happen if skin2/color2/name2 changed if (g_localplayers[1] != consoleplayer) @@ -1592,15 +1608,7 @@ static void SendNameAndColor2(void) return; // If you're not in a netgame, merely update the skin, color, and name. - if (botingame) - { - players[secondplaya].skincolor = botcolor; - if (players[secondplaya].mo) - players[secondplaya].mo->color = players[secondplaya].skincolor; - SetPlayerSkinByNum(secondplaya, botskin-1); - return; - } - else if (!netgame) + if (!netgame) { INT32 foundskin; @@ -1830,15 +1838,7 @@ static void SendNameAndColor4(void) return; // If you're not in a netgame, merely update the skin, color, and name. - if (botingame) - { - players[fourthplaya].skincolor = botcolor; - if (players[fourthplaya].mo) - players[fourthplaya].mo->color = players[fourthplaya].skincolor; - SetPlayerSkinByNum(fourthplaya, botskin-1); - return; - } - else if (!netgame) + if (!netgame) { INT32 foundskin; @@ -2248,7 +2248,7 @@ static void Got_LeaveParty(UINT8 **cp,INT32 playernum) void D_SendPlayerConfig(void) { SendNameAndColor(); - if (splitscreen || botingame) + if (splitscreen) SendNameAndColor2(); if (splitscreen > 1) SendNameAndColor3(); @@ -2809,29 +2809,6 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r return; } - // Kick bot from special stages - if (botskin) - { - if (G_IsSpecialStage(mapnum)) - { - if (botingame) - { - //CL_RemoveSplitscreenPlayer(); - botingame = false; - playeringame[1] = false; - } - } - else if (!botingame) - { - //CL_AddSplitscreenPlayer(); - botingame = true; - displayplayers[1] = 1; - playeringame[1] = true; - players[1].bot = 1; - SendNameAndColor2(); - } - } - chmappending++; if (netgame) WRITEUINT32(buf_p, M_RandomizedSeed()); // random seed @@ -2871,11 +2848,10 @@ void D_SetupVote(void) SendNetXCmd(XD_SETUPVOTE, buf, p - buf); } -void D_ModifyClientVote(SINT8 voted, UINT8 splitplayer) +void D_ModifyClientVote(UINT8 player, SINT8 voted, UINT8 splitplayer) { char buf[2]; char *p = buf; - UINT8 player = consoleplayer; if (splitplayer > 0) player = g_localplayers[splitplayer]; @@ -5724,8 +5700,7 @@ void Command_ExitGame_f(void) splitscreen = 0; SplitScreen_OnChange(); - botingame = false; - botskin = 0; + cv_debug = 0; emeralds = 0; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 536bace56..22ca441be 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -121,6 +121,7 @@ extern consvar_t cv_kartencore; extern consvar_t cv_kartvoterulechanges; extern consvar_t cv_kartspeedometer; extern consvar_t cv_kartvoices; +extern consvar_t cv_kartbot; extern consvar_t cv_karteliminatelast; extern consvar_t cv_kartusepwrlv; @@ -187,10 +188,11 @@ typedef enum XD_ACCEPTPARTYINVITE, // 28 XD_LEAVEPARTY, // 29 XD_CANCELPARTYINVITE, // 30 - XD_GIVEITEM, // 31 + XD_GIVEITEM, // 31 + XD_ADDBOT, // 32 #ifdef HAVE_BLUA - XD_LUACMD, // 32 - XD_LUAVAR, // 33 + XD_LUACMD, // 33 + XD_LUAVAR, // 34 #endif MAXNETXCMD } netxcmd_t; @@ -246,7 +248,7 @@ void Command_Retry_f(void); void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pencoremode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pfromlevelselect); void D_SetupVote(void); -void D_ModifyClientVote(SINT8 voted, UINT8 splitplayer); +void D_ModifyClientVote(UINT8 player, SINT8 voted, UINT8 splitplayer); void D_PickVote(void); void ObjectPlace_OnChange(void); boolean IsPlayerAdmin(INT32 playernum); diff --git a/src/d_player.h b/src/d_player.h index 1011d70f6..def13cc71 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -431,6 +431,17 @@ typedef struct respawnvars_s tic_t dropdash; // Drop Dash charge timer } respawnvars_t; +// player_t struct for all bot variables +typedef struct botvars_s +{ + UINT8 difficulty; + + tic_t itemdelay; + tic_t itemconfirm; + + SINT8 turnconfirm; +} botvars_t; + // ======================================================================== // PLAYER STRUCTURE // ======================================================================== @@ -600,7 +611,9 @@ typedef struct player_s angle_t awayviewaiming; // Used for cut-away view boolean spectator; - UINT8 bot; + + boolean bot; + botvars_t botvars; tic_t jointime; // Timer when player joins game to change skin/color diff --git a/src/dehacked.c b/src/dehacked.c index a5d58dc2c..9a44ad075 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -7695,6 +7695,8 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s "MT_WAYPOINT_RISER", "MT_WAYPOINT_ANCHOR", + "MT_BOTHINT", + "MT_RANDOMAUDIENCE", "MT_FLAYM", diff --git a/src/g_game.c b/src/g_game.c index 48b2dfa29..cca6c50ed 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -46,7 +46,7 @@ #include "lua_script.h" // LUA_ArchiveDemo and LUA_UnArchiveDemo #include "lua_hook.h" #include "lua_libs.h" // gL (Lua state) -#include "b_bot.h" +#include "k_bot.h" #include "m_cond.h" // condition sets #include "md5.h" // demo checksums #include "k_kart.h" // SRB2kart @@ -59,10 +59,6 @@ gameaction_t gameaction; gamestate_t gamestate = GS_NULL; UINT8 ultimatemode = false; -boolean botingame; -UINT8 botskin; -UINT8 botcolor; - JoyType_t Joystick; JoyType_t Joystick2; JoyType_t Joystick3; @@ -1280,10 +1276,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) else player = &players[g_localplayers[ssplayer-1]]; - if (ssplayer == 2) - thiscam = (player->bot == 2 ? &camera[0] : &camera[ssplayer-1]); - else - thiscam = &camera[ssplayer-1]; + thiscam = &camera[ssplayer-1]; lang = localangle[ssplayer-1]; laim = localaiming[ssplayer-1]; th = turnheld[ssplayer-1]; @@ -1317,6 +1310,11 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) return; } + if (K_PlayerUsesBotMovement(player)) + { + return; + } + switch (ssplayer) { case 2: @@ -1621,9 +1619,8 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) //Reset away view if a command is given. if ((cmd->forwardmove || cmd->sidemove || cmd->buttons) - && ! r_splitscreen && displayplayers[0] != consoleplayer && ssplayer == 1) + && !r_splitscreen && displayplayers[0] != consoleplayer && ssplayer == 1) displayplayers[0] = consoleplayer; - } // User has designated that they want @@ -1639,8 +1636,6 @@ static void UserAnalog_OnChange(void) static void UserAnalog2_OnChange(void) { - if (botingame) - return; /*if (cv_useranalog2.value) CV_SetValue(&cv_analog2, 1); else @@ -1649,8 +1644,6 @@ static void UserAnalog2_OnChange(void) static void UserAnalog3_OnChange(void) { - if (botingame) - return; /*if (cv_useranalog3.value) CV_SetValue(&cv_analog3, 1); else @@ -1659,8 +1652,6 @@ static void UserAnalog3_OnChange(void) static void UserAnalog4_OnChange(void) { - if (botingame) - return; /*if (cv_useranalog4.value) CV_SetValue(&cv_analog4, 1); else @@ -1686,7 +1677,7 @@ static void Analog_OnChange(void) static void Analog2_OnChange(void) { - if (!(splitscreen || botingame) || !cv_cam2_dist.string) + if (!splitscreen || !cv_cam2_dist.string) return; // cameras are not initialized at this point @@ -1778,7 +1769,7 @@ void G_DoLoadLevel(boolean resetplayer) for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - if (i > 0 && !(i == 1 && botingame) && r_splitscreen < i) + if (i > 0 && r_splitscreen < i) g_localplayers[i] = consoleplayer; } @@ -2403,9 +2394,17 @@ void G_Ticker(boolean run) if (playeringame[i]) { - G_CopyTiccmd(cmd, &netcmds[buf][i], 1); - // Use the leveltime sent in the player's ticcmd to determine control lag - cmd->latency = modeattacking ? 0 : min(((leveltime & 0xFF) - cmd->latency) & 0xFF, MAXPREDICTTICS-1); //@TODO add a cvar to allow setting this max + if (K_PlayerUsesBotMovement(&players[i])) + { + K_BuildBotTiccmd(&players[i], cmd); + cmd->latency = 0; + } + else + { + G_CopyTiccmd(cmd, &netcmds[buf][i], 1); + // Use the leveltime sent in the player's ticcmd to determine control lag + cmd->latency = modeattacking ? 0 : min(((leveltime & 0xFF) - cmd->latency) & 0xFF, MAXPREDICTTICS-1); //@TODO add a cvar to allow setting this max + } } } @@ -2584,7 +2583,8 @@ void G_PlayerReborn(INT32 player) tic_t jointime; UINT8 splitscreenindex; boolean spectator; - INT16 bot; + boolean bot; + UINT8 botdifficulty; SINT8 pity; // SRB2kart @@ -2631,6 +2631,7 @@ void G_PlayerReborn(INT32 player) mare = players[player].mare; bot = players[player].bot; + botdifficulty = players[player].botvars.difficulty; pity = players[player].pity; // SRB2kart @@ -2706,8 +2707,8 @@ void G_PlayerReborn(INT32 player) p->totalring = totalring; p->mare = mare; - if (bot) - p->bot = 1; // reset to AI-controlled + p->bot = bot; + p->botvars.difficulty = botdifficulty; p->pity = pity; // SRB2kart @@ -3161,96 +3162,9 @@ void G_DoReborn(INT32 playernum) player_t *player = &players[playernum]; boolean starpost = false; - /*if (modeattacking) // Not needed for SRB2Kart. - { - M_EndModeAttackRun(); - return; - }*/ - // Make sure objectplace is OFF when you first start the level! OP_ResetObjectplace(); - if (player->bot && playernum != consoleplayer) - { // Bots respawn next to their master. - mobj_t *oldmo = NULL; - - // first dissasociate the corpse - if (player->mo) - { - oldmo = player->mo; - // Don't leave your carcass stuck 10-billion feet in the ground! - P_RemoveMobj(player->mo); - } - - B_RespawnBot(playernum); - if (oldmo) - G_ChangePlayerReferences(oldmo, players[playernum].mo); - } - /*else if (countdowntimeup || (!multiplayer && !modeattacking)) - { - // reload the level from scratch - if (countdowntimeup) - { - player->starpostangle = 0; - player->starposttime = 0; - player->starpostx = 0; - player->starposty = 0; - player->starpostz = 0; - player->starpostnum = 0; - } - if (!countdowntimeup && (mapheaderinfo[gamemap-1]->levelflags & LF_NORELOAD)) - { - INT32 i; - - player->playerstate = PST_REBORN; - - P_LoadThingsOnly(); - - // Do a wipe - wipegamestate = -1; - - if (player->starpostnum) // SRB2kart - starpost = true; - - for (i = 0; i <= splitscreen; i++) - { - if (camera[i].chase) - P_ResetCamera(&players[displayplayers[i]], &camera[i]); - } - - // clear cmd building stuff - memset(gamekeydown, 0, sizeof (gamekeydown)); - for (i = 0;i < JOYAXISSET; i++) - { - joyxmove[i] = joyymove[i] = 0; - joy2xmove[i] = joy2ymove[i] = 0; - joy3xmove[i] = joy3ymove[i] = 0; - joy4xmove[i] = joy4ymove[i] = 0; - } - mousex = mousey = 0; - mouse2x = mouse2y = 0; - - // clear hud messages remains (usually from game startup) - CON_ClearHUD(); - - // Starpost support - G_SpawnPlayer(playernum, starpost); - - if (botingame) - { // Bots respawn next to their master. - players[displayplayers[1]].playerstate = PST_REBORN; - G_SpawnPlayer(displayplayers[1], false); - } - } - else - { -#ifdef HAVE_BLUA - LUAh_MapChange(gamemap); -#endif - G_DoLoadLevel(true); - } - }*/ - else { // respawn at the start mobj_t *oldmo = NULL; @@ -4612,9 +4526,6 @@ void G_DeferedInitNew(boolean pencoremode, const char *mapname, INT32 pickedchar if (savedata.lives > 0) { color = savedata.skincolor; - botskin = savedata.botskin; - botcolor = savedata.botcolor; - botingame = (botskin != 0); } else if (splitscreen != ssplayers) { diff --git a/src/g_state.h b/src/g_state.h index f9f1babd3..24904e67c 100644 --- a/src/g_state.h +++ b/src/g_state.h @@ -55,7 +55,4 @@ extern gamestate_t gamestate; extern UINT8 ultimatemode; // was sk_insane extern gameaction_t gameaction; -extern boolean botingame; -extern UINT8 botskin, botcolor; - #endif //__G_STATE__ diff --git a/src/hu_stuff.c b/src/hu_stuff.c index a4acabf79..7338a232b 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -3095,7 +3095,7 @@ static void HU_DrawRankings(void) if (G_RaceGametype()) { if (circuitmap) - tab[scorelines].count = players[i].laps+1; + tab[scorelines].count = players[i].laps; else tab[scorelines].count = players[i].realtime; } diff --git a/src/info.c b/src/info.c index 95e4fd445..6c695e705 100644 --- a/src/info.c +++ b/src/info.c @@ -16534,6 +16534,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_BOTHINT + 2004, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 100, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 1*FRACUNIT, // radius + 2*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIP|MF_NOGRAVITY|MF_SCENERY, // flags + S_NULL // raisestate + }, + { // MT_RANDOMAUDIENCE 1488, // doomednum S_RANDOMAUDIENCE, // spawnstate diff --git a/src/info.h b/src/info.h index b5523f434..2ccf6415a 100644 --- a/src/info.h +++ b/src/info.h @@ -4796,6 +4796,8 @@ typedef enum mobj_type MT_WAYPOINT_RISER, MT_WAYPOINT_ANCHOR, + MT_BOTHINT, + MT_RANDOMAUDIENCE, MT_FLAYM, diff --git a/src/k_bot.c b/src/k_bot.c new file mode 100644 index 000000000..6beb6fe9e --- /dev/null +++ b/src/k_bot.c @@ -0,0 +1,831 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Kart Krew +// +// 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 k_bot.c +/// \brief Bot logic & ticcmd generation code + +#include "doomdef.h" +#include "d_player.h" +#include "g_game.h" +#include "r_main.h" +#include "p_local.h" +#include "k_bot.h" +#include "lua_hook.h" +#include "byteptr.h" +#include "d_net.h" // nodetoplayer +#include "k_kart.h" +#include "z_zone.h" +#include "i_system.h" +#include "p_maputl.h" +#include "d_ticcmd.h" +#include "m_random.h" +#include "r_things.h" // numskins + + +/*-------------------------------------------------- + boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) + + See header file for description. +--------------------------------------------------*/ +boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p) +{ + UINT8 buf[3]; + UINT8 *buf_p = buf; + UINT8 newplayernum = *p; + + // search for a free playernum + // we can't use playeringame since it is not updated here + for (; newplayernum < MAXPLAYERS; newplayernum++) + { + UINT8 n; + + for (n = 0; n < MAXNETNODES; n++) + if (nodetoplayer[n] == newplayernum + || nodetoplayer2[n] == newplayernum + || nodetoplayer3[n] == newplayernum + || nodetoplayer4[n] == newplayernum) + break; + + if (n == MAXNETNODES) + break; + } + + while (playeringame[newplayernum] + && players[newplayernum].bot + && newplayernum < MAXPLAYERS) + { + newplayernum++; + } + + if (newplayernum >= MAXPLAYERS) + { + *p = newplayernum; + return false; + } + + WRITEUINT8(buf_p, newplayernum); + + if (skin > numskins) + { + skin = numskins; + } + + WRITEUINT8(buf_p, skin); + + if (difficulty < 1) + { + difficulty = 1; + } + else if (difficulty > MAXBOTDIFFICULTY) + { + difficulty = MAXBOTDIFFICULTY; + } + + WRITEUINT8(buf_p, difficulty); + + SendNetXCmd(XD_ADDBOT, buf, buf_p - buf); + + DEBFILE(va("Server added bot %d\n", newplayernum)); + // use the next free slot (we can't put playeringame[newplayernum] = true here) + newplayernum++; + + *p = newplayernum; + return true; +} + +/*-------------------------------------------------- + void K_UpdateMatchRaceBots(void) + + See header file for description. +--------------------------------------------------*/ +void K_UpdateMatchRaceBots(void) +{ + const UINT8 difficulty = cv_kartbot.value; + UINT8 pmax = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value); + UINT8 numplayers = 0; + UINT8 numbots = 0; + UINT8 numwaiting = 0; + SINT8 wantedbots = 0; + boolean skinusable[MAXSKINS]; + UINT8 i; + + if (!server) + { + return; + } + + // init usable bot skins list + for (i = 0; i < MAXSKINS; i++) + { + if (i < numskins) + { + skinusable[i] = true; + } + else + { + skinusable[i] = false; + } + } + + if (cv_ingamecap.value > 0) + { + pmax = min(pmax, cv_ingamecap.value); + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + if (!players[i].spectator) + { + skinusable[players[i].skin] = false; + + if (players[i].bot) + { + numbots++; + + // While we're here, we should update bot difficulty to the proper value. + players[i].botvars.difficulty = difficulty; + } + else + { + numplayers++; + } + } + else if (players[i].pflags & PF_WANTSTOJOIN) + { + numwaiting++; + } + } + } + + if (difficulty == 0) + { + wantedbots = 0; + } + else + { + wantedbots = pmax - numplayers - numwaiting; + + if (wantedbots < 0) + { + wantedbots = 0; + } + } + + if (numbots < wantedbots) + { + // We require MORE bots! + UINT8 newplayernum = 0; + boolean usedallskins = false; + + if (dedicated) + { + newplayernum = 1; + } + + while (numbots < wantedbots) + { + UINT8 skin = M_RandomKey(numskins); + + if (usedallskins == false) + { + UINT8 loops = 0; + + while (!skinusable[skin]) + { + if (loops >= numskins) + { + // no more skins, stick to our first choice + usedallskins = true; + break; + } + + skin++; + + if (skin >= numskins) + { + skin = 0; + } + + loops++; + } + } + + if (!K_AddBot(skin, difficulty, &newplayernum)) + { + // Not enough player slots to add the bot, break the loop. + break; + } + + skinusable[skin] = false; + numbots++; + } + } + else if (numbots > wantedbots) + { + UINT8 buf[2]; + + i = MAXPLAYERS; + + while (numbots > wantedbots && i > 0) + { + if (playeringame[i] && players[i].bot) + { + buf[0] = i; + buf[1] = KR_LEAVE; + SendNetXCmd(XD_REMOVEPLAYER, &buf, 2); + + numbots--; + } + + i--; + } + } + + // We should have enough bots now :) +} + +/*-------------------------------------------------- + boolean K_PlayerUsesBotMovement(player_t *player) + + See header file for description. +--------------------------------------------------*/ +boolean K_PlayerUsesBotMovement(player_t *player) +{ + if (player->bot || player->exiting) + return true; + + return false; +} + +/*-------------------------------------------------- + boolean K_BotCanTakeCut(player_t *player) + + See header file for description. +--------------------------------------------------*/ +boolean K_BotCanTakeCut(player_t *player) +{ + if (!K_ApplyOffroad(player) + || player->kartstuff[k_itemtype] == KITEM_SNEAKER + || player->kartstuff[k_itemtype] == KITEM_ROCKETSNEAKER + || player->kartstuff[k_itemtype] == KITEM_INVINCIBILITY + || player->kartstuff[k_itemtype] == KITEM_HYUDORO) + return true; + + return false; +} + +/*-------------------------------------------------- + static UINT32 K_BotRubberbandDistance(player_t *player) + + Calculates the distance away from 1st place that the + bot should rubberband to. + + Input Arguments:- + player - Player to compare. + + Return:- + Distance to add, as an integer. +--------------------------------------------------*/ +static UINT32 K_BotRubberbandDistance(player_t *player) +{ + const UINT32 spacing = 2048; + const UINT8 portpriority = player - players; + UINT8 pos = 0; + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (i == portpriority) + { + continue; + } + + if (playeringame[i] && players[i].bot) + { + // First check difficulty levels, then score, then settle it with port priority! + if (player->botvars.difficulty < players[i].botvars.difficulty) + { + pos++; + } + else if (player->score < players[i].score) + { + pos++; + } + else if (i < portpriority) + { + pos++; + } + } + } + + return (pos * spacing); +} + +/*-------------------------------------------------- + fixed_t K_BotRubberband(player_t *player) + + See header file for description. +--------------------------------------------------*/ +fixed_t K_BotRubberband(player_t *player) +{ + fixed_t rubberband = FRACUNIT; + player_t *firstplace = NULL; + UINT8 i; + + if (player->exiting) + { + return FRACUNIT; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + +#if 0 + // Only rubberband up to players. + if (players[i].bot) + { + continue; + } +#endif + + if (firstplace == NULL || players[i].distancetofinish < firstplace->distancetofinish) + { + firstplace = &players[i]; + } + } + + if (firstplace != NULL) + { + const UINT32 wanteddist = firstplace->distancetofinish + K_BotRubberbandDistance(player); + const INT32 distdiff = player->distancetofinish - wanteddist; + + if (wanteddist > player->distancetofinish) + { + // Whoa, you're too far ahead! + rubberband += (MAXBOTDIFFICULTY - player->botvars.difficulty) * distdiff; + } + else + { + // Catch up to your position! + rubberband += (2*player->botvars.difficulty) * distdiff; + } + } + + if (rubberband > 2*FRACUNIT) + { + rubberband = 2*FRACUNIT; + } + else if (rubberband < 7*FRACUNIT/8) + { + rubberband = 7*FRACUNIT/8; + } + + return rubberband; +} + +/*-------------------------------------------------- + fixed_t K_BotTopSpeedRubberband(player_t *player) + + See header file for description. +--------------------------------------------------*/ +fixed_t K_BotTopSpeedRubberband(player_t *player) +{ + fixed_t rubberband = K_BotRubberband(player); + + if (rubberband < FRACUNIT) + { + // Never go below your regular top speed + rubberband = FRACUNIT; + } + + // Only allow you to go faster than your regular top speed if you're facing the right direction + if (rubberband > FRACUNIT && player->mo != NULL && player->nextwaypoint != NULL) + { + const INT16 mindiff = 30; + const INT16 maxdiff = 60; + INT16 anglediff = 0; + fixed_t amt = rubberband - FRACUNIT; + angle_t destangle = R_PointToAngle2( + player->mo->x, player->mo->y, + player->nextwaypoint->mobj->x, player->nextwaypoint->mobj->y + ); + angle_t angle = player->mo->angle - destangle; + + if (angle < ANGLE_180) + { + anglediff = AngleFixed(angle) >> FRACBITS; + } + else + { + anglediff = 360 - (AngleFixed(angle) >> FRACBITS); + } + + anglediff = abs(anglediff); + + if (anglediff >= maxdiff) + { + rubberband = FRACUNIT; + } + else if (anglediff > mindiff) + { + amt = (amt * (maxdiff - anglediff)) / mindiff; + rubberband = FRACUNIT + amt; + } + } + + return rubberband; +} + +/*-------------------------------------------------- + fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy) + + See header file for description. +--------------------------------------------------*/ +fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy) +{ + fixed_t v1toc[2] = {cx - v1x, cy - v1y}; + fixed_t v1tov2[2] = {v2x - v1x, v2y - v1y}; + + fixed_t mag = FixedMul(v1tov2[0], v1tov2[0]) + FixedMul(v1tov2[1], v1tov2[1]); + fixed_t dot = FixedMul(v1toc[0], v1tov2[0]) + FixedMul(v1toc[1], v1tov2[1]); + + fixed_t t; + fixed_t px, py; + + if (mag == 0) + { + return 0; + } + + t = FixedDiv(dot, mag); + + px = v1x + FixedMul(v1tov2[0], t); + py = v1y + FixedMul(v1tov2[1], t); + + return P_AproxDistance(cx - px, cy - py); +} + +/*-------------------------------------------------- + static botprediction_t *K_CreateBotPrediction(player_t *player) + + Calculates a point further along the track to attempt to drive towards. + + Input Arguments:- + player - Player to compare. + + Return:- + Bot prediction struct. +--------------------------------------------------*/ +static botprediction_t *K_CreateBotPrediction(player_t *player) +{ + const INT16 handling = K_GetKartTurnValue(player, KART_FULLTURN); // Reduce prediction based on how fast you can turn + const INT16 normal = KART_FULLTURN; // "Standard" handling to compare to + + const fixed_t distreduce = K_BotReducePrediction(player); + const fixed_t radreduce = min(distreduce + FRACUNIT/4, FRACUNIT); + + const tic_t futuresight = (TICRATE * normal) / max(1, handling); // How far ahead into the future to try and predict + const fixed_t speed = max(P_AproxDistance(player->mo->momx, player->mo->momy), K_GetKartSpeed(player, false) / 4); + const INT32 distance = (FixedMul(speed, distreduce) / FRACUNIT) * futuresight; + + botprediction_t *predict = Z_Calloc(sizeof(botprediction_t), PU_LEVEL, NULL); + waypoint_t *wp = player->nextwaypoint; + + INT32 distanceleft = distance; + fixed_t smallestradius = INT32_MAX; + angle_t angletonext = ANGLE_MAX; + + size_t nwp; + size_t i; + + // Reduce distance left by your distance to the starting waypoint. + // This prevents looking too far ahead if the closest waypoint is really far away. + distanceleft -= P_AproxDistance(player->mo->x - wp->mobj->x, player->mo->y - wp->mobj->y) / FRACUNIT; + + // We don't want to look ahead at all, just go to the first waypoint. + if (distanceleft <= 0) + { + predict->x = wp->mobj->x; + predict->y = wp->mobj->y; + predict->radius = FixedMul(wp->mobj->radius, radreduce); + return predict; + } + + angletonext = R_PointToAngle2( + player->mo->x, player->mo->y, + wp->mobj->x, wp->mobj->y + ); + + // Go through waypoints until we've traveled the distance we wanted to predict ahead! + while (distanceleft > 0) + { + INT32 disttonext = INT32_MAX; + + if (wp->mobj->radius < smallestradius) + { + smallestradius = wp->mobj->radius; + } + + if (wp->numnextwaypoints == 0) + { + // Well, this is where I get off. + distanceleft = 0; + break; + } + + // Calculate nextwaypoints index to use + // nextwaypoints[0] by default + nwp = 0; + + // There are multiple nextwaypoints, + // so we need to find the most convenient one to us. + // Let's compare the angle to the player's! + if (wp->numnextwaypoints > 1) + { + angle_t delta = ANGLE_MAX; + angle_t a = ANGLE_MAX; + + for (i = 0; i < wp->numnextwaypoints; i++) + { + + if (K_GetWaypointIsShortcut(wp->nextwaypoints[i]) && !K_BotCanTakeCut(player)) + { + continue; + } + + // Unlike the other parts of this function, we're comparing the player's physical position, NOT the position of the waypoint!! + // This should roughly correspond with how players will think about path splits. + a = R_PointToAngle2( + player->mo->x, player->mo->y, + wp->nextwaypoints[i]->mobj->x, wp->nextwaypoints[i]->mobj->y + ); + if (a > ANGLE_180) + { + a = InvAngle(a); + } + + a = player->mo->angle - a; + + if (a < delta) + { + nwp = i; + delta = a; + } + } + } + + angletonext = R_PointToAngle2( + wp->mobj->x, wp->mobj->y, + wp->nextwaypoints[nwp]->mobj->x, wp->nextwaypoints[nwp]->mobj->y + ); + + disttonext = (INT32)wp->nextwaypointdistances[nwp]; + + if (disttonext > distanceleft) + { + break; + } + + distanceleft -= disttonext; + + wp = wp->nextwaypoints[nwp]; + } + + // Set our predicted point's coordinates, + // and use the smallest radius of all of the waypoints in the chain! + predict->x = wp->mobj->x; + predict->y = wp->mobj->y; + predict->radius = FixedMul(smallestradius, radreduce); + + // Set the prediction coordinates between the 2 waypoints if there's still distance left. + if (distanceleft > 0) + { + // Scaled with the leftover anglemul! + predict->x += P_ReturnThrustX(NULL, angletonext, distanceleft * FRACUNIT); + predict->y += P_ReturnThrustY(NULL, angletonext, distanceleft * FRACUNIT); + } + + return predict; +} + +/*-------------------------------------------------- + void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) + + See header file for description. +--------------------------------------------------*/ +void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) +{ + botprediction_t *predict = NULL; + INT32 turnamt = 0; + + // Can't build a ticcmd if we aren't spawned... + if (!player->mo) + { + return; + } + + // Remove any existing controls + memset(cmd, 0, sizeof(ticcmd_t)); + cmd->angleturn = (player->mo->angle >> 16) | TICCMD_RECEIVED; + + if (gamestate != GS_LEVEL) + { + // No need to do anything else. + return; + } + + if (player->playerstate == PST_DEAD) + { + cmd->buttons |= BT_ACCELERATE; + return; + } + +#ifdef HAVE_BLUA + // Complete override of all ticcmd functionality + if (LUAh_BotTiccmd(player, cmd)) + return; +#endif + + // Start boost handler + if (leveltime <= starttime) + { + tic_t boosthold = starttime - TICRATE; + + boosthold -= (MAXBOTDIFFICULTY - player->botvars.difficulty); + + if (leveltime >= boosthold) + { + cmd->buttons |= BT_ACCELERATE; + } + + return; + } + + // Handle steering towards waypoints! + if (player->nextwaypoint != NULL && player->nextwaypoint->mobj != NULL && !P_MobjWasRemoved(player->nextwaypoint->mobj)) + { + SINT8 turnsign = 0; + angle_t destangle, moveangle, angle; + INT16 anglediff; + + predict = K_CreateBotPrediction(player); + + destangle = R_PointToAngle2(player->mo->x, player->mo->y, predict->x, predict->y); + moveangle = player->mo->angle; + + angle = (moveangle - destangle); + + if (angle < ANGLE_180) + { + turnsign = -1; // Turn right + anglediff = AngleFixed(angle)>>FRACBITS; + } + else + { + turnsign = 1; // Turn left + anglediff = 360-(AngleFixed(angle)>>FRACBITS); + } + + anglediff = abs(anglediff); + turnamt = KART_FULLTURN * turnsign; + + if (anglediff > 90) + { + // Wrong way! + cmd->forwardmove = -25; + cmd->buttons |= BT_BRAKE; + } + else + { + const fixed_t playerwidth = (player->mo->radius * 2); + const fixed_t realrad = predict->radius - (playerwidth * 4); // Remove a "safe" distance away from the edges of the road + fixed_t rad = realrad; + fixed_t dirdist = K_DistanceOfLineFromPoint( + player->mo->x, player->mo->y, + player->mo->x + FINECOSINE(moveangle >> ANGLETOFINESHIFT), player->mo->y + FINESINE(moveangle >> ANGLETOFINESHIFT), + predict->x, predict->y + ); + + if (anglediff > 0) + { + // Become more precise based on how hard you need to turn + // This makes predictions into turns a little nicer + // Facing 90 degrees away from the predicted point gives you a 1/3 radius + rad = FixedMul(rad, ((135 - anglediff) * FRACUNIT) / 135); + } + + if (rad > realrad) + { + rad = realrad; + } + else if (rad < playerwidth) + { + rad = playerwidth; + } + + cmd->buttons |= BT_ACCELERATE; + + // Full speed ahead! + cmd->forwardmove = 50; + + if (dirdist <= rad) + { + fixed_t speedmul = FixedDiv(player->speed, K_GetKartSpeed(player, false)); + fixed_t speedrad = rad/4; + + if (speedmul > FRACUNIT) + { + speedmul = FRACUNIT; + } + + // Increase radius with speed + // At low speed, the CPU will try to be more accurate + // At high speed, they're more likely to lawnmower + speedrad += FixedMul(speedmul, rad - speedrad); + + if (speedrad < playerwidth) + { + speedrad = playerwidth; + } + + if (dirdist <= speedrad) + { + // Don't turn at all + turnamt = 0; + } + else + { + // Make minor adjustments + turnamt /= 4; + } + } + + if (anglediff > 60) + { + // Actually, don't go too fast... + cmd->forwardmove /= 2; + cmd->buttons |= BT_BRAKE; + } + else if (dirdist <= realrad) + { + // Steer towards/away from objects! + turnamt += K_BotFindObjects(player, turnamt); + } + } + } + + // Handle item usage + K_BotItemUsage(player, cmd, turnamt); + + if (turnamt != 0) + { + if (turnamt > KART_FULLTURN) + { + turnamt = KART_FULLTURN; + } + else if (turnamt < -KART_FULLTURN) + { + turnamt = -KART_FULLTURN; + } + + if (turnamt > 0) + { + if (player->botvars.turnconfirm < BOTTURNCONFIRM) + { + player->botvars.turnconfirm++; + } + } + else if (turnamt < 0) + { + if (player->botvars.turnconfirm > -BOTTURNCONFIRM) + { + player->botvars.turnconfirm--; + } + } + + if (abs(player->botvars.turnconfirm) >= BOTTURNCONFIRM) + { + // You're commiting to your turn, you're allowed! + cmd->driftturn = turnamt; + cmd->angleturn += K_GetKartTurnValue(player, turnamt); + } + } + + // Free the prediction we made earlier + if (predict != NULL) + { + Z_Free(predict); + } +} + diff --git a/src/k_bot.h b/src/k_bot.h new file mode 100644 index 000000000..48472b5fd --- /dev/null +++ b/src/k_bot.h @@ -0,0 +1,232 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Kart Krew +// +// 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 k_bot.h +/// \brief Bot logic & ticcmd generation code + +#ifndef __K_BOT__ +#define __K_BOT__ + +#include "k_waypoint.h" +#include "d_player.h" + +// Maximum value of botvars.difficulty +#define MAXBOTDIFFICULTY 9 + +// How many tics in a row do you need to turn in this direction before we'll let you turn. +// Made it as small as possible without making it look like the bots are twitching constantly. +#define BOTTURNCONFIRM 4 + +// Point for bots to aim for +typedef struct botprediction_s { + fixed_t x, y; + fixed_t radius; +} botprediction_t; + + +// AVAILABLE FOR LUA + + +/*-------------------------------------------------- + boolean K_PlayerUsesBotMovement(player_t *player); + + Tells if this player is being controlled via bot movement code (is a bot, or is exiting). + + Input Arguments:- + player - Player to check. + + Return:- + true if using bot movement code, otherwise false. +--------------------------------------------------*/ + +boolean K_PlayerUsesBotMovement(player_t *player); + + +/*-------------------------------------------------- + boolean K_BotCanTakeCut(player_t *player); + + Tells if this bot is able to take shortcuts (currently unaffected by offroad, + or has certain items ready). + + Input Arguments:- + player - Player to check. + + Return:- + true if able to take shortcuts, otherwise false. +--------------------------------------------------*/ + +boolean K_BotCanTakeCut(player_t *player); + + +/*-------------------------------------------------- + fixed_t K_BotRubberband(player_t *player); + + Gives a multiplier for a bot's rubberbanding. + Meant to be used for acceleration and handling. + + Input Arguments:- + player - Player to check. + + Return:- + A multiplier in fixed point scale. +--------------------------------------------------*/ + +fixed_t K_BotRubberband(player_t *player); + + +/*-------------------------------------------------- + fixed_t K_BotTopSpeedRubberband(player_t *player); + + Gives a multiplier for a bot's rubberbanding. + Adjusted from K_BotRubberband to be used for top speed. + + Input Arguments:- + player - Player to check. + + Return:- + A multiplier in fixed point scale. +--------------------------------------------------*/ + +fixed_t K_BotTopSpeedRubberband(player_t *player); + + +/*-------------------------------------------------- + fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy); + + Gets the distance of a point away from a line. + TODO: Could go in another file? + + Input Arguments:- + v1x - Line's first vertex x position. + v1y - Line's first vertex y position. + v2x - Line's second vertex x position. + v2y - Line's second vertex y position. + cx - Point's x position. + cy - Point's y position. + + Return:- + The distance between the point and the line. +--------------------------------------------------*/ + +fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy); + + +// NOT AVAILABLE FOR LUA + + +/*-------------------------------------------------- + boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *newplayernum); + + Returns the waypoint actually being used as the finish line. + + Input Arguments:- + skin - Skin number that the bot will use. + difficulty - Difficulty level this bot will use. + newplayernum - Pointer to the last valid player slot number. + Is a pointer so that this function can be called multiple times to add more than one bot. + + Return:- + true if a bot packet can be sent, otherwise false. +--------------------------------------------------*/ + +boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *newplayernum); + + +/*-------------------------------------------------- + void K_UpdateMatchRaceBots(void); + + Updates the number of bots in the server and their difficulties for Match Race. +--------------------------------------------------*/ + +void K_UpdateMatchRaceBots(void); + + +/*-------------------------------------------------- + UINT8 K_EggboxStealth(fixed_t x, fixed_t y); + + Gets a "stealth" value for a position, to figure out how + well Eggman boxes blend into random items. + + Input Arguments:- + x - X coordinate to check. + y - Y coordinate to check. + + Return:- + Stealth value for the position. +--------------------------------------------------*/ + +UINT8 K_EggboxStealth(fixed_t x, fixed_t y); + + +/*-------------------------------------------------- + fixed_t K_BotReducePrediction(player_t *player); + + Finds walls nearby the specified player, to create a multiplier + to pull bot predictions back by. + + Input Arguments:- + player - Player to compare. + + Return:- + Multiplier in fixed point scale. +--------------------------------------------------*/ + +fixed_t K_BotReducePrediction(player_t *player); + + +/*-------------------------------------------------- + INT16 K_BotFindObjects(player_t *player, INT16 turn); + + Generates a sum for objects to steer towards/away from. + + Input Arguments:- + player - Player to compare. + turn - Turn value before object steering. + + Return:- + Turn amount sum to add to final product. +--------------------------------------------------*/ + +INT16 K_BotFindObjects(player_t *player, INT16 turn); + + +/*-------------------------------------------------- + void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd); + + Gives a multiplier for a bot's rubberbanding. Meant to be used for top speed, + acceleration, and handling. + + Input Arguments:- + player - Player to generate the ticcmd for. + cmd - The player's ticcmd to modify. + + Return:- + None +--------------------------------------------------*/ + +void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd); + + +/*-------------------------------------------------- + void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt); + + Item usage part of ticcmd generation. + + Input Arguments:- + player - Player to generate the ticcmd for. + cmd - The player's ticcmd to modify. + turnamt - How hard the bot is turning. + + Return:- + None +--------------------------------------------------*/ + +void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt); + + +#endif diff --git a/src/k_botitem.c b/src/k_botitem.c new file mode 100644 index 000000000..55f8e15e4 --- /dev/null +++ b/src/k_botitem.c @@ -0,0 +1,1095 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Kart Krew +// +// 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 k_botitem.c +/// \brief Bot item usage logic + +#include "doomdef.h" +#include "d_player.h" +#include "g_game.h" +#include "r_main.h" +#include "p_local.h" +#include "k_bot.h" +#include "lua_hook.h" +#include "byteptr.h" +#include "d_net.h" // nodetoplayer +#include "k_kart.h" +#include "z_zone.h" +#include "i_system.h" +#include "p_maputl.h" +#include "d_ticcmd.h" +#include "m_random.h" +#include "r_things.h" // numskins + +/*-------------------------------------------------- + static boolean K_BotUseItemNearPlayer(player_t *player, ticcmd_t *cmd, fixed_t radius) + + Looks for players around the bot, and presses the item button + if there is one in range. + + Input Arguments:- + player - Bot to compare against. + cmd - The bot's ticcmd. + radius - The radius to look for players in. + + Return:- + true if a player was found & we can press the item button, otherwise false. +--------------------------------------------------*/ +static boolean K_BotUseItemNearPlayer(player_t *player, ticcmd_t *cmd, fixed_t radius) +{ + UINT8 i; + + if (player->pflags & PF_ATTACKDOWN) + { + return false; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *target = NULL; + fixed_t dist = INT32_MAX; + + if (!playeringame[i]) + { + continue; + } + + target = &players[i]; + + if (target->mo == NULL || P_MobjWasRemoved(target->mo) + || player == target || target->spectator + || target->powers[pw_flashing]) + { + continue; + } + + dist = P_AproxDistance(P_AproxDistance( + player->mo->x - target->mo->x, + player->mo->y - target->mo->y), + (player->mo->z - target->mo->z) / 4 + ); + + if (dist <= radius) + { + cmd->buttons |= BT_ATTACK; + return true; + } + } + + return false; +} + +/*-------------------------------------------------- + static boolean K_PlayerNearSpot(player_t *player, fixed_t x, fixed_t y, fixed_t radius) + + Looks for players around a specified x/y coordinate. + + Input Arguments:- + player - Bot to compare against. + x - X coordinate to look around. + y - Y coordinate to look around. + radius - The radius to look for players in. + + Return:- + true if a player was found around the coordinate, otherwise false. +--------------------------------------------------*/ +static boolean K_PlayerNearSpot(player_t *player, fixed_t x, fixed_t y, fixed_t radius) +{ + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *target = NULL; + fixed_t dist = INT32_MAX; + + if (!playeringame[i]) + { + continue; + } + + target = &players[i]; + + if (target->mo == NULL || P_MobjWasRemoved(target->mo) + || player == target || target->spectator + || target->powers[pw_flashing]) + { + continue; + } + + dist = P_AproxDistance( + x - target->mo->x, + y - target->mo->y + ); + + if (dist <= radius) + { + return true; + } + } + + return false; +} + +/*-------------------------------------------------- + static boolean K_PlayerPredictThrow(player_t *player, UINT8 extra) + + Looks for players around the predicted coordinates of their thrown item. + + Input Arguments:- + player - Bot to compare against. + extra - Extra throwing distance, for aim forward on mines. + + Return:- + true if a player was found around the coordinate, otherwise false. +--------------------------------------------------*/ +static boolean K_PlayerPredictThrow(player_t *player, UINT8 extra) +{ + const fixed_t dist = (30 + (extra * 10)) * player->mo->scale; + const UINT32 airtime = FixedDiv(dist + player->mo->momz, gravity); + const fixed_t throwspeed = FixedMul(82 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); + const fixed_t estx = player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, (throwspeed + player->speed) * airtime); + const fixed_t esty = player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, (throwspeed + player->speed) * airtime); + return K_PlayerNearSpot(player, estx, esty, player->mo->radius * 2); +} + +/*-------------------------------------------------- + static boolean K_PlayerInCone(player_t *player, UINT16 cone, boolean flip) + + Looks for players in the . + + Input Arguments:- + player - Bot to compare against. + radius - How far away the targets can be. + cone - Size of cone, in degrees as an integer. + flip - If true, look behind. Otherwise, check in front of the player. + + Return:- + true if a player was found in the cone, otherwise false. +--------------------------------------------------*/ +static boolean K_PlayerInCone(player_t *player, fixed_t radius, UINT16 cone, boolean flip) +{ + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *target = NULL; + fixed_t dist = INT32_MAX; + + if (!playeringame[i]) + { + continue; + } + + target = &players[i]; + + if (target->mo == NULL || P_MobjWasRemoved(target->mo) + || player == target || target->spectator + || target->powers[pw_flashing] + || !P_CheckSight(player->mo, target->mo)) + { + continue; + } + + dist = P_AproxDistance(P_AproxDistance( + player->mo->x - target->mo->x, + player->mo->y - target->mo->y), + (player->mo->z - target->mo->z) / 4 + ); + + if (dist <= radius) + { + angle_t a = player->mo->angle - R_PointToAngle2(player->mo->x, player->mo->y, target->mo->x, target->mo->y); + INT16 ad = 0; + + if (a < ANGLE_180) + { + ad = AngleFixed(a)>>FRACBITS; + } + else + { + ad = 360-(AngleFixed(a)>>FRACBITS); + } + + ad = abs(ad); + + if (flip) + { + if (ad >= 180-cone) + { + return true; + } + } + else + { + if (ad <= cone) + { + return true; + } + } + } + } + + return false; +} + +/*-------------------------------------------------- + static boolean K_BotGenericPressItem(player_t *player, ticcmd_t *cmd, SINT8 dir) + + Presses the item button & aim buttons for the bot. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + dir - Aiming direction: 1 for forwards, -1 for backwards, 0 for neutral. + + Return:- + true if we could press, false if not. +--------------------------------------------------*/ +static boolean K_BotGenericPressItem(player_t *player, ticcmd_t *cmd, SINT8 dir) +{ + if (player->pflags & PF_ATTACKDOWN) + { + return false; + } + + if (dir == 1) + { + cmd->buttons |= BT_FORWARD; + } + else if (dir == -1) + { + cmd->buttons |= BT_BACKWARD; + } + + cmd->buttons |= BT_ATTACK; + player->botvars.itemconfirm = 0; + return true; +} + +/*-------------------------------------------------- + static void K_BotItemGenericTap(player_t *player, ticcmd_t *cmd) + + Item usage for generic items that you need to tap. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemGenericTap(player_t *player, ticcmd_t *cmd) +{ + if (!(player->pflags & PF_ATTACKDOWN)) + { + cmd->buttons |= BT_ATTACK; + player->botvars.itemconfirm = 0; + } +} + +/*-------------------------------------------------- + static boolean K_BotRevealsGenericTrap(player_t *player, INT16 turnamt, boolean mine) + + Decides if a bot is ready to reveal their trap item or not. + + Input Arguments:- + player - Bot that has the banana. + turnamt - How hard they currently are turning. + mine - Set to true to handle Mine-specific behaviors. + + Return:- + true if we want the bot to reveal their banana, otherwise false. +--------------------------------------------------*/ +static boolean K_BotRevealsGenericTrap(player_t *player, INT16 turnamt, boolean mine) +{ + if (abs(turnamt) >= KART_FULLTURN/2) + { + // DON'T reveal on turns, we can place bananas on turns whenever we have multiple to spare, + // or if you missed your intentioned throw/place on a player. + return false; + } + + // Check the predicted throws. + if (K_PlayerPredictThrow(player, 0)) + { + return true; + } + + if (mine) + { + if (K_PlayerPredictThrow(player, 1)) + { + return true; + } + } + + // Check your behind. + if (K_PlayerInCone(player, player->mo->radius * 16, 10, true)) + { + return true; + } + + return false; +} + +/*-------------------------------------------------- + static void K_BotItemGenericTrapShield(player_t *player, ticcmd_t *cmd, INT16 turnamt, boolean mine) + + Item usage for Eggman shields. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + turnamt - How hard they currently are turning. + mine - Set to true to handle Mine-specific behaviors. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemGenericTrapShield(player_t *player, ticcmd_t *cmd, INT16 turnamt, boolean mine) +{ + if (player->kartstuff[k_itemheld]) + { + return; + } + + if (K_BotRevealsGenericTrap(player, turnamt, mine) || (player->botvars.itemconfirm++ > 5*TICRATE)) + { + K_BotGenericPressItem(player, cmd, 0); + } +} + +/*-------------------------------------------------- + static void K_BotItemGenericOrbitShield(player_t *player, ticcmd_t *cmd) + + Item usage for orbitting shields. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemGenericOrbitShield(player_t *player, ticcmd_t *cmd) +{ + if (player->kartstuff[k_itemheld]) + { + return; + } + + K_BotGenericPressItem(player, cmd, 0); +} + +/*-------------------------------------------------- + static void K_BotItemSneaker(player_t *player, ticcmd_t *cmd) + + Item usage for sneakers. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemSneaker(player_t *player, ticcmd_t *cmd) +{ + if ((player->kartstuff[k_offroad] && K_ApplyOffroad(player)) // Stuck in offroad, use it NOW + || K_GetWaypointIsShortcut(player->nextwaypoint) == true // Going toward a shortcut! + || player->speed < K_GetKartSpeed(player, false)/2 // Being slowed down too much + || player->kartstuff[k_speedboost] > (FRACUNIT/8) // Have another type of boost (tethering) + || player->botvars.itemconfirm > 4*TICRATE) // Held onto it for too long + { + if (!player->kartstuff[k_sneakertimer] && !(player->pflags & PF_ATTACKDOWN)) + { + cmd->buttons |= BT_ATTACK; + player->botvars.itemconfirm = 2*TICRATE; + } + } + else + { + player->botvars.itemconfirm++; + } +} + +/*-------------------------------------------------- + static void K_BotItemRocketSneaker(player_t *player, ticcmd_t *cmd) + + Item usage for rocket sneakers. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemRocketSneaker(player_t *player, ticcmd_t *cmd) +{ + if (player->botvars.itemconfirm > TICRATE) + { + if (!player->kartstuff[k_sneakertimer] && !(player->pflags & PF_ATTACKDOWN)) + { + cmd->buttons |= BT_ATTACK; + player->botvars.itemconfirm = 0; + } + } + else + { + player->botvars.itemconfirm++; + } +} + + +/*-------------------------------------------------- + static void K_BotItemBanana(player_t *player, ticcmd_t *cmd, INT16 turnamt) + + Item usage for trap item throwing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + turnamt - How hard they currently are turning. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemBanana(player_t *player, ticcmd_t *cmd, INT16 turnamt) +{ + SINT8 throwdir = -1; + + player->botvars.itemconfirm++; + + if (abs(turnamt) >= KART_FULLTURN/2) + { + player->botvars.itemconfirm += player->botvars.difficulty / 2; + throwdir = -1; + } + else + { + if (K_PlayerPredictThrow(player, 0)) + { + player->botvars.itemconfirm += player->botvars.difficulty * 2; + throwdir = 1; + } + } + + if (K_PlayerInCone(player, player->mo->radius * 16, 10, true)) + { + player->botvars.itemconfirm += player->botvars.difficulty; + throwdir = -1; + } + + if (player->botvars.itemconfirm > 2*TICRATE || player->kartstuff[k_bananadrag] >= TICRATE) + { + K_BotGenericPressItem(player, cmd, throwdir); + } +} + +/*-------------------------------------------------- + static void K_BotItemMine(player_t *player, ticcmd_t *cmd, INT16 turnamt) + + Item usage for trap item throwing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + turnamt - How hard they currently are turning. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemMine(player_t *player, ticcmd_t *cmd, INT16 turnamt) +{ + SINT8 throwdir = 0; + + player->botvars.itemconfirm++; + + if (K_PlayerInCone(player, player->mo->radius * 16, 10, true)) + { + player->botvars.itemconfirm += player->botvars.difficulty; + throwdir = -1; + } + + if (abs(turnamt) >= KART_FULLTURN/2) + { + player->botvars.itemconfirm += player->botvars.difficulty / 2; + throwdir = -1; + } + else + { + if (K_PlayerPredictThrow(player, 0)) + { + player->botvars.itemconfirm += player->botvars.difficulty * 2; + throwdir = 0; + } + + if (K_PlayerPredictThrow(player, 1)) + { + player->botvars.itemconfirm += player->botvars.difficulty * 2; + throwdir = 1; + } + } + + + + if (player->botvars.itemconfirm > 2*TICRATE || player->kartstuff[k_bananadrag] >= TICRATE) + { + K_BotGenericPressItem(player, cmd, throwdir); + } +} + +/*-------------------------------------------------- + static void K_BotItemEggman(player_t *player, ticcmd_t *cmd) + + Item usage for Eggman item throwing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemEggman(player_t *player, ticcmd_t *cmd) +{ + const UINT8 stealth = K_EggboxStealth(player->mo->x, player->mo->y); + SINT8 throwdir = -1; + + player->botvars.itemconfirm++; + + if (K_PlayerPredictThrow(player, 0)) + { + player->botvars.itemconfirm += player->botvars.difficulty / 2; + throwdir = 1; + } + + if (K_PlayerInCone(player, player->mo->radius * 16, 10, true)) + { + player->botvars.itemconfirm += player->botvars.difficulty; + throwdir = -1; + } + + if (stealth > 1 || player->kartstuff[k_itemroulette] > 0) + { + player->botvars.itemconfirm += player->botvars.difficulty * 4; + throwdir = -1; + } + + if (player->botvars.itemconfirm > 2*TICRATE || player->kartstuff[k_bananadrag] >= TICRATE) + { + K_BotGenericPressItem(player, cmd, throwdir); + } +} + +/*-------------------------------------------------- + static boolean K_BotRevealsEggbox(player_t *player) + + Decides if a bot is ready to place their Eggman item or not. + + Input Arguments:- + player - Bot that has the eggbox. + + Return:- + true if we want the bot to reveal their eggbox, otherwise false. +--------------------------------------------------*/ +static boolean K_BotRevealsEggbox(player_t *player) +{ + const UINT8 stealth = K_EggboxStealth(player->mo->x, player->mo->y); + + // This is a stealthy spot for an eggbox, lets reveal it! + if (stealth > 1) + { + return true; + } + + // Check the predicted throws. + if (K_PlayerPredictThrow(player, 0)) + { + return true; + } + + // Check your behind. + if (K_PlayerInCone(player, player->mo->radius * 16, 10, true)) + { + return true; + } + + return false; +} + +/*-------------------------------------------------- + static void K_BotItemEggmanShield(player_t *player, ticcmd_t *cmd) + + Item usage for Eggman shields. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemEggmanShield(player_t *player, ticcmd_t *cmd) +{ + if (player->kartstuff[k_eggmanheld]) + { + return; + } + + if (K_BotRevealsEggbox(player) || (player->botvars.itemconfirm++ > 20*TICRATE)) + { + K_BotGenericPressItem(player, cmd, 0); + } +} + +/*-------------------------------------------------- + static void K_BotItemEggmanExplosion(player_t *player, ticcmd_t *cmd) + + Item usage for Eggman explosions. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemEggmanExplosion(player_t *player, ticcmd_t *cmd) +{ + if (player->kartstuff[k_position] == 1) + { + cmd->forwardmove /= 2; + cmd->buttons |= BT_BRAKE; + } + + K_BotUseItemNearPlayer(player, cmd, 128*player->mo->scale); +} + +/*-------------------------------------------------- + static void K_BotItemOrbinaut(player_t *player, ticcmd_t *cmd) + + Item usage for Orbinaut throwing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemOrbinaut(player_t *player, ticcmd_t *cmd) +{ + const fixed_t topspeed = K_GetKartSpeed(player, false); + fixed_t radius = (player->mo->radius * 32); + SINT8 throwdir = -1; + + if (player->speed > topspeed) + { + radius = FixedMul(radius, FixedDiv(player->speed, topspeed)); + } + + player->botvars.itemconfirm++; + + if (K_PlayerInCone(player, radius, 10, false)) + { + player->botvars.itemconfirm += player->botvars.difficulty * 2; + throwdir = 1; + } + else if (K_PlayerInCone(player, radius, 10, true)) + { + player->botvars.itemconfirm += player->botvars.difficulty; + throwdir = -1; + } + + if (player->botvars.itemconfirm > 5*TICRATE) + { + K_BotGenericPressItem(player, cmd, throwdir); + } +} + +/*-------------------------------------------------- + static void K_BotItemJawz(player_t *player, ticcmd_t *cmd) + + Item usage for Jawz throwing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemJawz(player_t *player, ticcmd_t *cmd) +{ + const fixed_t topspeed = K_GetKartSpeed(player, false); + fixed_t radius = (player->mo->radius * 32); + SINT8 throwdir = 1; + + if (player->speed > topspeed) + { + radius = FixedMul(radius, FixedDiv(player->speed, topspeed)); + } + + player->botvars.itemconfirm++; + + if (K_PlayerInCone(player, radius, 10, true)) + { + player->botvars.itemconfirm += player->botvars.difficulty; + throwdir = -1; + } + + if (player->kartstuff[k_lastjawztarget] != -1) + { + player->botvars.itemconfirm += player->botvars.difficulty * 2; + throwdir = 1; + } + + if (player->botvars.itemconfirm > 5*TICRATE) + { + K_BotGenericPressItem(player, cmd, throwdir); + } +} + +/*-------------------------------------------------- + static void K_BotItemThunder(player_t *player, ticcmd_t *cmd) + + Item usage for Thunder Shield. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemThunder(player_t *player, ticcmd_t *cmd) +{ + if (!K_BotUseItemNearPlayer(player, cmd, 192*player->mo->scale)) + { + if (player->botvars.itemconfirm > 10*TICRATE) + { + K_BotGenericPressItem(player, cmd, 0); + } + else + { + player->botvars.itemconfirm++; + } + } +} + +/*-------------------------------------------------- + static void K_BotItemBubble(player_t *player, ticcmd_t *cmd) + + Item usage for Bubble Shield. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemBubble(player_t *player, ticcmd_t *cmd) +{ + boolean hold = false; + + if (player->kartstuff[k_bubbleblowup] <= 0) + { + UINT8 i; + + player->botvars.itemconfirm++; + + if (player->kartstuff[k_bubblecool] <= 0) + { + const fixed_t radius = 192 * player->mo->scale; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *target = NULL; + fixed_t dist = INT32_MAX; + + if (!playeringame[i]) + { + continue; + } + + target = &players[i]; + + if (target->mo == NULL || P_MobjWasRemoved(target->mo) + || player == target || target->spectator + || target->powers[pw_flashing]) + { + continue; + } + + dist = P_AproxDistance(P_AproxDistance( + player->mo->x - target->mo->x, + player->mo->y - target->mo->y), + (player->mo->z - target->mo->z) / 4 + ); + + if (dist <= radius) + { + hold = true; + break; + } + } + } + } + else if (player->kartstuff[k_bubbleblowup] >= bubbletime) + { + if (player->botvars.itemconfirm >= 10*TICRATE) + { + hold = true; + } + } + else if (player->kartstuff[k_bubbleblowup] < bubbletime) + { + hold = true; + } + + if (hold && player->kartstuff[k_holdready]) + { + cmd->buttons |= BT_ATTACK; + } +} + +/*-------------------------------------------------- + static void K_BotItemFlame(player_t *player, ticcmd_t *cmd) + + Item usage for Flame Shield. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemFlame(player_t *player, ticcmd_t *cmd) +{ + if (player->botvars.itemconfirm > 0) + { + player->botvars.itemconfirm--; + } + else if (player->kartstuff[k_holdready]) + { + INT32 flamemax = player->kartstuff[k_flamelength] * flameseg; + + if (player->kartstuff[k_flamemeter] < flamemax || flamemax == 0) + { + cmd->buttons |= BT_ATTACK; + } + else + { + player->botvars.itemconfirm = 3*flamemax/4; + } + } +} + +/*-------------------------------------------------- + static void K_BotItemRings(player_t *player, ticcmd_t *cmd) + + Item usage for rings. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemRings(player_t *player, ticcmd_t *cmd) +{ + INT32 saferingsval = 16 - K_GetKartRingPower(player); + + if (player->speed < K_GetKartSpeed(player, false)/2 // Being slowed down too much + || player->kartstuff[k_speedboost] > (FRACUNIT/5)) // Have another type of boost (tethering) + { + saferingsval -= 5; + } + + if (player->kartstuff[k_rings] > saferingsval) + { + cmd->buttons |= BT_ATTACK; + } +} + +/*-------------------------------------------------- + static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) + + Item usage for item roulette mashing. + + Input Arguments:- + player - Bot to do this for. + cmd - Bot's ticcmd to edit. + + Return:- + None +--------------------------------------------------*/ +static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) +{ + boolean mash = false; + + if (player->pflags & PF_ATTACKDOWN) + { + return; + } + + if (player->kartstuff[k_rings] < 0 && cv_superring.value) + { + // Uh oh, we need a loan! + // It'll be better in the long run for bots to lose an item set for 10 free rings. + mash = true; + } + + // TODO: Mash based on how far behind you are, when items are + // almost garantueed to be in your favor. + + if (mash == true) + { + cmd->buttons |= BT_ATTACK; + } +} + +/*-------------------------------------------------- + void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) + + See header file for description. +--------------------------------------------------*/ +void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) +{ + if (player->kartstuff[k_userings] == 1) + { + // Use rings! + + if (!player->exiting) + { + K_BotItemRings(player, cmd); + } + } + else + { + if (player->botvars.itemdelay) + { + player->botvars.itemdelay--; + player->botvars.itemconfirm = 0; + return; + } + + if (player->kartstuff[k_itemroulette]) + { + // Mashing behaviors + K_BotItemRouletteMash(player, cmd); + return; + } + + if (player->kartstuff[k_stealingtimer] == 0 && player->kartstuff[k_stolentimer] == 0) + { + if (player->kartstuff[k_eggmanexplode]) + { + K_BotItemEggmanExplosion(player, cmd); + } + else if (player->kartstuff[k_eggmanheld]) + { + K_BotItemEggman(player, cmd); + } + else if (player->kartstuff[k_rocketsneakertimer] > 0) + { + K_BotItemRocketSneaker(player, cmd); + } + else + { + switch (player->kartstuff[k_itemtype]) + { + default: + if (player->kartstuff[k_itemtype] != KITEM_NONE) + { + K_BotItemGenericTap(player, cmd); + } + + player->botvars.itemconfirm = 0; + break; + case KITEM_INVINCIBILITY: + case KITEM_SPB: + case KITEM_GROW: + case KITEM_SHRINK: + case KITEM_HYUDORO: + case KITEM_SUPERRING: + K_BotItemGenericTap(player, cmd); + break; + case KITEM_ROCKETSNEAKER: + if (player->kartstuff[k_rocketsneakertimer] <= 0) + { + K_BotItemGenericTap(player, cmd); + } + break; + case KITEM_SNEAKER: + K_BotItemSneaker(player, cmd); + break; + case KITEM_BANANA: + if (!player->kartstuff[k_itemheld]) + { + K_BotItemGenericTrapShield(player, cmd, turnamt, false); + } + else + { + K_BotItemBanana(player, cmd, turnamt); + } + break; + case KITEM_EGGMAN: + K_BotItemEggmanShield(player, cmd); + break; + case KITEM_ORBINAUT: + if (!player->kartstuff[k_itemheld]) + { + K_BotItemGenericOrbitShield(player, cmd); + } + else if (player->kartstuff[k_position] != 1) // Hold onto orbiting items when in 1st :) + /* FALL-THRU */ + case KITEM_BALLHOG: + { + K_BotItemOrbinaut(player, cmd); + } + break; + case KITEM_JAWZ: + if (!player->kartstuff[k_itemheld]) + { + K_BotItemGenericOrbitShield(player, cmd); + } + else if (player->kartstuff[k_position] != 1) // Hold onto orbiting items when in 1st :) + { + K_BotItemJawz(player, cmd); + } + break; + case KITEM_MINE: + if (!player->kartstuff[k_itemheld]) + { + K_BotItemGenericTrapShield(player, cmd, turnamt, true); + } + else + { + K_BotItemMine(player, cmd, turnamt); + } + break; + case KITEM_THUNDERSHIELD: + K_BotItemThunder(player, cmd); + break; + case KITEM_BUBBLESHIELD: + K_BotItemBubble(player, cmd); + break; + case KITEM_FLAMESHIELD: + K_BotItemFlame(player, cmd); + break; + } + } + } + } +} diff --git a/src/k_botsearch.c b/src/k_botsearch.c new file mode 100644 index 000000000..28e08f3cf --- /dev/null +++ b/src/k_botsearch.c @@ -0,0 +1,781 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Kart Krew +// +// 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 k_botsearch.c +/// \brief Bot blockmap search functions + +#include "doomdef.h" +#include "d_player.h" +#include "g_game.h" +#include "r_main.h" +#include "p_local.h" +#include "k_bot.h" +#include "lua_hook.h" +#include "byteptr.h" +#include "d_net.h" // nodetoplayer +#include "k_kart.h" +#include "z_zone.h" +#include "i_system.h" +#include "p_maputl.h" +#include "d_ticcmd.h" +#include "m_random.h" +#include "r_things.h" // numskins +#include "p_slopes.h" // P_GetZAt + +struct globalsmuggle +{ + mobj_t *botmo; + fixed_t distancetocheck; + + fixed_t closestlinedist; + + INT16 curturn; + INT16 steer; + + fixed_t eggboxx, eggboxy; + UINT8 randomitems; + UINT8 eggboxes; +} globalsmuggle; + +/*-------------------------------------------------- + static boolean K_FindEggboxes(mobj_t *thing) + + Blockmap search function. + Increments the random items and egg boxes counters. + + Input Arguments:- + thing - Object passed in from iteration. + + Return:- + true continues searching, false ends the search early. +--------------------------------------------------*/ +static boolean K_FindEggboxes(mobj_t *thing) +{ + fixed_t dist; + + if (thing->type != MT_RANDOMITEM && thing->type != MT_EGGMANITEM) + { + return true; + } + + if (!thing->health) + { + return true; + } + + dist = P_AproxDistance(thing->x - globalsmuggle.eggboxx, thing->y - globalsmuggle.eggboxy); + + if (dist > globalsmuggle.distancetocheck) + { + return true; + } + + if (thing->type == MT_RANDOMITEM) + { + globalsmuggle.randomitems++; + } + else + { + globalsmuggle.eggboxes++; + } + + return true; +} + +/*-------------------------------------------------- + UINT8 K_EggboxStealth(fixed_t x, fixed_t y) + + See header file for description. +--------------------------------------------------*/ +UINT8 K_EggboxStealth(fixed_t x, fixed_t y) +{ + INT32 xl, xh, yl, yh, bx, by; + + globalsmuggle.eggboxx = x; + globalsmuggle.eggboxy = y; + globalsmuggle.distancetocheck = (mapobjectscale * 256); + globalsmuggle.randomitems = 0; + globalsmuggle.eggboxes = 0; + + xl = (unsigned)(globalsmuggle.eggboxx - globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + xh = (unsigned)(globalsmuggle.eggboxx + globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + yl = (unsigned)(globalsmuggle.eggboxy - globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + yh = (unsigned)(globalsmuggle.eggboxy + globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + + BMBOUNDFIX(xl, xh, yl, yh); + + for (bx = xl; bx <= xh; bx++) + { + for (by = yl; by <= yh; by++) + { + P_BlockThingsIterator(bx, by, K_FindEggboxes); + } + } + + return (globalsmuggle.randomitems * globalsmuggle.eggboxes); +} + +/*-------------------------------------------------- + static boolean K_BotHatesThisSectorsSpecial(player_t *player, sector_t *sec) + + Tells us if a bot will play more careful around + this sector's special type. + + Input Arguments:- + player - Player to check against. + sec - Sector to check the specials of. + + Return:- + true if avoiding this sector special, false otherwise. +--------------------------------------------------*/ +static boolean K_BotHatesThisSectorsSpecial(player_t *player, sector_t *sec) +{ + switch (GETSECSPECIAL(sec->special, 1)) + { + case 1: // Damage + case 5: // Spikes + case 6: case 7: // Death Pit + case 8: // Instant Kill + return true; + //case 2: case 3: // Offroad (let's let them lawnmower) + case 4: // Offroad (Strong) + if (!K_BotCanTakeCut(player)) + { + return true; + } + break; + default: + break; + } + + return false; +} + +/*-------------------------------------------------- + static boolean K_BotHatesThisSector(player_t *player, sector_t *sec, fixed_t x, fixed_t y) + + Tells us if a bot will play more careful around + this sector. Checks FOFs in the sector, as well. + + Input Arguments:- + player - Player to check against. + sec - Sector to check against. + x - Linedef cross X position, for slopes + y - Linedef cross Y position, for slopes + + Return:- + true if avoiding this sector, false otherwise. +--------------------------------------------------*/ +static boolean K_BotHatesThisSector(player_t *player, sector_t *sec, fixed_t x, fixed_t y) +{ + const boolean flip = (player->mo->eflags & MFE_VERTICALFLIP); + INT32 specialflag = 0; + fixed_t highestfloor = INT32_MAX; + sector_t *bestsector = NULL; + ffloor_t *rover; + + if (flip == true) + { + specialflag = SF_FLIPSPECIAL_CEILING; + highestfloor = (sec->c_slope ? P_GetZAt(sec->c_slope, x, y) : sec->ceilingheight); + } + else + { + specialflag = SF_FLIPSPECIAL_FLOOR; + highestfloor = (sec->f_slope ? P_GetZAt(sec->f_slope, x, y) : sec->floorheight); + } + + if (sec->flags & specialflag) + { + bestsector = sec; + } + + for (rover = sec->ffloors; rover; rover = rover->next) + { + fixed_t top = INT32_MAX; + fixed_t bottom = INT32_MAX; + + if (!(rover->flags & FF_EXISTS)) + { + continue; + } + + top = (*rover->t_slope ? P_GetZAt(*rover->t_slope, x, y) : *rover->topheight); + bottom = (*rover->b_slope ? P_GetZAt(*rover->b_slope, x, y) : *rover->bottomheight); + + if (!(rover->flags & FF_BLOCKPLAYER)) + { + if ((top >= player->mo->z) && (bottom <= player->mo->z + player->mo->height) + && K_BotHatesThisSectorsSpecial(player, rover->master->frontsector)) + { + // Bad intangible sector at our height, so we DEFINITELY want to avoid + return true; + } + } + + if ((rover->flags & FF_BLOCKPLAYER) && !(rover->master->frontsector->flags & specialflag)) + { + continue; + } + + // Find the highest FOF floor beneath the player, and check it at the end. + if (flip == true) + { + if (bottom < highestfloor + && bottom >= player->mo->z + player->mo->height) + { + bestsector = rover->master->frontsector; + highestfloor = bottom; + } + } + else + { + if (top > highestfloor + && top <= player->mo->z) + { + bestsector = rover->master->frontsector; + highestfloor = top; + } + } + } + + if (bestsector == NULL) + { + return false; + } + + return K_BotHatesThisSectorsSpecial(player, bestsector); +} + +/*-------------------------------------------------- + static boolean K_FindBlockingWalls(line_t *line) + + Blockmap search function. + Reels the bot prediction back in based on solid walls + or other obstacles surrounding the bot. + + Input Arguments:- + line - Linedef passed in from iteration. + + Return:- + true continues searching, false ends the search early. +--------------------------------------------------*/ +static boolean K_FindBlockingWalls(line_t *line) +{ + // Condensed version of PIT_CheckLine + const fixed_t maxstepmove = FixedMul(MAXSTEPMOVE, mapobjectscale); + fixed_t maxstep = maxstepmove; + fixed_t linedist = INT32_MAX; + INT32 lineside = 0; + vertex_t pos; + + if (!globalsmuggle.botmo || P_MobjWasRemoved(globalsmuggle.botmo) || !globalsmuggle.botmo->player) + { + return false; + } + + if (line->polyobj && !(line->polyobj->flags & POF_SOLID)) + { + return true; + } + + if (tmbbox[BOXRIGHT] <= line->bbox[BOXLEFT] || tmbbox[BOXLEFT] >= line->bbox[BOXRIGHT] + || tmbbox[BOXTOP] <= line->bbox[BOXBOTTOM] || tmbbox[BOXBOTTOM] >= line->bbox[BOXTOP]) + { + return true; + } + + if (P_BoxOnLineSide(tmbbox, line) != -1) + { + return true; + } + + lineside = P_PointOnLineSide(globalsmuggle.botmo->x, globalsmuggle.botmo->y, line); + + // one sided line + if (!line->backsector) + { + if (lineside) + { + // don't hit the back side + return true; + } + + goto blocked; + } + + if ((line->flags & ML_IMPASSABLE) || (line->flags & ML_BLOCKPLAYERS)) + { + goto blocked; + } + + // set openrange, opentop, openbottom + P_LineOpening(line, globalsmuggle.botmo); + + if (globalsmuggle.botmo->player->kartstuff[k_waterskip]) + maxstep += maxstepmove; + + if (P_MobjTouchingSectorSpecial(globalsmuggle.botmo, 1, 13, false)) + maxstep <<= 1; + else if (P_MobjTouchingSectorSpecial(globalsmuggle.botmo, 1, 12, false)) + maxstep = 0; + + if ((openrange < globalsmuggle.botmo->height) // doesn't fit + || (opentop - globalsmuggle.botmo->z < globalsmuggle.botmo->height) // mobj is too high + || (openbottom - globalsmuggle.botmo->z > maxstep)) // too big a step up + { + goto blocked; + } + + // Treat damage sectors like walls + P_ClosestPointOnLine(globalsmuggle.botmo->x, globalsmuggle.botmo->y, line, &pos); + + if (lineside) + { + if (K_BotHatesThisSector(globalsmuggle.botmo->player, line->frontsector, pos.x, pos.y)) + goto blocked; + } + else + { + if (K_BotHatesThisSector(globalsmuggle.botmo->player, line->backsector, pos.x, pos.y)) + goto blocked; + } + + // We weren't blocked! + return true; + +blocked: + linedist = K_DistanceOfLineFromPoint(line->v1->x, line->v1->y, line->v2->x, line->v2->y, globalsmuggle.botmo->x, globalsmuggle.botmo->y); + linedist -= (globalsmuggle.botmo->radius * 8); // Maintain a reasonable distance away from it + + if (linedist > globalsmuggle.distancetocheck) + { + return true; + } + + if (linedist <= 0) + { + globalsmuggle.closestlinedist = 0; + return false; + } + + if (linedist < globalsmuggle.closestlinedist) + { + globalsmuggle.closestlinedist = linedist; + } + + return true; +} + +/*-------------------------------------------------- + fixed_t K_BotReducePrediction(player_t *player) + + See header file for description. +--------------------------------------------------*/ +fixed_t K_BotReducePrediction(player_t *player) +{ + INT32 xl, xh, yl, yh, bx, by; + + globalsmuggle.botmo = player->mo; + globalsmuggle.distancetocheck = (player->mo->radius * 16); + globalsmuggle.closestlinedist = INT32_MAX; + + tmx = player->mo->x; + tmy = player->mo->y; + + xl = (unsigned)(tmx - globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + xh = (unsigned)(tmx + globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + yl = (unsigned)(tmy - globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + yh = (unsigned)(tmy + globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + + BMBOUNDFIX(xl, xh, yl, yh); + + tmbbox[BOXTOP] = tmy + globalsmuggle.distancetocheck; + tmbbox[BOXBOTTOM] = tmy - globalsmuggle.distancetocheck; + tmbbox[BOXRIGHT] = tmx + globalsmuggle.distancetocheck; + tmbbox[BOXLEFT] = tmx - globalsmuggle.distancetocheck; + + // Check for lines that the bot might collide with + for (bx = xl; bx <= xh; bx++) + { + for (by = yl; by <= yh; by++) + { + P_BlockLinesIterator(bx, by, K_FindBlockingWalls); + } + } + + if (globalsmuggle.closestlinedist == INT32_MAX) + { + return FRACUNIT; + } + + return FixedDiv(globalsmuggle.closestlinedist, globalsmuggle.distancetocheck); +} + +/*-------------------------------------------------- + static void K_SteerFromObject(mobj_t *bot, mobj_t *thing, fixed_t fulldist, fixed_t xdist, boolean towards, INT16 amount) + + Handles steering away/towards the specified object. + + Input Arguments:- + bot - Bot's mobj. + thing - Mobj to steer towards/away from. + fulldist - Distance away from object. + xdist - Horizontal distance away from object. + towards - If true, steer towards the object. Otherwise, steer away. + amount - How hard to turn. + + Return:- + None +--------------------------------------------------*/ +static void K_SteerFromObject(mobj_t *bot, mobj_t *thing, fixed_t fulldist, fixed_t xdist, boolean towards, INT16 amount) +{ + angle_t destangle = R_PointToAngle2(bot->x, bot->y, thing->x, thing->y); + angle_t angle; + SINT8 flip = 1; + + amount = (amount * FixedDiv(globalsmuggle.distancetocheck - fulldist, globalsmuggle.distancetocheck)) / FRACUNIT; + + if (amount == 0) + { + // Shouldn't happen + return; + } + + if (towards) + { + if (xdist < FixedHypot(bot->radius, thing->radius)) + { + // Don't need to turn any harder! + + if (abs(globalsmuggle.steer) <= amount) + { + globalsmuggle.steer = 0; + } + else + { + if (globalsmuggle.steer > 0) + { + globalsmuggle.steer -= amount; + } + else if (globalsmuggle.steer < 0) + { + globalsmuggle.steer += amount; + } + } + + return; + } + + // Still turning towards it, flip. + flip = -flip; + } + + angle = (bot->angle - destangle); + if (angle < ANGLE_180) + { + flip = -flip; + } + + // If going in the opposite direction of where you wanted to turn, + // then reduce the amount that you can turn in that direction. + if ((flip == 1 && globalsmuggle.curturn < 0) + || (flip == -1 && globalsmuggle.curturn > 0)) + { + amount /= 4; + } + + globalsmuggle.steer += amount * flip; +} + +/*-------------------------------------------------- + static boolean K_BotSteerObjects(mobj_t *thing) + + Blockmap search function. + Finds objects around the bot to steer towards/away from. + + Input Arguments:- + thing - Object passed in from iteration. + + Return:- + true continues searching, false ends the search early. +--------------------------------------------------*/ +static boolean K_BotSteerObjects(mobj_t *thing) +{ + INT16 anglediff; + fixed_t xdist, ydist, fulldist; + angle_t destangle, angle; + INT16 attack = ((9 - globalsmuggle.botmo->player->kartspeed) * KART_FULLTURN) / 8; // Acceleration chars are more aggressive + INT16 dodge = ((9 - globalsmuggle.botmo->player->kartweight) * KART_FULLTURN) / 8; // Handling chars dodge better + + if (!globalsmuggle.botmo || P_MobjWasRemoved(globalsmuggle.botmo) || !globalsmuggle.botmo->player) + { + return false; + } + + if (!thing->health) + { + return true; + } + + if (globalsmuggle.botmo == thing) + { + return true; + } + + xdist = K_DistanceOfLineFromPoint( + globalsmuggle.botmo->x, globalsmuggle.botmo->y, + globalsmuggle.botmo->x + FINECOSINE(globalsmuggle.botmo->angle >> ANGLETOFINESHIFT), globalsmuggle.botmo->y + FINESINE(globalsmuggle.botmo->angle >> ANGLETOFINESHIFT), + thing->x, thing->y + ) / 2; // weight x dist more heavily than y dist + + ydist = K_DistanceOfLineFromPoint( + globalsmuggle.botmo->x, globalsmuggle.botmo->y, + globalsmuggle.botmo->x + FINECOSINE((globalsmuggle.botmo->angle + ANGLE_90) >> ANGLETOFINESHIFT), globalsmuggle.botmo->y + FINESINE((globalsmuggle.botmo->angle + ANGLE_90) >> ANGLETOFINESHIFT), + thing->x, thing->y + ); + + fulldist = FixedHypot(xdist, ydist); + + if (fulldist > globalsmuggle.distancetocheck) + { + return true; + } + + if (!P_CheckSight(globalsmuggle.botmo, thing)) + { + return true; + } + + destangle = R_PointToAngle2(globalsmuggle.botmo->x, globalsmuggle.botmo->y, thing->x, thing->y); + angle = (globalsmuggle.botmo->angle - destangle); + + if (angle < ANGLE_180) + { + anglediff = AngleFixed(angle)>>FRACBITS; + } + else + { + anglediff = 360-(AngleFixed(angle)>>FRACBITS); + } + + anglediff = abs(anglediff); + +#define PlayerAttackSteer(botcond, thingcond) \ + if ((botcond) && !(thingcond)) \ + { \ + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, 2 * (KART_FULLTURN + attack)); \ + } \ + else if ((thingcond) && !(botcond)) \ + { \ + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); \ + } + + switch (thing->type) + { + case MT_BANANA: + case MT_BANANA_SHIELD: + case MT_EGGMANITEM_SHIELD: + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + case MT_JAWZ: + case MT_JAWZ_DUD: + case MT_JAWZ_SHIELD: + case MT_SSMINE: + case MT_SSMINE_SHIELD: + case MT_BALLHOG: + case MT_SPB: + case MT_BUBBLESHIELDTRAP: + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); + break; + case MT_RANDOMITEM: + if (anglediff >= 60) + { + break; + } + + if (P_CanPickupItem(globalsmuggle.botmo->player, 1)) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, KART_FULLTURN + attack); + } + break; + case MT_EGGMANITEM: + if (anglediff >= 60) + { + break; + } + + if (P_CanPickupItem(globalsmuggle.botmo->player, 1)) // Can pick up an actual item + { + const UINT8 stealth = K_EggboxStealth(thing->x, thing->y); + const UINT8 requiredstealth = (globalsmuggle.botmo->player->botvars.difficulty * globalsmuggle.botmo->player->botvars.difficulty); + + if (stealth >= requiredstealth) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, 2 * (KART_FULLTURN + attack)); + } + else + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); + } + } + break; + case MT_FLOATINGITEM: + if (anglediff >= 60) + { + break; + } + + if (P_CanPickupItem(globalsmuggle.botmo->player, 3)) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, KART_FULLTURN + attack); + } + break; + case MT_RING: + case MT_FLINGRING: + if (anglediff >= 60) + { + break; + } + + if ((RINGTOTAL(globalsmuggle.botmo->player) < 20 && !globalsmuggle.botmo->player->kartstuff[k_ringlock] + && P_CanPickupItem(globalsmuggle.botmo->player, 0)) + && !thing->extravalue1 + && (globalsmuggle.botmo->player->kartstuff[k_itemtype] != KITEM_THUNDERSHIELD)) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, + (RINGTOTAL(globalsmuggle.botmo->player) < 3 + ? (4 * (KART_FULLTURN + attack)) + : (KART_FULLTURN + attack)) + ); + } + break; + case MT_PLAYER: + if (thing->player + && !thing->player->kartstuff[k_hyudorotimer] + && !globalsmuggle.botmo->player->kartstuff[k_hyudorotimer]) + { + // There REALLY ought to be a better way to handle this logic, right?! + // Squishing + PlayerAttackSteer( + globalsmuggle.botmo->scale > thing->scale + (mapobjectscale/8), + thing->scale > globalsmuggle.botmo->scale + (mapobjectscale/8) + ) + // Invincibility + else PlayerAttackSteer( + globalsmuggle.botmo->player->kartstuff[k_invincibilitytimer], + thing->player->kartstuff[k_invincibilitytimer] + ) + // Thunder Shield + else PlayerAttackSteer( + globalsmuggle.botmo->player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD, + thing->player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD + ) + // Bubble Shield + else PlayerAttackSteer( + globalsmuggle.botmo->player->kartstuff[k_itemtype] == KITEM_BUBBLESHIELD, + thing->player->kartstuff[k_itemtype] == KITEM_BUBBLESHIELD + ) + // Flame Shield + else PlayerAttackSteer( + globalsmuggle.botmo->player->kartstuff[k_itemtype] == KITEM_FLAMESHIELD, + thing->player->kartstuff[k_itemtype] == KITEM_FLAMESHIELD + ) + // Has held item shield + else PlayerAttackSteer( + (globalsmuggle.botmo->player->kartstuff[k_itemheld] || globalsmuggle.botmo->player->kartstuff[k_eggmanheld]), + (thing->player->kartstuff[k_itemheld] || thing->player->kartstuff[k_eggmanheld]) + ) + // Ring Sting + else PlayerAttackSteer( + thing->player->kartstuff[k_rings] <= 0, + globalsmuggle.botmo->player->kartstuff[k_rings] <= 0 + ) + else + { + // After ALL of that, we can do standard bumping + fixed_t ourweight = K_GetMobjWeight(globalsmuggle.botmo, thing); + fixed_t theirweight = K_GetMobjWeight(thing, globalsmuggle.botmo); + fixed_t weightdiff = 0; + + if (anglediff >= 90) + { + weightdiff = theirweight - ourweight; + } + else + { + weightdiff = ourweight - theirweight; + } + + if (weightdiff > mapobjectscale) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, KART_FULLTURN + attack); + } + else + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, KART_FULLTURN + dodge); + } + } + } + break; + case MT_BOTHINT: + if (anglediff >= 60) + { + break; + } + + if (thing->extravalue1 == 0) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, thing->extravalue2 * (KART_FULLTURN + dodge)); + } + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, true, thing->extravalue2 * (KART_FULLTURN + attack)); + } + break; + default: + if (thing->flags & (MF_SOLID|MF_ENEMY|MF_BOSS|MF_PAIN|MF_MISSILE|MF_FIRE)) + { + K_SteerFromObject(globalsmuggle.botmo, thing, fulldist, xdist, false, 2 * (KART_FULLTURN + dodge)); + } + break; + } + + return true; +} + +/*-------------------------------------------------- + INT16 K_BotFindObjects(player_t *player, INT16 turn) + + See header file for description. +--------------------------------------------------*/ +INT16 K_BotFindObjects(player_t *player, INT16 turn) +{ + INT32 xl, xh, yl, yh, bx, by; + + globalsmuggle.steer = 0; + globalsmuggle.botmo = player->mo; + globalsmuggle.curturn = turn; + globalsmuggle.distancetocheck = (player->mo->radius * 32) + (player->speed * 4); + + xl = (unsigned)(globalsmuggle.botmo->x - globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + xh = (unsigned)(globalsmuggle.botmo->x + globalsmuggle.distancetocheck - bmaporgx)>>MAPBLOCKSHIFT; + yl = (unsigned)(globalsmuggle.botmo->y - globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + yh = (unsigned)(globalsmuggle.botmo->y + globalsmuggle.distancetocheck - bmaporgy)>>MAPBLOCKSHIFT; + + BMBOUNDFIX(xl, xh, yl, yh); + + for (bx = xl; bx <= xh; bx++) + { + for (by = yl; by <= yh; by++) + { + P_BlockThingsIterator(bx, by, K_BotSteerObjects); + } + } + + return globalsmuggle.steer; +} diff --git a/src/k_kart.c b/src/k_kart.c index e7fe69c2d..c24e8aeff 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -29,6 +29,7 @@ #include "lua_hook.h" // For MobjDamage and ShouldDamage #include "k_waypoint.h" +#include "k_bot.h" // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) @@ -119,6 +120,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartvoterulechanges); CV_RegisterVar(&cv_kartspeedometer); CV_RegisterVar(&cv_kartvoices); + CV_RegisterVar(&cv_kartbot); CV_RegisterVar(&cv_karteliminatelast); CV_RegisterVar(&cv_kartusepwrlv); CV_RegisterVar(&cv_votetime); @@ -298,6 +300,9 @@ static void K_KartGetItemResult(player_t *player, SINT8 getitem) if (getitem == KITEM_HYUDORO) // Hyudoro cooldown hyubgone = 5*TICRATE; + player->botvars.itemdelay = TICRATE; + player->botvars.itemconfirm = 0; + switch (getitem) { // Special roulettes first, then the generic ones are handled by default @@ -346,7 +351,7 @@ static void K_KartGetItemResult(player_t *player, SINT8 getitem) \return void */ -static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush) +static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush, boolean bot) { INT32 newodds; INT32 i; @@ -432,6 +437,46 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp #define COOLDOWNONSTART (leveltime < (30*TICRATE)+starttime) + /* + if (bot) + { + // TODO: Item use on bots should all be passed-in functions. + // Instead of manually inserting these, it should return 0 + // for any items without an item use function supplied + + switch (item) + { + case KITEM_SNEAKER: + case KITEM_ROCKETSNEAKER: + case KITEM_INVINCIBILITY: + case KITEM_BANANA: + case KITEM_EGGMAN: + case KITEM_ORBINAUT: + case KITEM_JAWZ: + case KITEM_MINE: + case KITEM_BALLHOG: + case KITEM_SPB: + case KITEM_GROW: + case KITEM_SHRINK: + case KITEM_HYUDORO: + case KITEM_SUPERRING: + case KITEM_THUNDERSHIELD: + case KITEM_BUBBLESHIELD: + case KITEM_FLAMESHIELD: + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEBANANA: + case KRITEM_TENFOLDBANANA: + case KRITEM_TRIPLEORBINAUT: + case KRITEM_QUADORBINAUT: + case KRITEM_DUALJAWZ: + break; + default: + return 0; + } + } + */ + (void)bot; + switch (item) { case KITEM_ROCKETSNEAKER: @@ -523,7 +568,7 @@ static UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 for (j = 1; j < NUMKARTRESULTS; j++) { - if (K_KartGetItemOdds(i, j, mashed, spbrush) > 0) + if (K_KartGetItemOdds(i, j, mashed, spbrush, player->bot) > 0) { available = true; break; @@ -821,7 +866,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); for (i = 1; i < NUMKARTRESULTS; i++) - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds(useodds, i, mashed, spbrush)); + spawnchance[i] = (totalspawnchance += K_KartGetItemOdds(useodds, i, mashed, spbrush, player->bot)); // Award the player whatever power is rolled if (totalspawnchance > 0) @@ -876,7 +921,7 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against) return weight; } -static fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against) +fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against) { fixed_t weight = 5*FRACUNIT; @@ -1836,6 +1881,13 @@ void K_MomentumToFacing(player_t *player) player->mo->momy = FixedMul(player->mo->momy - player->cmomy, player->mo->friction) + player->cmomy; } +boolean K_ApplyOffroad(player_t *player) +{ + if (player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_hyudorotimer] || EITHERSNEAKER(player)) + return false; + return true; +} + static fixed_t K_FlameShieldDashVar(INT32 val) { // 1 second = 75% + 50% top speed @@ -1856,8 +1908,7 @@ static void K_GetKartBoostPower(player_t *player) } // Offroad is separate, it's difficult to factor it in with a variable value anyway. - if (!(player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_hyudorotimer] || EITHERSNEAKER(player)) - && player->kartstuff[k_offroad] >= 0) + if (K_ApplyOffroad(player) && player->kartstuff[k_offroad] >= 0) boostpower = FixedDiv(boostpower, FixedMul(player->kartstuff[k_offroad], K_GetKartGameSpeedScalar(gamespeed)) + FRACUNIT); if (player->kartstuff[k_bananadrag] > TICRATE) @@ -1936,11 +1987,26 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower) finalspeed = K_GetKartSpeedFromStat(kartspeed); + if (K_PlayerUsesBotMovement(player)) + { + // Give top speed a buff for bots, since it's a fairly weak stat without drifting + fixed_t speedmul = ((kartspeed-1) * FRACUNIT / 8) / 10; // +10% for speed 9 + finalspeed = FixedMul(finalspeed, FRACUNIT + speedmul); + } + if (player->mo && !P_MobjWasRemoved(player->mo)) finalspeed = FixedMul(finalspeed, player->mo->scale); if (doboostpower) + { + if (K_PlayerUsesBotMovement(player)) + { + finalspeed = FixedMul(finalspeed, K_BotTopSpeedRubberband(player)); + } + return FixedMul(finalspeed, player->kartstuff[k_boostpower]+player->kartstuff[k_speedboost]); + } + return finalspeed; } @@ -1955,6 +2021,11 @@ fixed_t K_GetKartAccel(player_t *player) //k_accel += 3 * (9 - kartspeed); // 36 - 60 k_accel += 4 * (9 - kartspeed); // 32 - 64 + if (K_PlayerUsesBotMovement(player)) + { + k_accel = FixedMul(k_accel, K_BotRubberband(player)); + } + return FixedMul(k_accel, FRACUNIT+player->kartstuff[k_accelboost]); } @@ -2009,6 +2080,9 @@ fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove // 0 with no gas, and // -25 when only braking. + if (EITHERSNEAKER(player)) + forwardmove = 50; + finalspeed *= forwardmove/25; finalspeed /= 2; @@ -4829,7 +4903,7 @@ static void K_UpdateEngineSounds(player_t *player, ticcmd_t *cmd) class = s+(3*w); // Silence the engines - if (leveltime < 8 || player->spectator || player->exiting) + if (leveltime < 8 || player->spectator) { player->karthud[khud_enginesnd] = 0; // Reset sound number return; @@ -5555,7 +5629,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->kartstuff[k_bubblecool] = 0; } - if (player->kartstuff[k_itemtype] != KITEM_FLAMESHIELD || player->exiting) + if (player->kartstuff[k_itemtype] != KITEM_FLAMESHIELD) { if (player->kartstuff[k_flamedash]) K_FlameDashLeftoverSmoke(player->mo); @@ -5726,10 +5800,31 @@ static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) angle_t nextbestmomdelta = momdelta; size_t i = 0U; + if (K_PlayerUsesBotMovement(player)) + { + // Try to force bots to use a next waypoint + nextbestdelta = ANGLE_MAX; + nextbestmomdelta = ANGLE_MAX; + } + if ((waypoint->nextwaypoints != NULL) && (waypoint->numnextwaypoints > 0U)) { for (i = 0U; i < waypoint->numnextwaypoints; i++) { + if (K_PlayerUsesBotMovement(player) == true + && K_GetWaypointIsShortcut(waypoint->nextwaypoints[i]) == true + && K_BotCanTakeCut(player) == false) + { + // Bots that aren't able to take a shortcut will ignore shortcut waypoints. + // (However, if they're already on a shortcut, then we want them to keep going.) + + if (player->nextwaypoint == NULL + || K_GetWaypointIsShortcut(player->nextwaypoint) == false) + { + continue; + } + } + angletowaypoint = R_PointToAngle2( player->mo->x, player->mo->y, waypoint->nextwaypoints[i]->mobj->x, waypoint->nextwaypoints[i]->mobj->y); @@ -5766,7 +5861,8 @@ static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) } } - if ((waypoint->prevwaypoints != NULL) && (waypoint->numprevwaypoints > 0U)) + if ((waypoint->prevwaypoints != NULL) && (waypoint->numprevwaypoints > 0U) + && !(K_PlayerUsesBotMovement(player))) // Bots do not need prev waypoints { for (i = 0U; i < waypoint->numprevwaypoints; i++) { @@ -5898,70 +5994,77 @@ void K_UpdateDistanceFromFinishLine(player_t *const player) { if ((player != NULL) && (player->mo != NULL)) { - if (player->exiting || player->spectator) + waypoint_t *finishline = K_GetFinishLineWaypoint(); + waypoint_t *nextwaypoint = NULL; + + if (player->spectator) { - player->nextwaypoint = K_GetFinishLineWaypoint(); - player->distancetofinish = 0U; + // Don't update waypoints while spectating + nextwaypoint = finishline; } else { - waypoint_t *finishline = K_GetFinishLineWaypoint(); - waypoint_t *nextwaypoint = K_GetPlayerNextWaypoint(player); + nextwaypoint = K_GetPlayerNextWaypoint(player); + } - if (nextwaypoint != NULL) + if (nextwaypoint != NULL) + { + // If nextwaypoint is NULL, it means we don't want to update the waypoint until we touch another one. + // player->nextwaypoint will keep its previous value in this case. + player->nextwaypoint = nextwaypoint; + } + + // nextwaypoint is now the waypoint that is in front of us + if (player->exiting || player->spectator) + { + // Player has finished, we don't need to calculate this + player->distancetofinish = 0U; + } + else if ((player->nextwaypoint != NULL) && (finishline != NULL)) + { + const boolean useshortcuts = false; + const boolean huntbackwards = false; + boolean pathfindsuccess = false; + path_t pathtofinish = {}; + + pathfindsuccess = + K_PathfindToWaypoint(player->nextwaypoint, finishline, &pathtofinish, useshortcuts, huntbackwards); + + // Update the player's distance to the finish line if a path was found. + // Using shortcuts won't find a path, so distance won't be updated until the player gets back on track + if (pathfindsuccess == true) { - // If nextwaypoint is NULL, it means we don't want to update the waypoint until we touch another one. - // player->nextwaypoint will keep its previous value in this case. - player->nextwaypoint = nextwaypoint; - } + // Add euclidean distance to the next waypoint to the distancetofinish + UINT32 adddist; + fixed_t disttowaypoint = + P_AproxDistance( + (player->mo->x >> FRACBITS) - (player->nextwaypoint->mobj->x >> FRACBITS), + (player->mo->y >> FRACBITS) - (player->nextwaypoint->mobj->y >> FRACBITS)); + disttowaypoint = P_AproxDistance(disttowaypoint, (player->mo->z >> FRACBITS) - (player->nextwaypoint->mobj->z >> FRACBITS)); - // nextwaypoint is now the waypoint that is in front of us - if ((player->nextwaypoint != NULL) && (finishline != NULL)) - { - const boolean useshortcuts = false; - const boolean huntbackwards = false; - boolean pathfindsuccess = false; - path_t pathtofinish = {}; + adddist = (UINT32)disttowaypoint; - pathfindsuccess = - K_PathfindToWaypoint(player->nextwaypoint, finishline, &pathtofinish, useshortcuts, huntbackwards); + player->distancetofinish = pathtofinish.totaldist + adddist; + Z_Free(pathtofinish.array); - // Update the player's distance to the finish line if a path was found. - // Using shortcuts won't find a path, so distance won't be updated until the player gets back on track - if (pathfindsuccess == true) + // distancetofinish is currently a flat distance to the finish line, but in order to be fully + // correct we need to add to it the length of the entire circuit multiplied by the number of laps + // left after this one. This will give us the total distance to the finish line, and allow item + // distance calculation to work easily + if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == 0U) { - // Add euclidean distance to the next waypoint to the distancetofinish - UINT32 adddist; - fixed_t disttowaypoint = - P_AproxDistance( - (player->mo->x >> FRACBITS) - (player->nextwaypoint->mobj->x >> FRACBITS), - (player->mo->y >> FRACBITS) - (player->nextwaypoint->mobj->y >> FRACBITS)); - disttowaypoint = P_AproxDistance(disttowaypoint, (player->mo->z >> FRACBITS) - (player->nextwaypoint->mobj->z >> FRACBITS)); + const UINT8 numfulllapsleft = ((UINT8)cv_numlaps.value - player->laps); - adddist = (UINT32)disttowaypoint; + player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); - player->distancetofinish = pathtofinish.totaldist + adddist; - Z_Free(pathtofinish.array); - - // distancetofinish is currently a flat distance to the finish line, but in order to be fully - // correct we need to add to it the length of the entire circuit multiplied by the number of laps - // left after this one. This will give us the total distance to the finish line, and allow item - // distance calculation to work easily - if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == 0U) + // An additional HACK, to fix looking backwards towards the finish line + // If the player's next waypoint is the finishline and the angle distance from player to + // connectin waypoints implies they're closer to a next waypoint, add a full track distance + if (player->nextwaypoint == finishline) { - const UINT8 numfulllapsleft = ((UINT8)cv_numlaps.value - player->laps); - - player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); - - // An additional HACK, to fix looking backwards towards the finish line - // If the player's next waypoint is the finishline and the angle distance from player to - // connectin waypoints implies they're closer to a next waypoint, add a full track distance - if (player->nextwaypoint == finishline) + if (K_PlayerCloserToNextWaypoints(player->nextwaypoint, player) == true) { - if (K_PlayerCloserToNextWaypoints(player->nextwaypoint, player) == true) - { - player->distancetofinish += K_GetCircuitLength(); - } + player->distancetofinish += K_GetCircuitLength(); } } } @@ -5970,6 +6073,11 @@ void K_UpdateDistanceFromFinishLine(player_t *const player) } } +INT32 K_GetKartRingPower(player_t *player) +{ + return (((9 - player->kartspeed) + (9 - player->kartweight)) / 2); +} + // Returns false if this player being placed here causes them to collide with any other player // Used in g_game.c for match etc. respawning // This does not check along the z because the z is not correctly set for the spawnee at this point @@ -6038,6 +6146,15 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) return turnvalue; } + if (K_PlayerUsesBotMovement(player)) + { + turnvalue = 5*turnvalue/4; // Base increase to turning + turnvalue = FixedMul( + turnvalue * FRACUNIT, + K_BotRubberband(player) + ) / FRACUNIT; + } + if (player->kartstuff[k_drift] != 0 && P_IsObjectOnGround(player->mo)) { fixed_t countersteer = FixedDiv(turnvalue*FRACUNIT, KART_FULLTURN*FRACUNIT); @@ -6235,10 +6352,7 @@ static void K_KartDrift(player_t *player, boolean onground) // Disable drift-sparks until you're going fast enough if (player->kartstuff[k_getsparks] == 0 - || (player->kartstuff[k_offroad] - && !player->kartstuff[k_invincibilitytimer] - && !player->kartstuff[k_hyudorotimer] - && !EITHERSNEAKER(player))) + || (player->kartstuff[k_offroad] && K_ApplyOffroad(player))) driftadditive = 0; // Inbetween minspeed and minspeed*2, it'll keep your previous drift-spark state. @@ -6512,7 +6626,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) else if (cmd->buttons & BT_ATTACK) player->pflags |= PF_ATTACKDOWN; - if (player && player->mo && player->mo->health > 0 && !player->spectator && !(player->exiting || mapreset) && leveltime > starttime + if (player && player->mo && player->mo->health > 0 && !player->spectator && !mapreset && leveltime > starttime && player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_squishedtimer] == 0 && (player->respawn.state == RESPAWNST_NONE)) { // First, the really specific, finicky items that function without the item being directly in your item slot. @@ -8932,15 +9046,23 @@ void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, I if (players[tab[i].num].spectator || !players[tab[i].num].mo) continue; //ignore them. - if (netgame // don't draw it offline - && ( tab[i].num != serverplayer || ! server_lagless )) - HU_drawPing(x + ((i < 8) ? -17 : rightoffset + 11), y-4, playerpingtable[tab[i].num], 0); + if (netgame) // don't draw ping offline + { + if (players[tab[i].num].bot) + { + ; // TODO: Put a graphic here to indicate this player is a bot! + } + else if (tab[i].num != serverplayer || !server_lagless) + { + HU_drawPing(x + ((i < 8) ? -17 : rightoffset + 11), y-4, playerpingtable[tab[i].num], 0); + } + } STRBUFCPY(strtime, tab[i].name); y2 = y; - if (playerconsole[tab[i].num] == 0 && server_lagless) + if (netgame && playerconsole[tab[i].num] == 0 && server_lagless && !players[tab[i].num].bot) { y2 = ( y - 4 ); @@ -9710,7 +9832,7 @@ static void K_drawKartMinimap(void) g = g->next; } - if (!stplyr->mo || stplyr->spectator) // do we need the latter..? + if (!stplyr->mo || stplyr->spectator || stplyr->exiting) return; localplayers[numlocalplayers] = stplyr-players; @@ -9722,7 +9844,7 @@ static void K_drawKartMinimap(void) { if (!playeringame[i]) continue; - if (!players[i].mo || players[i].spectator) + if (!players[i].mo || players[i].spectator || players[i].exiting) continue; if (i != displayplayers[0] || r_splitscreen) @@ -10432,7 +10554,7 @@ static void K_drawDistributionDebugger(void) for (i = 1; i < NUMKARTRESULTS; i++) { - const INT32 itemodds = K_KartGetItemOdds(useodds, i, 0, spbrush); + const INT32 itemodds = K_KartGetItemOdds(useodds, i, 0, spbrush, stplyr->bot); if (itemodds <= 0) continue; diff --git a/src/k_kart.h b/src/k_kart.h index 647bdeda0..7a3a3581c 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -22,6 +22,7 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value); extern consvar_t *KartItemCVars[NUMKARTRESULTS-1]; INT32 K_GetShieldFromItem(INT32 item); +fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against); void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid); void K_KartPainEnergyFling(player_t *player); void K_FlipFromObject(mobj_t *mo, mobj_t *master); @@ -52,6 +53,7 @@ void K_UpdateHnextList(player_t *player, boolean clean); void K_DropHnextList(player_t *player, boolean keepshields); void K_RepairOrbitChain(mobj_t *orbit); player_t *K_FindJawzTarget(mobj_t *actor, player_t *source); +INT32 K_GetKartRingPower(player_t *player); void K_UpdateDistanceFromFinishLine(player_t *const player); boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y); INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue); @@ -61,6 +63,7 @@ void K_DropItems(player_t *player); void K_StripItems(player_t *player); void K_StripOther(player_t *player); void K_MomentumToFacing(player_t *player); +boolean K_ApplyOffroad(player_t *player); fixed_t K_GetKartSpeedFromStat(UINT8 kartspeed); fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower); fixed_t K_GetKartAccel(player_t *player); diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index b8d78e0ee..8101a013e 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -305,9 +305,10 @@ void K_PlayerForfeit(UINT8 playernum, boolean pointloss) if (i == playernum) continue; - theirpower = PWRLVRECORD_DEF; - if (clientpowerlevels[i][powertype] != 0) // No power level acts as 5000 (used for splitscreen guests) - theirpower = clientpowerlevels[i][powertype]; + if (clientpowerlevels[i][powertype] == 0) // No power level (splitscreen guests, bots) + continue; + + theirpower = clientpowerlevels[i][powertype]; diff = yourpower - theirpower; inc -= K_CalculatePowerLevelInc(diff); diff --git a/src/k_waypoint.c b/src/k_waypoint.c index d320d4a19..69b488dd6 100644 --- a/src/k_waypoint.c +++ b/src/k_waypoint.c @@ -271,9 +271,12 @@ waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj) waypoint_t *checkwaypoint = NULL; fixed_t closestdist = INT32_MAX; fixed_t checkdist = INT32_MAX; + fixed_t bestfindist = INT32_MAX; for (i = 0; i < numwaypoints; i++) { + fixed_t rad; + checkwaypoint = &waypointheap[i]; checkdist = P_AproxDistance( @@ -281,7 +284,34 @@ waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj) (mobj->y / FRACUNIT) - (checkwaypoint->mobj->y / FRACUNIT)); checkdist = P_AproxDistance(checkdist, ((mobj->z / FRACUNIT) - (checkwaypoint->mobj->z / FRACUNIT)) * 4); - if (checkdist < closestdist) + rad = (checkwaypoint->mobj->radius / FRACUNIT); + + if (closestdist < rad && checkdist < rad && finishline != NULL) + { + const boolean useshortcuts = false; + const boolean huntbackwards = false; + boolean pathfindsuccess = false; + path_t pathtofinish = {}; + + // If the mobj is touching multiple waypoints at once, + // then solve ties by taking the one closest to the finish line. + // Prevents position from flickering wildly when taking turns. + + pathfindsuccess = + K_PathfindToWaypoint(checkwaypoint, finishline, &pathtofinish, useshortcuts, huntbackwards); + + if (pathfindsuccess == true) + { + if ((INT32)(pathtofinish.totaldist) < bestfindist) + { + bestwaypoint = checkwaypoint; + bestfindist = pathtofinish.totaldist; + } + + Z_Free(pathtofinish.array); + } + } + else if (checkdist < closestdist && bestfindist == INT32_MAX) { if (!P_CheckSight(mobj, checkwaypoint->mobj)) { diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 41a436e01..1e7299461 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -16,7 +16,7 @@ #include "p_mobj.h" #include "g_game.h" #include "r_things.h" -#include "b_bot.h" +#include "k_bot.h" #include "z_zone.h" #include "lua_script.h" @@ -968,6 +968,12 @@ boolean LUAh_PlayerCmd(player_t *player, ticcmd_t *cmd) // Hook for B_BuildTailsTiccmd by skin name boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd) { +#if 1 + (void)sonic; + (void)tails; + (void)cmd; + return false; +#else hook_p hookp; boolean hooked = false; if (!gL || !(hooksAvailable[hook_BotAI/8] & (1<<(hook_BotAI%8)))) @@ -1024,6 +1030,7 @@ boolean LUAh_BotAI(mobj_t *sonic, mobj_t *tails, ticcmd_t *cmd) lua_settop(gL, 0); return hooked; +#endif } // Hook for linedef executors diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 1eb1e3d5f..89b354c19 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -369,7 +369,7 @@ static int player_get(lua_State *L) else if (fastcmp(field,"spectator")) lua_pushboolean(L, plr->spectator); else if (fastcmp(field,"bot")) - lua_pushinteger(L, plr->bot); + lua_pushboolean(L, plr->bot); else if (fastcmp(field,"jointime")) lua_pushinteger(L, plr->jointime); else if (fastcmp(field,"splitscreenindex")) diff --git a/src/p_enemy.c b/src/p_enemy.c index c5601ba4d..fb7e95fea 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -3571,7 +3571,6 @@ void A_AttractChase(mobj_t *actor) if (actor->extravalue1) // SRB2Kart { -#define RINGBOOSTPWR (((9 - actor->target->player->kartspeed) + (9 - actor->target->player->kartweight)) / 2) if (!actor->target || P_MobjWasRemoved(actor->target) || !actor->target->player) { P_RemoveMobj(actor); @@ -3589,7 +3588,7 @@ void A_AttractChase(mobj_t *actor) angle_t offset = FixedAngle(18<target->player->kartstuff[k_ringboost] += RINGBOOSTPWR+3; + actor->target->player->kartstuff[k_ringboost] += K_GetKartRingPower(actor->target->player)+3; S_StartSound(actor->target, sfx_s1b5); sparkle = P_SpawnMobj(actor->target->x, actor->target->y, actor->target->z, MT_RINGSPARKS); @@ -3617,7 +3616,7 @@ void A_AttractChase(mobj_t *actor) if (actor->extravalue1 >= 16) { if (actor->target->player->kartstuff[k_rings] >= 20) - actor->target->player->kartstuff[k_ringboost] += RINGBOOSTPWR+3; + actor->target->player->kartstuff[k_ringboost] += K_GetKartRingPower(actor->target->player)+3; else P_GivePlayerRings(actor->target->player, 1); @@ -3646,7 +3645,6 @@ void A_AttractChase(mobj_t *actor) actor->extravalue1++; } } -#undef RINGBOOSTPWR } else { diff --git a/src/p_inter.c b/src/p_inter.c index 06b0cffa8..49d3546ce 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -161,8 +161,6 @@ 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) - player = &players[consoleplayer]; if (G_IsSpecialStage(gamemap)) // Global link count? Maybe not a good idea... { @@ -758,8 +756,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) // ***************************** // // Special Stage Token case MT_EMMY: - if (player->bot) - return; tokenlist += special->health; if (ALL7EMERALDS(emeralds)) // Got all 7 @@ -776,9 +772,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) // Emerald Hunt case MT_EMERHUNT: - if (player->bot) - return; - if (hunt1 == special) hunt1 = NULL; else if (hunt2 == special) @@ -807,9 +800,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) case MT_EMERALD5: case MT_EMERALD6: case MT_EMERALD7: - if (player->bot) - return; - if (special->threshold) player->powers[pw_emeralds] |= special->info->speed; else @@ -837,12 +827,11 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) // Secret emblem thingy case MT_EMBLEM: { - if (demo.playback || player->bot) + if (demo.playback) return; + emblemlocations[special->health-1].collected = true; - M_UpdateUnlockablesAndExtraEmblems(false); - G_SaveGameData(false); break; } @@ -850,8 +839,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) // CTF Flags case MT_REDFLAG: case MT_BLUEFLAG: - if (player->bot) - return; if (player->powers[pw_flashing] || player->tossdelay) return; if (!special->spawnpoint) @@ -922,8 +909,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) // NiGHTS gameplay items and powerups // // ********************************** // /*case MT_NIGHTSDRONE: - if (player->bot) - return; if (player->exiting) return; if (player->bonustime) @@ -1077,9 +1062,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } return; case MT_EGGCAPSULE: - if (player->bot) - return; - // make sure everything is as it should be, THEN take rings from players in special stages if (player->pflags & PF_NIGHTSMODE && !toucher->target) return; @@ -1181,7 +1163,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } return; /*case MT_NIGHTSSUPERLOOP: - if (player->bot || !(player->pflags & PF_NIGHTSMODE)) + if (!(player->pflags & PF_NIGHTSMODE)) return; if (!G_IsSpecialStage(gamemap)) player->powers[pw_nights_superloop] = (UINT16)special->info->speed; @@ -1203,7 +1185,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } break; case MT_NIGHTSDRILLREFILL: - if (player->bot || !(player->pflags & PF_NIGHTSMODE)) + if (!(player->pflags & PF_NIGHTSMODE)) return; if (!G_IsSpecialStage(gamemap)) player->drillmeter = special->info->speed; @@ -1225,7 +1207,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } break; case MT_NIGHTSHELPER: - if (player->bot || !(player->pflags & PF_NIGHTSMODE)) + if (!(player->pflags & PF_NIGHTSMODE)) return; if (!G_IsSpecialStage(gamemap)) { @@ -1257,7 +1239,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } break; case MT_NIGHTSEXTRATIME: - if (player->bot || !(player->pflags & PF_NIGHTSMODE)) + if (!(player->pflags & PF_NIGHTSMODE)) return; if (!G_IsSpecialStage(gamemap)) { @@ -1287,7 +1269,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } break; case MT_NIGHTSLINKFREEZE: - if (player->bot || !(player->pflags & PF_NIGHTSMODE)) + if (!(player->pflags & PF_NIGHTSMODE)) return; if (!G_IsSpecialStage(gamemap)) { @@ -1358,8 +1340,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (playeringame[i] && players[i].pflags & PF_NIGHTSMODE) players[i].drillmeter += TICRATE/2; } - else if (player->bot) - players[consoleplayer].drillmeter += TICRATE/2; else player->drillmeter += TICRATE/2; @@ -1393,9 +1373,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) thinker_t *th; mobj_t *mo2; - if (player->bot) - return; - junk.tag = 649; EV_DoElevator(&junk, bridgeFall, false); @@ -1415,8 +1392,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } break; case MT_FIREFLOWER: - if (player->bot) - return; player->powers[pw_shield] |= SH_FIREFLOWER; toucher->color = SKINCOLOR_WHITE; G_GhostAddColor(player - players, GHC_FIREFLOWER); @@ -1426,9 +1401,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) // Misc touchables // // *************** // case MT_STARPOST: - if (player->bot) - return; - if (circuitmap && special->health - player->starpostnum > 1) { // blatant reuse of a variable that's normally unused in circuit @@ -1627,7 +1599,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || (player->powers[pw_super] && !(ALL7EMERALDS(player->powers[pw_emeralds])))) return; - if (player->powers[pw_shield] || player->bot) //If One-Hit Shield + if (player->powers[pw_shield]) //If One-Hit Shield { P_RemoveShield(player); S_StartSound(toucher, sfx_shldls); // Ba-Dum! Shield loss. @@ -1712,8 +1684,6 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; default: // SOC or script pickup - if (player->bot) - return; P_SetTarget(&special->target, toucher); break; } @@ -2004,7 +1974,7 @@ boolean P_CheckRacers(void) // Check if all the players in the race have finished. If so, end the level. for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i] || players[i].spectator || players[i].exiting || !players[i].lives) + if (!playeringame[i] || players[i].spectator || players[i].exiting || players[i].bot || !players[i].lives) continue; break; @@ -2276,8 +2246,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source) target->flags |= MF_NOBLOCKMAP|MF_NOCLIPHEIGHT; P_SetThingPosition(target); - if (!target->player->bot && !G_IsSpecialStage(gamemap) - && G_GametypeUsesLives()) + if (!target->player->bot && !G_IsSpecialStage(gamemap) && G_GametypeUsesLives()) { target->player->lives -= 1; // Lose a life Tails 03-11-2000 @@ -2290,6 +2259,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source) } } } + target->player->playerstate = PST_DEAD; if (target->player == &players[consoleplayer]) @@ -3034,7 +3004,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) || (inflictor && !inflictor->player))) + if (target->flags & MF_MONITOR && ((!source || !source->player) || (inflictor && !inflictor->player))) return false; } diff --git a/src/p_map.c b/src/p_map.c index fcbb2d692..00d93efe7 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -492,89 +492,6 @@ static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object) } } -#if 0 -static void P_DoTailsCarry(player_t *sonic, player_t *tails) -{ - INT32 p; - fixed_t zdist; // z distance between the two players' bottoms - - if ((tails->pflags & PF_CARRIED) && tails->mo->tracer == sonic->mo) - return; - if ((sonic->pflags & PF_CARRIED) && sonic->mo->tracer == tails->mo) - return; - - //if (!tails->powers[pw_tailsfly] && !(tails->charability == CA_FLY && (tails->mo->state >= &states[S_PLAY_SPC1] && tails->mo->state <= &states[S_PLAY_SPC4]))) - // return; // SRB2kart - no changey statey - - if (tails->bot == 1) - return; - - if (sonic->pflags & PF_NIGHTSMODE) - return; - - if (sonic->mo->tracer && sonic->mo->tracer->type == MT_TUBEWAYPOINT - && !(sonic->pflags & PF_ROPEHANG)) - return; // don't steal players from zoomtubes! - - if ((sonic->mo->eflags & MFE_VERTICALFLIP) != (tails->mo->eflags & MFE_VERTICALFLIP)) - return; // Both should be in same gravity - - if (tails->mo->eflags & MFE_VERTICALFLIP) - { - if (tails->mo->ceilingz - (tails->mo->z + tails->mo->height) < sonic->mo->height-FixedMul(2*FRACUNIT, sonic->mo->scale)) - return; - } - else if (tails->mo->z - tails->mo->floorz < sonic->mo->height-FixedMul(2*FRACUNIT, sonic->mo->scale)) - return; // No room to pick up this guy! - - // Search in case another player is already being carried by this fox. - for (p = 0; p < MAXPLAYERS; p++) - if (playeringame[p] && players[p].mo - && players[p].pflags & PF_CARRIED && players[p].mo->tracer == tails->mo) - return; - - if (tails->mo->eflags & MFE_VERTICALFLIP) - zdist = (sonic->mo->z + sonic->mo->height) - (tails->mo->z + tails->mo->height); - else - zdist = tails->mo->z - sonic->mo->z; - - if (zdist <= sonic->mo->height + FixedMul(FRACUNIT, sonic->mo->scale) - && zdist > sonic->mo->height*2/3 - && P_MobjFlip(tails->mo)*sonic->mo->momz <= 0) - { - // Why block opposing teams from tailsflying each other? - // Sneaking into the hands of a flying tails player in Race might be a viable strategy, who knows. - /* - if (gametype == GT_RACE || gametype == GT_COMPETITION - || (netgame && (tails->spectator || sonic->spectator)) - || (G_TagGametype() && (!(tails->pflags & PF_TAGIT) != !(sonic->pflags & PF_TAGIT))) - || (gametype == GT_MATCH) - || (G_GametypeHasTeams() && tails->ctfteam != sonic->ctfteam)) - sonic->pflags &= ~PF_CARRIED; */ - if (tails->spectator || sonic->spectator || G_RaceGametype()) // SRB2kart - sonic->pflags &= ~PF_CARRIED; - else - { - if (sonic-players == consoleplayer && botingame) - //CV_SetValue(&cv_analog2, false); - P_ResetPlayer(sonic); - P_SetTarget(&sonic->mo->tracer, tails->mo); - sonic->pflags |= PF_CARRIED; - S_StartSound(sonic->mo, sfx_s3k4a); - P_UnsetThingPosition(sonic->mo); - sonic->mo->x = tails->mo->x; - sonic->mo->y = tails->mo->y; - P_SetThingPosition(sonic->mo); - } - } - else { - if (sonic-players == consoleplayer && botingame) - //CV_SetValue(&cv_analog2, true); - sonic->pflags &= ~PF_CARRIED; - } -} -#endif - // // PIT_CheckThing // @@ -1464,8 +1381,6 @@ static boolean PIT_CheckThing(mobj_t *thing) } } else if (thing->player) { - if (thing->player-players == consoleplayer && botingame) - //CV_SetValue(&cv_analog2, true); thing->player->pflags &= ~PF_CARRIED; }*/ diff --git a/src/p_mobj.c b/src/p_mobj.c index b1d00c0a5..935ecb14a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -30,7 +30,7 @@ #include "info.h" #include "i_video.h" #include "lua_hook.h" -#include "b_bot.h" +#include "k_bot.h" #ifdef ESLOPE #include "p_slopes.h" #endif @@ -1692,10 +1692,6 @@ void P_XYMovement(mobj_t *mo) // blocked move moved = false; - if (player) { - if (player->bot) - B_MoveBlocked(player); - } //{ SRB2kart - Jawz if (mo->type == MT_JAWZ || mo->type == MT_JAWZ_DUD) { @@ -11796,9 +11792,20 @@ void P_SpawnPlayer(INT32 playernum) } // spawn as spectator determination - if (multiplayer && demo.playback); // Don't mess with spectator values since the demo setup handles them already. + if (multiplayer && demo.playback) + { + ; // Don't mess with spectator values since the demo setup handles them already. + } else if (!G_GametypeHasSpectators()) + { + // We don't have spectators p->spectator = false; + } + else if (p->bot) + { + // No point in a spectating bot! + p->spectator = false; + } else if (netgame && p->jointime <= 1 && pcount) { p->spectator = true; @@ -12748,12 +12755,44 @@ ML_NOCLIMB : Direction not controllable mobj->extravalue2 = 0; } - // Sryder 2018-12-7: Grabbed this from the old MT_BOSS3WAYPOINT section so they'll be in the waypointcap instead P_SetTarget(&mobj->tracer, waypointcap); P_SetTarget(&waypointcap, mobj); break; } + case MT_BOTHINT: + { + // Change size + if (mthing->angle > 0) + { + mobj->radius = mthing->angle * FRACUNIT; + } + else + { + mobj->radius = 32 * mapobjectscale; + } + + // Steer away instead of towards + if (mthing->options & MTF_AMBUSH) + { + mobj->extravalue1 = 0; + } + else + { + mobj->extravalue1 = 1; + } + + // Steering amount + if (mthing->extrainfo == 0) + { + mobj->extravalue2 = 2; + } + else + { + mobj->extravalue2 = mthing->extrainfo; + } + break; + } // SRB2Kart case MT_BALLOON: mobj->color = (1 + (mthing->angle % (MAXSKINCOLORS-1))); diff --git a/src/p_saveg.c b/src/p_saveg.c index e854b4b42..f420c6f3e 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -79,12 +79,6 @@ static inline void P_ArchivePlayer(void) WRITEUINT32(save_p, player->score); WRITEINT32(save_p, pllives); WRITEINT32(save_p, player->continues); - - if (botskin) - { - WRITEUINT8(save_p, botskin); - WRITEUINT8(save_p, botcolor); - } } // @@ -98,16 +92,6 @@ static inline void P_UnArchivePlayer(void) savedata.score = READINT32(save_p); savedata.lives = READINT32(save_p); savedata.continues = READINT32(save_p); - - if (savedata.botcolor) - { - savedata.botskin = READUINT8(save_p); - if (savedata.botskin-1 >= numskins) - savedata.botskin = 0; - savedata.botcolor = READUINT8(save_p); - } - else - savedata.botskin = 0; } // @@ -290,6 +274,12 @@ static void P_NetArchivePlayers(void) WRITEUINT32(save_p, players[i].respawn.timer); WRITEUINT32(save_p, players[i].respawn.distanceleft); WRITEUINT32(save_p, players[i].respawn.dropdash); + + // botvars_t + WRITEUINT8(save_p, players[i].botvars.difficulty); + WRITEUINT32(save_p, players[i].botvars.itemdelay); + WRITEUINT32(save_p, players[i].botvars.itemconfirm); + WRITESINT8(save_p, players[i].botvars.turnconfirm); } } @@ -467,6 +457,12 @@ static void P_NetUnArchivePlayers(void) players[i].respawn.timer = READUINT32(save_p); players[i].respawn.distanceleft = READUINT32(save_p); players[i].respawn.dropdash = READUINT32(save_p); + + // botvars_t + players[i].botvars.difficulty = READUINT8(save_p); + players[i].botvars.itemdelay = READUINT32(save_p); + players[i].botvars.itemconfirm = READUINT32(save_p); + players[i].botvars.turnconfirm = READSINT8(save_p); } } @@ -3121,41 +3117,53 @@ static void P_RelinkPointers(void) if (!(mobj->itnext = P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "itnext not found on %d\n", mobj->type); } - if (mobj->player && mobj->player->capsule) + if (mobj->player) && { - temp = (UINT32)(size_t)mobj->player->capsule; - mobj->player->capsule = NULL; - if (!P_SetTarget(&mobj->player->capsule, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "capsule not found on %d\n", mobj->type); - } - if (mobj->player && mobj->player->axis1) - { - temp = (UINT32)(size_t)mobj->player->axis1; - mobj->player->axis1 = NULL; - if (!P_SetTarget(&mobj->player->axis1, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "axis1 not found on %d\n", mobj->type); - } - if (mobj->player && mobj->player->axis2) - { - temp = (UINT32)(size_t)mobj->player->axis2; - mobj->player->axis2 = NULL; - if (!P_SetTarget(&mobj->player->axis2, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "axis2 not found on %d\n", mobj->type); - } - if (mobj->player && mobj->player->awayviewmobj) - { - temp = (UINT32)(size_t)mobj->player->awayviewmobj; - mobj->player->awayviewmobj = NULL; - if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type); - } - if (mobj->player && mobj->player->nextwaypoint) - { - temp = (UINT32)(size_t)mobj->player->nextwaypoint; - mobj->player->nextwaypoint = K_GetWaypointFromIndex(temp); - if (mobj->player->nextwaypoint == NULL) + if (mobj->player->capsule) { - CONS_Debug(DBG_GAMELOGIC, "nextwaypoint not found on %d\n", mobj->type); + temp = (UINT32)(size_t)mobj->player->capsule; + mobj->player->capsule = NULL; + if (!P_SetTarget(&mobj->player->capsule, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "capsule not found on %d\n", mobj->type); + } + if (mobj->player->axis1) + { + temp = (UINT32)(size_t)mobj->player->axis1; + mobj->player->axis1 = NULL; + if (!P_SetTarget(&mobj->player->axis1, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "axis1 not found on %d\n", mobj->type); + } + if (mobj->player->axis2) + { + temp = (UINT32)(size_t)mobj->player->axis2; + mobj->player->axis2 = NULL; + if (!P_SetTarget(&mobj->player->axis2, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "axis2 not found on %d\n", mobj->type); + } + if (mobj->player->awayviewmobj) + { + temp = (UINT32)(size_t)mobj->player->awayviewmobj; + mobj->player->awayviewmobj = NULL; + if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type); + } + if (mobj->player->nextwaypoint) + { + temp = (UINT32)(size_t)mobj->player->nextwaypoint; + mobj->player->nextwaypoint = K_GetWaypointFromIndex(temp); + if (mobj->player->nextwaypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "nextwaypoint not found on %d\n", mobj->type); + } + } + if (mobj->player->respawn.wp) + { + temp = (UINT32)(size_t)mobj->player->respawn.wp; + mobj->player->respawn.wp = K_GetWaypointFromIndex(temp); + if (mobj->player->respawn.wp == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "respawn.wp not found on %d\n", mobj->type); + } } } } @@ -3259,7 +3267,7 @@ static inline void P_ArchiveMisc(void) lastmapsaved = gamemap; - WRITEUINT16(save_p, (botskin ? (emeralds|(1<<10)) : emeralds)+357); + WRITEUINT16(save_p, emeralds+357); WRITESTRINGN(save_p, timeattackfolder, sizeof(timeattackfolder)); } diff --git a/src/p_setup.c b/src/p_setup.c index 574fb9ec0..463b14814 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -87,6 +87,7 @@ #include "k_battle.h" // K_SpawnBattleCapsules #include "k_pwrlv.h" #include "k_waypoint.h" +#include "k_bot.h" // // Map MD5, calculated on level load. @@ -2489,6 +2490,11 @@ static void P_LevelInitStuff(void) memset(&battleovertime, 0, sizeof(struct battleovertime)); speedscramble = encorescramble = -1; + + if (!modeattacking) + { + K_UpdateMatchRaceBots(); + } } // @@ -2888,7 +2894,7 @@ boolean P_SetupLevel(boolean skipprecip) P_Initsecnode(); if (netgame || multiplayer) - cv_debug = botskin = 0; + cv_debug = 0; if (metalplayback) G_StopMetalDemo(); @@ -3333,7 +3339,7 @@ boolean P_SetupLevel(boolean skipprecip) /*if (cv_useranalog.value) CV_SetValue(&cv_analog, true); - if ((splitscreen && cv_useranalog2.value) || botingame) + if (splitscreen && cv_useranalog2.value) CV_SetValue(&cv_analog2, true); if (splitscreen > 1 && cv_useranalog3.value) @@ -3404,9 +3410,6 @@ boolean P_SetupLevel(boolean skipprecip) players[consoleplayer].continues = savedata.continues; players[consoleplayer].lives = savedata.lives; players[consoleplayer].score = savedata.score; - botskin = savedata.botskin; - botcolor = savedata.botcolor; - botingame = (savedata.botskin != 0); emeralds = savedata.emeralds; savedata.lives = 0; } diff --git a/src/p_spec.c b/src/p_spec.c index 1bc00cbf9..03c08fdee 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2209,7 +2209,8 @@ static void K_HandleLapDecrement(player_t *player) void P_CrossSpecialLine(line_t *line, INT32 side, mobj_t *thing) { // only used for the players currently - if (thing && thing->player) + if (!(thing && thing->player && !thing->player->spectator && !(thing->player->pflags & PF_TIMEOVER))) + return; { player_t *player = thing->player; switch (line->special) @@ -2280,13 +2281,9 @@ static mobj_t *P_GetObjectTypeInSectorNum(mobjtype_t type, size_t s) static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) { INT32 secnum = -1; - mobj_t *bot = NULL; I_Assert(!mo || !P_MobjWasRemoved(mo)); // If mo is there, mo must be valid! - if (mo && mo->player && botingame) - bot = players[displayplayers[1]].mo; - // note: only commands with linedef types >= 400 && < 500 can be used switch (line->special) { @@ -2425,9 +2422,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) { UINT8 i; - if (bot) // This might put poor Tails in a wall if he's too far behind! D: But okay, whatever! >:3 - P_TeleportMove(bot, bot->x + x, bot->y + y, bot->z + z); - for (i = 0; i <= r_splitscreen; i++) { if (mo->player == &players[displayplayers[i]] && camera[i].chase) @@ -2450,8 +2444,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (!dest) return; - if (bot) - P_Teleport(bot, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ? mo->angle : dest->angle, (line->flags & ML_BLOCKPLAYERS) == 0, (line->flags & ML_EFFECT4) == ML_EFFECT4); if (line->flags & ML_BLOCKPLAYERS) P_Teleport(mo, dest->x, dest->y, dest->z, (line->flags & ML_NOCLIMB) ? mo->angle : dest->angle, false, (line->flags & ML_EFFECT4) == ML_EFFECT4); else @@ -2884,19 +2876,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) mo->player->rmomx = mo->player->rmomy = 1; mo->player->cmomx = mo->player->cmomy = 0; P_ResetPlayer(mo->player); - P_SetPlayerMobjState(mo, S_KART_STILL1); // SRB2kart - was S_PLAY_STND - - // Reset bot too. - if (bot) { - if (line->flags & ML_NOCLIMB) - P_TeleportMove(bot, mo->x, mo->y, mo->z); - bot->momx = bot->momy = bot->momz = 1; - bot->pmomz = 0; - bot->player->rmomx = bot->player->rmomy = 1; - bot->player->cmomx = bot->player->cmomy = 0; - P_ResetPlayer(bot->player); - P_SetPlayerMobjState(bot, S_KART_STILL1); // SRB2kart - was S_PLAY_STND - } + P_SetPlayerMobjState(mo, S_KART_STILL1); } break; @@ -2928,13 +2908,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) mo->flags2 &= ~MF2_TWOD; else mo->flags2 |= MF2_TWOD; - - // Copy effect to bot if necessary - // (Teleport them to you so they don't break it.) - if (bot && (bot->flags2 & MF2_TWOD) != (mo->flags2 & MF2_TWOD)) { - bot->flags2 = (bot->flags2 & ~MF2_TWOD) | (mo->flags2 & MF2_TWOD); - P_TeleportMove(bot, mo->x, mo->y, mo->z); - } } break; @@ -2943,8 +2916,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) mo->flags2 &= ~MF2_OBJECTFLIP; else mo->flags2 |= MF2_OBJECTFLIP; - if (bot) - bot->flags2 = (bot->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP); break; case 434: // Custom Power @@ -2964,10 +2935,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) P_SetTarget(&dummy->target, mo); A_CustomPower(dummy); - if (bot) { - P_SetTarget(&dummy->target, bot); - A_CustomPower(dummy); - } P_RemoveMobj(dummy); } break; @@ -3036,8 +3003,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) if (line->flags & ML_NOCLIMB) fractime |= 1<<15; //more crazy &ing, as if music stuff wasn't enough mo->player->powers[pw_nocontrol] = fractime; - if (bot) - bot->player->powers[pw_nocontrol] = fractime; } break; @@ -3047,8 +3012,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) mo->destscale = FixedDiv(P_AproxDistance(line->dx, line->dy), 100<destscale < FRACUNIT/100) mo->destscale = FRACUNIT/100; - if (mo->player && bot) - bot->destscale = mo->destscale; } break; @@ -3879,7 +3842,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers } break; case 11: // Special Stage Damage - Kind of like a mini-P_DamageMobj() - if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super] || player->exiting || player->bot) + if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super] || player->exiting) break; if (!(player->powers[pw_shield] || player->mo->health > 1)) // Don't do anything if no shield or rings anyway @@ -3923,7 +3886,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers case 3: // Linedef executor requires all players present /// \todo check continues for proper splitscreen support? for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i] && !players[i].bot && players[i].mo && (gametype != GT_COOP || players[i].lives > 0)) + if (playeringame[i] && players[i].mo && (gametype != GT_COOP || players[i].lives > 0)) { if (roversector) { @@ -3981,8 +3944,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers case 5: // Linedef executor case 6: // Linedef executor (7 Emeralds) case 7: // Linedef executor (NiGHTS Mare) - if (!player->bot) - P_LinedefExecute(sector->tag, player->mo, sector); + P_LinedefExecute(sector->tag, player->mo, sector); break; case 8: // Tells pushable things to check FOFs break; @@ -3992,7 +3954,7 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers mobj_t *mo2; line_t junk; - if (player->bot || sector->ceilingdata || sector->floordata) + if (sector->ceilingdata || sector->floordata) return; // Find the center of the Eggtrap and release all the pretty animals! @@ -4168,8 +4130,6 @@ DoneSection2: } case 2: // Special stage GOAL sector / Exit Sector / CTF Flag Return - if (player->bot) - break; if (!useNightsSS && G_IsSpecialStage(gamemap) && sstimer > 6) sstimer = 6; // Just let P_Ticker take care of the rest. diff --git a/src/p_user.c b/src/p_user.c index 5be3e779e..d3623b78c 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -40,7 +40,7 @@ #include "st_stuff.h" #include "lua_script.h" #include "lua_hook.h" -#include "b_bot.h" +#include "k_bot.h" // Objectplace #include "m_cheat.h" // SRB2kart @@ -707,10 +707,6 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime) { INT32 oldmare; - // Bots can't be super, silly!1 :P - if (player->bot) - return; - if (!(player->pflags & PF_NIGHTSMODE)) { P_SetTarget(&player->mo->tracer, P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_NIGHTSCHAR)); @@ -936,8 +932,6 @@ void P_ResetPlayer(player_t *player) player->powers[pw_tailsfly] = 0; player->onconveyor = 0; player->skidtime = 0; - /*if (player-players == consoleplayer && botingame) - CV_SetValue(&cv_analog2, true);*/ } // @@ -1031,9 +1025,6 @@ void P_AddPlayerScore(player_t *player, UINT32 amount) if (!(G_BattleGametype())) return; - if (player->bot) - player = &players[consoleplayer]; - if (player->exiting) // srb2kart return; @@ -2286,7 +2277,7 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player) } // Underwater audio cues - if (P_IsLocalPlayer(player) && !player->bot) + if (P_IsLocalPlayer(player)) { if (player->powers[pw_underwater] == 11*TICRATE + 1 && player == &players[consoleplayer]) @@ -4024,11 +4015,9 @@ static void P_3dMovement(player_t *player) cmd = &player->cmd; - if ((player->exiting || mapreset) || player->pflags & PF_STASIS || player->kartstuff[k_spinouttimer]) // pw_introcam? + if (player->pflags & PF_STASIS || player->kartstuff[k_spinouttimer]) // pw_introcam? { cmd->forwardmove = cmd->sidemove = 0; - if (EITHERSNEAKER(player)) - cmd->forwardmove = 50; } if (!(player->pflags & PF_FORCESTRAFE) && !player->kartstuff[k_pogospring]) @@ -4106,9 +4095,8 @@ static void P_3dMovement(player_t *player) player->aiming = cmd->aiming<exiting || mapreset) || (P_PlayerInPain(player) && !onground))) + if (!(P_PlayerInPain(player) && !onground)) { - //movepushforward = cmd->forwardmove * (thrustfactor * acceleration); movepushforward = K_3dKartMovement(player, onground, cmd->forwardmove); if (player->mo->movefactor != FRACUNIT) // Friction-scaled acceleration... @@ -4126,12 +4114,8 @@ static void P_3dMovement(player_t *player) movepushforward = 0; } -#ifdef ESLOPE totalthrust.x += P_ReturnThrustX(player->mo, movepushangle, movepushforward); totalthrust.y += P_ReturnThrustY(player->mo, movepushangle, movepushforward); -#else - P_Thrust(player->mo, movepushangle, movepushforward); -#endif } else if (!(player->kartstuff[k_spinouttimer])) { @@ -4146,15 +4130,10 @@ static void P_3dMovement(player_t *player) else movepushside = (cmd->sidemove * FRACUNIT/128) - FixedDiv(player->speed, K_GetKartSpeed(player, true)); -#ifdef ESLOPE totalthrust.x += P_ReturnThrustX(player->mo, movepushsideangle, movepushside); totalthrust.y += P_ReturnThrustY(player->mo, movepushsideangle, movepushside); -#else - P_Thrust(player->mo, movepushsideangle, movepushside); -#endif } -#ifdef ESLOPE if ((totalthrust.x || totalthrust.y) && player->mo->standingslope && (!(player->mo->standingslope->flags & SL_NOPHYSICS)) && abs(player->mo->standingslope->zdelta) > FRACUNIT/2) { // Factor thrust to slope, but only for the part pushing up it! @@ -4172,21 +4151,45 @@ static void P_3dMovement(player_t *player) } } + if (K_PlayerUsesBotMovement(player)) + { + K_MomentumToFacing(player); + } + player->mo->momx += totalthrust.x; player->mo->momy += totalthrust.y; if (!onground) { - fixed_t airspeedcap = (50*mapobjectscale); - fixed_t speed = R_PointToDist2(0, 0, player->mo->momx, player->mo->momy); + const fixed_t airspeedcap = (50*mapobjectscale); + const fixed_t speed = R_PointToDist2(0, 0, player->mo->momx, player->mo->momy); + if (speed > airspeedcap) { - fixed_t newspeed = speed - ((speed - airspeedcap) / 32); + fixed_t div = 32*FRACUNIT; + fixed_t newspeed; + + if (K_PlayerUsesBotMovement(player)) + { + fixed_t baserubberband = K_BotRubberband(player); + fixed_t rubberband = FixedMul(baserubberband, + FixedMul(baserubberband, + FixedMul(baserubberband, + baserubberband + ))); // This looks extremely goofy, but we need this really high, but at the same time, proportional. + + if (rubberband > FRACUNIT) + { + div = FixedMul(div, rubberband); + } + } + + newspeed = speed - FixedDiv((speed - airspeedcap), div); + player->mo->momx = FixedMul(FixedDiv(player->mo->momx, speed), newspeed); player->mo->momy = FixedMul(FixedDiv(player->mo->momy, speed), newspeed); } } -#endif // Time to ask three questions: // 1) Are we over topspeed? @@ -8317,17 +8320,6 @@ void P_PlayerThink(player_t *player) player->playerstate = PST_DEAD; } - if (player->bot) - { - if (player->playerstate == PST_LIVE || player->playerstate == PST_DEAD) - { - if (B_CheckRespawn(player)) - player->playerstate = PST_REBORN; - } - if (player->playerstate == PST_REBORN) - return; - } - #ifdef SEENAMES if (netgame && player == &players[displayplayers[0]] && !(leveltime % (TICRATE/5)) && !r_splitscreen) { diff --git a/src/r_main.c b/src/r_main.c index fdc78f140..5f7b0cadd 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -204,7 +204,7 @@ void SplitScreen_OnChange(void) // recompute screen size R_ExecuteSetViewSize(); - if (!demo.playback && !botingame) + if (!demo.playback) { for (i = 1; i < MAXSPLITSCREENPLAYERS; i++) { @@ -261,8 +261,6 @@ static void ChaseCam_OnChange(void) static void ChaseCam2_OnChange(void) { - if (botingame) - return; /*if (!cv_chasecam2.value || !cv_useranalog2.value) CV_SetValue(&cv_analog2, 0); else diff --git a/src/y_inter.c b/src/y_inter.c index c19df2f8b..94e24aed1 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -440,7 +440,7 @@ void Y_IntermissionDrawer(void) int y2; if (data.match.rankingsmode) - timeheader = "PWR.LV"; + timeheader = (powertype != -1 ? "PWR.LV" : "RANK"); else timeheader = ((intertype == int_race || (intertype == int_match && battlecapsules)) ? "TIME" : "SCORE"); @@ -497,7 +497,7 @@ void Y_IntermissionDrawer(void) y2 = y; - if (playerconsole[data.match.num[i]] == 0 && server_lagless) + if (netgame && playerconsole[data.match.num[i]] == 0 && server_lagless && !players[data.match.num[i]].bot) { static int alagles_timer = 0; patch_t *alagles; @@ -533,7 +533,7 @@ void Y_IntermissionDrawer(void) if (data.match.rankingsmode) { - if (!clientpowerlevels[data.match.num[i]][powertype]) // No power level (splitscreen guests) + if (powertype != -1 && !clientpowerlevels[data.match.num[i]][powertype]) // No power level (splitscreen guests) STRBUFCPY(strtime, "----"); else { @@ -939,9 +939,11 @@ static void K_UpdatePowerLevels(void) continue; } - theirpower = PWRLVRECORD_DEF; - if (clientpowerlevels[jpnum][powertype] != 0) // No power level acts as 5000 (used for splitscreen guests) - theirpower = clientpowerlevels[jpnum][powertype]; + if (clientpowerlevels[jpnum][powertype] == 0) // No power level (splitscreen guests, bots) + continue; + + theirpower = clientpowerlevels[jpnum][powertype]; + CONS_Debug(DBG_GAMELOGIC, "Player %d's PWR.LV: %d\n", jpnum, theirpower); if (G_RaceGametype()) @@ -981,9 +983,11 @@ static void K_UpdatePowerLevels(void) CONS_Debug(DBG_GAMELOGIC, "Player %d VS Player %d (griefer):\n", ipnum, jpnum); - theirpower = PWRLVRECORD_DEF; - if (nospectategrief[jpnum] != 0) // No power level acts as 5000 (used for splitscreen guests) - theirpower = nospectategrief[jpnum]; + if (nospectategrief[jpnum] == 0) // No power level (splitscreen guests, bots) + continue; + + theirpower = nospectategrief[jpnum]; + CONS_Debug(DBG_GAMELOGIC, "Player %d's PWR.LV: %d\n", jpnum, theirpower); diff = theirpower - yourpower; @@ -1509,6 +1513,7 @@ static void Y_VoteStops(SINT8 pick, SINT8 level) void Y_VoteTicker(void) { INT32 i; + boolean everyone_voted; if (paused || P_AutoPause() || !voteclient.loaded) return; @@ -1667,7 +1672,7 @@ void Y_VoteTicker(void) if ((InputDown(gc_accelerate, i+1) || JoyAxis(AXISMOVE, i+1) > 0) && !pressed) { - D_ModifyClientVote(voteclient.playerinfo[i].selection, i); + D_ModifyClientVote(consoleplayer, voteclient.playerinfo[i].selection, i); pressed = true; } } @@ -1682,6 +1687,8 @@ void Y_VoteTicker(void) if (server) { + everyone_voted = true;/* the default condition */ + if (timer == 0) { for (i = 0; i < MAXPLAYERS; i++) @@ -1695,15 +1702,27 @@ void Y_VoteTicker(void) for (i = 0; i < MAXPLAYERS; i++) { if ((playeringame[i] && !players[i].spectator) && votes[i] == -1) - return; + { + if (players[i].bot) + { + if (( M_RandomFixed() % 100 ) == 0) + D_ModifyClientVote(i, M_RandomKey(4), 0); + } + + if (votes[i] == -1) + everyone_voted = false; + } } } - timer = 0; - if (voteendtic == -1) + if (everyone_voted) { - votenotyetpicked = false;/* don't pick vote twice */ - D_PickVote(); + timer = 0; + if (voteendtic == -1) + { + votenotyetpicked = false;/* don't pick vote twice */ + D_PickVote(); + } } } }