From 1e2e833d80910790e3c90e8cd35f63682464db33 Mon Sep 17 00:00:00 2001 From: JugadorXEI Date: Fri, 22 Aug 2025 01:24:13 +0000 Subject: [PATCH] Bot library for Lua (botvars getter/setter, bot functions) --- src/CMakeLists.txt | 1 + src/deh_tables.c | 37 +++++++++ src/g_game.c | 3 + src/k_bot.cpp | 18 ++++- src/k_bot.h | 4 +- src/k_grandprix.c | 3 +- src/lua_baselib.c | 163 ++++++++++++++++++++++++++++++++++++++- src/lua_botvarslib.c | 177 +++++++++++++++++++++++++++++++++++++++++++ src/lua_hook.h | 3 + src/lua_hooklib.c | 11 +++ src/lua_libs.h | 4 + src/lua_maplib.c | 68 +++++++++++++++++ src/lua_playerlib.c | 4 + src/lua_script.c | 1 + src/p_saveg.c | 2 + src/p_user.c | 6 ++ 16 files changed, 499 insertions(+), 6 deletions(-) create mode 100644 src/lua_botvarslib.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 59c0e81f6..eb975c36e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/deh_tables.c b/src/deh_tables.c index e5ffd288d..0d91f51ad 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5209,7 +5209,44 @@ struct int_const_s const INT_CONST[] = { {"TN_NIGHTCOREABLE",TN_NIGHTCOREABLE}, {"TN_CHANGEPITCH",TN_CHANGEPITCH}, {"TN_LOOPING",TN_LOOPING}, + + // 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}, diff --git a/src/g_game.c b/src/g_game.c index b2f4d8b27..ad2dbdeac 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2144,6 +2144,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) boolean spectator; boolean bot; UINT8 botdifficulty; + botStyle_e style; INT16 rings; INT16 spheres; @@ -2242,6 +2243,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; @@ -2474,6 +2476,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; diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 45220cd99..a32f28766 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -179,6 +179,8 @@ void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e st SetPlayerSkinByNum(newplayernum, skinnum); + LUA_HookPlayer(&players[newplayernum], HOOK(BotJoin)); + for (UINT8 i = 0; i < PWRLV_NUMTYPES; i++) { clientpowerlevels[newplayernum][i] = 0; @@ -422,6 +424,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), + HOOK(PlayerUsesBotMovement)); + if (shouldOverride == 1) + return true; + if (shouldOverride == 2) + return false; + } + + if (player->exiting) return true; @@ -523,11 +537,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; diff --git a/src/k_bot.h b/src/k_bot.h index 8a4cfe893..b32481916 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -87,7 +87,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. @@ -99,7 +99,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); /*-------------------------------------------------- diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 761e3a8d5..a868793a6 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -21,7 +21,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; @@ -799,6 +799,7 @@ void K_RetireBots(void) K_SetNameForBot(i, skins[skinnum]->realname); bot->score = 0; + LUA_HookPlayer(bot, HOOK(BotJoin)); bot->pflags &= ~PF_NOCONTEST; } } diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 2ef58527a..aa84c76d0 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -236,11 +236,15 @@ 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} }; @@ -2736,7 +2740,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"); @@ -4938,6 +4941,151 @@ static int lib_vsRandomPointOnArena(lua_State *L) return 2; } +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)); @@ -5416,6 +5564,19 @@ static luaL_Reg lib[] = { {"VS_PredictAroundArena", lib_vsPredictAroundArena}, {"VS_RandomPointOnArena", lib_vsRandomPointOnArena}, + // 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}, diff --git a/src/lua_botvarslib.c b/src/lua_botvarslib.c new file mode 100644 index 000000000..fbe9074b5 --- /dev/null +++ b/src/lua_botvarslib.c @@ -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; +} diff --git a/src/lua_hook.h b/src/lua_hook.h index 15b938cff..7a5b9476d 100644 --- a/src/lua_hook.h +++ b/src/lua_hook.h @@ -79,6 +79,8 @@ automatically. X (GameQuit),\ X (PlayerCmd),/* building the player's ticcmd struct */\ X (VoteThinker),/* Y_VoteTicker */\ + X (PlayerUsesBotMovement),/* K_PlayerUsesBotMovement */\ + X (BotJoin),\ X (GPRankPoints),/* K_CalculateGPRankPoints */\ #define STRING_HOOK_LIST(X) \ @@ -127,6 +129,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 diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index af629b640..2a3d683de 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -660,6 +660,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; diff --git a/src/lua_libs.h b/src/lua_libs.h index 8613b2638..2615c0c5c 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -107,6 +107,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*" @@ -131,6 +134,7 @@ int LUA_PolyObjLib(lua_State *L); int LUA_BlockmapLib(lua_State *L); int LUA_HudLib(lua_State *L); int LUA_FollowerLib(lua_State *L); +int LUA_BotVarsLib(lua_State *L); int LUA_TerrainLib(lua_State *L); #ifdef __cplusplus diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 89b2e7c4c..dd4598a46 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -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", @@ -393,6 +395,20 @@ static const char *const activator_opt[] = { "sector", "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, §or->botController, META_BOTCONTROLLER); + return 1; case sector_action: // action lua_pushinteger(L, (INT16)sector->action); return 1; @@ -2639,6 +2658,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); @@ -2805,6 +2867,12 @@ int LUA_MapLib(lua_State *L) lua_pushcfunction(L, activator_get); 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, diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index a8046763f..daaa1445d 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -659,6 +659,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")) @@ -1224,6 +1226,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")) diff --git a/src/lua_script.c b/src/lua_script.c index 08ca9a6aa..2bf84e0d7 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -61,6 +61,7 @@ static lua_CFunction liblist[] = { LUA_BlockmapLib, // blockmap stuff LUA_HudLib, // HUD stuff LUA_FollowerLib, // follower_t, followers[] + LUA_BotVarsLib, // botvars_t LUA_TerrainLib, // t_splash_t, t_footstep_t, t_overlay_t, terrain_t NULL }; diff --git a/src/p_saveg.c b/src/p_saveg.c index b697777e5..ec4f07014 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -688,6 +688,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); @@ -1292,6 +1293,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) // botvars_t players[i].bot = READUINT8(save->p); + players[i].botvars.style = 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); diff --git a/src/p_user.c b/src/p_user.c index 88a3a3a71..1bc20ba04 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3310,6 +3310,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]; }