From 2d8c10a694cf124f5c44d9a6c482b7eaa19d8abb Mon Sep 17 00:00:00 2001 From: luigi budd <4997-luigi-budd@users.noreply.git.do.srb2.org> Date: Sat, 10 May 2025 21:33:06 +0000 Subject: [PATCH 1/8] Interpolate hitlag jitters --- src/hardware/hw_main.c | 16 ++++++++++++---- src/hardware/hw_md2.c | 5 ++++- src/k_hitlag.h | 1 + src/k_hud.cpp | 6 +++++- src/r_things.cpp | 11 ++++++++--- 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index a9a90fbed..de43b9958 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -3115,11 +3115,15 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) R_InterpolateMobjState(thing, FRACUNIT, &interp); } - // hitlag vibrating (todo: interp somehow?) + // hitlag vibrating if (thing->hitlag > 0 && (thing->eflags & MFE_DAMAGEHITLAG)) { - fixed_t mul = thing->hitlag * HITLAGJITTERS; + fixed_t jitters = HITLAGJITTERS; + if (R_UsingFrameInterpolation() && !paused) + jitters += (rendertimefrac / HITLAGDIV); + fixed_t mul = thing->hitlag * jitters; + // perhaps there could be a way to interp this too? if (leveltime & 1) { mul = -mul; @@ -4685,10 +4689,14 @@ static void HWR_ProjectSprite(mobj_t *thing) dispoffset = thing->dispoffset; - // hitlag vibrating (todo: interp somehow?) + // hitlag vibrating if (thing->hitlag > 0 && (thing->eflags & MFE_DAMAGEHITLAG)) { - fixed_t mul = thing->hitlag * HITLAGJITTERS; + fixed_t jitters = HITLAGJITTERS; + if (R_UsingFrameInterpolation() && !paused) + jitters += (rendertimefrac / HITLAGDIV); + + fixed_t mul = thing->hitlag * jitters; if (leveltime & 1) { diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 7a49edcb8..07dc450ef 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1402,7 +1402,10 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) // hitlag vibrating if (spr->mobj->hitlag > 0 && (spr->mobj->eflags & MFE_DAMAGEHITLAG)) { - fixed_t mul = spr->mobj->hitlag * HITLAGJITTERS; + fixed_t jitters = HITLAGJITTERS; + if (R_UsingFrameInterpolation() && !paused) + jitters += (rendertimefrac / HITLAGDIV); + fixed_t mul = spr->mobj->hitlag * jitters; if (leveltime & 1) { diff --git a/src/k_hitlag.h b/src/k_hitlag.h index c5c0944d5..f999c2181 100644 --- a/src/k_hitlag.h +++ b/src/k_hitlag.h @@ -22,6 +22,7 @@ extern "C" { #define MAXHITLAGTICS (30) #define HITLAGJITTERS (FRACUNIT / 20) +#define HITLAGDIV (20) // define this so we arent using a magic number for interp #define NUM_HITLAG_STATES (9) #define NUM_HITLAG_SOUNDS (4) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 4e6adebaa..7eaa48447 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5317,7 +5317,11 @@ static void K_drawKartFirstPerson(void) // hitlag vibrating if (stplyr->mo->hitlag > 0 && (stplyr->mo->eflags & MFE_DAMAGEHITLAG)) { - fixed_t mul = stplyr->mo->hitlag * HITLAGJITTERS; + fixed_t jitters = HITLAGJITTERS; + if (R_UsingFrameInterpolation() && !paused) + jitters += (rendertimefrac / HITLAGDIV); + + fixed_t mul = stplyr->mo->hitlag * jitters; if (r_splitscreen && mul > FRACUNIT) mul = FRACUNIT; diff --git a/src/r_things.cpp b/src/r_things.cpp index ac23b679c..e48bb9b15 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1794,7 +1794,7 @@ static void R_ProjectSprite(mobj_t *thing) this_scale = interp.scale; - // hitlag vibrating (todo: interp somehow?) + // hitlag vibrating if (thing->hitlag > 0 && (thing->eflags & MFE_DAMAGEHITLAG)) { fixed_t mul = thing->hitlag * HITLAGJITTERS; @@ -2172,10 +2172,15 @@ static void R_ProjectSprite(mobj_t *thing) R_InterpolateMobjState(thing, FRACUNIT, &tracer_interp); } - // hitlag vibrating (todo: interp somehow?) + // hitlag vibrating if (thing->hitlag > 0 && (thing->eflags & MFE_DAMAGEHITLAG)) { - fixed_t mul = thing->hitlag * (FRACUNIT / 10); + // previous code multiplied by (FRACUNIT / 10) instead of HITLAGJITTERS, um wadaflip + fixed_t jitters = HITLAGJITTERS; + if (R_UsingFrameInterpolation() && !paused) + jitters += (rendertimefrac / HITLAGDIV); + + fixed_t mul = thing->hitlag * jitters; if (leveltime & 1) { From 84110bfb2f5aff2ef97482ea8edac80b1c17b39d Mon Sep 17 00:00:00 2001 From: luigi budd <4997-luigi-budd@users.noreply.git.do.srb2.org> Date: Sat, 10 May 2025 21:38:57 +0000 Subject: [PATCH 2/8] use HITLAGDIV in HITLAGJITTERS :grin: --- src/k_hitlag.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_hitlag.h b/src/k_hitlag.h index f999c2181..6b072c8f3 100644 --- a/src/k_hitlag.h +++ b/src/k_hitlag.h @@ -21,8 +21,8 @@ extern "C" { #endif #define MAXHITLAGTICS (30) -#define HITLAGJITTERS (FRACUNIT / 20) -#define HITLAGDIV (20) // define this so we arent using a magic number for interp +#define HITLAGDIV (20) // define this so we arent using a magic number +#define HITLAGJITTERS (FRACUNIT / HITLAGDIV) #define NUM_HITLAG_STATES (9) #define NUM_HITLAG_SOUNDS (4) From 227c3fa3adca793e1542a3d7b3b94d8dd82ceeb9 Mon Sep 17 00:00:00 2001 From: JugadorXEI Date: Sat, 23 Aug 2025 00:14:38 +0000 Subject: [PATCH 3/8] Respawn/Waypoints library for Lua --- src/CMakeLists.txt | 2 + src/deh_tables.c | 12 ++ src/k_respawn.c | 13 +- src/k_respawn.h | 13 ++ src/k_waypoint.cpp | 27 ++- src/k_waypoint.h | 12 ++ src/lua_baselib.c | 417 +++++++++++++++++++++++++++++++++++++++ src/lua_libs.h | 3 + src/lua_playerlib.c | 99 ++-------- src/lua_respawnvarslib.c | 221 +++++++++++++++++++++ src/lua_script.c | 6 + src/lua_waypointslib.c | 293 +++++++++++++++++++++++++++ 12 files changed, 1020 insertions(+), 98 deletions(-) create mode 100644 src/lua_respawnvarslib.c create mode 100644 src/lua_waypointslib.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eb975c36e..1cca0ab78 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -125,6 +125,8 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 lua_hudlib.c lua_hudlib_drawlist.c lua_followerlib.c + lua_respawnvarslib.c + lua_waypointslib.c lua_profile.cpp k_kart.c k_respawn.c diff --git a/src/deh_tables.c b/src/deh_tables.c index c22fe33e1..91cfccaa4 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -26,6 +26,8 @@ #include "k_boss.h" // spottype_t (for lua) #include "k_follower.h" // followermode_t (for lua) #include "music.h" // tune flags (for lua) +#include "k_respawn.h" // respawn values (for lua) +#include "k_waypoint.h" // waypoint values (for lua) #include "deh_tables.h" @@ -5214,6 +5216,16 @@ struct int_const_s const INT_CONST[] = { {"TN_NIGHTCOREABLE",TN_NIGHTCOREABLE}, {"TN_CHANGEPITCH",TN_CHANGEPITCH}, {"TN_LOOPING",TN_LOOPING}, + + // k_respawn.h values + {"RESPAWN_DIST",RESPAWN_DIST}, + {"RESPAWN_TIME",RESPAWN_TIME}, + {"RESPAWNST_NONE",RESPAWNST_NONE}, + {"RESPAWNST_MOVE",RESPAWNST_MOVE}, + {"RESPAWNST_DROP",RESPAWNST_DROP}, + + // k_waypoint.h values + {"DEFAULT_WAYPOINT_RADIUS",DEFAULT_WAYPOINT_RADIUS}, // k_bot.h constants {"MAXBOTDIFFICULTY",MAXBOTDIFFICULTY}, diff --git a/src/k_respawn.c b/src/k_respawn.c index 127dc7a3c..da3e9a0c5 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -61,18 +61,11 @@ static void K_FudgeRespawn(player_t *player, const waypoint_t *const waypoint) } /*-------------------------------------------------- - static void K_RespawnAtWaypoint(player_t *player, waypoint_t *waypoint) + void K_RespawnAtWaypoint(player_t *player, waypoint_t *waypoint) - Updates a player's respawn variables to go to the provided waypoint. - - Input Arguments:- - player - Player to preform for. - waypoint - Waypoint to respawn to. - - Return:- - None + See header file for description. --------------------------------------------------*/ -static void K_RespawnAtWaypoint(player_t *player, waypoint_t *waypoint) +void K_RespawnAtWaypoint(player_t *player, waypoint_t *waypoint) { if (player == NULL || player->mo == NULL || P_MobjWasRemoved(player->mo)) { diff --git a/src/k_respawn.h b/src/k_respawn.h index fdf4f48e5..375fdde74 100644 --- a/src/k_respawn.h +++ b/src/k_respawn.h @@ -42,6 +42,19 @@ extern "C" { fixed_t K_RespawnOffset(player_t *player, boolean flip); +/*-------------------------------------------------- + void K_RespawnAtWaypoint(player_t *player, waypoint_t *waypoint) + + Updates a player's respawn variables to go to the provided waypoint. + + Input Arguments:- + player - Player to preform for. + waypoint - Waypoint to respawn to. + + Return:- + None +--------------------------------------------------*/ +void K_RespawnAtWaypoint(player_t *player, waypoint_t *waypoint); /*-------------------------------------------------- void K_DoFault(player_t *player); diff --git a/src/k_waypoint.cpp b/src/k_waypoint.cpp index c7e6a8f35..3dd4ea17b 100644 --- a/src/k_waypoint.cpp +++ b/src/k_waypoint.cpp @@ -149,6 +149,27 @@ boolean K_GetWaypointIsEnabled(waypoint_t *waypoint) return waypointisenabled; } +/*-------------------------------------------------- + boolean K_SetWaypointIsEnabled(waypoint_t *waypoint, boolean enabled) + + See header file for description. +--------------------------------------------------*/ +void K_SetWaypointIsEnabled(waypoint_t *waypoint, boolean enabled) +{ + if (waypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_SetWaypointIsEnabled.\n"); + } + else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_SetWaypointIsEnabled.\n"); + } + else + { + waypoint->mobj->extravalue1 = enabled ? 1 : 0; + } +} + /*-------------------------------------------------- boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint) @@ -160,11 +181,11 @@ boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint) if (waypoint == NULL) { - CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointIsEnabled.\n"); + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointIsSpawnpoint.\n"); } else if ((waypoint->mobj == NULL) || (P_MobjWasRemoved(waypoint->mobj) == true)) { - CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointIsEnabled.\n"); + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint mobj in K_GetWaypointIsSpawnpoint.\n"); } else { @@ -514,7 +535,7 @@ size_t K_GetWaypointHeapIndex(waypoint_t *waypoint) if (waypoint == NULL) { - CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointID.\n"); + CONS_Debug(DBG_GAMELOGIC, "NULL waypoint in K_GetWaypointHeapIndex.\n"); } else { diff --git a/src/k_waypoint.h b/src/k_waypoint.h index 41ae1cdf8..e2542939e 100644 --- a/src/k_waypoint.h +++ b/src/k_waypoint.h @@ -115,6 +115,18 @@ boolean K_GetWaypointIsShortcut(waypoint_t *waypoint); boolean K_GetWaypointIsEnabled(waypoint_t *waypoint); +/*-------------------------------------------------- + boolean K_SetWaypointIsEnabled(waypoint_t *waypoint, boolean enabled) + + Sets whether the waypoint is enabled or not. + + Input Arguments:- + waypoint - The waypoint to set its enabled status to. + enabled - Boolean that sets the waypoint's enabled status. +--------------------------------------------------*/ + +void K_SetWaypointIsEnabled(waypoint_t *waypoint, boolean enabled); + /*-------------------------------------------------- boolean K_GetWaypointIsSpawnpoint(waypoint_t *waypoint) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 77a6461ee..47db97843 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -33,6 +33,8 @@ #include "k_color.h" #include "k_endcam.h" #include "k_hud.h" +#include "k_waypoint.h" +#include "k_respawn.h" #include "k_specialstage.h" #include "d_netcmd.h" // IsPlayerAdmin #include "k_menu.h" // Player Setup menu color stuff @@ -5480,6 +5482,392 @@ static int lib_kRemoveBot(lua_State *L) return 0; } +static int lib_kRespawnOffset(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + boolean flip = lua_optboolean(L, 2); + INLEVEL + if (!player) + return LUA_ErrInvalid(L, "player_t"); + + lua_pushfixed(L, K_RespawnOffset(player, flip)); + return 1; +} + +static int lib_kRespawnAtWaypoint(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + waypoint_t *wp = *((waypoint_t **)luaL_checkudata(L, 2, META_WAYPOINT)); + + INLEVEL + if (!player) + return LUA_ErrInvalid(L, "player_t"); + if (!wp) + return LUA_ErrInvalid(L, "waypoint_t"); + + K_RespawnAtWaypoint(player, wp); + return 0; +} + +static int lib_kDoFault(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + + INLEVEL + if (!player) + return LUA_ErrInvalid(L, "player_t"); + + K_DoFault(player); + return 0; +} + +static int lib_kDoIngameRespawn(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + + INLEVEL + if (!player) + return LUA_ErrInvalid(L, "player_t"); + + K_DoIngameRespawn(player); + return 0; +} + +static int lib_kNextRespawnWaypointIndex(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + + INLEVEL + if (!waypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + lua_pushinteger(L, K_NextRespawnWaypointIndex(waypoint)); + return 1; +} + +static int lib_kGetFinishLineWaypoint(lua_State *L) +{ + INLEVEL + LUA_PushUserdata(L, K_GetFinishLineWaypoint(), META_WAYPOINT); + return 1; +} + +static int lib_kGetStartingWaypoint(lua_State *L) +{ + INLEVEL + LUA_PushUserdata(L, K_GetStartingWaypoint(), META_WAYPOINT); + return 1; +} + +static int lib_kGetWaypointIsFinishline(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + INLEVEL + + if (!waypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + lua_pushboolean(L, K_GetWaypointIsFinishline(waypoint)); + return 1; +} + +static int lib_kGetWaypointIsShortcut(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + INLEVEL + + if (!waypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + lua_pushboolean(L, K_GetWaypointIsShortcut(waypoint)); + return 1; +} + +static int lib_kGetWaypointIsEnabled(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + INLEVEL + + if (!waypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + lua_pushboolean(L, K_GetWaypointIsEnabled(waypoint)); + return 1; +} + +static int lib_kSetWaypointIsEnabled(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + boolean enabled = luaL_checkboolean(L, 2); + INLEVEL + + if (!waypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + K_SetWaypointIsEnabled(waypoint, enabled); + return 0; +} + +static int lib_kGetWaypointIsSpawnpoint(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + INLEVEL + + if (!waypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + lua_pushboolean(L, K_GetWaypointIsSpawnpoint(waypoint)); + return 1; +} + +static int lib_kGetWaypointNextID(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + INLEVEL + + if (!waypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + lua_pushinteger(L, K_GetWaypointNextID(waypoint)); + return 1; +} + +static int lib_kGetWaypointID(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + INLEVEL + + if (!waypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + lua_pushinteger(L, K_GetWaypointID(waypoint)); + return 1; +} + +static int lib_kGetWaypointFromID(lua_State *L) +{ + INT32 waypointId = luaL_checkinteger(L, 1); + INLEVEL + + LUA_PushUserdata(L, K_GetWaypointFromID(waypointId), META_WAYPOINT); + return 1; +} + +static int lib_kGetCircuitLength(lua_State *L) +{ + INLEVEL + lua_pushinteger(L, K_GetCircuitLength()); + return 1; +} + +static int lib_kGetTrackComplexity(lua_State *L) +{ + INLEVEL + lua_pushinteger(L, K_GetTrackComplexity()); + return 1; +} + +static int lib_kGetClosestWaypointToMobj(lua_State *L) +{ + mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + INLEVEL + if (!mobj) + return LUA_ErrInvalid(L, "mobj_t"); + + LUA_PushUserdata(L, K_GetClosestWaypointToMobj(mobj), META_WAYPOINT); + return 1; +} + +static int lib_kGetBestWaypointForMobj(lua_State *L) +{ + mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + waypoint_t *hint = NULL; + INLEVEL + if (!mobj) + return LUA_ErrInvalid(L, "mobj_t"); + // Optional waypoint parameter: + if (lua_isuserdata(L, 2) && lua_getmetatable(L, 2)) + { + lua_getfield(L, LUA_REGISTRYINDEX, META_WAYPOINT); + int result = lua_rawequal(L, -1, -2); + lua_pop(L, 2); + + if (!result) + { + return LUA_ErrInvalid(L, "waypoint_t"); + } + else + hint = *((waypoint_t **)lua_touserdata(L, 2)); + } + else if (!lua_isnoneornil(L, 2)) + { + // If we reach this point and it isn't an userdata, + // the scripter used a basic data type. Let them know + // they messed up. (Just use nil or nothing, please.) + return LUA_ErrInvalid(L, "waypoint_t"); + } + + LUA_PushUserdata(L, K_GetBestWaypointForMobj(mobj, hint), META_WAYPOINT); + return 1; +} + +/* + JugadorXEI @ 01/11/2025 (MM/DD/AAAA) + This was my way to work around giving path_t and pathfindnode_t objects + to Lua, as usually these are dynamically allocated. We give them a deep + copy of the values and then we free this memory after the fact. + Lua can manage its own copy itself. +*/ +static void pushDeepCopyOfPathTypeAsTable(lua_State *L, path_t *const path) +{ + lua_createtable(L, 0, 3); + + lua_pushinteger(L, path->numnodes); + lua_setfield(L, -2, "numnodes"); + + lua_createtable(L, path->numnodes, 0); + for (size_t i = 0; i < path->numnodes; i++) + { + lua_createtable(L, 0, 3); + + // It doesn't make sense for heap-related stuff to be exposed to Lua. + // lua_pushinteger(L, path->array[i].heapindex); + // lua_setfield(L, -2, "heapindex"); + + LUA_PushUserdata(L, (waypoint_t *)path->array[i].nodedata, META_WAYPOINT); + lua_setfield(L, -2, "nodedata"); + + lua_pushinteger(L, path->array[i].gscore); + lua_setfield(L, -2, "gscore"); + + lua_pushinteger(L, path->array[i].hscore); + lua_setfield(L, -2, "hscore"); + + lua_rawseti(L, -2, 1 + i); + } + lua_setfield(L, -2, "array"); + + lua_pushinteger(L, path->totaldist); + lua_setfield(L, -2, "totaldist"); +} + +static int lib_kPathfindToWaypoint(lua_State *L) +{ + waypoint_t *sourcewaypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + waypoint_t *destinationwaypoint = *((waypoint_t **)luaL_checkudata(L, 2, META_WAYPOINT)); + boolean useshortcuts = lua_optboolean(L, 3); + boolean huntbackwards = lua_optboolean(L, 4); + + INLEVEL + if (!sourcewaypoint || !destinationwaypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + path_t returnpath = {0}; + boolean success = K_PathfindToWaypoint(sourcewaypoint, destinationwaypoint, &returnpath, useshortcuts, huntbackwards); + + lua_pushboolean(L, success); + if (success) + { + pushDeepCopyOfPathTypeAsTable(L, &returnpath); + } + else + lua_pushnil(L); + + Z_Free(returnpath.array); + return 2; +} + +static int lib_kPathfindThruCircuit(lua_State *L) +{ + waypoint_t *sourcewaypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + fixed_t traveldistance = luaL_checkfixed(L, 2); + boolean useshortcuts = lua_optboolean(L, 3); + boolean huntbackwards = lua_optboolean(L, 4); + + INLEVEL + if (!sourcewaypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + path_t returnpath = {0}; + boolean success = K_PathfindThruCircuit(sourcewaypoint, traveldistance, &returnpath, useshortcuts, huntbackwards); + + lua_pushboolean(L, success); + if (success) + { + pushDeepCopyOfPathTypeAsTable(L, &returnpath); + } + else + lua_pushnil(L); + + Z_Free(returnpath.array); + return 2; +} + +static int lib_kPathfindThruCircuitSpawnable(lua_State *L) +{ + waypoint_t *sourcewaypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + fixed_t traveldistance = luaL_checkfixed(L, 2); + boolean useshortcuts = lua_optboolean(L, 3); + boolean huntbackwards = lua_optboolean(L, 4); + + INLEVEL + if (!sourcewaypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + path_t returnpath = {0}; + boolean success = K_PathfindThruCircuitSpawnable(sourcewaypoint, traveldistance, &returnpath, useshortcuts, huntbackwards); + + lua_pushboolean(L, success); + if (success) + { + pushDeepCopyOfPathTypeAsTable(L, &returnpath); + } + else + lua_pushnil(L); + + Z_Free(returnpath.array); + return 2; +} + +static int lib_kGetNextWaypointToDestination(lua_State *L) +{ + waypoint_t *sourcewaypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + waypoint_t *destinationwaypoint = *((waypoint_t **)luaL_checkudata(L, 2, META_WAYPOINT)); + boolean useshortcuts = lua_optboolean(L, 3); + boolean huntbackwards = lua_optboolean(L, 4); + + INLEVEL + if (!sourcewaypoint || !destinationwaypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + LUA_PushUserdata(L, K_GetNextWaypointToDestination(sourcewaypoint, destinationwaypoint, useshortcuts, huntbackwards), META_WAYPOINT); + return 1; +} + +static int lib_kSearchWaypointGraphForMobj(lua_State *L) +{ + mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + + INLEVEL + if (!mobj) + return LUA_ErrInvalid(L, "mobj_t"); + + LUA_PushUserdata(L, K_SearchWaypointGraphForMobj(mobj), META_WAYPOINT); + return 1; +} + +static int lib_kSearchWaypointHeapForMobj(lua_State *L) +{ + mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); + + INLEVEL + if (!mobj) + return LUA_ErrInvalid(L, "mobj_t"); + + LUA_PushUserdata(L, K_SearchWaypointHeapForMobj(mobj), META_WAYPOINT); + return 1; +} + static int lib_getTimeMicros(lua_State *L) { lua_pushinteger(L, I_GetPreciseTime() / (I_GetPrecisePrecision() / 1000000)); @@ -5993,6 +6381,27 @@ static luaL_Reg lib[] = { {"VS_PredictAroundArena", lib_vsPredictAroundArena}, {"VS_RandomPointOnArena", lib_vsRandomPointOnArena}, + // k_respawn + {"K_RespawnOffset", lib_kRespawnOffset}, + {"K_RespawnAtWaypoint", lib_kRespawnAtWaypoint}, + {"K_DoFault", lib_kDoFault}, + {"K_DoIngameRespawn", lib_kDoIngameRespawn}, + {"K_NextRespawnWaypointIndex", lib_kNextRespawnWaypointIndex}, + + // k_waypoint + {"K_GetFinishLineWaypoint", lib_kGetFinishLineWaypoint}, + {"K_GetStartingWaypoint", lib_kGetStartingWaypoint}, + {"K_GetWaypointIsFinishline", lib_kGetWaypointIsFinishline}, + {"K_GetWaypointIsShortcut", lib_kGetWaypointIsShortcut}, + {"K_GetWaypointIsEnabled", lib_kGetWaypointIsEnabled}, + {"K_SetWaypointIsEnabled", lib_kSetWaypointIsEnabled}, + {"K_GetWaypointIsSpawnpoint", lib_kGetWaypointIsSpawnpoint}, + {"K_GetWaypointNextID", lib_kGetWaypointNextID}, + {"K_GetWaypointID", lib_kGetWaypointID}, + {"K_GetWaypointFromID", lib_kGetWaypointFromID}, + {"K_GetCircuitLength", lib_kGetCircuitLength}, + {"K_GetTrackComplexity", lib_kGetTrackComplexity}, + // k_bot {"K_PlayerUsesBotMovement", lib_kPlayerUsesBotMovement}, {"K_BotCanTakeCut", lib_kBotCanTakeCut}, @@ -6005,6 +6414,14 @@ static luaL_Reg lib[] = { {"K_SetNameForBot", lib_kSetNameForBot}, // Lua-only function to allow safely removing bots. {"K_RemoveBot", lib_kRemoveBot}, + {"K_GetClosestWaypointToMobj", lib_kGetClosestWaypointToMobj}, + {"K_GetBestWaypointForMobj", lib_kGetBestWaypointForMobj}, + {"K_PathfindToWaypoint", lib_kPathfindToWaypoint}, + {"K_PathfindThruCircuit", lib_kPathfindThruCircuit}, + {"K_PathfindThruCircuitSpawnable", lib_kPathfindThruCircuitSpawnable}, + {"K_GetNextWaypointToDestination", lib_kGetNextWaypointToDestination}, + {"K_SearchWaypointGraphForMobj", lib_kSearchWaypointGraphForMobj}, + {"K_SearchWaypointHeapForMobj", lib_kSearchWaypointHeapForMobj}, // hu_stuff technically? {"HU_DoTitlecardCEcho", lib_startTitlecardCecho}, diff --git a/src/lua_libs.h b/src/lua_libs.h index 2615c0c5c..5acb8a1e4 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -103,6 +103,7 @@ extern lua_State *gL; #define META_ACTIVATOR "ACTIVATOR_T*" #define META_FOLLOWER "FOLLOWER_T*" +#define META_WAYPOINT "WAYPOINT_T*" #define META_SONICLOOPVARS "SONICLOOPVARS_T*" #define META_SONICLOOPCAMVARS "SONICLOOPCAMVARS_T*" @@ -136,6 +137,8 @@ int LUA_HudLib(lua_State *L); int LUA_FollowerLib(lua_State *L); int LUA_BotVarsLib(lua_State *L); int LUA_TerrainLib(lua_State *L); +int LUA_RespawnVarsLib(lua_State *L); +int LUA_WaypointLib(lua_State *L); #ifdef __cplusplus } // extern "C" diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index daaa1445d..4ef45908e 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -219,10 +219,16 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->lastpickupdistance); else if (fastcmp(field,"lastpickuptype")) lua_pushinteger(L, plr->lastpickuptype); + else if (fastcmp(field,"currentwaypoint")) + LUA_PushUserdata(L, plr->currentwaypoint, META_WAYPOINT); + else if (fastcmp(field,"nextwaypoint")) + LUA_PushUserdata(L, plr->nextwaypoint, META_WAYPOINT); else if (fastcmp(field,"airtime")) lua_pushinteger(L, plr->airtime); else if (fastcmp(field,"lastairtime")) lua_pushinteger(L, plr->lastairtime); + else if (fastcmp(field,"bigwaypointgap")) + lua_pushinteger(L, plr->bigwaypointgap); else if (fastcmp(field,"flashing")) lua_pushinteger(L, plr->flashing); else if (fastcmp(field,"spinouttimer")) @@ -283,6 +289,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->gateSound); else if (fastcmp(field,"startboost")) lua_pushinteger(L, plr->startboost); + else if (fastcmp(field,"dropdashboost")) + lua_pushinteger(L, plr->dropdashboost); else if (fastcmp(field,"aizdriftstrat")) lua_pushinteger(L, plr->aizdriftstrat); else if (fastcmp(field,"aizdriftextend")) @@ -788,15 +796,17 @@ static int player_set(lua_State *L) else if (fastcmp(field,"positiondelay")) plr->positiondelay = luaL_checkinteger(L, 3); else if (fastcmp(field,"distancetofinish")) - return NOSET; + plr->distancetofinish = luaL_checkfixed(L, 3); else if (fastcmp(field,"distancetofinishprev")) - return NOSET; + plr->distancetofinishprev = luaL_checkfixed(L, 3); else if (fastcmp(field,"lastpickupdistance")) plr->lastpickupdistance = luaL_checkinteger(L, 3); else if (fastcmp(field,"airtime")) plr->airtime = luaL_checkinteger(L, 3); else if (fastcmp(field,"lastairtime")) plr->lastairtime = luaL_checkinteger(L, 3); + else if (fastcmp(field,"bigwaypointgap")) + plr->bigwaypointgap = luaL_checkinteger(L, 3); else if (fastcmp(field,"flashing")) plr->flashing = luaL_checkinteger(L, 3); else if (fastcmp(field,"spinouttimer")) @@ -857,6 +867,8 @@ static int player_set(lua_State *L) plr->gateSound = luaL_checkinteger(L, 3); else if (fastcmp(field,"startboost")) plr->startboost = luaL_checkinteger(L, 3); + else if (fastcmp(field,"dropdashboost")) + plr->dropdashboost = luaL_checkinteger(L, 3); else if (fastcmp(field,"aizdriftstrat")) plr->aizdriftstrat = luaL_checkinteger(L, 3); else if (fastcmp(field,"aizdrifttilt")) @@ -1385,81 +1397,6 @@ static int ticcmd_set(lua_State *L) #undef NOFIELD -// Same shit for player.respawn variable... Why is everything in different sub-variables again now??? -#define RNOFIELD luaL_error(L, LUA_QL("respawnvars_t") " has no field named " LUA_QS, field) -#define RUNIMPLEMENTED luaL_error(L, LUA_QL("respawnvars_t") " unimplemented field " LUA_QS " cannot be read or set.", field) -// @TODO: Waypoints in Lua possibly maybe? No don't count on me to do it... - -static int respawn_get(lua_State *L) -{ - respawnvars_t *rsp = *((respawnvars_t **)luaL_checkudata(L, 1, META_RESPAWN)); - const char *field = luaL_checkstring(L, 2); - if (!rsp) - return LUA_ErrInvalid(L, "player_t"); - - if (fastcmp(field,"state")) - lua_pushinteger(L, rsp->state); - else if (fastcmp(field,"waypoint")) - return RUNIMPLEMENTED; - else if (fastcmp(field,"pointx")) - lua_pushfixed(L, rsp->pointx); - else if (fastcmp(field,"pointy")) - lua_pushfixed(L, rsp->pointy); - else if (fastcmp(field,"pointz")) - lua_pushfixed(L, rsp->pointz); - else if (fastcmp(field,"flip")) - lua_pushboolean(L, rsp->flip); - else if (fastcmp(field,"timer")) - lua_pushinteger(L, rsp->timer); - else if (fastcmp(field,"distanceleft")) - lua_pushinteger(L, rsp->distanceleft); // Can't possibly foresee any problem when pushing UINT32 to Lua's INT32 hahahahaha, get ready for dumb hacky shit on high distances. - else if (fastcmp(field,"dropdash")) - lua_pushinteger(L, rsp->dropdash); - else - return RNOFIELD; - - return 1; -} - -static int respawn_set(lua_State *L) -{ - respawnvars_t *rsp = *((respawnvars_t **)luaL_checkudata(L, 1, META_RESPAWN)); - const char *field = luaL_checkstring(L, 2); - if (!rsp) - return LUA_ErrInvalid(L, "respawnvars_t"); - - if (hud_running) - return luaL_error(L, "Do not alter player_t in HUD rendering code!"); - if (hook_cmd_running) - return luaL_error(L, "Do not alter player_t in CMD building code!"); - - if (fastcmp(field,"state")) - rsp->state = (UINT8)luaL_checkinteger(L, 3); - else if (fastcmp(field,"waypoint")) - return RUNIMPLEMENTED; - else if (fastcmp(field,"pointx")) - rsp->pointx = luaL_checkfixed(L, 3); - else if (fastcmp(field,"pointy")) - rsp->pointy = luaL_checkfixed(L, 3); - else if (fastcmp(field,"pointz")) - rsp->pointz = luaL_checkfixed(L, 3); - else if (fastcmp(field,"flip")) - rsp->flip = luaL_checkboolean(L, 3); - else if (fastcmp(field,"timer")) - rsp->timer = (tic_t)luaL_checkinteger(L, 3); - else if (fastcmp(field,"distanceleft")) - rsp->distanceleft = (UINT32)luaL_checkinteger(L, 3); - else if (fastcmp(field,"dropdash")) - rsp->dropdash = (tic_t)luaL_checkinteger(L, 3); - else - return RNOFIELD; - - return 0; -} - -#undef RNOFIELD -#undef RUNIMPLEMENTED - enum sonicloopvars { sonicloopvars_radius = 0, sonicloopvars_revolution, @@ -1641,14 +1578,6 @@ int LUA_PlayerLib(lua_State *L) lua_setfield(L, -2, "__len"); lua_pop(L,1); - luaL_newmetatable(L, META_RESPAWN); - lua_pushcfunction(L, respawn_get); - lua_setfield(L, -2, "__index"); - - lua_pushcfunction(L, respawn_set); - lua_setfield(L, -2, "__newindex"); - lua_pop(L,1); - luaL_newmetatable(L, META_TICCMD); lua_pushcfunction(L, ticcmd_get); lua_setfield(L, -2, "__index"); diff --git a/src/lua_respawnvarslib.c b/src/lua_respawnvarslib.c new file mode 100644 index 000000000..0f4b75262 --- /dev/null +++ b/src/lua_respawnvarslib.c @@ -0,0 +1,221 @@ +// 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_respawnvarslib.c +/// \brief player respawn variables library for Lua scripting + +#include "doomdef.h" +#include "fastcmp.h" + +#include "lua_script.h" +#include "lua_libs.h" +#include "lua_hud.h" // hud_running errors +#include "lua_hook.h" // hook_cmd_running errors + +enum respawnvars { + respawnvars_state = 0, + respawnvars_waypoint, + respawnvars_pointx, + respawnvars_pointy, + respawnvars_pointz, + respawnvars_pointangle, + respawnvars_flip, + respawnvars_timer, + respawnvars_airtimer, + respawnvars_distanceleft, + respawnvars_dropdash, + respawnvars_truedeath, + respawnvars_manual, + respawnvars_fromringshooter, + respawnvars_init, + respawnvars_fast, + respawnvars_returnspeed, +}; + +static const char *const respawnvars_opt[] = { + "state", + "waypoint", + "pointx", + "pointy", + "pointz", + "pointangle", + "flip", + "timer", + "airtimer", + "distanceleft", + "dropdash", + "truedeath", + "manual", + "fromringshooter", + "init", + "fast", + "returnspeed", + NULL +}; + +#define RNOFIELD luaL_error(L, LUA_QL("respawnvars_t") " has no field named " LUA_QS, field) +#define RUNIMPLEMENTED luaL_error(L, LUA_QL("respawnvars_t") " unimplemented field " LUA_QS " cannot be read or set.", field) + +static int respawn_get(lua_State *L) +{ + respawnvars_t *rsp = *((respawnvars_t **)luaL_checkudata(L, 1, META_RESPAWN)); + enum respawnvars field = luaL_checkoption(L, 2, NULL, respawnvars_opt); + + if (!rsp) + return LUA_ErrInvalid(L, "player_t"); + + switch (field) + { + case respawnvars_state: + lua_pushinteger(L, rsp->state); + break; + case respawnvars_waypoint: + LUA_PushUserdata(L, rsp->wp, META_WAYPOINT); + break; + case respawnvars_pointx: + lua_pushfixed(L, rsp->pointx); + break; + case respawnvars_pointy: + lua_pushfixed(L, rsp->pointy); + break; + case respawnvars_pointz: + lua_pushfixed(L, rsp->pointz); + break; + case respawnvars_pointangle: + lua_pushangle(L, rsp->pointangle); + break; + case respawnvars_flip: + lua_pushboolean(L, rsp->flip); + break; + case respawnvars_timer: + lua_pushinteger(L, rsp->timer); + break; + case respawnvars_airtimer: + lua_pushinteger(L, rsp->airtimer); + break; + case respawnvars_distanceleft: + lua_pushinteger(L, rsp->distanceleft); + break; + case respawnvars_dropdash: + lua_pushinteger(L, rsp->dropdash); + break; + case respawnvars_truedeath: + lua_pushboolean(L, rsp->truedeath); + break; + case respawnvars_manual: + lua_pushboolean(L, rsp->manual); + break; + case respawnvars_fromringshooter: + lua_pushboolean(L, rsp->fromRingShooter); + break; + case respawnvars_init: + lua_pushboolean(L, rsp->init); + break; + case respawnvars_fast: + lua_pushboolean(L, rsp->fast); + break; + case respawnvars_returnspeed: + lua_pushfixed(L, rsp->returnspeed); + break; + default: + return RNOFIELD; + } + + return 1; +} + +static int respawn_set(lua_State *L) +{ + respawnvars_t *rsp = *((respawnvars_t **)luaL_checkudata(L, 1, META_RESPAWN)); + enum respawnvars field = luaL_checkoption(L, 2, respawnvars_opt[0], respawnvars_opt); + + if (!rsp) + return LUA_ErrInvalid(L, "respawnvars_t"); + + if (hud_running) + return luaL_error(L, "Do not alter player_t in HUD rendering code!"); + if (hook_cmd_running) + return luaL_error(L, "Do not alter player_t in CMD building code!"); + + switch (field) + { + case respawnvars_state: + rsp->state = (UINT8)luaL_checkinteger(L, 3); + break; + case respawnvars_waypoint: + rsp->wp = *((waypoint_t **)luaL_checkudata(L, 3, META_WAYPOINT)); + break; + case respawnvars_pointx: + rsp->pointx = luaL_checkfixed(L, 3); + break; + case respawnvars_pointy: + rsp->pointy = luaL_checkfixed(L, 3); + break; + case respawnvars_pointz: + rsp->pointz = luaL_checkfixed(L, 3); + break; + case respawnvars_pointangle: + rsp->pointangle = luaL_checkangle(L, 3); + break; + case respawnvars_flip: + rsp->flip = luaL_checkboolean(L, 3); + break; + case respawnvars_timer: + rsp->timer = (tic_t)luaL_checkinteger(L, 3); + break; + case respawnvars_airtimer: + rsp->airtimer = (tic_t)luaL_checkinteger(L, 3); + break; + case respawnvars_distanceleft: + rsp->distanceleft = (UINT32)luaL_checkinteger(L, 3); + break; + case respawnvars_dropdash: + rsp->dropdash = (tic_t)luaL_checkinteger(L, 3); + break; + case respawnvars_truedeath: + rsp->truedeath = luaL_checkboolean(L, 3); + break; + case respawnvars_manual: + rsp->manual = luaL_checkboolean(L, 3); + break; + case respawnvars_fromringshooter: + rsp->fromRingShooter = luaL_checkboolean(L, 3); + break; + case respawnvars_init: + rsp->init = luaL_checkboolean(L, 3); + break; + case respawnvars_fast: + rsp->fast = luaL_checkboolean(L, 3); + break; + case respawnvars_returnspeed: + rsp->returnspeed = luaL_checkfixed(L, 3); + break; + default: + return RNOFIELD; + } + + return 0; +} + +#undef RNOFIELD +#undef RUNIMPLEMENTED + +int LUA_RespawnVarsLib(lua_State *L) +{ + luaL_newmetatable(L, META_RESPAWN); + lua_pushcfunction(L, respawn_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, respawn_set); + lua_setfield(L, -2, "__newindex"); + lua_pop(L,1); + + return 0; +} diff --git a/src/lua_script.c b/src/lua_script.c index 2bf84e0d7..e94dd53b3 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -63,6 +63,8 @@ static lua_CFunction liblist[] = { LUA_FollowerLib, // follower_t, followers[] LUA_BotVarsLib, // botvars_t LUA_TerrainLib, // t_splash_t, t_footstep_t, t_overlay_t, terrain_t + LUA_RespawnVarsLib, // respawnvars_t + LUA_WaypointLib, // waypoint_t NULL }; @@ -899,6 +901,10 @@ void LUA_InvalidateLevel(void) LUA_InvalidateUserdata(nodes[i].children); } #endif + for (i = 0; i < K_GetNumWaypoints(); i++) + { + LUA_InvalidateUserdata(K_GetWaypointFromIndex(i)); + } } void LUA_InvalidateMapthings(void) diff --git a/src/lua_waypointslib.c b/src/lua_waypointslib.c new file mode 100644 index 000000000..33b250cc6 --- /dev/null +++ b/src/lua_waypointslib.c @@ -0,0 +1,293 @@ +// 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_waypointslib.c +/// \brief wapoint structure library for Lua scripting + +#include "doomdef.h" +#include "fastcmp.h" + +#include "lua_script.h" +#include "lua_libs.h" + +enum waypointvars { + waypointvars_valid = 0, + waypointvars_mobj, + waypointvars_x, + waypointvars_y, + waypointvars_z, + waypointvars_onaline, + waypointvars_nextwaypoints, + waypointvars_prevwaypoints, + waypointvars_nextwaypointdistances, + waypointvars_prevwaypointdistances, + waypointvars_numnextwaypoints, + waypointvars_numprevwaypoints, +}; + +static const char *const waypointvars_opt[] = { + "valid", + "mobj", + "x", + "y", + "z", + "onaline", + "nextwaypoints", + "prevwaypoints", + "nextwaypointdistances", + "prevwaypointdistances", + "numnextwaypoints", + "numprevwaypoints", + NULL +}; + +#define RNOFIELD luaL_error(L, LUA_QL("waypoint_t") " has no field named " LUA_QS, field) +#define RNOSET luaL_error(L, LUA_QL("waypoint_t") " field " LUA_QS " cannot be set.", field) +#define RNOGET luaL_error(L, LUA_QL("waypoint_t") " field " LUA_QS " cannot be get.", field) + +static int waypoint_get(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + enum waypointvars field = luaL_checkoption(L, 2, NULL, waypointvars_opt); + + if (!waypoint) + { + switch (field) + { + case waypointvars_valid: + lua_pushboolean(L, false); + return 1; + default: + return LUA_ErrInvalid(L, "waypoint_t"); + } + } + + // It could totally happen that some scripter just deletes the mobj, + // which would have us check a null pointer, so we're checking against that + // just in case. + mobj_t *waypointMobj = NULL; + if (waypoint->mobj != NULL && P_MobjWasRemoved(waypoint->mobj) == false) + waypointMobj = waypoint->mobj; + + switch (field) + { + case waypointvars_valid: + lua_pushboolean(L, true); + break; + case waypointvars_mobj: + return RNOGET; + case waypointvars_x: + if (waypointMobj) + lua_pushfixed(L, waypointMobj->x); + else + lua_pushnil(L); + break; + case waypointvars_y: + if (waypointMobj) + lua_pushinteger(L, waypointMobj->y); + else + lua_pushnil(L); + break; + case waypointvars_z: + if (waypointMobj) + lua_pushinteger(L, waypointMobj->z); + else + lua_pushnil(L); + break; + case waypointvars_onaline: + lua_pushboolean(L, waypoint->onaline); + break; + case waypointvars_nextwaypoints: + lua_createtable(L, waypoint->numnextwaypoints, 0); + for (size_t i = 0; i < waypoint->numnextwaypoints; i++) + { + LUA_PushUserdata(L, waypoint->nextwaypoints[i], META_WAYPOINT); + lua_rawseti(L, -2, 1 + i); + } + break; + case waypointvars_prevwaypoints: + lua_createtable(L, waypoint->numprevwaypoints, 0); + for (size_t i = 0; i < waypoint->numprevwaypoints; i++) + { + LUA_PushUserdata(L, waypoint->prevwaypoints[i], META_WAYPOINT); + lua_rawseti(L, -2, 1 + i); + } + break; + case waypointvars_nextwaypointdistances: + lua_createtable(L, waypoint->numnextwaypoints, 0); + for (size_t i = 0; i < waypoint->numnextwaypoints; i++) + { + lua_pushinteger(L, waypoint->nextwaypointdistances[i]); + lua_rawseti(L, -2, 1 + i); + } + break; + case waypointvars_prevwaypointdistances: + lua_createtable(L, waypoint->numprevwaypoints, 0); + for (size_t i = 0; i < waypoint->numprevwaypoints; i++) + { + lua_pushinteger(L, waypoint->prevwaypointdistances[i]); + lua_rawseti(L, -2, 1 + i); + } + break; + case waypointvars_numnextwaypoints: + lua_pushinteger(L, waypoint->numnextwaypoints); + break; + case waypointvars_numprevwaypoints: + lua_pushinteger(L, waypoint->numprevwaypoints); + break; + default: + return RNOFIELD; + } + + return 1; +} + +static int waypoint_set(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + enum waypointvars field = luaL_checkoption(L, 2, waypointvars_opt[0], waypointvars_opt); + + if (!waypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + INLEVEL + + switch (field) + { + case waypointvars_mobj: + return RNOSET; + case waypointvars_x: + return RNOSET; + case waypointvars_y: + return RNOSET; + case waypointvars_z: + return RNOSET; + case waypointvars_onaline: + return RNOSET; + // A function should be used to set these instead. + case waypointvars_nextwaypoints: + return RNOSET; + case waypointvars_prevwaypoints: + return RNOSET; + case waypointvars_nextwaypointdistances: + return RNOSET; + case waypointvars_prevwaypointdistances: + return RNOSET; + case waypointvars_numnextwaypoints: + return RNOSET; + case waypointvars_numprevwaypoints: + return RNOSET; + default: + return RNOFIELD; + } + + return 0; +} + +#undef RNOSET +#undef RNOFIELD + +static int waypoint_num(lua_State *L) +{ + waypoint_t *waypoint = *((waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)); + + if (!waypoint) + return LUA_ErrInvalid(L, "waypoint_t"); + + lua_pushinteger(L, K_GetWaypointHeapIndex(waypoint)); + return 1; +} + +static int lib_iterateWaypoints(lua_State *L) +{ + size_t i; + + if (lua_gettop(L) < 2) + { + lua_pushcfunction(L, lib_iterateWaypoints); + return 1; + } + + lua_settop(L, 2); + lua_remove(L, 1); // state is unused. + + if (!lua_isnil(L, 1)) + i = K_GetWaypointHeapIndex(*(waypoint_t **)luaL_checkudata(L, 1, META_WAYPOINT)) + 1; + else + i = 0; + + if (i < K_GetNumWaypoints()) + { + LUA_PushUserdata(L, K_GetWaypointFromIndex(i), META_WAYPOINT); + return 1; + } + + return 0; +} + +static int lib_getWaypoint(lua_State *L) +{ + const char *field; + size_t i; + + // find waypoint by number + if (lua_type(L, 2) == LUA_TNUMBER) + { + i = luaL_checkinteger(L, 2); + if (i >= K_GetNumWaypoints()) + return luaL_error(L, "waypoints[] index %d out of range (0 - %d)", i, K_GetNumWaypoints()-1); + LUA_PushUserdata(L, K_GetWaypointFromIndex(i), META_WAYPOINT); + return 1; + } + + field = luaL_checkstring(L, 2); + + // special function iterate + if (fastcmp(field,"iterate")) + { + lua_pushcfunction(L, lib_iterateWaypoints); + return 1; + } + + return 0; +} + +static int lib_numWaypoints(lua_State *L) +{ + lua_pushinteger(L, K_GetNumWaypoints()); + return 1; +} + + +int LUA_WaypointLib(lua_State *L) +{ + luaL_newmetatable(L, META_WAYPOINT); + lua_pushcfunction(L, waypoint_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, waypoint_set); + lua_setfield(L, -2, "__newindex"); + + lua_pushcfunction(L, waypoint_num); + lua_setfield(L, -2, "__len"); + lua_pop(L,1); + + lua_newuserdata(L, 0); + lua_createtable(L, 0, 2); + lua_pushcfunction(L, lib_getWaypoint); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, lib_numWaypoints); + lua_setfield(L, -2, "__len"); + lua_setmetatable(L, -2); + lua_setglobal(L, "waypoints"); + + return 0; +} From fc5a3f2dc38290a6ba20cba3b3ced27bccad7751 Mon Sep 17 00:00:00 2001 From: "Spring E. Thing" Date: Sat, 23 Aug 2025 11:00:14 -0500 Subject: [PATCH 4/8] Racer Setup - Improved 'skincolor' Selection Information --- src/k_menudraw.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9dce49251..0bcdb8936 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2122,6 +2122,9 @@ static void M_DrawCharSelectPreview(UINT8 num) if (p->showextra == true) { INT32 randomskin = 0; + char variadicInfoBuffer[(MAXCOLORNAME*2) + 1 + 2 + 1];//+1 for spacing, +2 for brackets, +1 for null terminator + UINT16 folcol; + switch (p->mdepth) { case CSSTEP_ALTS: // Select clone @@ -2150,7 +2153,12 @@ static void M_DrawCharSelectPreview(UINT8 num) case CSSTEP_COLORS: // Select color if (p->color < numskincolors) { - V_DrawThinString(x-3, y+2, 0, skincolors[p->color].name); + if(p->color == SKINCOLOR_NONE) //'default' handling + sprintf(variadicInfoBuffer, "%s (%s)", skincolors[p->color].name, skincolors[skins[p->skin].prefcolor].name); + else + sprintf(variadicInfoBuffer, "%s", skincolors[p->color].name); + + V_DrawThinString(x-3, y+2, 0, variadicInfoBuffer); } else { @@ -2180,17 +2188,26 @@ static void M_DrawCharSelectPreview(UINT8 num) } break; case CSSTEP_FOLLOWERCOLORS: + folcol = K_GetEffectiveFollowerColor(p->followercolor, &followers[p->followern], p->color, &skins[p->skin]); + if (p->followercolor == FOLLOWERCOLOR_MATCH) { - V_DrawThinString(x-3, y+2, 0, "Match"); + sprintf(variadicInfoBuffer, "Match (%s)", skincolors[folcol].name); + V_DrawThinString(x-3, y+2, 0, variadicInfoBuffer); } else if (p->followercolor == FOLLOWERCOLOR_OPPOSITE) { - V_DrawThinString(x-3, y+2, 0, "Opposite"); + sprintf(variadicInfoBuffer, "Opposite (%s)", skincolors[folcol].name); + V_DrawThinString(x-3, y+2, 0, variadicInfoBuffer); } else if (p->followercolor < numskincolors) { - V_DrawThinString(x-3, y+2, 0, skincolors[p->followercolor].name); + if(p->followercolor == SKINCOLOR_NONE) //'default' handling + sprintf(variadicInfoBuffer, "%s (%s)", skincolors[p->followercolor].name, skincolors[folcol].name); + else + sprintf(variadicInfoBuffer, "%s", skincolors[p->followercolor].name); + + V_DrawThinString(x-3, y+2, 0, variadicInfoBuffer); } else { From 2d9700e23fd82d51ef14a497d869f2b19813ee28 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sat, 23 Aug 2025 10:58:38 -0500 Subject: [PATCH 5/8] Fix Spring E. Thing's skincolor name change for dynamic skins --- src/k_menudraw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 0bcdb8936..45e72a35f 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2154,7 +2154,7 @@ static void M_DrawCharSelectPreview(UINT8 num) if (p->color < numskincolors) { if(p->color == SKINCOLOR_NONE) //'default' handling - sprintf(variadicInfoBuffer, "%s (%s)", skincolors[p->color].name, skincolors[skins[p->skin].prefcolor].name); + sprintf(variadicInfoBuffer, "%s (%s)", skincolors[p->color].name, skincolors[skins[p->skin]->prefcolor].name); else sprintf(variadicInfoBuffer, "%s", skincolors[p->color].name); @@ -2188,7 +2188,7 @@ static void M_DrawCharSelectPreview(UINT8 num) } break; case CSSTEP_FOLLOWERCOLORS: - folcol = K_GetEffectiveFollowerColor(p->followercolor, &followers[p->followern], p->color, &skins[p->skin]); + folcol = K_GetEffectiveFollowerColor(p->followercolor, &followers[p->followern], p->color, skins[p->skin]); if (p->followercolor == FOLLOWERCOLOR_MATCH) { From 0d9a67b8b54c9429770123c779b0b0f81a5f817b Mon Sep 17 00:00:00 2001 From: FreakyMutantMan Date: Fri, 22 Nov 2024 21:34:57 -0800 Subject: [PATCH 6/8] Increased NUMSPRITEFREESLOTS to NUMMMOBJFREESLOTS*2. --- src/info.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/info.h b/src/info.h index 884dd3781..c73f97b0a 100644 --- a/src/info.h +++ b/src/info.h @@ -562,7 +562,7 @@ extern boolean actionsoverridden[NUMACTIONS]; // ratio of states to sprites to mobj types is roughly 6 : 1 : 1 #define NUMMOBJFREESLOTS 1024 -#define NUMSPRITEFREESLOTS NUMMOBJFREESLOTS +#define NUMSPRITEFREESLOTS (NUMMOBJFREESLOTS*2) #define NUMSTATEFREESLOTS (NUMMOBJFREESLOTS*8) // Hey, moron! If you change this table, don't forget about sprnames in info.c and the sprite lights in hw_light.c! From 19fdf19eb4f23f4d368de2bb100f5b92381e02e5 Mon Sep 17 00:00:00 2001 From: Chearii Date: Wed, 12 Mar 2025 08:13:53 -0400 Subject: [PATCH 7/8] Add baked offset parameters --- src/acs/call-funcs.cpp | 12 +++++ src/deh_tables.c | 1 + src/hardware/hw_main.c | 41 +++++++++++++++- src/hardware/hw_md2.c | 107 +++++++++++++++++++++++++++++++++++++++-- src/k_kart.c | 7 +++ src/lua_mobjlib.c | 46 ++++++++++++++++++ src/mobj.hpp | 7 +++ src/p_local.h | 1 + src/p_mobj.c | 12 +++++ src/p_mobj.h | 2 + src/p_saveg.c | 27 +++++++++++ src/p_user.c | 9 ++++ src/r_patch.h | 5 ++ src/r_patchrotation.c | 92 +++++++++++++++++++++++++++++++++++ src/r_things.cpp | 43 ++++++++++++++++- src/r_things.h | 2 + 16 files changed, 408 insertions(+), 6 deletions(-) diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index c74e18d6e..8c653382a 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -3367,6 +3367,12 @@ enum THING_PROP_HNEXT, THING_PROP_HPREV, THING_PROP_ITNEXT, + THING_PROP_BAKEDXOFFSET, + THING_PROP_BAKEDYOFFSET, + THING_PROP_BAKEDZOFFSET, + THING_PROP_BAKEDXPIVOT, + THING_PROP_BAKEDYPIVOT, + THING_PROP_BAKEDZPIVOT, THING_PROP__MAX }; @@ -3552,6 +3558,12 @@ bool CallFunc_GetThingProperty(ACSVM::Thread *thread, const ACSVM::Word *argV, A PROP_MOBJ(THING_PROP_HNEXT, hnext) PROP_MOBJ(THING_PROP_HPREV, hprev) PROP_MOBJ(THING_PROP_ITNEXT, itnext) + PROP_INT(THING_PROP_BAKEDXOFFSET, bakexoff) + PROP_INT(THING_PROP_BAKEDYOFFSET, bakeyoff) + PROP_INT(THING_PROP_BAKEDZOFFSET, bakezoff) + PROP_INT(THING_PROP_BAKEDXPIVOT, bakexpiv) + PROP_INT(THING_PROP_BAKEDYPIVOT, bakeypiv) + PROP_INT(THING_PROP_BAKEDZPIVOT, bakezpiv) default: { CONS_Alert(CONS_WARNING, "GetThingProperty type %d out of range (expected 0 - %d).\n", property, THING_PROP__MAX-1); diff --git a/src/deh_tables.c b/src/deh_tables.c index 91cfccaa4..014beef4b 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4756,6 +4756,7 @@ struct int_const_s const INT_CONST[] = { {"OV_DONT3DOFFSET", OV_DONT3DOFFSET}, {"OV_DONTXYSCALE", OV_DONTXYSCALE}, {"OV_DONTROLL", OV_DONTROLL}, + {"OV_DONTBAKEOFFSET", OV_DONTBAKEOFFSET}, // Player state (playerstate_t) {"PST_LIVE",PST_LIVE}, // Playing or camping. diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 3a7649afb..085769040 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -4670,6 +4670,7 @@ static void HWR_ProjectSprite(mobj_t *thing) patch_t *rotsprite = NULL; INT32 rollangle = 0; angle_t spriterotangle = 0; + vector2_t visoffs; #endif // uncapped/interpolation @@ -4869,12 +4870,31 @@ static void HWR_ProjectSprite(mobj_t *thing) flip = 0; } } + + // initialize and rotate pitch/roll vector + visoffs.x = 0; + visoffs.y = 0; + + const fixed_t visoffymul = (vflip ? -FRACUNIT : FRACUNIT); + + if (R_ThingIsUsingBakedOffsets(thing)) + { + R_RotateSpriteOffsetsByPitchRoll(thing, + vflip, + hflip, + &visoffs); + } #endif if (thing->renderflags & RF_ABSOLUTEOFFSETS) { spr_offset = interp.spritexoffset; +#ifdef ROTSPRITE + spr_topoffset = (interp.spriteyoffset + FixedDiv((visoffs.y * visoffymul), + mapobjectscale)); +#else spr_topoffset = interp.spriteyoffset; +#endif } else { @@ -4884,7 +4904,13 @@ static void HWR_ProjectSprite(mobj_t *thing) flipoffset = -1; spr_offset += interp.spritexoffset * flipoffset; +#ifdef ROTSPRITE + spr_topoffset += (interp.spriteyoffset + FixedDiv((visoffs.y * visoffymul), + mapobjectscale)) + * flipoffset; +#else spr_topoffset += interp.spriteyoffset * flipoffset; +#endif } if (papersprite) @@ -4942,17 +4968,28 @@ static void HWR_ProjectSprite(mobj_t *thing) } else { +#ifdef ROTSPRITE + if (visoffs.x) + { + visoffs.x = (FixedDiv((visoffs.x * FRACUNIT), mapobjectscale)); + } +#endif if (flip) { - x1 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale); +#ifdef ROTSPRITE + spr_offset -= visoffs.x; +#endif + x1 = (FIXED_TO_FLOAT((spr_width - spr_offset)) * this_xscale); x2 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); } else { +#ifdef ROTSPRITE + spr_offset += visoffs.x; +#endif x1 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale); } - // test if too close /* if (papersprite) diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 4227208bc..16849bd23 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1674,14 +1674,23 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) p.angley = FIXED_TO_FLOAT(anglef); } + angle_t pitchR, rollR, fixedAngY; + + pitchR = 0; + rollR = 0; + fixedAngY = 0; + { fixed_t anglef = AngleFixed(R_ModelRotationAngle(spr->mobj, NULL)); p.rollangle = 0.0f; + // make fixedAngY a disguised fixed_t first + fixedAngY = FLOAT_TO_FIXED(p.angley); + if (anglef) { - fixed_t camAngleDiff = AngleFixed(viewangle) - FLOAT_TO_FIXED(p.angley); // dumb reconversion back, I know + fixed_t camAngleDiff = AngleFixed(viewangle) - (fixed_t)(fixedAngY); // dumb reconversion back, I know p.rollangle = FIXED_TO_FLOAT(anglef); p.roll = true; @@ -1691,9 +1700,19 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) p.centery = FIXED_TO_FLOAT(spr->mobj->height / 2); // rotation axes relative to camera - p.rollx = FIXED_TO_FLOAT(FINECOSINE(FixedAngle(camAngleDiff) >> ANGLETOFINESHIFT)); - p.rollz = FIXED_TO_FLOAT(FINESINE(FixedAngle(camAngleDiff) >> ANGLETOFINESHIFT)); + pitchR = FINESINE(FixedAngle(camAngleDiff) >> ANGLETOFINESHIFT); + rollR = FINECOSINE(FixedAngle(camAngleDiff) >> ANGLETOFINESHIFT); + + p.rollx = FIXED_TO_FLOAT((fixed_t)rollR); + p.rollz = FIXED_TO_FLOAT((fixed_t)pitchR); + + // convert to angles + pitchR = FixedAngle((fixed_t)pitchR); + rollR = FixedAngle((fixed_t)rollR); } + + // make this a proper angle now + fixedAngY = FixedAngle(fixedAngY); } p.anglez = FIXED_TO_FLOAT(AngleFixed(R_InterpolateAngle(spr->mobj->old_pitch, spr->mobj->pitch))); @@ -1717,6 +1736,88 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) p.y += ox * gl_viewcos; p.z += oy; + if (R_ThingIsUsingBakedOffsets(spr->mobj)) + { + // visoffset stuff + float xx, xy, yx, yy; + float zx, zy, zz; + float xpiv, ypiv, zpiv; + fixed_t zh; + fixed_t xoffs, yoffs; + + // xoffs = (cos(xoff) + sin(yoff)) + xoffs = + FixedMul(spr->mobj->bakeyoff, -FINECOSINE(fixedAngY >> ANGLETOFINESHIFT)) + + FixedMul(spr->mobj->bakexoff, FINESINE(fixedAngY >> ANGLETOFINESHIFT)); + + // yoffs = (-sin(xoff) + cos(yoff)) + yoffs = + FixedMul(spr->mobj->bakeyoff, -FINESINE(fixedAngY >> ANGLETOFINESHIFT)) + + FixedMul(spr->mobj->bakexoff, FINECOSINE(fixedAngY >> ANGLETOFINESHIFT)); + + const fixed_t hflipmul = hflip ? -FRACUNIT : FRACUNIT; + + xpiv = FIXED_TO_FLOAT( + FixedMul( + FixedMul(spr->mobj->bakeypiv, + -FINECOSINE(fixedAngY >> ANGLETOFINESHIFT)) + + FixedMul(spr->mobj->bakexpiv, + FINESINE(fixedAngY >> ANGLETOFINESHIFT)), + hflipmul)); + ypiv = FIXED_TO_FLOAT( + FixedMul( + FixedMul(spr->mobj->bakeypiv, + -FINESINE(fixedAngY >> ANGLETOFINESHIFT)) + + FixedMul(spr->mobj->bakexpiv, + FINECOSINE(fixedAngY >> ANGLETOFINESHIFT)), + hflipmul)); + zpiv = FIXED_TO_FLOAT(spr->mobj->bakezpiv * ((flip) ? -1 : 1)); + + pitchR = ((pitchR + spr->mobj->pitch) * ((flip) ? -1 : 1)); + rollR = ((rollR + spr->mobj->roll) * ((flip) ? -1 : 1)); + + // x offset + xx = FIXED_TO_FLOAT(FixedMul(FixedMul( + FixedMul(xoffs,spr->mobj->spritexscale), + hflipmul), + FINECOSINE(pitchR >> ANGLETOFINESHIFT) + )); + xy = FIXED_TO_FLOAT(FixedMul(FixedMul( + FixedMul(xoffs,spr->mobj->spritexscale), + hflipmul), + -FINESINE(pitchR >> ANGLETOFINESHIFT) + )); + + // y offset + yx = FIXED_TO_FLOAT(FixedMul(FixedMul( + FixedMul(yoffs,spr->mobj->spritexscale), + hflipmul), + FINECOSINE(rollR >> ANGLETOFINESHIFT) + )); + + yy = FIXED_TO_FLOAT(FixedMul(FixedMul( + FixedMul(yoffs,spr->mobj->spritexscale), + hflipmul), + -FINESINE(rollR >> ANGLETOFINESHIFT) + )); + + // z offset + zh = FixedMul(FixedMul(spr->mobj->bakezoff,spr->mobj->spriteyscale), + FINECOSINE(rollR >> ANGLETOFINESHIFT)); + + zz = FIXED_TO_FLOAT(FixedMul(zh, + FINECOSINE(pitchR >> ANGLETOFINESHIFT))); + zx = FIXED_TO_FLOAT(FixedMul(zh, + FINESINE(pitchR >> ANGLETOFINESHIFT))); + zy = FIXED_TO_FLOAT(FixedMul(zh, + FINESINE(rollR >> ANGLETOFINESHIFT))); + + // do these namings even make sense at this point? + p.x += xx + zx + xpiv; + p.z += (xy + yy + zz * (flip ? -1 : 1)) + zpiv; + p.y += yx + zy + ypiv; + } + HWD.pfnDrawModel(md2->model, frame, durs, tics, nextFrame, &p, md2->scale * xs, md2->scale * ys, flip, hflip, &Surf); } } diff --git a/src/k_kart.c b/src/k_kart.c index 63b5ed3e5..bb84b6323 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8514,6 +8514,13 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->mo->spritexoffset = 0; player->mo->spriteyoffset = 0; + player->mo->bakexoff = 0; + player->mo->bakeyoff = 0; + player->mo->bakezoff = 0; + player->mo->bakexpiv = 0; + player->mo->bakeypiv = 0; + player->mo->bakezpiv = 0; + player->cameraOffset = 0; player->pflags &= ~(PF_CASTSHADOW); diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index ba2fbb6d1..42c8d0d33 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -101,6 +101,12 @@ enum mobj_e { mobj_sprxoff, mobj_spryoff, mobj_sprzoff, + mobj_bakexoff, + mobj_bakeyoff, + mobj_bakezoff, + mobj_bakexpiv, + mobj_bakeypiv, + mobj_bakezpiv, mobj_terrain, mobj_hitlag, mobj_waterskip, @@ -192,6 +198,12 @@ static const char *const mobj_opt[] = { "sprxoff", "spryoff", "sprzoff", + "bakexoff", + "bakeyoff", + "bakezoff", + "bakexpiv", + "bakeypiv", + "bakezpiv", "terrain", "hitlag", "waterskip", @@ -481,6 +493,23 @@ static int mobj_get(lua_State *L) case mobj_sprzoff: lua_pushfixed(L, mo->sprzoff); break; + case mobj_bakexoff: + lua_pushfixed(L, mo->bakexoff); + break; + case mobj_bakeyoff: + lua_pushfixed(L, mo->bakeyoff); + break; + case mobj_bakezoff: + lua_pushfixed(L, mo->bakezoff); + break; + case mobj_bakexpiv: + lua_pushfixed(L, mo->bakexpiv); + break; + case mobj_bakeypiv: + lua_pushfixed(L, mo->bakeypiv); + break; + case mobj_bakezpiv: + lua_pushfixed(L, mo->bakezpiv); case mobj_terrain: LUA_PushUserdata(L, mo->terrain, META_TERRAIN); break; @@ -899,6 +928,23 @@ static int mobj_set(lua_State *L) case mobj_sprzoff: mo->sprzoff = luaL_checkfixed(L, 3); break; + case mobj_bakexoff: + mo->bakexoff = luaL_checkfixed(L, 3); + break; + case mobj_bakeyoff: + mo->bakeyoff = luaL_checkfixed(L, 3); + break; + case mobj_bakezoff: + mo->bakezoff = luaL_checkfixed(L, 3); + break; + case mobj_bakexpiv: + mo->bakexpiv = luaL_checkfixed(L, 3); + break; + case mobj_bakeypiv: + mo->bakeypiv = luaL_checkfixed(L, 3); + break; + case mobj_bakezpiv: + mo->bakezpiv = luaL_checkfixed(L, 3); case mobj_terrain: mo->terrain = *((terrain_t **)luaL_checkudata(L, 3, META_TERRAIN)); break; diff --git a/src/mobj.hpp b/src/mobj.hpp index 5acdfed10..321d5aee3 100644 --- a/src/mobj.hpp +++ b/src/mobj.hpp @@ -207,6 +207,13 @@ struct Mobj : mobj_t FIXED_METHOD(spryoff) FIXED_METHOD(sprzoff) + FIXED_METHOD(bakexoff) + FIXED_METHOD(bakeyoff) + FIXED_METHOD(bakezoff) + FIXED_METHOD(bakexpiv) + FIXED_METHOD(bakeypiv) + FIXED_METHOD(bakezpiv) + vec2 spritescale() const { return {spritexscale(), spriteyscale()}; } void spritescale(const vec2& v) { diff --git a/src/p_local.h b/src/p_local.h index 6339204ae..1bfa8fcf5 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -276,6 +276,7 @@ void P_RunOverlays(void); #define OV_DONT3DOFFSET 1<<1 #define OV_DONTXYSCALE 1<<2 #define OV_DONTROLL 1<<3 +#define OV_DONTBAKEOFFSET 1<<4 void P_HandleMinecartSegments(mobj_t *mobj); void P_MobjThinker(mobj_t *mobj); diff --git a/src/p_mobj.c b/src/p_mobj.c index 4e4cc0827..ad3166db9 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -5442,6 +5442,18 @@ void P_RunOverlays(void) mo->roll = mo->target->roll; } + if (!(mo->threshold & OV_DONTBAKEOFFSET)) + { + // offsets + mo->bakexoff = mo->target->bakexoff; + mo->bakeyoff = mo->target->bakeyoff; + mo->bakezoff = mo->target->bakezoff; + // pivots + mo->bakexpiv = mo->target->bakexpiv; + mo->bakeypiv = mo->target->bakeypiv; + mo->bakezpiv = mo->target->bakezpiv; + } + mo->hitlag = mo->target->hitlag; mo->eflags = (mo->eflags & ~MFE_DAMAGEHITLAG) | (mo->target->eflags & MFE_DAMAGEHITLAG); diff --git a/src/p_mobj.h b/src/p_mobj.h index 756546c51..e97d92623 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -420,6 +420,8 @@ struct mobj_t UINT8 shadowcolor; // Palette index to use for rendering the shadow fixed_t sprxoff, spryoff, sprzoff; // Sprite offsets in real space, does NOT affect position or collision + fixed_t bakexoff, bakeyoff, bakezoff; // BAKED sprite offsets. Simulates visuals in real space, and rotates along the object's sprite + fixed_t bakexpiv, bakeypiv, bakezpiv; // Pivot points for baked offsets. These are *not* rotated with a sprite terrain_t *terrain; // Terrain definition of the floor this object last hit. NULL when in the air. mobj_t *terrainOverlay; // Overlay sprite object for terrain diff --git a/src/p_saveg.c b/src/p_saveg.c index ec4f07014..d025a8a70 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2884,6 +2884,7 @@ typedef enum MD3_REAPPEAR = 1<<1, MD3_PUNT_REF = 1<<2, MD3_OWNER = 1<<3, + MD3_BAKEDOFFSET = 1<<4, } mobj_diff3_t; typedef enum @@ -3210,6 +3211,9 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 diff3 |= MD3_PUNT_REF; if (mobj->owner) diff3 |= MD3_OWNER; + if (mobj->bakexoff || mobj->bakeyoff || mobj->bakezoff || mobj->bakexpiv || + mobj->bakeypiv || mobj->bakezpiv) + diff3 |= MD3_BAKEDOFFSET; if (diff3 != 0) diff2 |= MD2_MORE; @@ -3502,6 +3506,15 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 { WRITEUINT32(save->p, mobj->owner->mobjnum); } + if (diff3 & MD3_BAKEDOFFSET) + { + WRITEFIXED(save->p, mobj->bakexoff); + WRITEFIXED(save->p, mobj->bakeyoff); + WRITEFIXED(save->p, mobj->bakezoff); + WRITEFIXED(save->p, mobj->bakexpiv); + WRITEFIXED(save->p, mobj->bakeypiv); + WRITEFIXED(save->p, mobj->bakezpiv); + } WRITEUINT32(save->p, mobj->mobjnum); } @@ -4788,6 +4801,20 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) { mobj->owner = (mobj_t *)(size_t)READUINT32(save->p); } + if (diff3 & MD3_BAKEDOFFSET) + { + mobj->bakexoff = READFIXED(save->p); + mobj->bakeyoff = READFIXED(save->p); + mobj->bakezoff = READFIXED(save->p); + mobj->bakexpiv = READFIXED(save->p); + mobj->bakeypiv = READFIXED(save->p); + mobj->bakezpiv = READFIXED(save->p); + } + else + { + mobj->bakexoff = mobj->bakeyoff = mobj->bakezoff = 0; + mobj->bakexpiv = mobj->bakeypiv = mobj->bakezpiv = 0; + } // link tid set earlier P_AddThingTID(mobj); diff --git a/src/p_user.c b/src/p_user.c index 1bc20ba04..8585b1f9a 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1098,6 +1098,15 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj) ghost->sprxoff = mobj->sprxoff; ghost->spryoff = mobj->spryoff; ghost->sprzoff = mobj->sprzoff; + + // baked offsets + ghost->bakexoff = mobj->bakexoff; + ghost->bakeyoff = mobj->bakeyoff; + ghost->bakezoff = mobj->bakezoff; + ghost->bakexpiv = mobj->bakexpiv; + ghost->bakeypiv = mobj->bakeypiv; + ghost->bakezpiv = mobj->bakezpiv; + ghost->rollangle = mobj->rollangle; ghost->spritexscale = mobj->spritexscale; diff --git a/src/r_patch.h b/src/r_patch.h index 2c5fd6596..47de59696 100644 --- a/src/r_patch.h +++ b/src/r_patch.h @@ -52,6 +52,11 @@ INT32 R_GetRollAngle(angle_t rollangle); angle_t R_GetPitchRollAngle(mobj_t *mobj, player_t *viewPlayer); angle_t R_ModelRotationAngle(mobj_t *mobj, player_t *viewPlayer); angle_t R_SpriteRotationAngle(mobj_t *mobj, player_t *viewPlayer); +vector2_t* R_RotateSpriteOffsetsByPitchRoll( + mobj_t* mobj, + boolean vflip, + boolean hflip, + vector2_t* out); #endif #ifdef __cplusplus diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index 215246347..00432999f 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -105,6 +105,98 @@ INT32 R_GetRollAngle(angle_t rollangle) return ra; } +#define VISROTMUL (ANG1 * ROTANGDIFF) + +vector2_t* R_RotateSpriteOffsetsByPitchRoll( + mobj_t* mobj, + boolean vflip, + boolean hflip, + vector2_t* out) +{ + fixed_t rotcos, rotsin, finx, finy; + vector2_t xvec, yvec; + + // input offsets + fixed_t xoffs, yoffs, xpiv, ypiv; + + // final offsets + INT16 visx, visy, visz; + INT16 vxpiv, vypiv; + + // visual rotation + angle_t visrollang; + + // camera angle + angle_t viewingAngle = R_PointToAngle(mobj->x, mobj->y); + + // rotate ourselves entirely by the sprite's own rotation angle + angle_t visrot = R_SpriteRotationAngle(mobj, NULL); + + // xoffs = (-cos(xoff) + sin(yoff)) + xoffs = + FixedMul(mobj->bakeyoff, -FINECOSINE(mobj->angle >> ANGLETOFINESHIFT)) + + FixedMul(mobj->bakexoff, FINESINE(mobj->angle >> ANGLETOFINESHIFT)); + xpiv = + FixedMul(mobj->bakeypiv, -FINECOSINE(mobj->angle >> ANGLETOFINESHIFT)) + + FixedMul(mobj->bakexpiv, FINESINE(mobj->angle >> ANGLETOFINESHIFT)); + + // yoffs = (-sin(yoff) + cos(xoff)) + yoffs = + FixedMul(mobj->bakeyoff, -FINESINE(mobj->angle >> ANGLETOFINESHIFT)) + + FixedMul(mobj->bakexoff, FINECOSINE(mobj->angle >> ANGLETOFINESHIFT)); + ypiv = + FixedMul(mobj->bakeypiv, -FINESINE(mobj->angle >> ANGLETOFINESHIFT)) + + FixedMul(mobj->bakexpiv, FINECOSINE(mobj->angle >> ANGLETOFINESHIFT)); + + visrollang = (R_GetRollAngle(visrot) * VISROTMUL) * (hflip ? -1 : 1); + + // get pitch and roll multipliers, mainly used to align the + // viewpoint with the camera + fixed_t pitchMul = -FINESINE(viewingAngle >> ANGLETOFINESHIFT); + fixed_t rollMul = FINECOSINE(viewingAngle >> ANGLETOFINESHIFT); + + // get visual positions + visz = visy = visx = 0; + visz = (INT16)(-(mobj->bakezoff / FRACUNIT)); + visx = (INT16)(FixedMul((yoffs / FRACUNIT), rollMul)); + visy = (INT16)(FixedMul((xoffs / FRACUNIT), pitchMul)); + + vxpiv = (INT16)(FixedMul((ypiv / FRACUNIT), rollMul)); + vypiv = (INT16)(FixedMul((xpiv / FRACUNIT), pitchMul)); + + // rotate by rollangle + finx = (visx + visy); + finy = -visz; + + rotcos = FINECOSINE(visrollang >> ANGLETOFINESHIFT); + rotsin = FINESINE(visrollang >> ANGLETOFINESHIFT); + + xvec.x = FixedMul(finx, rotcos); + xvec.y = FixedMul(finx, -rotsin); + + yvec.x = FixedMul(finy, rotsin); + yvec.y = FixedMul(finy, -rotcos); + + // set finalized offsets + out->x = (fixed_t)(xvec.x + yvec.x + vxpiv + vypiv); + out->y = (fixed_t)(xvec.y - yvec.y) + (mobj->bakezpiv / FRACUNIT); + + // flip based on vflip and hflip + // flip the view angle if we're horizontally flipped + if (hflip) + { + out->x *= -1; + } + + if (vflip) + { + out->y *= -1; + } + return out; +} + +#undef VISROTMUL + patch_t *Patch_GetRotated(patch_t *patch, INT32 angle, boolean flip) { rotsprite_t *rotsprite = patch->rotated; diff --git a/src/r_things.cpp b/src/r_things.cpp index e48650c25..a78b9e690 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -834,6 +834,12 @@ boolean R_ThingIsFlashing(mobj_t *thing) baddie_is_flashing(thing); } +boolean R_ThingIsUsingBakedOffsets(mobj_t* thing) +{ + return ((thing->bakexoff) || (thing->bakeyoff) || (thing->bakezoff) || + (thing->bakexpiv) || (thing->bakeypiv) || (thing->bakezpiv)); +} + UINT8 *R_GetSpriteTranslation(vissprite_t *vis) { if (vis->cut & SC_PRECIP) @@ -1777,6 +1783,7 @@ static void R_ProjectSprite(mobj_t *thing) patch_t *rotsprite = NULL; INT32 rollangle = 0; angle_t spriterotangle = 0; + vector2_t visoffs; #endif // uncapped/interpolation @@ -1987,10 +1994,31 @@ static void R_ProjectSprite(mobj_t *thing) if (spritexscale < 1 || spriteyscale < 1) return; +#ifdef ROTSPRITE + // initialize and rotate pitch/roll vector + visoffs.x = 0; + visoffs.y = 0; + + const fixed_t visoffymul = (vflip ? -FRACUNIT : FRACUNIT); + + if (R_ThingIsUsingBakedOffsets(thing)) + { + R_RotateSpriteOffsetsByPitchRoll(thing, + vflip, + hflip, + &visoffs); + } +#endif + if (thing->renderflags & RF_ABSOLUTEOFFSETS) { spr_offset = interp.spritexoffset; +#ifdef ROTSPRITE + spr_topoffset = (interp.spriteyoffset + FixedDiv((visoffs.y * visoffymul), + mapobjectscale)); +#else spr_topoffset = interp.spriteyoffset; +#endif } else { @@ -1999,8 +2027,14 @@ static void R_ProjectSprite(mobj_t *thing) if ((thing->renderflags & RF_FLIPOFFSETS) && flip) flipoffset = -1; - spr_offset += interp.spritexoffset * flipoffset; + spr_offset += (interp.spritexoffset) * flipoffset; +#ifdef ROTSPRITE + spr_topoffset += (interp.spriteyoffset + FixedDiv((visoffs.y * visoffymul), + mapobjectscale)) + * flipoffset; +#else spr_topoffset += interp.spriteyoffset * flipoffset; +#endif } if (flip) @@ -2008,6 +2042,13 @@ static void R_ProjectSprite(mobj_t *thing) else offset = -spr_offset; +#ifdef ROTSPRITE + if (visoffs.x) + { + offset -= FixedDiv((visoffs.x * FRACUNIT), mapobjectscale); + } +#endif + offset = FixedMul(offset, FixedMul(spritexscale, this_scale)); offset2 = FixedMul(spr_width, FixedMul(spritexscale, this_scale)); diff --git a/src/r_things.h b/src/r_things.h index 64b2a9051..88a5f28ca 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -96,6 +96,8 @@ boolean R_ThingModelUsesDirectionalLighting(mobj_t *thing); boolean R_ThingIsFlashing(mobj_t *thing); +boolean R_ThingIsUsingBakedOffsets(mobj_t *thing); + INT32 R_ThingLightLevel(mobj_t *thing); boolean R_SplatSlope(mobj_t *thing, vector3_t position, pslope_t *slope); boolean R_CustomShadowZ(mobj_t *thing, fixed_t *return_z, pslope_t **return_slope); From 80edcb4710a8cee7585e10e9a2d6322b6a4c67f4 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sat, 23 Aug 2025 11:24:03 -0500 Subject: [PATCH 8/8] Fix bakezpiv offset case fallthrus --- src/lua_mobjlib.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index 42c8d0d33..f79c27b34 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -510,6 +510,7 @@ static int mobj_get(lua_State *L) break; case mobj_bakezpiv: lua_pushfixed(L, mo->bakezpiv); + break; case mobj_terrain: LUA_PushUserdata(L, mo->terrain, META_TERRAIN); break; @@ -945,6 +946,7 @@ static int mobj_set(lua_State *L) break; case mobj_bakezpiv: mo->bakezpiv = luaL_checkfixed(L, 3); + break; case mobj_terrain: mo->terrain = *((terrain_t **)luaL_checkudata(L, 3, META_TERRAIN)); break;