Expose item timers to lua

This commit is contained in:
Indev 2025-12-24 16:48:54 +03:00 committed by NepDisk
parent d989cfa370
commit cf1ac5f864
5 changed files with 131 additions and 179 deletions

View file

@ -32,6 +32,7 @@
#include "k_kart.h" // awardscaledrings_t
#include "k_hud.h" // scoreboardmod_e
#include "k_waypoint.h" // waypoint values (for lua)
#include "h_timers.h" // timerflags_t
#include "deh_tables.h"
@ -1821,5 +1822,9 @@ struct int_const_s const INT_CONST[] = {
{"AIRDROP_HEAVY", AIRDROP_HEAVY},
{"AIRDROP_FUSION", AIRDROP_FUSION},
// item timer stuff
{"TIMER_COUNTER", TIMER_COUNTER},
{"TIMER_BAD", TIMER_BAD},
{NULL,0}
};

View file

@ -42,14 +42,11 @@ typedef struct itimer_s
{
const char* name; // name of timer
INT32 timer; // current time
//std::vector<patch_t*> patches; // timer graphics (big/freeplay)
std::vector<patch_t*> patches_small; // timer graphics (small)
INT32 anim_frames; // tic duration for each graphic
//INT32 xoffs = 0, yoffs = 0; // offsets for freeplay mode
INT32 xoffs_small = 0, yoffs_small = 0; // offsets for normal mode
boolean counter; // not a timer, show a counter instead
boolean badtimer; // timer is colored red
INT32 *timer_p; // if defined, uses a pointer for the timer instead
std::vector<patch_t*> patches; // timer graphics (small)
INT32 anim_frames = 1; // tic duration for each graphic
UINT8 flags = 0; // settings for timer
lua_Integer timer_func_ref = LUA_NOREF; // if set, calls a function from registry to get timer value
boolean timer_func_error = false; // don't print error every frame
} itimer_t;
static bool sorttimers(itimer_t a, itimer_t b)
@ -142,29 +139,23 @@ static std::vector<itimer_t> addTimers_unsorted;
void K_AddItemTimerEx(
const char *name,
INT32 *timer,
/*char **patches,*/ char **patches_small,
/*INT32 patches_size,*/ INT32 patches_small_size,
lua_Integer timer_func_ref,
const char **patches,
INT32 patches_size,
INT32 anim_duration,
INT32 xoffs, INT32 yoffs,
INT32 xoffs_small, INT32 yoffs_small,
boolean counter, boolean unsorted)
UINT8 flags, boolean unsorted)
{
itimer_t t; // woo yeah baby
t.name = name;
t.timer_p = timer;
t.timer_func_ref = timer_func_ref;
INT32 i;
//for (i = 0; i < patches_size; i++)
//t.patches.push_back(qche(patches[i]));
for (i = 0; i < patches_small_size; i++)
t.patches_small.push_back(qche(patches_small[i]));
for (i = 0; i < patches_size; i++)
t.patches.push_back(qche(patches[i]));
t.anim_frames = anim_duration;
//t.xoffs = xoffs; t.yoffs = yoffs;
t.xoffs_small = xoffs_small; t.yoffs_small = yoffs_small;
t.counter = counter;
t.flags = flags;
// yeah
if (unsorted)
@ -204,6 +195,40 @@ void K_getTimersDrawinfo(drawinfo_t *out)
out->flags = flags;
}
static INT32 getLuaTimer(itimer_t *itimer)
{
lua_pushcfunction(gL, LUA_GetErrorMessage);
lua_Integer errorh = lua_gettop(gL);
lua_getfield(gL, LUA_REGISTRYINDEX, LREG_TIMERS);
lua_rawgeti(gL, -1, itimer->timer_func_ref);
I_Assert(lua_isfunction(gL, -1));
LUA_PushUserdata(gL, stplyr, META_PLAYER);
if (lua_pcall(gL, 1, 1, errorh) != 0)
{
if (!itimer->timer_func_error)
{
itimer->timer_func_error = true;
CONS_Alert(CONS_WARNING, "%s\n", lua_tostring(gL, -1));
}
lua_pop(gL, 3); // Pop error message, timers table and error handler
return 0;
}
// TODO - print error if returned value isn't integer?
if (lua_isnumber(gL, -1))
itimer->timer = lua_tointeger(gL, -1);
lua_pop(gL, 3); // Pop function result, timers table and error handler
return itimer->timer;
}
void K_DisplayItemTimers(void)
{
if (!cv_itemtimers.value) return;
@ -216,65 +241,56 @@ void K_DisplayItemTimers(void)
{ // sneaker
"shoe",
stplyr->sneakertimer,
//{qche("K_ISSHOE")},
{qche("K_TISHOE")},
1, /*-15, -15,*/ -4, -2,
1,
},
{ // invinc
"invincible",
stplyr->invincibilitytimer,
//{qche("K_ISINV1"), qche("K_ISINV2"), qche("K_ISINV3"), qche("K_ISINV4"), qche("K_ISINV5"), qche("K_ISINV6")},
{qche("K_TIINV1"), qche("K_TIINV2"), qche("K_TIINV3"), qche("K_TIINV4"), qche("K_TIINV5"), qche("K_TIINV6")},
3, /*-15, -15,*/ -4, -2,
3,
},
{ // grow
"grow",
std::max<INT16>(0, stplyr->growshrinktimer),
//{qche("K_ISGROW")},
{qche("K_TIGROW")},
1, /*-15, -15,*/ -4, -2,
1,
},
{ // rocket sneakers
"rocketsneakers",
stplyr->rocketsneakertimer,
//{qche("K_ISRSHE")},
{qche("K_TIRSHE")},
1, /*-15, -15,*/ -4, -2,
1,
},
{ // hyudoro
"hyudoro",
stplyr->hyudorotimer,
//{qche("K_ISHYUD")},
{qche("K_TIHYUD")},
1, /*-15, -15,*/ -4, -2,
1,
},
{ // drift boost
"driftsparkboost",
stplyr->driftboost,
//{qche("K_DRSP1"), qche("K_DRSP2")},
{qche("K_TISRK1"), qche("K_TISRK2")},
2, /*-15, -15,*/ -4, -2,
2,
},
{ // start boost
"startboost",
stplyr->startboost,
//{qche("K_ISSTB")},
{qche("K_TISTB")},
1, /*-15, -15,*/ -4, -2,
1,
},
{ // ring boost
"ringboost",
stplyr->ringboost,
//{qche("K_ISRING")},
{qche("K_TIRING")},
1, /*-15, -15,*/ -4, -2,
1,
},
{ // flameshield
"flameshield",
stplyr->flametimer,
//{qche("K_ISFLMS")},
{qche("K_TIFLMS")},
1, /*-15, -15,*/ -4, -2,
1,
},
};
@ -289,41 +305,27 @@ void K_DisplayItemTimers(void)
{ // spinout
"spinout",
std::max<UINT16>(stplyr->spinouttimer, stplyr->wipeoutslow),
//{qche("K_DIZZ1"), qche("K_DIZZ2"), qche("K_DIZZ3"), qche("K_DIZZ4")},
{qche("K_TISPN1"), qche("K_TISPN2"), qche("K_TISPN3"), qche("K_TISPN4")},
3, /*-15, -15,*/ -4, -2,
false,
true,
3,
TIMER_BAD,
}
);
timers.push_back(
{ // shrink
"shrink",
std::max<INT16>(0, -stplyr->growshrinktimer),
//{qche("K_ISSHRK")},
{qche("K_TISHRK")},
1,
//-15,
//-15,
-4,
-2,
false,
true,
TIMER_BAD,
}
);
timers.push_back(
{ // spb
"spb",
(INT32)spbTimers[stplyr-players],
//{qche("K_ISSPB")},
{qche("K_TISPB")},
1,
//-15,
//-15,
-4,
-2,
false,
(stplyr->position == K_GetBestRank())
(UINT8)((stplyr->position == K_GetBestRank()) ? TIMER_BAD : 0),
}
);
// Same with boost stacks
@ -331,39 +333,20 @@ void K_DisplayItemTimers(void)
{ // boosts
"boosts",
K_StackingActive() ? stplyr->numboosts : 0,
//{qche("K_DRSP1"), qche("K_DRSP2")},
{qche("K_TISRK1"), qche("K_TISRK2")},
2, /*-15, -15,*/ -3, -2,
true,
2,
TIMER_BAD,
}
);
timers.push_back(
{ // item cooldown
"itemusecooldown",
(INT32)stplyr->itemusecooldown,
//{qche("K_ISFLMS")},
{qche("SERVLOCK")},
1, /*-15, -15,*/ -4, -2,
false, false
1,
}
);
/*timers.push_back(
{ // dead
"spr_realisticexplosion",
stplyr->deadtimer ? std::max<INT32>(0, TICRATE - stplyr->deadtimer) : 0,
//{qche("IT_DED1"), qche("IT_DED2"), qche("IT_DED3"), qche("IT_DED4"), qche("IT_DED5"), qche("IT_DED6"), qche("IT_DED7"), qche("IT_DED8")},
{qche("IT_DEAD1"), qche("IT_DEAD2"), qche("IT_DEAD3"), qche("IT_DEAD4"), qche("IT_DEAD5"), qche("IT_DEAD6"), qche("IT_DEAD7"), qche("IT_DEAD8")},
1,
//0,
//-4,
0,
0,
false,
true,
}
);*/
// insert unsortable timers
timers.insert(timers.end(), addTimers_unsorted.begin(), addTimers_unsorted.end());
@ -390,7 +373,7 @@ void K_DisplayItemTimers(void)
for (itimer_t& t : timers)
{
INT32 timer = t.timer;
if (t.timer_p) timer = *t.timer_p;
if (t.timer_func_ref != LUA_NOREF) timer = getLuaTimer(&t);
if (stplyr->deadtimer && !fastcmp(t.name, "spr_realisticexplosion")) continue;
if (timer > 0)
offs += stepx;
@ -403,7 +386,6 @@ void K_DisplayItemTimers(void)
if (t.timer <= 0) continue;
if (stplyr->deadtimer && !fastcmp(t.name, "spr_realisticexplosion")) continue;
INT32 timer = t.timer;
if (t.timer_p) timer = *t.timer_p;
INT32 seconds = timer / TICRATE, _centiseconds = G_TicsToCentiseconds(timer);
const char* str = va("%d.%02d", seconds, _centiseconds);
@ -411,7 +393,7 @@ void K_DisplayItemTimers(void)
str = va("%d.%01d", seconds, G_TicsToDeciseconds(timer));
if (seconds > 99) // exceeds 100 seconds?
str = va("%d", seconds);
INT32 animsize = t.patches_small.size();
INT32 animsize = t.patches.size();
INT32 patchnum = (leveltime % (t.anim_frames * animsize) / t.anim_frames);
UINT8 *cmap = NULL, *textcmap = NULL;
INT32 font = TINY_FONT;
@ -434,7 +416,7 @@ void K_DisplayItemTimers(void)
flags |= V_GREENMAP;
}
if (t.counter) // don't show up as time
if (t.flags & TIMER_COUNTER) // don't show up as time
{
str = va("%d", timer);
font = HU_FONT;
@ -442,14 +424,14 @@ void K_DisplayItemTimers(void)
INT32 width = V_StringScaledWidth(FRACUNIT, FRACUNIT, FRACUNIT, flags|V_MONOSPACE, font, str) >> FRACBITS;
// very bad!
if (t.badtimer)
if (t.flags & TIMER_BAD)
{
flags |= V_REDMAP;
//textcmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE);
}
patch_t *item = t.patches_small[patchnum];
V_DrawFixedPatch((itemx - (item->width/2))<<FRACBITS, (itemy + t.yoffs_small)<<FRACBITS, FRACUNIT, flags, t.patches_small[patchnum], cmap);
patch_t *item = t.patches[patchnum];
V_DrawFixedPatch((itemx - (item->width/2))<<FRACBITS, (itemy - 2)<<FRACBITS, FRACUNIT, flags, t.patches[patchnum], cmap);
V_DrawStringScaledEx(
(itemx - (width/2) - item->leftoffset) << FRACBITS,
(itemy + 12) << FRACBITS,
@ -466,86 +448,4 @@ void K_DisplayItemTimers(void)
itemx += stepx;
}
}
/*else // FREEPLAY - Move these to the left side, where the rankings usually are
{
if (r_splitscreen) // How?
return;
INT32 fx = 9, fy = 92, step = 20;
INT32 flags = V_SNAPTOLEFT|V_SLIDEIN;
// center it
INT32 offs = -step;
for (itimer_t& t : timers)
{
INT32 timer = t.timer;
if (t.timer_p) timer = *t.timer_p;
if (stplyr->deadtimer && !fastcmp(t.name, "spr_realisticexplosion")) continue;
if (timer > 0)
offs += step;
}
fy -= offs/2;
// draw relevant timers
for (itimer_t& t : timers)
{
if (t.timer <= 0) continue;
if (stplyr->deadtimer && !fastcmp(t.name, "spr_realisticexplosion")) continue;
INT32 timer = t.timer;
if (t.timer_p) timer = *t.timer_p;
INT32 seconds = timer / TICRATE, _centiseconds = G_TicsToCentiseconds(timer);
INT32 patchnum = (leveltime % (t.anim_frames * t.patches.size()) / t.anim_frames);
UINT8 *cmap = NULL, *textcmap = NULL;
const char* str = va("%d.%02d", seconds, _centiseconds);
if (seconds > 9) // exceeds 9 seconds?
str = va("%d.%01d", seconds, G_TicsToDeciseconds(timer));
if (seconds > 99) // exceeds 100 seconds?
str = va("%d", seconds);
boolean bost = false;
if (fastcmp(t.name, "boosts")) // don't show up as time
{
str = va("%d", timer);
bost = true;
}
INT32 width = V_StringScaledWidth(FRACUNIT, FRACUNIT, FRACUNIT, flags|V_MONOSPACE, OPPRNK_FONT, str) >> FRACBITS;
if (fastcmp(t.name, "driftsparkboost"))
{
if (timer > 85) // rainbow
cmap = R_GetTranslationColormap(TC_DEFAULT, (skincolornum_t)K_RainbowColor(leveltime), GTC_CACHE);
else if (timer > 50 && K_PurpleDriftActive()) // purple
cmap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_PURPLE, GTC_CACHE);
else if (timer > 25) // ketchup
cmap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_KETCHUP, GTC_CACHE);
else // blu
cmap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_SAPPHIRE, GTC_CACHE);
textcmap = cmap;
}
// very bad!
if (t.badtimer)
textcmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE);
patch_t *item = t.patches[patchnum];
V_DrawFixedPatch((fx+t.xoffs)<<FRACBITS, (fy+t.yoffs)<<FRACBITS, FRACUNIT, flags, item, cmap);
V_DrawStringScaledEx(
(fx + (item->width/2) - (width/2) - item->leftoffset + t.xoffs) << FRACBITS,
(fy+5) << FRACBITS,
FRACUNIT,
FRACUNIT,
FRACUNIT,
FRACUNIT,
flags|V_MONOSPACE,
textcmap,
TINY_FONT,
str
);
fy += step;
}
}
*/
}

View file

@ -19,6 +19,14 @@ extern "C" {
#include "command.h"
#include "d_player.h"
#include "k_hud.h"
#include "lua_script.h"
#include "lua_libs.h"
typedef enum timerflags_e
{
TIMER_COUNTER = 0x1,
TIMER_BAD = 0x2,
} timerflags_t;
extern consvar_t cv_itemtimers;
extern tic_t spbTimers[MAXPLAYERS];
@ -27,13 +35,11 @@ void K_UpdateSPBTimer(void);
// Adds an item timer.
void K_AddItemTimerEx(
const char *name,
INT32 *timer,
char **patches, char **patches_small,
INT32 patches_size, INT32 patches_small_size,
lua_Integer timer_func_ref,
const char **patches,
INT32 patches_size,
INT32 anim_duration,
INT32 xoffs, INT32 yoffs,
INT32 xoffs_small, INT32 yoffs_small,
boolean counter, boolean unsorted);
UINT8 flags, boolean unsorted);
void K_DisplayItemTimers(void);
void K_getTimersDrawinfo(drawinfo_t *out);

View file

@ -26,6 +26,7 @@
#include "w_wad.h"
#include "z_zone.h"
#include "hu_stuff.h"
#include "h_timers.h"
#include "lua_script.h"
#include "lua_libs.h"
@ -1770,6 +1771,41 @@ static int lib_hudgetoffsets(lua_State *L)
return 2;
}
static int lib_hudaddtimer(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
luaL_checktype(L, 2, LUA_TFUNCTION); // We get to it later
luaL_checktype(L, 3, LUA_TTABLE);
INT32 patches_size = lua_objlen(L, 3);
CLEANUP(pfree) const char **patches = calloc(patches_size, sizeof(const char*));
for (INT32 i = 0; i < patches_size; ++i)
{
lua_rawgeti(L, 3, i);
patches[i] = lua_tostring(L, -1);
lua_pop(L, 1);
}
INT32 anim_duration = luaL_optinteger(L, 4, 1);
UINT8 flags = (UINT8)luaL_optinteger(L, 5, 0);
boolean unsorted = lua_toboolean(L, 6);
lua_Integer timer_func_ref;
// Store function into registry
lua_getfield(L, LUA_REGISTRYINDEX, LREG_TIMERS);
lua_pushvalue(L, 2);
timer_func_ref = luaL_ref(L, -2);
K_AddItemTimerEx(name, timer_func_ref, patches, patches_size, anim_duration, flags, unsorted);
return 0;
}
// add a HUD element for rendering
extern int lib_hudadd(lua_State *L);
@ -1780,6 +1816,7 @@ static luaL_Reg lib_hud[] = {
{"add", lib_hudadd},
{"setVoteBackground", lib_hudsetvotebackground},
{"getOffsets", lib_hudgetoffsets},
{"addTimer", lib_hudaddtimer},
{NULL, NULL}
};
@ -1795,6 +1832,9 @@ int LUA_HudLib(lua_State *L)
luaL_register(L, NULL, lib_draw);
lib_draw_ref = luaL_ref(L, LUA_REGISTRYINDEX);
lua_newtable(L);
lua_setfield(L, LUA_REGISTRYINDEX, LREG_TIMERS);
luaL_newmetatable(L, META_COLORMAP);
lua_pushcfunction(L, colormap_get);
lua_setfield(L, -2, "__index");

View file

@ -25,6 +25,7 @@ extern lua_State *gL;
#define LREG_STATEACTION "STATE_ACTION"
#define LREG_ACTIONS "MOBJ_ACTION"
#define LREG_METATABLES "METATABLES"
#define LREG_TIMERS "ITEM_TIMERS"
#define META_STATE "STATE_T*"
#define META_MOBJINFO "MOBJINFO_T*"