Merge branch 'pet-robo' into 'master'

This is my PET ROBO!

See merge request KartKrew/Kart!257
This commit is contained in:
Sal 2020-05-29 13:27:28 -04:00
commit dcd2f790ba
36 changed files with 3540 additions and 819 deletions

View file

@ -165,6 +165,9 @@ set(SRB2_CORE_GAME_SOURCES
k_pwrlv.c
k_waypoint.c
k_color.c
k_bot.c
k_botitem.c
k_botsearch.c
p_local.h
p_maputl.h
@ -184,6 +187,7 @@ set(SRB2_CORE_GAME_SOURCES
k_pwrlv.h
k_waypoint.h
k_color.h
k_bot.h
)
if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))

View file

@ -562,7 +562,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) \

View file

@ -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<<FRACBITS>>16;
if (backward)
cmd->forwardmove -= MAXPLMOVE<<FRACBITS>>16;
if (left || strafeleft)
cmd->sidemove -= MAXPLMOVE<<FRACBITS>>16;
if (right || straferight)
cmd->sidemove += MAXPLMOVE<<FRACBITS>>16;
} else {
// In standard 2D mode, interpret "forward" as "the way you're facing" and everything else as "the way you're not facing"
if (left || right)
backward = true;
left = right = false;
if (forward) {
if (mo->angle < ANGLE_90 || mo->angle > ANGLE_270)
right = true;
else
left = true;
} else if (backward) {
if (mo->angle < ANGLE_90 || mo->angle > ANGLE_270)
left = true;
else
right = true;
}
if (left || strafeleft)
cmd->sidemove -= MAXPLMOVE<<FRACBITS>>16;
if (right || straferight)
cmd->sidemove += MAXPLMOVE<<FRACBITS>>16;
}
} else {
if (forward)
cmd->forwardmove += MAXPLMOVE<<FRACBITS>>16;
if (backward)
cmd->forwardmove -= MAXPLMOVE<<FRACBITS>>16;
if (left)
cmd->angleturn += 1280;
if (right)
cmd->angleturn -= 1280;
if (strafeleft)
cmd->sidemove -= MAXPLMOVE<<FRACBITS>>16;
if (straferight)
cmd->sidemove += MAXPLMOVE<<FRACBITS>>16;
}
if (jump)
cmd->buttons |= BT_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;
}

View file

@ -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);

View file

@ -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
@ -645,6 +646,12 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i)
rsp->splitscreenindex = players[i].splitscreenindex;
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.
@ -768,6 +775,12 @@ static void resynch_read_player(resynch_pak *rsp)
players[i].splitscreenindex = rsp->splitscreenindex;
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;
@ -1298,8 +1311,6 @@ static boolean CL_SendJoin(void)
if (splitscreen)
localplayers += splitscreen;
else if (botingame)
localplayers++;
netbuffer->u.clientcfg.localplayers = localplayers;
netbuffer->u.clientcfg._255 = 255;
@ -1550,6 +1561,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);
@ -2607,8 +2620,7 @@ static void Command_connect(void)
splitscreen = cv_splitplayers.value-1;
SplitScreen_OnChange();
}
botingame = false;
botskin = 0;
CL_ConnectToServer(viams);
}
#endif
@ -2659,7 +2671,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
@ -3283,6 +3295,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)
@ -3312,6 +3325,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);
@ -3524,8 +3538,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
{
@ -3544,6 +3556,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] =
@ -3597,6 +3610,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;
@ -3614,6 +3684,7 @@ static boolean SV_AddWaitingPlayers(void)
{
UINT8 buf[4];
UINT8 *buf_p = buf;
UINT8 nobotoverwrite;
newplayer = true;
@ -3631,6 +3702,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);
@ -4130,6 +4216,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;
@ -4965,7 +5052,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);
@ -5164,7 +5251,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)
@ -5637,9 +5724,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;
}

View file

@ -285,6 +285,12 @@ typedef struct
UINT8 splitscreenindex;
boolean bot;
UINT8 bot_difficulty;
tic_t bot_itemdelay;
tic_t bot_itemconfirm;
SINT8 bot_turnconfirm;
//player->mo stuff
UINT8 hasmo; // Boolean
@ -328,6 +334,8 @@ typedef struct
UINT8 playerskins[MAXPLAYERS];
UINT8 playercolor[MAXPLAYERS];
UINT8 playerisbot[MAXPLAYERS];
UINT8 gametype;
UINT8 modifiedgame;
SINT8 adminplayers[MAXPLAYERS]; // Needs to be signed

View file

@ -772,8 +772,7 @@ void D_StartTitle(void)
splitscreen = 0;
SplitScreen_OnChange();
botingame = false;
botskin = 0;
cv_debug = 0;
emeralds = 0;

View file

@ -392,6 +392,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};
@ -549,6 +564,7 @@ const char *netxcmdnames[MAXNETXCMD - 1] =
"LEAVEPARTY",
"CANCELPARTYINVITE",
"GIVEITEM",
"ADDBOT",
#ifdef HAVE_BLUA
"LUACMD",
"LUAVAR"
@ -1553,7 +1569,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)
@ -1591,15 +1607,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;
@ -1829,15 +1837,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;
@ -2247,7 +2247,7 @@ static void Got_LeaveParty(UINT8 **cp,INT32 playernum)
void D_SendPlayerConfig(void)
{
SendNameAndColor();
if (splitscreen || botingame)
if (splitscreen)
SendNameAndColor2();
if (splitscreen > 1)
SendNameAndColor3();
@ -2808,29 +2808,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
@ -2870,11 +2847,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];
@ -5723,8 +5699,7 @@ void Command_ExitGame_f(void)
splitscreen = 0;
SplitScreen_OnChange();
botingame = false;
botskin = 0;
cv_debug = 0;
emeralds = 0;

View file

@ -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);

View file

@ -420,6 +420,17 @@ typedef enum
RW_RAIL = 32
} ringweapons_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
// ========================================================================
@ -594,7 +605,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

View file

@ -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",

View file

@ -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
@ -58,10 +58,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;
@ -1279,10 +1275,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];
@ -1316,6 +1309,11 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
return;
}
if (K_PlayerUsesBotMovement(player))
{
return;
}
switch (ssplayer)
{
case 2:
@ -1620,9 +1618,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
@ -1638,8 +1635,6 @@ static void UserAnalog_OnChange(void)
static void UserAnalog2_OnChange(void)
{
if (botingame)
return;
/*if (cv_useranalog2.value)
CV_SetValue(&cv_analog2, 1);
else
@ -1648,8 +1643,6 @@ static void UserAnalog2_OnChange(void)
static void UserAnalog3_OnChange(void)
{
if (botingame)
return;
/*if (cv_useranalog3.value)
CV_SetValue(&cv_analog3, 1);
else
@ -1658,8 +1651,6 @@ static void UserAnalog3_OnChange(void)
static void UserAnalog4_OnChange(void)
{
if (botingame)
return;
/*if (cv_useranalog4.value)
CV_SetValue(&cv_analog4, 1);
else
@ -1685,7 +1676,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
@ -1777,7 +1768,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;
}
@ -2402,9 +2393,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
}
}
}
@ -2591,7 +2590,8 @@ void G_PlayerReborn(INT32 player)
tic_t jointime;
UINT8 splitscreenindex;
boolean spectator;
INT16 bot;
boolean bot;
UINT8 botdifficulty;
SINT8 pity;
// SRB2kart
@ -2644,6 +2644,7 @@ void G_PlayerReborn(INT32 player)
mare = players[player].mare;
bot = players[player].bot;
botdifficulty = players[player].botvars.difficulty;
pity = players[player].pity;
// SRB2kart
@ -2722,8 +2723,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
@ -3174,96 +3175,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;
@ -4625,9 +4539,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)
{

View file

@ -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__

View file

@ -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;
}

View file

@ -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

View file

@ -4796,6 +4796,8 @@ typedef enum mobj_type
MT_WAYPOINT_RISER,
MT_WAYPOINT_ANCHOR,
MT_BOTHINT,
MT_RANDOMAUDIENCE,
MT_FLAYM,

831
src/k_bot.c Normal file
View file

@ -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);
}
}

232
src/k_bot.h Normal file
View file

@ -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

1095
src/k_botitem.c Normal file

File diff suppressed because it is too large Load diff

781
src/k_botsearch.c Normal file
View file

@ -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;
}

View file

@ -28,6 +28,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)
@ -118,6 +119,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);
@ -297,6 +299,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
@ -345,7 +350,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;
@ -431,6 +436,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:
@ -522,7 +567,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;
@ -820,7 +865,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)
@ -875,7 +920,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;
@ -2192,6 +2237,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
@ -2212,8 +2264,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)
@ -2292,11 +2343,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;
}
@ -2311,6 +2377,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]);
}
@ -2365,6 +2436,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;
@ -5185,7 +5259,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;
@ -5911,7 +5985,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);
@ -6086,10 +6160,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);
@ -6126,7 +6221,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++)
{
@ -6281,70 +6377,67 @@ void K_UpdateDistanceFromFinishLine(player_t *const player)
{
if ((player != NULL) && (player->mo != NULL))
{
waypoint_t *finishline = K_GetFinishLineWaypoint();
waypoint_t *nextwaypoint = K_GetPlayerNextWaypoint(player);
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->nextwaypoint = K_GetFinishLineWaypoint();
// Player has finished, we don't need to calculate this
player->distancetofinish = 0U;
}
else
else if ((player->nextwaypoint != NULL) && (finishline != NULL))
{
waypoint_t *finishline = K_GetFinishLineWaypoint();
waypoint_t *nextwaypoint = K_GetPlayerNextWaypoint(player);
const boolean useshortcuts = false;
const boolean huntbackwards = false;
boolean pathfindsuccess = false;
path_t pathtofinish = {};
if (nextwaypoint != NULL)
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();
}
}
}
@ -6353,6 +6446,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
@ -6421,6 +6519,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);
@ -6618,10 +6725,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.
@ -6895,7 +6999,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->kartstuff[k_respawn] == 0)
{
// First, the really specific, finicky items that function without the item being directly in your item slot.
@ -9315,15 +9419,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 );
@ -10093,7 +10205,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;
@ -10105,7 +10217,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)
@ -10815,7 +10927,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;

View file

@ -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);
@ -53,6 +54,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);
@ -62,6 +64,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);

View file

@ -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);

View file

@ -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))
{

View file

@ -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

View file

@ -377,7 +377,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"))

View file

@ -3570,7 +3570,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);
@ -3588,7 +3587,7 @@ void A_AttractChase(mobj_t *actor)
angle_t offset = FixedAngle(18<<FRACBITS);
// Base add is 3 tics for 9,9, adds 1 tic for each point closer to the 1,1 end
actor->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);
@ -3616,7 +3615,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);
@ -3645,7 +3644,6 @@ void A_AttractChase(mobj_t *actor)
actor->extravalue1++;
}
}
#undef RINGBOOSTPWR
}
else
{

View file

@ -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;
//
// SRB2kart: make sure the player will have enough checkpoints to touch
if (circuitmap && special->health - player->starpostnum > 1)
{
@ -1631,7 +1603,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.
@ -1716,8 +1688,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;
}
@ -2008,7 +1978,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;
@ -2280,8 +2250,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
@ -2294,6 +2263,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source)
}
}
}
target->player->playerstate = PST_DEAD;
if (target->player == &players[consoleplayer])
@ -3038,7 +3008,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;
}

View file

@ -491,89 +491,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
//
@ -1463,8 +1380,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;
}*/

View file

@ -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
@ -1691,10 +1691,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)
{
@ -11793,9 +11789,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;
@ -12755,12 +12762,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)));

View file

@ -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;
}
//
@ -284,6 +268,11 @@ static void P_NetArchivePlayers(void)
WRITEUINT32(save_p, players[i].distancetofinish);
WRITEUINT32(save_p, K_GetWaypointHeapIndex(players[i].nextwaypoint));
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);
}
}
@ -455,6 +444,11 @@ static void P_NetUnArchivePlayers(void)
players[i].distancetofinish = READUINT32(save_p);
players[i].nextwaypoint = (waypoint_t *)(size_t)READUINT32(save_p);
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);
}
}
@ -3247,7 +3241,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));
}

View file

@ -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();
@ -3339,7 +3345,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)
@ -3410,9 +3416,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;
}

View file

@ -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<<FRACBITS);
if (mo->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.

View file

@ -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
@ -706,10 +706,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));
@ -935,8 +931,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);*/
}
//
@ -1030,9 +1024,6 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
if (!(G_BattleGametype()))
return;
if (player->bot)
player = &players[consoleplayer];
if (player->exiting) // srb2kart
return;
@ -2285,7 +2276,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])
@ -4023,11 +4014,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])
@ -4105,9 +4094,8 @@ static void P_3dMovement(player_t *player)
player->aiming = cmd->aiming<<FRACBITS;
// Forward movement
if (!((player->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...
@ -4125,12 +4113,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]))
{
@ -4145,15 +4129,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!
@ -4171,21 +4150,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?
@ -8316,17 +8319,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)
{

View file

@ -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

View file

@ -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();
}
}
}
}