Merge public master

This commit is contained in:
Eidolon 2025-08-21 20:35:52 -05:00
commit 55388c17c2
16 changed files with 519 additions and 26 deletions

View file

@ -121,6 +121,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
lua_terrainlib.c
lua_polyobjlib.c
lua_blockmaplib.c
lua_botvarslib.c
lua_hudlib.c
lua_hudlib_drawlist.c
lua_followerlib.c

View file

@ -5339,6 +5339,43 @@ struct int_const_s const INT_CONST[] = {
{"PICKUP_EGGBOX", PICKUP_EGGBOX},
{"PICKUP_PAPERITEM", PICKUP_PAPERITEM},
// k_bot.h constants
{"MAXBOTDIFFICULTY",MAXBOTDIFFICULTY},
{"DIFFICULTBOT",DIFFICULTBOT},
{"BOTTURNCONFIRM",BOTTURNCONFIRM},
{"BOTSPINDASHCONFIRM",BOTSPINDASHCONFIRM},
{"BOTRESPAWNCONFIRM",BOTRESPAWNCONFIRM},
{"BOT_ITEM_DECISION_TIME",BOT_ITEM_DECISION_TIME},
// botStyle_e
{"BOT_STYLE_NORMAL",BOT_STYLE_NORMAL},
{"BOT_STYLE_STAY",BOT_STYLE_STAY},
{"BOT_STYLE__MAX",BOT_STYLE__MAX},
// botItemPriority_e
{"BOT_ITEM_PR__FALLBACK",BOT_ITEM_PR__FALLBACK},
{"BOT_ITEM_PR_NEUTRAL",BOT_ITEM_PR_NEUTRAL},
{"BOT_ITEM_PR_FRONTRUNNER",BOT_ITEM_PR_FRONTRUNNER},
{"BOT_ITEM_PR_SPEED",BOT_ITEM_PR_SPEED},
{"BOT_ITEM_PR__OVERRIDES",BOT_ITEM_PR__OVERRIDES},
{"BOT_ITEM_PR_RINGDEBT",BOT_ITEM_PR_RINGDEBT},
{"BOT_ITEM_PR_POWER",BOT_ITEM_PR_POWER},
{"BOT_ITEM_PR_SPB",BOT_ITEM_PR_SPB},
{"BOT_ITEM_PR__MAX",BOT_ITEM_PR__MAX},
// textmapbotcontroller_t
{"TMBOT_NORUBBERBAND",TMBOT_NORUBBERBAND},
{"TMBOT_NOCONTROL",TMBOT_NOCONTROL},
{"TMBOT_FORCEDIR",TMBOT_FORCEDIR},
{"TMBOT_FASTFALL",TMBOT_FASTFALL},
// textmapbottrick_t
{"TMBOTTR_NONE",TMBOTTR_NONE},
{"TMBOTTR_LEFT",TMBOTTR_LEFT},
{"TMBOTTR_RIGHT",TMBOTTR_RIGHT},
{"TMBOTTR_UP",TMBOTTR_UP},
{"TMBOTTR_DOWN",TMBOTTR_DOWN},
// t_overlay_action_t
{"TOV_UNDEFINED",TOV_UNDEFINED},
{"TOV_STILL",TOV_STILL},

View file

@ -2275,6 +2275,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
boolean spectator;
boolean bot;
UINT8 botdifficulty;
botStyle_e style;
INT16 rings;
INT16 spheres;
@ -2392,6 +2393,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
followitem = players[player].followitem;
bot = players[player].bot;
style = players[player].botvars.style;
botdifficulty = players[player].botvars.difficulty;
botdiffincrease = players[player].botvars.diffincrease;
@ -2655,6 +2657,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
}
p->bot = bot;
p->botvars.style = style;
p->botvars.difficulty = botdifficulty;
p->rings = rings;
p->spheres = spheres;

View file

@ -177,6 +177,8 @@ void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e st
K_SetNameForBot(newplayernum, realname);
LUA_HookPlayer(&players[newplayernum], HOOK(BotJoin));
for (UINT8 i = 0; i < PWRLV_NUMTYPES; i++)
{
clientpowerlevels[newplayernum][i] = 0;
@ -428,6 +430,18 @@ boolean K_PlayerUsesBotMovement(const player_t *player)
if (K_PodiumSequence() == true)
return true;
// Lua can't override the podium sequence result, but it can
// override the following results:
{
UINT8 shouldOverride = LUA_HookPlayerForceResults(const_cast<player_t*>(player),
HOOK(PlayerUsesBotMovement));
if (shouldOverride == 1)
return true;
if (shouldOverride == 2)
return false;
}
if (player->exiting)
return true;
@ -534,11 +548,11 @@ static fixed_t K_BotSpeedScaled(const player_t *player, fixed_t speed)
}
/*--------------------------------------------------
const botcontroller_t *K_GetBotController(const mobj_t *mobj)
botcontroller_t *K_GetBotController(const mobj_t *mobj)
See header file for description.
--------------------------------------------------*/
const botcontroller_t *K_GetBotController(const mobj_t *mobj)
botcontroller_t *K_GetBotController(const mobj_t *mobj)
{
botcontroller_t *ret = nullptr;

View file

@ -93,7 +93,7 @@ boolean K_BotCanTakeCut(const player_t *player);
/*--------------------------------------------------
const botcontroller_t *K_GetBotController(const mobj_t *mobj);
botcontroller_t *K_GetBotController(const mobj_t *mobj);
Retrieves the current bot controller values from
the player's current sector.
@ -105,7 +105,7 @@ boolean K_BotCanTakeCut(const player_t *player);
Pointer to the sector's bot controller struct.
--------------------------------------------------*/
const botcontroller_t *K_GetBotController(const mobj_t *mobj);
botcontroller_t *K_GetBotController(const mobj_t *mobj);
/*--------------------------------------------------

View file

@ -25,7 +25,7 @@
#include "m_random.h"
#include "p_local.h"
#include "r_things.h"
#include "lua_hook.h" // LUA_HookGPRankPoints
#include "lua_hook.h"
struct grandprixinfo grandprixinfo;
@ -1032,6 +1032,7 @@ void K_RetireBots(void)
G_UpdatePlayerPreferences(bot);
bot->score = 0;
LUA_HookPlayer(bot, HOOK(BotJoin));
bot->pflags &= ~PF_NOCONTEST;
}
}

View file

@ -241,10 +241,14 @@ static const struct {
{META_SONICLOOPVARS, "sonicloopvars_t"},
{META_SONICLOOPCAMVARS, "sonicloopcamvars_t"},
{META_BOTVARS, "botvars_t"},
{META_BOTCONTROLLER, "botcontroller_t"},
{META_SPLASH, "t_splash_t"},
{META_FOOTSTEP, "t_footstep_t"},
{META_OVERLAY, "t_overlay_t"},
{META_TERRAIN, "terrain_t"},
{NULL, NULL}
};
@ -2740,7 +2744,6 @@ static int lib_rSetPlayerSkin(lua_State *L)
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INT32 i = -1, j = -1;
NOHUD
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
@ -5422,6 +5425,151 @@ static int lib_kCanChangeRules(lua_State *L)
return 1;
}
static int lib_kPlayerUsesBotMovement(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushboolean(L, K_PlayerUsesBotMovement(player));
return 1;
}
static int lib_kBotCanTakeCut(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushboolean(L, K_BotCanTakeCut(player));
return 1;
}
static int lib_kGetBotController(lua_State *L)
{
mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ));
INLEVEL
if (!mobj)
return LUA_ErrInvalid(L, "mobj_t");
botcontroller_t *botController = K_GetBotController(mobj);
if (botController != NULL)
LUA_PushUserdata(L, botController, META_BOTCONTROLLER);
else
lua_pushnil(L);
return 1;
}
static int lib_kBotMapModifier(lua_State *L)
{
INLEVEL
lua_pushfixed(L, K_BotMapModifier());
return 1;
}
static int lib_kBotRubberband(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushfixed(L, K_BotRubberband(player));
return 1;
}
static int lib_kUpdateRubberband(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
lua_pushfixed(L, K_UpdateRubberband(player));
return 1;
}
static int lib_kDistanceOfLineFromPoint(lua_State *L)
{
fixed_t v1x = luaL_checkfixed(L, 1);
fixed_t v1y = luaL_checkfixed(L, 2);
fixed_t v2x = luaL_checkfixed(L, 3);
fixed_t v2y = luaL_checkfixed(L, 4);
fixed_t cx = luaL_checkfixed(L, 5);
fixed_t cy = luaL_checkfixed(L, 6);
lua_pushfixed(L, K_DistanceOfLineFromPoint(v1x, v1y, v2x, v2y, cx, cy));
return 1;
}
static int lib_kAddBot(lua_State *L)
{
INT32 skinid = -1;
UINT8 difficulty = luaL_checkinteger(L, 2);
botStyle_e style = luaL_checkinteger(L, 3);
UINT8 newplayernum = 0;
// Copypaste of libd_getSprite2Patch, but fails loudly on each fail state instead.
// get skin first!
if (lua_isnumber(L, 1)) // find skin by number
{
skinid = lua_tonumber(L, 1);
if (skinid < 0 || skinid >= MAXSKINS)
return luaL_error(L, "skin number %d out of range (0 - %d)", skinid, MAXSKINS-1);
if (skinid >= (demo.playback ? demo.numskins : numskins))
return luaL_error(L, "skin number %d out of range in demo (0 - %d)",
skinid, (demo.playback ? demo.numskins : numskins));
}
else // find skin by name
{
const char *name = luaL_checkstring(L, 1);
skinid = R_SkinAvailable(name);
if (skinid == -1)
return luaL_error(L, "could not find skin %s by name", name);
}
INLEVEL
boolean success = K_AddBot(skinid, difficulty, style, &newplayernum);
lua_pushboolean(L, success);
if (success)
LUA_PushUserdata(L, &players[newplayernum - 1], META_PLAYER);
else
lua_pushnil(L);
return 2;
}
static int lib_kSetNameForBot(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
const char *realname = luaL_checkstring(L, 2);
if (!player)
return LUA_ErrInvalid(L, "player_t");
if (!player->bot)
return luaL_error(L, "You may only change bot names.");
K_SetNameForBot(player-players, realname);
return 0;
}
static int lib_kRemoveBot(lua_State *L)
{
player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
INLEVEL
if (!player)
return LUA_ErrInvalid(L, "player_t");
if (!player->bot)
return luaL_error(L, "You may only remove bots.");
CL_RemovePlayer(player-players, KR_LEAVE);
return 0;
}
static int lib_getTimeMicros(lua_State *L)
{
lua_pushinteger(L, I_GetPreciseTime() / (I_GetPrecisePrecision() / 1000000));
@ -5936,6 +6084,19 @@ static luaL_Reg lib[] = {
// k_grandprix
{"K_CanChangeRules", lib_kCanChangeRules},
// k_bot
{"K_PlayerUsesBotMovement", lib_kPlayerUsesBotMovement},
{"K_BotCanTakeCut", lib_kBotCanTakeCut},
{"K_GetBotController", lib_kGetBotController},
{"K_BotMapModifier", lib_kBotMapModifier},
{"K_BotRubberband", lib_kBotRubberband},
{"K_UpdateRubberband", lib_kUpdateRubberband},
{"K_DistanceOfLineFromPoint", lib_kDistanceOfLineFromPoint},
{"K_AddBot", lib_kAddBot},
{"K_SetNameForBot", lib_kSetNameForBot},
// Lua-only function to allow safely removing bots.
{"K_RemoveBot", lib_kRemoveBot},
// hu_stuff technically?
{"HU_DoTitlecardCEcho", lib_startTitlecardCecho},

177
src/lua_botvarslib.c Normal file
View file

@ -0,0 +1,177 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2024 by Kart Krew.
// Copyright (C) 2020 by Sonic Team Junior.
// Copyright (C) 2016 by John "JTE" Muniz.
//
// 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 lua_botvarslib.c
/// \brief player botvars structure library for Lua scripting
#include "doomdef.h"
#include "fastcmp.h"
#include "lua_script.h"
#include "lua_libs.h"
enum botvars {
botvars_valid = 0,
botvars_style,
botvars_difficulty,
botvars_diffincrease,
botvars_rival,
botvars_rubberband,
botvars_itemdelay,
botvars_itemconfirm,
botvars_turnconfirm,
botvars_spindashconfirm,
botvars_respawnconfirm,
botvars_roulettepriority,
botvars_roulettetimeout,
};
static const char *const botvars_opt[] = {
"valid",
"style",
"difficulty",
"diffincrease",
"rival",
"rubberband",
"itemdelay",
"itemconfirm",
"turnconfirm",
"spindashconfirm",
"respawnconfirm",
"roulettepriority",
"roulettetimeout",
NULL
};
#define UNIMPLEMENTED luaL_error(L, LUA_QL("botvars_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", follower_opt[field])
static int botvars_get(lua_State *L)
{
botvars_t *botvars = *((botvars_t **)luaL_checkudata(L, 1, META_BOTVARS));
enum botvars field = luaL_checkoption(L, 2, NULL, botvars_opt);
// This is a property that always exists in a player.
I_Assert(botvars != NULL);
switch (field)
{
case botvars_valid:
lua_pushboolean(L, botvars != NULL);
break;
case botvars_style:
lua_pushinteger(L, botvars->style);
break;
case botvars_difficulty:
lua_pushinteger(L, botvars->difficulty);
break;
case botvars_diffincrease:
lua_pushinteger(L, botvars->diffincrease);
break;
case botvars_rival:
lua_pushboolean(L, botvars->rival);
break;
case botvars_rubberband:
lua_pushfixed(L, botvars->rubberband);
break;
case botvars_itemdelay:
lua_pushinteger(L, botvars->itemdelay);
break;
case botvars_itemconfirm:
lua_pushinteger(L, botvars->itemconfirm);
break;
case botvars_turnconfirm:
lua_pushinteger(L, botvars->turnconfirm);
break;
case botvars_spindashconfirm:
lua_pushinteger(L, botvars->spindashconfirm);
break;
case botvars_respawnconfirm:
lua_pushinteger(L, botvars->respawnconfirm);
break;
case botvars_roulettepriority:
lua_pushinteger(L, botvars->roulettePriority);
break;
case botvars_roulettetimeout:
lua_pushinteger(L, botvars->rouletteTimeout);
break;
}
return 1;
}
#define NOSET luaL_error(L, LUA_QL("itemroulette_t") " field " LUA_QS " should not be set directly.", botvars_opt[field])
static int botvars_set(lua_State *L)
{
botvars_t *botvars = *((botvars_t **)luaL_checkudata(L, 1, META_BOTVARS));
enum botvars field = luaL_checkoption(L, 2, botvars_opt[0], botvars_opt);
// This is a property that always exists in a player.
I_Assert(botvars != NULL);
INLEVEL
switch(field)
{
case botvars_valid:
return NOSET;
case botvars_style:
botvars->style = luaL_checkinteger(L, 3);
break;
case botvars_difficulty:
botvars->difficulty = luaL_checkinteger(L, 3);
break;
case botvars_diffincrease:
botvars->diffincrease = luaL_checkinteger(L, 3);
break;
case botvars_rival:
botvars->rival = luaL_checkboolean(L, 3);
break;
case botvars_rubberband:
botvars->rubberband = luaL_checkfixed(L, 3);
break;
case botvars_itemdelay:
botvars->itemdelay = luaL_checkinteger(L, 3);
break;
case botvars_itemconfirm:
botvars->itemconfirm = luaL_checkinteger(L, 3);
break;
case botvars_turnconfirm:
botvars->turnconfirm = luaL_checkinteger(L, 3);
break;
case botvars_spindashconfirm:
botvars->spindashconfirm = luaL_checkinteger(L, 3);
break;
case botvars_respawnconfirm:
botvars->respawnconfirm = luaL_checkinteger(L, 3);
break;
case botvars_roulettepriority:
botvars->roulettePriority = luaL_checkinteger(L, 3);
break;
case botvars_roulettetimeout:
botvars->rouletteTimeout = luaL_checkinteger(L, 3);
break;
}
return 0;
}
#undef NOSET
int LUA_BotVarsLib(lua_State *L)
{
luaL_newmetatable(L, META_BOTVARS);
lua_pushcfunction(L, botvars_get);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, botvars_set);
lua_setfield(L, -2, "__newindex");
lua_pop(L,1);
return 0;
}

View file

@ -81,6 +81,8 @@ automatically.
X (VoteThinker),/* Y_VoteTicker */\
X (PreFillItemRoulette),/* K_FillItemRouletteData, before attempted reel build */\
X (FillItemRoulette),/* K_FillItemRouletteData, after built reel is in place */\
X (PlayerUsesBotMovement),/* K_PlayerUsesBotMovement */\
X (BotJoin),\
X (GPRankPoints),/* K_CalculateGPRankPoints */\
#define STRING_HOOK_LIST(X) \
@ -129,6 +131,7 @@ int LUA_Hook2Mobj(mobj_t *, mobj_t *, int hook);
void LUA_HookInt(INT32 integer, int hook);
void LUA_HookBool(boolean value, int hook);
int LUA_HookPlayer(player_t *, int hook);
int LUA_HookPlayerForceResults(player_t *, int hook);
int LUA_HookTiccmd(player_t *, ticcmd_t *, int hook);
int LUA_HookKey(event_t *event, int hook); // Hooks for key events

View file

@ -661,6 +661,17 @@ int LUA_HookPlayer(player_t *player, int hook_type)
return hook.status;
}
int LUA_HookPlayerForceResults(player_t *player, int hook_type)
{
Hook_State hook;
if (prepare_hook(&hook, 0, hook_type))
{
LUA_PushUserdata(gL, player, META_PLAYER);
call_hooks(&hook, 1, res_force);
}
return hook.status;
}
int LUA_HookTiccmd(player_t *player, ticcmd_t *cmd, int hook_type)
{
Hook_State hook;

View file

@ -109,6 +109,9 @@ extern lua_State *gL;
#define META_SONICLOOPVARS "SONICLOOPVARS_T*"
#define META_SONICLOOPCAMVARS "SONICLOOPCAMVARS_T*"
#define META_BOTVARS "BOTVARS_T*"
#define META_BOTCONTROLLER "BOTCONTROLLER_T*"
#define META_SPLASH "T_SPLASH_T*"
#define META_FOOTSTEP "T_FOOTSTEP_T*"
#define META_OVERLAY "T_OVERLAY_T*"
@ -134,6 +137,7 @@ int LUA_BlockmapLib(lua_State *L);
int LUA_HudLib(lua_State *L);
int LUA_FollowerLib(lua_State *L);
int LUA_ItemRouletteLib(lua_State *L);
int LUA_BotVarsLib(lua_State *L);
int LUA_TerrainLib(lua_State *L);
#ifdef __cplusplus

View file

@ -57,6 +57,7 @@ enum sector_e {
sector_triggerer,
sector_friction,
sector_gravity,
sector_botcontroller,
sector_action,
sector_args,
sector_stringargs,
@ -91,6 +92,7 @@ static const char *const sector_opt[] = {
"triggerer",
"friction",
"gravity",
"botcontroller",
"action",
"args"
"stringargs",
@ -394,6 +396,20 @@ static const char *const activator_opt[] = {
"po",
NULL};
enum botcontroller_e {
botcontroller_valid = 0,
botcontroller_trick,
botcontroller_flags,
botcontroller_forceangle,
};
static const char *const botcontroller_opt[] = {
"valid",
"trick",
"flags",
"forceangle",
NULL};
static const char *const array_opt[] ={"iterate",NULL};
static const char *const valid_opt[] ={"valid",NULL};
@ -751,6 +767,9 @@ static int sector_get(lua_State *L)
case sector_gravity: // gravity
lua_pushfixed(L, sector->gravity);
return 1;
case sector_botcontroller: // botController
LUA_PushUserdata(L, &sector->botController, META_BOTCONTROLLER);
return 1;
case sector_action: // action
lua_pushinteger(L, (INT16)sector->action);
return 1;
@ -2641,6 +2660,49 @@ static int activator_get(lua_State *L)
return 0;
}
/////////////////////
// botcontroller_t //
/////////////////////
static int botcontroller_get(lua_State *L)
{
botcontroller_t *botcontroller = *((botcontroller_t **)luaL_checkudata(L, 1, META_BOTCONTROLLER));
enum botcontroller_e field = luaL_checkoption(L, 2, botcontroller_opt[0], botcontroller_opt);
if (!botcontroller)
{
if (field == botcontroller_valid) {
lua_pushboolean(L, false);
return 1;
}
return luaL_error(L, "accessed botcontroller_t doesn't exist anymore.");
}
switch (field)
{
case botcontroller_valid:
lua_pushboolean(L, true);
return 1;
case botcontroller_trick:
lua_pushinteger(L, botcontroller->trick);
return 1;
case botcontroller_flags:
lua_pushinteger(L, botcontroller->flags);
return 1;
case botcontroller_forceangle:
lua_pushangle(L, botcontroller->forceAngle);
return 1;
default:
break;
}
return 0;
}
int LUA_MapLib(lua_State *L)
{
luaL_newmetatable(L, META_SECTORLINES);
@ -2808,6 +2870,12 @@ int LUA_MapLib(lua_State *L)
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
luaL_newmetatable(L, META_BOTCONTROLLER);
lua_pushcfunction(L, botcontroller_get);
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
LUA_PushTaggableObjectArray(L, "sectors",
lib_iterateSectors,
lib_getSector,

View file

@ -737,6 +737,8 @@ static int player_get(lua_State *L)
lua_pushboolean(L, plr->spectator);
else if (fastcmp(field,"bot"))
lua_pushboolean(L, plr->bot);
else if (fastcmp(field,"botvars"))
LUA_PushUserdata(L, &plr->botvars, META_BOTVARS);
else if (fastcmp(field,"jointime"))
lua_pushinteger(L, plr->jointime);
else if (fastcmp(field,"spectatorreentry"))
@ -1381,6 +1383,8 @@ static int player_set(lua_State *L)
plr->spectator = lua_toboolean(L, 3);
else if (fastcmp(field,"bot"))
return NOSET;
else if (fastcmp(field,"botvars"))
return NOSET;
else if (fastcmp(field,"jointime"))
return NOSET;
else if (fastcmp(field,"spectatorreentry"))

View file

@ -62,6 +62,7 @@ static lua_CFunction liblist[] = {
LUA_HudLib, // HUD stuff
LUA_FollowerLib, // follower_t, followers[]
LUA_ItemRouletteLib, // itemroulette_t
LUA_BotVarsLib, // botvars_t
LUA_TerrainLib, // t_splash_t, t_footstep_t, t_overlay_t, terrain_t
NULL
};

View file

@ -768,6 +768,7 @@ static void P_NetArchivePlayers(savebuffer_t *save)
// botvars_t
WRITEUINT8(save->p, players[i].bot);
WRITEUINT8(save->p, players[i].botvars.style);
WRITEUINT8(save->p, players[i].botvars.difficulty);
WRITEUINT8(save->p, players[i].botvars.diffincrease);
WRITEUINT8(save->p, players[i].botvars.rival);
@ -1437,6 +1438,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
// botvars_t
players[i].bot = READUINT8(save->p);
players[i].botvars.style = static_cast<botStyle_e>(READUINT8(save->p));
players[i].botvars.difficulty = READUINT8(save->p);
players[i].botvars.diffincrease = READUINT8(save->p);
players[i].botvars.rival = (boolean)READUINT8(save->p);

View file

@ -3387,6 +3387,12 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
}
else
{
// Players-turned-bots outside of end of race contexts (Lua)
// don't update their local camera angle, so it should be updated
// somewhere - I choose here because it makes the most sense.
if (K_PlayerUsesBotMovement(player) && !player->bot)
P_ForceLocalAngle(player, mo->angle);
focusangle = localangle[num];
focusaiming = localaiming[num];
}