From 3dc57d59080da637ee40f79d0a4a3dd7983f6676 Mon Sep 17 00:00:00 2001 From: Freaky Mutant Man Date: Mon, 20 Oct 2025 19:01:27 +0000 Subject: [PATCH] Expose multiple Grand Prix, cup and round queue related structs to Lua. --- src/CMakeLists.txt | 1 + src/lua_baselib.c | 7 + src/lua_grandprixlib.c | 969 +++++++++++++++++++++++++++++++++++++++++ src/lua_libs.h | 7 + src/lua_script.c | 1 + 5 files changed, 985 insertions(+) create mode 100644 src/lua_grandprixlib.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 54196be6d..9eb0bcbf1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -130,6 +130,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 lua_itemroulettelib.c lua_respawnvarslib.c lua_waypointslib.c + lua_grandprixlib.c lua_profile.cpp k_kart.c k_respawn.c diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 102dde47d..0e4cba54a 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -46,6 +46,7 @@ #include "k_hitlag.h" #include "music.h" // music functions necessary for lua integration #include "k_terrain.h" +#include "k_grandprix.h" #include "lua_script.h" #include "lua_libs.h" @@ -256,6 +257,12 @@ static const struct { {META_POWERUPVARS, "powerupvars_t"}, {META_ICECUBEVARS, "icecubevars_t"}, {META_SKYBOX, "skybox_t"}, + + {META_CUP, "cupheader_t"}, + {META_GPRANK, "gprank_t"}, + {META_GPRANKLEVEL, "gprank_level_t"}, + {META_GPRANKLEVELPERPLAYER, "gprank_level_perplayer_t"}, + {META_ROUNDENTRY, "roundentry_t"}, {NULL, NULL} }; diff --git a/src/lua_grandprixlib.c b/src/lua_grandprixlib.c new file mode 100644 index 000000000..bd9c263e6 --- /dev/null +++ b/src/lua_grandprixlib.c @@ -0,0 +1,969 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Freaky Mutant Man. +// Copyright (C) 2025 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_grandprixlib.c +/// \brief Grand Prix, cup and rank info for Lua scripting. + +#include "doomdef.h" +#include "fastcmp.h" +#include "doomstat.h" +#include "k_grandprix.h" +#include "k_rank.h" +#include "g_game.h" + +#include "lua_script.h" +#include "lua_libs.h" + +#define UNIMPLEMENTED luaL_error(L, LUA_QL("cupheader_t") " field " LUA_QS " is not implemented for Lua and cannot be accessed.", cup_opt[field]) +#define RNOFIELDGP luaL_error(L, LUA_QL("grandprixinfo") " has no field named " LUA_QS, field) +#define RNOFIELDCH luaL_error(L, LUA_QL("cupheader_t") " has no field named " LUA_QS, field) +#define RNOFIELDGR luaL_error(L, LUA_QL("gprank_t") " has no field named " LUA_QS, field) +#define RNOFIELDGRL luaL_error(L, LUA_QL("gprank_level_t") " has no field named " LUA_QS, field) +#define RNOFIELDGRLP luaL_error(L, LUA_QL("gprank_level_perplayer_t") " has no field named " LUA_QS, field) +#define RNOFIELDRQ luaL_error(L, LUA_QL("roundqueue") " has no field named " LUA_QS, field) +#define RNOFIELDRE luaL_error(L, LUA_QL("roundentry_t") " has no field named " LUA_QS, field) +#define GPERR luaL_error(L, LUA_QL("grandprixinfo") " field " LUA_QS " cannot be accessed while grandprixinfo.gp is false.", grandprix_opt[field]) +#define ROUNDCUEERR luaL_error(L, LUA_QL("roundqueue") " field " LUA_QS " cannot be accessed while roundqueue.size is 0.", grandprix_opt[field]) + +enum grandprix { + grandprix_gp = 0, + grandprix_cup, + grandprix_gamespeed, + grandprix_encore, + grandprix_masterbots, + grandprix_initialize, + grandprix_initalize, + grandprix_wonround, + grandprix_eventmode, + grandprix_specialdamage, + grandprix_rank, +}; + +enum cup { + cup_valid = 0, + cup_id, + cup_monitor, + cup_name, + cup_namehash, + cup_realname, + cup_icon, + cup_levellist, + cup_cachedlevels, + cup_numlevels, + cup_numbonus, + cup_emeraldnum, + cup_playcredits, + cup_hintcondition, + cup_cache_cuplock, + cup_windata, + cup_next, +}; + +enum gprank { + gprank_valid = 0, + gprank_numplayers, + gprank_totalplayers, + gprank_position, + gprank_skin, + gprank_winpoints, + gprank_totalpoints, + gprank_exp, + gprank_totalexp, + gprank_continuesused, + gprank_prisons, + gprank_totalprisons, + gprank_rings, + gprank_totalrings, + gprank_specialwon, + gprank_scoreposition, + gprank_scoregppoints, + gprank_scoreexp, + gprank_scoreprisons, + gprank_scorerings, + gprank_scorecontinues, + gprank_scoretotal, + gprank_numlevels, + gprank_levels, +}; + +enum gprank_level { + gprank_level_valid = 0, + gprank_level_id, + gprank_level_event, + gprank_level_time, + gprank_level_totalexp, + gprank_level_totalprisons, + gprank_level_continues, + gprank_level_perplayer, +}; + +enum gprank_level_perplayer { + gprank_level_perplayer_valid = 0, + gprank_level_perplayer_position, + gprank_level_perplayer_rings, + gprank_level_perplayer_exp, + gprank_level_perplayer_prisons, + gprank_level_perplayer_gotspecialprize, + gprank_level_perplayer_grade, +}; + +enum roundcue { // named "roundcue" to avoid overlap with actual roundqueue struct + roundcue_size = 0, // placed first since we'll be checking this to see if the roundqueue is currently in use + roundcue_roundnum, + roundcue_position, + roundcue_netcommunicate, + roundcue_writetextmap, + roundcue_snapshotmaps, + roundcue_entries, +}; + +enum roundentry { + roundentry_valid = 0, + roundentry_mapnum, + roundentry_gametype, + roundentry_encore, + roundentry_rankrestricted, + roundentry_overridden, +}; + +static const char *const grandprix_opt[] = { + "gp", + "cup", + "gamespeed", + "encore", + "masterbots", + "initialize", + "initalize", + "wonround", + "eventmode", + "specialdamage", + "rank", + NULL +}; + +static const char *const cup_opt[] = { + "valid", + "id", + "monitor", + "name", + "namehash", + "realname", + "icon", + "levellist", + "cachedlevels", + "numlevels", + "numbonus", + "emeraldnum", + "playcredits", + "hintcondition", + "cache_cuplock", + "windata", + "next", + NULL +}; + +static const char *const gprank_opt[] = { + "valid", + "numplayers", + "totalplayers", + "position", + "skin", + "winpoints", + "totalpoints", + "exp", + "totalexp", + "continuesused", + "prisons", + "totalprisons", + "rings", + "totalrings", + "specialwon", + "scoreposition", + "scoregppoints", + "scoreexp", + "scoreprisons", + "scorerings", + "scorecontinues", + "scoretotal", + "numlevels", + "levels", + NULL +}; + +static const char *const gprank_level_opt[] = { + "valid", + "id", + "event", + "time", + "totalexp", + "totalprisons", + "continues", + "perplayer", + NULL +}; + +static const char *const gprank_level_perplayer_opt[] = { + "valid", + "position", + "rings", + "exp", + "prisons", + "gotspecialprize", + "grade", + NULL +}; + +static const char *const roundcue_opt[] = { + "size", + "roundnum", + "position", + "netcommunicate", + "writetextmap", + "snapshotmaps", + "entries", + NULL +}; + +static const char *const roundentry_opt[] = { + "valid", + "mapnum", + "gametype", + "encore", + "rankrestricted", + "overridden", + NULL +}; + +static int grandprix_get(lua_State *L) +{ + enum grandprix field = luaL_checkoption(L, 2, grandprix_opt[0], grandprix_opt); + + // Don't return any grandprixinfo values while not in a GP. + if (!grandprixinfo.gp) + { + switch (field) + { + case grandprix_gp: + lua_pushboolean(L, false); + return 1; + default: + return GPERR; + } + } + + switch (field) + { + case grandprix_gp: + lua_pushboolean(L, grandprixinfo.gp); + break; + case grandprix_cup: + LUA_PushUserdata(L, grandprixinfo.cup, META_CUP); + break; + case grandprix_gamespeed: + lua_pushnumber(L, grandprixinfo.gamespeed); + break; + case grandprix_encore: + lua_pushboolean(L, grandprixinfo.encore); + break; + case grandprix_masterbots: + lua_pushboolean(L, grandprixinfo.masterbots); + break; + case grandprix_initialize: + case grandprix_initalize: // when the struct misspelled the variable... + lua_pushboolean(L, grandprixinfo.initalize); + break; + case grandprix_wonround: + lua_pushboolean(L, grandprixinfo.wonround); + break; + case grandprix_eventmode: + lua_pushnumber(L, grandprixinfo.eventmode); + break; + case grandprix_specialdamage: + lua_pushnumber(L, grandprixinfo.specialDamage); + break; + case grandprix_rank: + LUA_PushUserdata(L, &grandprixinfo.rank, META_GPRANK); + break; + default: + return RNOFIELDGP; + } + + return 1; +} + +static int grandprix_set(lua_State *L) +{ + return luaL_error(L, LUA_QL("grandprixinfo") " struct cannot be edited by Lua."); +} + +static int cup_get(lua_State *L) +{ + cupheader_t *cup = *((cupheader_t **)luaL_checkudata(L, 1, META_CUP)); + enum cup field = luaL_checkoption(L, 2, cup_opt[0], cup_opt); + + if (!cup) + { + switch (field) + { + case cup_valid: + lua_pushboolean(L, false); + return 1; + default: + return LUA_ErrInvalid(L, "cupheader_t"); + } + } + + switch (field) + { + case cup_valid: + lua_pushboolean(L, true); + break; + case cup_id: + lua_pushnumber(L, cup->id); + break; + case cup_monitor: + lua_pushnumber(L, cup->monitor); + break; + case cup_name: + lua_pushstring(L, cup->name); + break; + case cup_namehash: + return UNIMPLEMENTED; + break; + case cup_realname: + lua_pushstring(L, cup->realname); + break; + case cup_icon: + lua_pushstring(L, cup->icon); + break; + case cup_levellist: + lua_createtable(L, ((cup->numlevels) + (cup->numbonus)), 0); + for (size_t i = 0; i < ((cup->numlevels) + (cup->numbonus)); i++) + { + lua_pushstring(L, cup->levellist[i]); + lua_rawseti(L, -2, 1 + i); + } + //return UNIMPLEMENTED; + break; + case cup_cachedlevels: + lua_createtable(L, ((cup->numlevels) + (cup->numbonus)), 0); + for (size_t i = 0; i < ((cup->numlevels) + (cup->numbonus)); i++) + { + lua_pushnumber(L, cup->cachedlevels[i]); + lua_rawseti(L, -2, 1 + i); + } + break; + case cup_numlevels: + lua_pushnumber(L, cup->numlevels); + break; + case cup_numbonus: + lua_pushnumber(L, cup->numbonus); + break; + case cup_emeraldnum: + lua_pushnumber(L, cup->emeraldnum); + break; + case cup_playcredits: + lua_pushboolean(L, cup->playcredits); + break; + case cup_hintcondition: + lua_pushnumber(L, cup->hintcondition); + break; + case cup_cache_cuplock: + return UNIMPLEMENTED; + break; + case cup_windata: + return UNIMPLEMENTED; + break; + case cup_next: + return UNIMPLEMENTED; + break; + default: + return RNOFIELDCH; + } + + return 1; +} + +static int cup_set(lua_State *L) +{ + return luaL_error(L, LUA_QL("cupheader_t") " struct cannot be edited by Lua."); +} + +static int gprank_get(lua_State *L) +{ + gpRank_t *gprank = *((gpRank_t **)luaL_checkudata(L, 1, META_GPRANK)); + enum gprank field = luaL_checkoption(L, 2, gprank_opt[0], gprank_opt); + + if (!gprank) + { + switch (field) + { + case gprank_valid: + lua_pushboolean(L, false); + return 1; + default: + return LUA_ErrInvalid(L, "gprank_t"); + } + } + + switch (field) + { + case gprank_valid: + lua_pushboolean(L, true); + break; + case gprank_numplayers: + lua_pushnumber(L, gprank->numPlayers); + break; + case gprank_totalplayers: + lua_pushnumber(L, gprank->totalPlayers); + break; + case gprank_position: + lua_pushnumber(L, gprank->position); + break; + case gprank_skin: + lua_pushnumber(L, gprank->skin); + break; + case gprank_winpoints: + lua_pushnumber(L, gprank->winPoints); + break; + case gprank_totalpoints: + lua_pushnumber(L, gprank->totalPoints); + break; + case gprank_exp: + lua_pushnumber(L, gprank->exp); + break; + case gprank_totalexp: + lua_pushnumber(L, gprank->totalExp); + break; + case gprank_continuesused: + lua_pushnumber(L, gprank->continuesUsed); + break; + case gprank_prisons: + lua_pushnumber(L, gprank->prisons); + break; + case gprank_totalprisons: + lua_pushnumber(L, gprank->totalPrisons); + break; + case gprank_rings: + lua_pushnumber(L, gprank->rings); + break; + case gprank_totalrings: + lua_pushnumber(L, gprank->totalRings); + break; + case gprank_specialwon: + lua_pushboolean(L, gprank->specialWon); + break; + case gprank_scoreposition: + lua_pushnumber(L, gprank->scorePosition); + break; + case gprank_scoregppoints: + lua_pushnumber(L, gprank->scoreGPPoints); + break; + case gprank_scoreexp: + lua_pushnumber(L, gprank->scoreExp); + break; + case gprank_scoreprisons: + lua_pushnumber(L, gprank->scorePrisons); + break; + case gprank_scorerings: + lua_pushnumber(L, gprank->scoreRings); + break; + case gprank_scorecontinues: + lua_pushnumber(L, gprank->scoreContinues); + break; + case gprank_scoretotal: + lua_pushnumber(L, gprank->scoreTotal); + break; + case gprank_numlevels: + lua_pushnumber(L, gprank->numLevels); + break; + case gprank_levels: + lua_createtable(L, ((grandprixinfo.cup->numlevels) + (grandprixinfo.cup->numbonus)), 0); + for (size_t i = 0; i < ((grandprixinfo.cup->numlevels) + (grandprixinfo.cup->numbonus)); i++) + { + LUA_PushUserdata(L, &gprank->levels[i], META_GPRANKLEVEL); + lua_rawseti(L, -2, 1 + i); + } + break; + default: + return RNOFIELDGR; + } + + return 1; +} + +static int gprank_set(lua_State *L) +{ + return luaL_error(L, LUA_QL("gprank_t") " struct cannot be edited by Lua."); +} + +static int gprank_level_get(lua_State *L) +{ + gpRank_level_t *gprank_level = *((gpRank_level_t **)luaL_checkudata(L, 1, META_GPRANKLEVEL)); + enum gprank_level field = luaL_checkoption(L, 2, gprank_level_opt[0], gprank_level_opt); + + if (!gprank_level) + { + switch (field) + { + case gprank_level_valid: + lua_pushboolean(L, false); + return 1; + default: + return LUA_ErrInvalid(L, "gprank_level_t"); + } + } + + switch (field) + { + case gprank_level_valid: + lua_pushboolean(L, true); + break; + case gprank_level_id: + lua_pushnumber(L, gprank_level->id); + break; + case gprank_level_event: + lua_pushnumber(L, gprank_level->event); + break; + case gprank_level_time: + lua_pushnumber(L, gprank_level->time); + break; + case gprank_level_totalexp: + lua_pushnumber(L, gprank_level->totalExp); + break; + case gprank_level_totalprisons: + lua_pushnumber(L, gprank_level->totalPrisons); + break; + case gprank_level_continues: + lua_pushnumber(L, gprank_level->continues); + break; + case gprank_level_perplayer: + lua_createtable(L, grandprixinfo.rank.numPlayers, 0); + for (size_t i = 0; i < grandprixinfo.rank.numPlayers; i++) + { + LUA_PushUserdata(L, &gprank_level->perPlayer[i], META_GPRANKLEVELPERPLAYER); + lua_rawseti(L, -2, 1 + i); + } + break; + default: + return RNOFIELDGRL; + } + + return 1; +} + +static int gprank_level_set(lua_State *L) +{ + return luaL_error(L, LUA_QL("gprank_level_t") " struct cannot be edited by Lua."); +} + +static int gprank_level_perplayer_get(lua_State *L) +{ + // "perplaya" to avoid shadowed declaration + gpRank_level_perplayer_t *gprank_level_perplaya = *((gpRank_level_perplayer_t **)luaL_checkudata(L, 1, META_GPRANKLEVELPERPLAYER)); + enum gprank_level_perplayer field = luaL_checkoption(L, 2, gprank_level_perplayer_opt[0], gprank_level_perplayer_opt); + + if (!gprank_level_perplaya) + { + switch (field) + { + case gprank_level_perplayer_valid: + lua_pushboolean(L, false); + return 1; + default: + return LUA_ErrInvalid(L, "gprank_level_perplayer_t"); + } + } + + switch (field) + { + case gprank_level_perplayer_valid: + lua_pushboolean(L, true); + break; + case gprank_level_perplayer_position: + lua_pushnumber(L, gprank_level_perplaya->position); + break; + case gprank_level_perplayer_rings: + lua_pushnumber(L, gprank_level_perplaya->rings); + break; + case gprank_level_perplayer_exp: + lua_pushnumber(L, gprank_level_perplaya->exp); + break; + case gprank_level_perplayer_prisons: + lua_pushnumber(L, gprank_level_perplaya->prisons); + break; + case gprank_level_perplayer_gotspecialprize: + lua_pushboolean(L, gprank_level_perplaya->gotSpecialPrize); + break; + case gprank_level_perplayer_grade: + lua_pushnumber(L, gprank_level_perplaya->grade); + break; + default: + return RNOFIELDGRLP; + } + + return 1; +} + +static int gprank_level_perplayer_set(lua_State *L) +{ + return luaL_error(L, LUA_QL("gprank_level_perplayer") " struct cannot be edited by Lua."); +} + +static int roundcue_get(lua_State *L) +{ + enum roundcue field = luaL_checkoption(L, 2, roundcue_opt[0], roundcue_opt); + + // Don't return any grandprixinfo values while not in a GP. + if (!roundqueue.size) + { + switch (field) + { + case roundcue_size: + lua_pushboolean(L, false); + return 1; + default: + return ROUNDCUEERR; + } + } + + switch (field) + { + case roundcue_size: + lua_pushnumber(L, roundqueue.size); + break; + case roundcue_roundnum: + lua_pushnumber(L, roundqueue.roundnum); + break; + case roundcue_position: + lua_pushnumber(L, roundqueue.position); + break; + case roundcue_netcommunicate: + return UNIMPLEMENTED; + break; + case roundcue_writetextmap: + return UNIMPLEMENTED; + break; + case roundcue_snapshotmaps: + lua_pushboolean(L, roundqueue.snapshotmaps); + break; + case roundcue_entries: + lua_createtable(L, roundqueue.size, 0); + for (size_t i = 0; i < roundqueue.size; i++) + { + LUA_PushUserdata(L, &roundqueue.entries[i], META_ROUNDENTRY); + lua_rawseti(L, -2, 1 + i); + } + break; + default: + return RNOFIELDRQ; + } + + return 1; +} + +static int roundcue_set(lua_State *L) +{ + return luaL_error(L, LUA_QL("roundqueue") " struct cannot be edited by Lua."); +} + +static int roundentry_get(lua_State *L) +{ + roundentry_t *roundentry = *((roundentry_t **)luaL_checkudata(L, 1, META_ROUNDENTRY)); + enum roundentry field = luaL_checkoption(L, 2, roundentry_opt[0], roundentry_opt); + + if (!roundentry) + { + switch (field) + { + case roundentry_valid: + lua_pushboolean(L, false); + return 1; + default: + return LUA_ErrInvalid(L, "roundentry_t"); + } + } + + switch (field) + { + case roundentry_valid: + lua_pushboolean(L, true); + break; + case roundentry_mapnum: + lua_pushnumber(L, roundentry->mapnum); + break; + case roundentry_gametype: + lua_pushnumber(L, roundentry->gametype); + break; + case roundentry_encore: + lua_pushboolean(L, roundentry->encore); + break; + case roundentry_rankrestricted: + lua_pushboolean(L, roundentry->rankrestricted); + break; + case roundentry_overridden: + lua_pushboolean(L, roundentry->overridden); + break; + default: + return RNOFIELDRE; + } + + return 1; +} + +static int roundentry_set(lua_State *L) +{ + return luaL_error(L, LUA_QL("roundentry_t") " struct cannot be edited by Lua."); +} + +#undef UNIMPLEMENTED +#undef RNOFIELDGP +#undef RNOFIELDCH +#undef RNOFIELDGR +#undef RNOFIELDGRL +#undef RNOFIELDGRLP +#undef RNOFIELDRQ +#undef RNOFIELDRE +#undef GPERR +#undef ROUNDCUEERR + +static int lib_numCupheaders(lua_State *L) +{ + lua_pushinteger(L, numkartcupheaders); + return 1; +} + +// There was, in fact, a better thing to do here - thanks toaster +#define GETCUPERR UINT16_MAX + +// copied and edited from G_MapNumber +static UINT16 LUA_GetCupByNum(UINT16 cupnum) +{ + cupheader_t *checkcup; + // find by cup id + if (cupnum != GETCUPERR) + { + if (cupnum >= numkartcupheaders) + return GETCUPERR; // id outta range + for (checkcup = kartcupheaders; checkcup->id <= numkartcupheaders; checkcup = checkcup->next) + { + if (checkcup->id != cupnum) + continue; + else + break; + return GETCUPERR; // id invalid + } + return checkcup->id; + } + + return GETCUPERR; +} + +// copied and edited from G_MapNumber +static UINT16 LUA_GetCupByName(const char * name) +{ + cupheader_t *checkcup; + + UINT32 hash = quickncasehash(name, MAXCUPNAME); + + // find by cup name/realname + for (checkcup = kartcupheaders; checkcup != NULL; checkcup = checkcup->next) + { + if (hash != checkcup->namehash) + continue; + + if (strcasecmp(checkcup->name, name) != 0) + continue; + + return checkcup->id; + } + + return GETCUPERR; +} + +static int lib_iterateCups(lua_State *L) +{ + INT32 i = -1; + cupheader_t *tempcup = kartcupheaders; + + if (lua_gettop(L) < 2) + { + lua_pushcfunction(L, lib_iterateCups); + return 1; + } + + lua_settop(L, 2); + lua_remove(L, 1); // state is unused. + + if (!lua_isnil(L, 1)) + { + i = ((*((cupheader_t **)luaL_checkudata(L, 1, META_CUP)))->id) + 1; + } + else + i = 0; + + for (tempcup = kartcupheaders; tempcup->id < numkartcupheaders; tempcup = tempcup->next) + { + if (tempcup->next == NULL) + break; + + if (tempcup->id >= i) + break; + } + + // cups are always valid, only added, never removed + if (i < numkartcupheaders) + { + LUA_PushUserdata(L, tempcup, META_CUP); + return 1; + } + + return 0; +} + +// Shamelessly copied and edited from lua_waypointslib.c (with thanks to JugadorXEI) +static int lib_getCupheader(lua_State *L) +{ + const char *field; + size_t i; + cupheader_t *checkcup; + UINT16 getResult = GETCUPERR; + + // find cup by id number + if (lua_type(L, 2) == LUA_TNUMBER) + { + i = luaL_checkinteger(L, 2); + if (i > numkartcupheaders) + return luaL_error(L, "cupheader_t id %d out of loaded range (0 - %d)", i, numkartcupheaders); + getResult = LUA_GetCupByNum(i); + if (getResult == GETCUPERR) + return luaL_error(L, "cupheader_t id %d invalid", i); + for (checkcup = kartcupheaders; checkcup->id < numkartcupheaders; checkcup = checkcup->next) + { + if (checkcup->id != getResult) + continue; + else + break; + + return luaL_error(L, "cupheader_t id %d invalid (LUA_GetCupByNum failed?)", i); + } + LUA_PushUserdata(L, checkcup, META_CUP); + return 1; + } + + field = luaL_checkstring(L, 2); + + // special function iterate + if (fastcmp(field,"iterate")) + { + lua_pushcfunction(L, lib_iterateCups); + return 1; + } + + if (lua_type(L, 2) == LUA_TSTRING) + { + getResult = LUA_GetCupByName(field); + if (getResult == GETCUPERR) + return luaL_error(L, "no cupheader_t with name %s", field); + } + + // If, after all this... + if (getResult == GETCUPERR) + return luaL_error(L, "internal failure in lua_grandprixlib.c???"); + + for (checkcup = kartcupheaders; checkcup->id < numkartcupheaders; checkcup = checkcup->next) + { + if (checkcup->id != getResult) + continue; + else + break; + + return luaL_error(L, "cupheader_t id %d invalid (LUA_GetCupByName failed?)", i); + } + + LUA_PushUserdata(L, checkcup, META_CUP); + return 1; +} + +int LUA_GrandPrixLib(lua_State *L) +{ + lua_newuserdata(L, 0); + lua_createtable(L, 0, 2); + lua_pushcfunction(L, grandprix_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, grandprix_set); + lua_setfield(L, -2, "__newindex"); + lua_setmetatable(L, -2); + lua_setglobal(L, "grandprixinfo"); + + lua_newuserdata(L, 0); + lua_createtable(L, 0, 2); + lua_pushcfunction(L, roundcue_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, roundcue_set); + lua_setfield(L, -2, "__newindex"); + lua_setmetatable(L, -2); + lua_setglobal(L, "roundqueue"); + + luaL_newmetatable(L, META_CUP); + lua_pushcfunction(L, cup_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, cup_set); + lua_setfield(L, -2, "__newindex"); + lua_pop(L,1); + + luaL_newmetatable(L, META_GPRANK); + lua_pushcfunction(L, gprank_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, gprank_set); + lua_setfield(L, -2, "__newindex"); + lua_pop(L,1); + + luaL_newmetatable(L, META_GPRANKLEVEL); + lua_pushcfunction(L, gprank_level_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, gprank_level_set); + lua_setfield(L, -2, "__newindex"); + lua_pop(L,1); + + luaL_newmetatable(L, META_GPRANKLEVELPERPLAYER); + lua_pushcfunction(L, gprank_level_perplayer_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, gprank_level_perplayer_set); + lua_setfield(L, -2, "__newindex"); + lua_pop(L,1); + + luaL_newmetatable(L, META_ROUNDENTRY); + lua_pushcfunction(L, roundentry_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, roundentry_set); + lua_setfield(L, -2, "__newindex"); + lua_pop(L,1); + + lua_newuserdata(L, 0); + lua_createtable(L, 0, 2); + lua_pushcfunction(L, lib_getCupheader); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, lib_numCupheaders); + lua_setfield(L, -2, "__len"); + lua_setmetatable(L, -2); + lua_setglobal(L, "cups"); + + return 0; +} \ No newline at end of file diff --git a/src/lua_libs.h b/src/lua_libs.h index c40cedb70..8e2c85c0e 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -122,6 +122,12 @@ extern lua_State *gL; #define META_ICECUBEVARS "ICECUBEVARS_T*" #define META_SKYBOX "SKYBOX_T*" +#define META_CUP "CUPHEADER_T*" +#define META_GPRANK "GPRANK_T*" +#define META_GPRANKLEVEL "GPRANK_LEVEL_T*" +#define META_GPRANKLEVELPERPLAYER "GPRANK_LEVEL_PERPLAYER_T*" +#define META_ROUNDENTRY "ROUNDENTRY_T*" + boolean luaL_checkboolean(lua_State *L, int narg); int LUA_EnumLib(lua_State *L); @@ -146,6 +152,7 @@ int LUA_BotVarsLib(lua_State *L); int LUA_TerrainLib(lua_State *L); int LUA_RespawnVarsLib(lua_State *L); int LUA_WaypointLib(lua_State *L); +int LUA_GrandPrixLib(lua_State *L); #ifdef __cplusplus } // extern "C" diff --git a/src/lua_script.c b/src/lua_script.c index b0ba602f4..51cef5129 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -66,6 +66,7 @@ static lua_CFunction liblist[] = { LUA_TerrainLib, // t_splash_t, t_footstep_t, t_overlay_t, terrain_t LUA_RespawnVarsLib, // respawnvars_t LUA_WaypointLib, // waypoint_t + LUA_GrandPrixLib, // grandprixinfo, cupheader_t, gprank_t, skinrecord_t, etc. NULL };