Merge branch 'training-mode' into 'master'

Add bot styles & bot spawn ACS function

Closes #564

See merge request KartKrew/Kart!1288
This commit is contained in:
Oni 2023-06-19 03:16:30 +00:00
commit 8b2b3aaee8
12 changed files with 330 additions and 121 deletions

View file

@ -41,6 +41,7 @@
#include "../r_skins.h"
#include "../k_battle.h"
#include "../k_podium.h"
#include "../k_bot.h"
#include "../z_zone.h"
#include "call-funcs.hpp"
@ -1710,6 +1711,52 @@ bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Wor
return false;
}
/*--------------------------------------------------
bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
Inserts a bot, if there's room for them.
--------------------------------------------------*/
bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)
{
ACSVM::MapScope *map = NULL;
ACSVM::String *skinStr = nullptr;
INT32 skin = -1;
UINT8 difficulty = 0;
botStyle_e style = BOT_STYLE_NORMAL;
UINT8 newplayernum = 0;
bool success = false;
(void)argC;
map = thread->scopeMap;
skinStr = map->getString(argV[0]);
if (skinStr->len != 0)
{
skin = R_SkinAvailable(skinStr->str);
}
if (skin == -1)
{
skin = R_BotDefaultSkin();
}
difficulty = std::clamp(static_cast<int>(argV[1]), 1, MAXBOTDIFFICULTY);
style = static_cast<botStyle_e>(argV[2]);
if (style < BOT_STYLE_NORMAL || style >= BOT_STYLE__MAX)
{
style = BOT_STYLE_NORMAL;
}
success = K_AddBot(skin, difficulty, style, &newplayernum);
thread->dataStk.push(success ? newplayernum : -1);
return false;
}
/*--------------------------------------------------
bool CallFunc_Get/SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC)

View file

@ -84,6 +84,7 @@ bool CallFunc_PodiumFinish(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM
bool CallFunc_SetLineRenderStyle(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_MapWarp(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_AddBot(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_GetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);
bool CallFunc_SetLineProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC);

View file

@ -166,6 +166,7 @@ Environment::Environment()
addFuncDataACS0( 502, addCallFunc(CallFunc_PodiumFinish));
addFuncDataACS0( 503, addCallFunc(CallFunc_SetLineRenderStyle));
addFuncDataACS0( 504, addCallFunc(CallFunc_MapWarp));
addFuncDataACS0( 505, addCallFunc(CallFunc_AddBot));
}
ACSVM::Thread *Environment::allocThread()

View file

@ -15,7 +15,6 @@
#include "thread.hpp"
extern "C" {
#include "../doomtype.h"
#include "../doomdef.h"
#include "../doomstat.h"
@ -26,7 +25,6 @@ extern "C" {
#include "../r_defs.h"
#include "../r_state.h"
#include "../p_polyobj.h"
}
using namespace srb2::acs;

View file

@ -16,6 +16,7 @@
#include "acsvm.hpp"
extern "C" {
#include "../doomtype.h"
#include "../doomdef.h"
#include "../doomstat.h"
@ -23,7 +24,7 @@
#include "../r_defs.h"
#include "../r_state.h"
#include "../p_spec.h"
}
namespace srb2::acs {

View file

@ -4010,6 +4010,7 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
INT16 newplayernum;
UINT8 skinnum = 0;
UINT8 difficulty = DIFFICULTBOT;
botStyle_e style = BOT_STYLE_NORMAL;
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
{
@ -4025,41 +4026,9 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
newplayernum = READUINT8(*p);
skinnum = READUINT8(*p);
difficulty = READUINT8(*p);
style = READUINT8(*p);
CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum);
// Clear player before joining, lest some things get set incorrectly
CL_ClearPlayer(newplayernum);
G_DestroyParty(newplayernum);
playeringame[newplayernum] = true;
G_AddPlayer(newplayernum);
if (newplayernum+1 > doomcom->numslots)
doomcom->numslots = (INT16)(newplayernum+1);
playernode[newplayernum] = servernode;
// this will permit unlocks
memcpy(&players[newplayernum].availabilities, R_GetSkinAvailabilities(false, true), MAXAVAILABILITY*sizeof(UINT8));
players[newplayernum].splitscreenindex = 0;
players[newplayernum].bot = true;
players[newplayernum].botvars.difficulty = difficulty;
players[newplayernum].lives = 9;
players[newplayernum].skincolor = skins[skinnum].prefcolor;
sprintf(player_names[newplayernum], "%s", skins[skinnum].realname);
SetPlayerSkinByNum(newplayernum, skinnum);
playerconsole[newplayernum] = newplayernum;
G_BuildLocalSplitscreenParty(newplayernum);
if (netgame)
{
HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false);
}
LUA_HookInt(newplayernum, HOOK(PlayerJoin));
K_SetBot(newplayernum, skinnum, difficulty, style);
}
static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities,

View file

@ -313,9 +313,20 @@ struct respawnvars_t
boolean init;
};
typedef enum
{
BOT_STYLE_NORMAL,
BOT_STYLE_STAY,
//BOT_STYLE_CHASE,
//BOT_STYLE_ESCAPE,
BOT_STYLE__MAX
} botStyle_e;
// player_t struct for all bot variables
struct botvars_t
{
botStyle_e style; // Training mode-style CPU mode
UINT8 difficulty; // Bot's difficulty setting
UINT8 diffincrease; // In GP: bot difficulty will increase this much next round
boolean rival; // If true, they're the GP rival

View file

@ -31,20 +31,102 @@
#include "k_podium.h"
#include "k_respawn.h"
#include "m_easing.h"
#include "d_clisrv.h"
#include "g_party.h"
#include "k_grandprix.h" // K_CanChangeRules
#include "hu_stuff.h" // HU_AddChatText
#include "discord.h" // DRPC_UpdatePresence
#include "i_net.h" // doomcom
#ifdef DEVELOP
consvar_t cv_botcontrol = CVAR_INIT ("botcontrol", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL);
#endif
/*--------------------------------------------------
boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p)
void K_SetBot(UINT8 playerNum, UINT8 skinnum, UINT8 difficulty, botStyle_e style)
See header file for description.
--------------------------------------------------*/
boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p)
void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e style)
{
CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum);
// Clear player before joining, lest some things get set incorrectly
CL_ClearPlayer(newplayernum);
G_DestroyParty(newplayernum);
playeringame[newplayernum] = true;
G_AddPlayer(newplayernum);
if (newplayernum+1 > doomcom->numslots)
doomcom->numslots = (INT16)(newplayernum+1);
playernode[newplayernum] = servernode;
// this will permit unlocks
memcpy(&players[newplayernum].availabilities, R_GetSkinAvailabilities(false, true), MAXAVAILABILITY*sizeof(UINT8));
players[newplayernum].splitscreenindex = 0;
players[newplayernum].bot = true;
players[newplayernum].botvars.difficulty = difficulty;
players[newplayernum].botvars.style = style;
players[newplayernum].lives = 9;
players[newplayernum].skincolor = skins[skinnum].prefcolor;
sprintf(player_names[newplayernum], "%s", skins[skinnum].realname);
SetPlayerSkinByNum(newplayernum, skinnum);
playerconsole[newplayernum] = newplayernum;
G_BuildLocalSplitscreenParty(newplayernum);
if (netgame)
{
HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false);
}
LUA_HookInt(newplayernum, HOOK(PlayerJoin));
}
/*--------------------------------------------------
boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p)
See header file for description.
--------------------------------------------------*/
boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p)
{
UINT8 newplayernum = *p;
for (; newplayernum < MAXPLAYERS; newplayernum++)
{
if (playeringame[newplayernum] == false)
{
// free player slot
break;
}
}
if (newplayernum >= MAXPLAYERS)
{
// nothing is free
*p = MAXPLAYERS;
return false;
}
K_SetBot(newplayernum, skin, difficulty, style);
DEBFILE(va("Everyone added bot %d\n", newplayernum));
// use the next free slot
*p = newplayernum+1;
return true;
}
/*--------------------------------------------------
boolean K_AddBotFromServer(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p)
See header file for description.
--------------------------------------------------*/
boolean K_AddBotFromServer(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p)
{
UINT8 buf[3];
UINT8 *buf_p = buf;
UINT8 newplayernum = *p;
// search for a free playernum
@ -54,56 +136,66 @@ boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p)
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)
for (; newplayernum < MAXPLAYERS; newplayernum++)
{
newplayernum++;
if (playeringame[newplayernum] == false)
{
// free player slot
break;
}
}
if (newplayernum >= MAXPLAYERS)
{
*p = newplayernum;
// nothing is free
*p = MAXPLAYERS;
return false;
}
WRITEUINT8(buf_p, newplayernum);
if (skin > numskins)
if (server)
{
skin = numskins;
UINT8 buf[4];
UINT8 *buf_p = buf;
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);
WRITEUINT8(buf_p, style);
SendNetXCmd(XD_ADDBOT, buf, buf_p - buf);
DEBFILE(va("Server added bot %d\n", newplayernum));
}
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;
*p = newplayernum+1;
return true;
}
@ -125,11 +217,6 @@ void K_UpdateMatchRaceBots(void)
UINT8 grabskins[MAXSKINS+1];
UINT8 i;
if (!server)
{
return;
}
// Init usable bot skins list
for (i = 0; i < numskins; i++)
{
@ -156,6 +243,9 @@ void K_UpdateMatchRaceBots(void)
// While we're here, we should update bot difficulty to the proper value.
players[i].botvars.difficulty = difficulty;
// Enforce normal style for Match Race
players[i].botvars.style = BOT_STYLE_NORMAL;
}
else
{
@ -169,12 +259,16 @@ void K_UpdateMatchRaceBots(void)
}
}
if (difficulty == 0 || !(gametyperules & GTR_BOTS))
if (K_CanChangeRules(true) == false
|| (gametyperules & GTR_BOTS) == 0
|| difficulty == 0)
{
// Remove bots if there are any.
wantedbots = 0;
}
else
{
// Add bots to fill up MAXPLAYERS
wantedbots = pmax - numplayers - numwaiting;
if (wantedbots < 0)
@ -197,11 +291,15 @@ void K_UpdateMatchRaceBots(void)
for (i = 0; i < usableskins; i++)
{
if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true)))
{
continue;
}
while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true)))
{
usableskins--;
}
grabskins[i] = grabskins[usableskins];
grabskins[usableskins] = MAXSKINS;
}
@ -212,12 +310,12 @@ void K_UpdateMatchRaceBots(void)
if (usableskins > 0)
{
UINT8 index = M_RandomKey(usableskins);
UINT8 index = P_RandomKey(PR_BOTS, usableskins);
skinnum = grabskins[index];
grabskins[index] = grabskins[--usableskins];
}
if (!K_AddBot(skinnum, difficulty, &newplayernum))
if (!K_AddBot(skinnum, difficulty, BOT_STYLE_NORMAL, &newplayernum))
{
// Not enough player slots to add the bot, break the loop.
break;
@ -228,26 +326,25 @@ void K_UpdateMatchRaceBots(void)
}
else if (numbots > wantedbots)
{
UINT8 buf[2];
i = MAXPLAYERS;
while (numbots > wantedbots && i > 0)
{
i--;
if (playeringame[i] && players[i].bot)
{
buf[0] = i;
buf[1] = KR_LEAVE;
SendNetXCmd(XD_REMOVEPLAYER, &buf, 2);
CL_RemovePlayer(i, KR_LEAVE);
numbots--;
}
}
}
// We should have enough bots now :)
#ifdef HAVE_DISCORDRPC
// Player count change was possible, so update presence
DRPC_UpdatePresence();
#endif
}
/*--------------------------------------------------
@ -1473,11 +1570,11 @@ static void K_BuildBotPodiumTiccmd(player_t *player, ticcmd_t *cmd)
}
/*--------------------------------------------------
void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd)
static void K_BuildBotTiccmdNormal(player_t *player, ticcmd_t *cmd)
See header file for description.
Build ticcmd for bots with a style of BOT_STYLE_NORMAL
--------------------------------------------------*/
void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd)
static void K_BuildBotTiccmdNormal(player_t *player, ticcmd_t *cmd)
{
precise_t t = 0;
botprediction_t *predict = NULL;
@ -1487,29 +1584,6 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd)
INT32 turnamt = 0;
const line_t *botController = player->botvars.controller != UINT16_MAX ? &lines[player->botvars.controller] : NULL;
// Remove any existing controls
memset(cmd, 0, sizeof(ticcmd_t));
if (player->mo == NULL
|| player->spectator == true
|| G_GamestateUsesLevel() == false)
{
// Not in the level.
return;
}
// Complete override of all ticcmd functionality
if (LUA_HookTiccmd(player, cmd, HOOK(BotTiccmd)) == true)
{
return;
}
if (K_PodiumSequence() == true)
{
K_BuildBotPodiumTiccmd(player, cmd);
return;
}
if (!(gametyperules & GTR_BOTS) // No bot behaviors
|| K_GetNumWaypoints() == 0 // No waypoints
|| leveltime <= introtime // During intro camera
@ -1762,6 +1836,54 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd)
}
}
/*--------------------------------------------------
void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd)
See header file for description.
--------------------------------------------------*/
void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd)
{
// Remove any existing controls
memset(cmd, 0, sizeof(ticcmd_t));
if (player->mo == NULL
|| player->spectator == true
|| G_GamestateUsesLevel() == false)
{
// Not in the level.
return;
}
// Complete override of all ticcmd functionality.
// May add more hooks to individual pieces of bot ticcmd,
// but this should always be here so anyone can roll
// their own :)
if (LUA_HookTiccmd(player, cmd, HOOK(BotTiccmd)) == true)
{
return;
}
if (K_PodiumSequence() == true)
{
K_BuildBotPodiumTiccmd(player, cmd);
return;
}
switch (player->botvars.style)
{
case BOT_STYLE_STAY:
{
// Hey, this one's pretty easy :P
break;
}
default:
{
K_BuildBotTiccmdNormal(player, cmd);
break;
}
}
}
/*--------------------------------------------------
void K_UpdateBotGameplayVars(player_t *player);

View file

@ -138,25 +138,67 @@ fixed_t K_UpdateRubberband(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);
// NOT AVAILABLE FOR LUA
/*--------------------------------------------------
boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *newplayernum);
boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p);
Returns the waypoint actually being used as the finish line.
Adds a new bot, using code intended to run on all clients.
Input Arguments:-
skin - Skin number that the bot will use.
difficulty - Difficulty level this bot will use.
style - Bot style to spawn this bot with, see botStyle_e.
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.
true if a bot was added, otherwise false.
--------------------------------------------------*/
boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *newplayernum);
boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p);
// NOT AVAILABLE FOR LUA
/*--------------------------------------------------
void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e style);
Sets a player ID to be a new bot directly. Invoked directly
by K_AddBot, and indirectly by K_AddBotFromServer by sending
a packet.
Input Arguments:-
newplayernum - Player slot number to set as a bot.
skin - Skin number that the bot will use.
difficulty - Difficulty level this bot will use.
style - Bot style to spawn this bot with, see botStyle_e.
Return:-
None
--------------------------------------------------*/
void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e style);
/*--------------------------------------------------
boolean K_AddBotFromServer(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *newplayernum);
Adds a new bot, using a server-sided packet sent to all clients.
Using regular K_AddBot wherever possible is better, but this is kept
as a back-up measure if this is the only option.
Input Arguments:-
skin - Skin number that the bot will use.
difficulty - Difficulty level this bot will use.
style - Bot style to spawn this bot with, see botStyle_e.
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 can be added via a packet later, otherwise false.
--------------------------------------------------*/
boolean K_AddBotFromServer(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p);
/*--------------------------------------------------

View file

@ -186,6 +186,13 @@ void K_InitGrandPrixBots(void)
{
if (playeringame[i])
{
if (players[i].bot == true)
{
// Remove existing bots.
CL_RemovePlayer(i, KR_LEAVE);
continue;
}
if (numplayers < MAXSPLITSCREENPLAYERS && !players[i].spectator)
{
competitors[numplayers] = i;
@ -227,11 +234,15 @@ void K_InitGrandPrixBots(void)
for (i = 0; i < usableskins; i++)
{
if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true)))
{
continue;
}
while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true)))
{
usableskins--;
}
grabskins[i] = grabskins[usableskins];
grabskins[usableskins] = MAXSKINS;
}
@ -245,7 +256,7 @@ void K_InitGrandPrixBots(void)
if (usableskins > 0)
{
UINT8 index = M_RandomKey(usableskins);
UINT8 index = P_RandomKey(PR_BOTS, usableskins);
skinnum = grabskins[index];
grabskins[index] = grabskins[--usableskins];
}
@ -256,7 +267,7 @@ void K_InitGrandPrixBots(void)
for (i = 0; i < wantedbots; i++)
{
if (!K_AddBot(botskinlist[i], difficultylevels[i], &newplayernum))
if (!K_AddBot(botskinlist[i], difficultylevels[i], BOT_STYLE_NORMAL, &newplayernum))
{
break;
}
@ -327,8 +338,10 @@ void K_UpdateGrandPrixBots(void)
UINT16 newrivalscore = 0;
UINT8 i;
if (K_PodiumSequence())
if (K_PodiumSequence() == true)
{
return;
}
for (i = 0; i < MAXPLAYERS; i++)
{

View file

@ -40,6 +40,8 @@ typedef enum
// However each instance of RNG being used for
// gameplay should be split up as much as possible.
// Place new ones at the end for demo compatibility.
PR_EXECUTOR, // Linedef executor
PR_ACS, // ACS scripts
@ -72,6 +74,8 @@ typedef enum
PR_MOVINGTARGET, // Randomised moving targets
PR_BOTS, // Bot spawning
PRNUMCLASS
} pr_class_t;

View file

@ -7742,7 +7742,7 @@ static void P_InitGametype(void)
grandprixinfo.wonround = false;
}
}
else if (!modeattacking)
else
{
// We're in a Match Race, use simplistic randomized bots.
K_UpdateMatchRaceBots();