c751971d57
Apart from the fact that UnArchiveValue reads UINT16 for both anyway (which alone causes problems), but UINT8 isn't even enough to store the higher end of the object types list and definitely most of the states welp
1002 lines
23 KiB
C
1002 lines
23 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2012-2016 by John "JTE" Muniz.
|
|
// Copyright (C) 2012-2016 by Sonic Team Junior.
|
|
//
|
|
// 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_script.c
|
|
/// \brief Lua scripting basics
|
|
|
|
#include "doomdef.h"
|
|
#ifdef HAVE_BLUA
|
|
#include "fastcmp.h"
|
|
#include "dehacked.h"
|
|
#include "z_zone.h"
|
|
#include "w_wad.h"
|
|
#include "p_setup.h"
|
|
#include "r_state.h"
|
|
#include "g_game.h"
|
|
#include "byteptr.h"
|
|
#include "p_saveg.h"
|
|
#include "p_local.h"
|
|
#ifdef LUA_ALLOW_BYTECODE
|
|
#include "d_netfil.h" // for LUA_DumpFile
|
|
#endif
|
|
|
|
#include "lua_script.h"
|
|
#include "lua_libs.h"
|
|
#include "lua_hook.h"
|
|
|
|
#include "doomstat.h"
|
|
|
|
lua_State *gL = NULL;
|
|
|
|
// List of internal libraries to load from SRB2
|
|
static lua_CFunction liblist[] = {
|
|
LUA_EnumLib, // global metatable for enums
|
|
LUA_SOCLib, // A_Action functions, freeslot
|
|
LUA_BaseLib, // string concatination by +, CONS_Printf, p_local.h stuff (P_InstaThrust, P_Move), etc.
|
|
LUA_MathLib, // fixed_t and angle_t math functions
|
|
LUA_HookLib, // hookAdd and hook-calling functions
|
|
LUA_ConsoleLib, // console command/variable functions and structs
|
|
LUA_InfoLib, // info.h stuff: mobjinfo_t, mobjinfo[], state_t, states[]
|
|
LUA_MobjLib, // mobj_t, mapthing_t
|
|
LUA_PlayerLib, // player_t
|
|
LUA_SkinLib, // skin_t, skins[]
|
|
LUA_ThinkerLib, // thinker_t
|
|
LUA_MapLib, // line_t, side_t, sector_t, subsector_t
|
|
LUA_HudLib, // HUD stuff
|
|
NULL
|
|
};
|
|
|
|
// Lua asks for memory using this.
|
|
static void *LUA_Alloc(void *ud, void *ptr, size_t osize, size_t nsize)
|
|
{
|
|
(void)ud;
|
|
if (nsize == 0) {
|
|
if (osize != 0)
|
|
Z_Free(ptr);
|
|
return NULL;
|
|
} else
|
|
return Z_Realloc(ptr, nsize, PU_LUA, NULL);
|
|
}
|
|
|
|
// Panic function Lua calls when there's an unprotected error.
|
|
// This function cannot return. Lua would kill the application anyway if it did.
|
|
FUNCNORETURN static int LUA_Panic(lua_State *L)
|
|
{
|
|
CONS_Alert(CONS_ERROR,"LUA PANIC! %s\n",lua_tostring(L,-1));
|
|
I_Error("An unfortunate Lua processing error occurred in the exe itself. This is not a scripting error on your part.");
|
|
#ifndef __GNUC__
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
// This function decides which global variables you are allowed to set.
|
|
static int noglobals(lua_State *L)
|
|
{
|
|
const char *csname;
|
|
char *name;
|
|
|
|
lua_remove(L, 1); // we're not gonna be using _G
|
|
csname = lua_tostring(L, 1);
|
|
|
|
// make an uppercase copy of the name
|
|
name = Z_StrDup(csname);
|
|
strupr(name);
|
|
|
|
if (fastncmp(name, "A_", 2) && lua_isfunction(L, 2))
|
|
{
|
|
// Accept new A_Action functions
|
|
// Add the action to Lua actions refrence table
|
|
lua_getfield(L, LUA_REGISTRYINDEX, LREG_ACTIONS);
|
|
lua_pushstring(L, name); // "A_ACTION"
|
|
lua_pushvalue(L, 2); // function
|
|
lua_rawset(L, -3); // rawset doesn't trigger this metatable again.
|
|
// otherwise we would've used setfield, obviously.
|
|
|
|
Z_Free(name);
|
|
return 0;
|
|
}
|
|
|
|
Z_Free(name);
|
|
return luaL_error(L, "Implicit global " LUA_QS " prevented. Create a local variable instead.", csname);
|
|
}
|
|
|
|
// Clear and create a new Lua state, laddo!
|
|
// There's SCRIPTIN to be had!
|
|
static void LUA_ClearState(void)
|
|
{
|
|
lua_State *L;
|
|
int i;
|
|
|
|
// close previous state
|
|
if (gL)
|
|
lua_close(gL);
|
|
gL = NULL;
|
|
|
|
CONS_Printf(M_GetText("Pardon me while I initialize the Lua scripting interface...\n"));
|
|
|
|
// allocate state
|
|
L = lua_newstate(LUA_Alloc, NULL);
|
|
lua_atpanic(L, LUA_Panic);
|
|
|
|
// open base libraries
|
|
luaL_openlibs(L);
|
|
lua_pop(L, -1);
|
|
|
|
// make LREG_VALID table for all pushed userdata cache.
|
|
lua_newtable(L);
|
|
lua_setfield(L, LUA_REGISTRYINDEX, LREG_VALID);
|
|
|
|
// open srb2 libraries
|
|
for(i = 0; liblist[i]; i++) {
|
|
lua_pushcfunction(L, liblist[i]);
|
|
lua_call(L, 0, 0);
|
|
}
|
|
|
|
// lock the global namespace
|
|
lua_getmetatable(L, LUA_GLOBALSINDEX);
|
|
lua_pushcfunction(L, noglobals);
|
|
lua_setfield(L, -2, "__newindex");
|
|
lua_newtable(L);
|
|
lua_setfield(L, -2, "__metatable");
|
|
lua_pop(L, 1);
|
|
|
|
// lua state is ready!
|
|
gL = L;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
void LUA_ClearExtVars(void)
|
|
{
|
|
if (!gL)
|
|
return;
|
|
lua_newtable(gL);
|
|
lua_setfield(gL, LUA_REGISTRYINDEX, LREG_EXTVARS);
|
|
}
|
|
#endif
|
|
|
|
// Load a script from a MYFILE
|
|
static inline void LUA_LoadFile(MYFILE *f, char *name)
|
|
{
|
|
if (!name)
|
|
name = wadfiles[f->wad]->filename;
|
|
CONS_Printf("Loading Lua script from %s\n", name);
|
|
if (!gL) // Lua needs to be initialized
|
|
LUA_ClearState();
|
|
lua_pushinteger(gL, f->wad);
|
|
lua_setfield(gL, LUA_REGISTRYINDEX, "WAD");
|
|
if (luaL_loadbuffer(gL, f->data, f->size, va("@%s",name)) || lua_pcall(gL, 0, 0, 0)) {
|
|
CONS_Alert(CONS_WARNING,"%s\n",lua_tostring(gL,-1));
|
|
lua_pop(gL,1);
|
|
}
|
|
lua_gc(gL, LUA_GCCOLLECT, 0);
|
|
}
|
|
|
|
// Load a script from a lump
|
|
void LUA_LoadLump(UINT16 wad, UINT16 lump)
|
|
{
|
|
MYFILE f;
|
|
char *name;
|
|
f.wad = wad;
|
|
f.size = W_LumpLengthPwad(wad, lump);
|
|
f.data = Z_Malloc(f.size, PU_LUA, NULL);
|
|
W_ReadLumpPwad(wad, lump, f.data);
|
|
f.curpos = f.data;
|
|
|
|
name = malloc(strlen(wadfiles[wad]->filename)+10);
|
|
strcpy(name, wadfiles[wad]->filename);
|
|
if (!fasticmp(&name[strlen(name) - 4], ".lua")) {
|
|
// If it's not a .lua file, copy the lump name in too.
|
|
name[strlen(wadfiles[wad]->filename)] = '|';
|
|
M_Memcpy(name+strlen(wadfiles[wad]->filename)+1, wadfiles[wad]->lumpinfo[lump].name, 8);
|
|
name[strlen(wadfiles[wad]->filename)+9] = '\0';
|
|
}
|
|
|
|
LUA_LoadFile(&f, name);
|
|
|
|
free(name);
|
|
Z_Free(f.data);
|
|
}
|
|
|
|
#ifdef LUA_ALLOW_BYTECODE
|
|
// must match lua_Writer
|
|
static int dumpWriter(lua_State *L, const void *p, size_t sz, void *ud)
|
|
{
|
|
FILE *handle = (FILE*)ud;
|
|
I_Assert(handle != NULL);
|
|
(void)L;
|
|
if (!sz) return 0; // nothing to write? can't fail that! :D
|
|
return (fwrite(p, 1, sz, handle) != sz); // if fwrite != sz, we've failed.
|
|
}
|
|
|
|
// Compile a script by name and dump it back to disk.
|
|
void LUA_DumpFile(const char *filename)
|
|
{
|
|
FILE *handle;
|
|
char filenamebuf[MAX_WADPATH];
|
|
|
|
if (!gL) // Lua needs to be initialized
|
|
LUA_ClearState(false);
|
|
|
|
// find the file the SRB2 way
|
|
strncpy(filenamebuf, filename, MAX_WADPATH);
|
|
filenamebuf[MAX_WADPATH - 1] = '\0';
|
|
filename = filenamebuf;
|
|
if ((handle = fopen(filename, "rb")) == NULL)
|
|
{
|
|
// If we failed to load the file with the path as specified by
|
|
// the user, strip the directories and search for the file.
|
|
nameonly(filenamebuf);
|
|
|
|
// If findfile finds the file, the full path will be returned
|
|
// in filenamebuf == filename.
|
|
if (findfile(filenamebuf, NULL, true))
|
|
{
|
|
if ((handle = fopen(filename, "rb")) == NULL)
|
|
{
|
|
CONS_Alert(CONS_ERROR, M_GetText("Can't open %s\n"), filename);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CONS_Alert(CONS_ERROR, M_GetText("File %s not found.\n"), filename);
|
|
return;
|
|
}
|
|
}
|
|
fclose(handle);
|
|
|
|
// pass the path we found to Lua
|
|
// luaL_loadfile will open and read the file in as a Lua function
|
|
if (luaL_loadfile(gL, filename)) {
|
|
CONS_Alert(CONS_ERROR,"%s\n",lua_tostring(gL,-1));
|
|
lua_pop(gL, 1);
|
|
return;
|
|
}
|
|
|
|
// dump it back to disk
|
|
if ((handle = fopen(filename, "wb")) == NULL)
|
|
CONS_Alert(CONS_ERROR, M_GetText("Can't write to %s\n"), filename);
|
|
if (lua_dump(gL, dumpWriter, handle))
|
|
CONS_Printf("Failed while writing %s to disk... Sorry!\n", filename);
|
|
else
|
|
CONS_Printf("Successfully compiled %s into bytecode.\n", filename);
|
|
fclose(handle);
|
|
lua_pop(gL, 1); // function is still on stack after lua_dump
|
|
lua_gc(gL, LUA_GCCOLLECT, 0);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
fixed_t LUA_EvalMath(const char *word)
|
|
{
|
|
lua_State *L = NULL;
|
|
char buf[1024], *b;
|
|
const char *p;
|
|
fixed_t res = 0;
|
|
|
|
// make a new state so SOC can't interefere with scripts
|
|
// allocate state
|
|
L = lua_newstate(LUA_Alloc, NULL);
|
|
lua_atpanic(L, LUA_Panic);
|
|
|
|
// open only enum lib
|
|
lua_pushcfunction(L, LUA_EnumLib);
|
|
lua_pushboolean(L, true);
|
|
lua_call(L, 1, 0);
|
|
|
|
// change ^ into ^^ for Lua.
|
|
strcpy(buf, "return ");
|
|
b = buf+strlen(buf);
|
|
for (p = word; *p && b < &buf[1022]; p++)
|
|
{
|
|
*b++ = *p;
|
|
if (*p == '^')
|
|
*b++ = '^';
|
|
}
|
|
*b = '\0';
|
|
|
|
// eval string.
|
|
lua_pop(L, -1);
|
|
if (luaL_dostring(L, buf))
|
|
{
|
|
p = lua_tostring(L, -1);
|
|
while (*p++ != ':' && *p) ;
|
|
p += 3; // "1: "
|
|
CONS_Alert(CONS_WARNING, "%s\n", p);
|
|
}
|
|
else
|
|
res = lua_tointeger(L, -1);
|
|
|
|
// clean up and return.
|
|
lua_close(L);
|
|
return res;
|
|
}
|
|
|
|
// Takes a pointer, any pointer, and a metatable name
|
|
// Creates a userdata for that pointer with the given metatable
|
|
// Pushes it to the stack and stores it in the registry.
|
|
void LUA_PushUserdata(lua_State *L, void *data, const char *meta)
|
|
{
|
|
void **userdata;
|
|
|
|
if (!data) { // push a NULL
|
|
lua_pushnil(L);
|
|
return;
|
|
}
|
|
|
|
lua_getfield(L, LUA_REGISTRYINDEX, LREG_VALID);
|
|
I_Assert(lua_istable(L, -1));
|
|
lua_pushlightuserdata(L, data);
|
|
lua_rawget(L, -2);
|
|
if (lua_isnil(L, -1)) { // no userdata? deary me, we'll have to make one.
|
|
lua_pop(L, 1); // pop the nil
|
|
|
|
// create the userdata
|
|
userdata = lua_newuserdata(L, sizeof(void *));
|
|
*userdata = data;
|
|
luaL_getmetatable(L, meta);
|
|
lua_setmetatable(L, -2);
|
|
|
|
// Set it in the registry so we can find it again
|
|
lua_pushlightuserdata(L, data); // k (store the userdata via the data's pointer)
|
|
lua_pushvalue(L, -2); // v (copy of the userdata)
|
|
lua_rawset(L, -4);
|
|
|
|
// stack is left with the userdata on top, as if getting it had originally succeeded.
|
|
}
|
|
lua_remove(L, -2); // remove LREG_VALID
|
|
}
|
|
|
|
// When userdata is freed, use this function to remove it from Lua.
|
|
void LUA_InvalidateUserdata(void *data)
|
|
{
|
|
void **userdata;
|
|
if (!gL)
|
|
return;
|
|
|
|
// fetch the userdata
|
|
lua_getfield(gL, LUA_REGISTRYINDEX, LREG_VALID);
|
|
I_Assert(lua_istable(gL, -1));
|
|
lua_pushlightuserdata(gL, data);
|
|
lua_rawget(gL, -2);
|
|
if (lua_isnil(gL, -1)) { // not found, not in lua
|
|
lua_pop(gL, 2); // pop nil and LREG_VALID
|
|
return;
|
|
}
|
|
|
|
// nullify any additional data
|
|
lua_getfield(gL, LUA_REGISTRYINDEX, LREG_EXTVARS);
|
|
I_Assert(lua_istable(gL, -1));
|
|
lua_pushlightuserdata(gL, data);
|
|
lua_pushnil(gL);
|
|
lua_rawset(gL, -3);
|
|
lua_pop(gL, 1);
|
|
|
|
// invalidate the userdata
|
|
userdata = lua_touserdata(gL, -1);
|
|
*userdata = NULL;
|
|
lua_pop(gL, 1);
|
|
|
|
// remove it from the registry
|
|
lua_pushlightuserdata(gL, data);
|
|
lua_pushnil(gL);
|
|
lua_rawset(gL, -3);
|
|
lua_pop(gL, 1); // pop LREG_VALID
|
|
}
|
|
|
|
// Invalidate level data arrays
|
|
void LUA_InvalidateLevel(void)
|
|
{
|
|
thinker_t *th;
|
|
size_t i;
|
|
if (!gL)
|
|
return;
|
|
|
|
for (th = thinkercap.next; th && th != &thinkercap; th = th->next)
|
|
LUA_InvalidateUserdata(th);
|
|
|
|
LUA_InvalidateMapthings();
|
|
|
|
for (i = 0; i < numsubsectors; i++)
|
|
LUA_InvalidateUserdata(&subsectors[i]);
|
|
for (i = 0; i < numsectors; i++)
|
|
LUA_InvalidateUserdata(§ors[i]);
|
|
for (i = 0; i < numlines; i++)
|
|
{
|
|
LUA_InvalidateUserdata(&lines[i]);
|
|
LUA_InvalidateUserdata(lines[i].sidenum);
|
|
}
|
|
for (i = 0; i < numsides; i++)
|
|
LUA_InvalidateUserdata(&sides[i]);
|
|
for (i = 0; i < numvertexes; i++)
|
|
LUA_InvalidateUserdata(&vertexes[i]);
|
|
}
|
|
|
|
void LUA_InvalidateMapthings(void)
|
|
{
|
|
size_t i;
|
|
if (!gL)
|
|
return;
|
|
|
|
for (i = 0; i < nummapthings; i++)
|
|
LUA_InvalidateUserdata(&mapthings[i]);
|
|
}
|
|
|
|
void LUA_InvalidatePlayer(player_t *player)
|
|
{
|
|
if (!gL)
|
|
return;
|
|
LUA_InvalidateUserdata(player);
|
|
LUA_InvalidateUserdata(player->powers);
|
|
LUA_InvalidateUserdata(&player->cmd);
|
|
}
|
|
|
|
enum
|
|
{
|
|
ARCH_NULL=0,
|
|
ARCH_BOOLEAN,
|
|
ARCH_SIGNED,
|
|
ARCH_STRING,
|
|
ARCH_TABLE,
|
|
|
|
ARCH_MOBJINFO,
|
|
ARCH_STATE,
|
|
ARCH_MOBJ,
|
|
ARCH_PLAYER,
|
|
ARCH_MAPTHING,
|
|
ARCH_VERTEX,
|
|
ARCH_LINE,
|
|
ARCH_SIDE,
|
|
ARCH_SUBSECTOR,
|
|
ARCH_SECTOR,
|
|
ARCH_MAPHEADER,
|
|
|
|
ARCH_TEND=0xFF,
|
|
};
|
|
|
|
static const struct {
|
|
const char *meta;
|
|
UINT8 arch;
|
|
} meta2arch[] = {
|
|
{META_MOBJINFO, ARCH_MOBJINFO},
|
|
{META_STATE, ARCH_STATE},
|
|
{META_MOBJ, ARCH_MOBJ},
|
|
{META_PLAYER, ARCH_PLAYER},
|
|
{META_MAPTHING, ARCH_MAPTHING},
|
|
{META_VERTEX, ARCH_VERTEX},
|
|
{META_LINE, ARCH_LINE},
|
|
{META_SIDE, ARCH_SIDE},
|
|
{META_SUBSECTOR,ARCH_SUBSECTOR},
|
|
{META_SECTOR, ARCH_SECTOR},
|
|
{META_MAPHEADER, ARCH_MAPHEADER},
|
|
{NULL, ARCH_NULL}
|
|
};
|
|
|
|
static UINT8 GetUserdataArchType(void)
|
|
{
|
|
UINT8 i;
|
|
lua_getmetatable(gL, -1);
|
|
|
|
for (i = 0; meta2arch[i].meta; i++)
|
|
{
|
|
luaL_getmetatable(gL, meta2arch[i].meta);
|
|
if (lua_rawequal(gL, -1, -2))
|
|
{
|
|
lua_pop(gL, 2);
|
|
return meta2arch[i].arch;
|
|
}
|
|
lua_pop(gL, 1);
|
|
}
|
|
|
|
lua_pop(gL, 1);
|
|
return ARCH_NULL;
|
|
}
|
|
|
|
static UINT8 ArchiveValue(int TABLESINDEX, int myindex)
|
|
{
|
|
if (myindex < 0)
|
|
myindex = lua_gettop(gL)+1+myindex;
|
|
switch (lua_type(gL, myindex))
|
|
{
|
|
case LUA_TNONE:
|
|
case LUA_TNIL:
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
break;
|
|
// This might be a problem. D:
|
|
case LUA_TLIGHTUSERDATA:
|
|
case LUA_TTHREAD:
|
|
case LUA_TFUNCTION:
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
return 2;
|
|
case LUA_TBOOLEAN:
|
|
WRITEUINT8(save_p, ARCH_BOOLEAN);
|
|
WRITEUINT8(save_p, lua_toboolean(gL, myindex));
|
|
break;
|
|
case LUA_TNUMBER:
|
|
{
|
|
lua_Integer number = lua_tointeger(gL, myindex);
|
|
WRITEUINT8(save_p, ARCH_SIGNED);
|
|
WRITEFIXED(save_p, number);
|
|
break;
|
|
}
|
|
case LUA_TSTRING:
|
|
WRITEUINT8(save_p, ARCH_STRING);
|
|
WRITESTRING(save_p, lua_tostring(gL, myindex));
|
|
break;
|
|
case LUA_TTABLE:
|
|
{
|
|
boolean found = false;
|
|
INT32 i;
|
|
UINT16 t = (UINT16)lua_objlen(gL, TABLESINDEX);
|
|
|
|
for (i = 1; i <= t && !found; i++)
|
|
{
|
|
lua_rawgeti(gL, TABLESINDEX, i);
|
|
if (lua_rawequal(gL, myindex, -1))
|
|
{
|
|
t = i;
|
|
found = true;
|
|
}
|
|
lua_pop(gL, 1);
|
|
}
|
|
if (!found)
|
|
t++;
|
|
|
|
WRITEUINT8(save_p, ARCH_TABLE);
|
|
WRITEUINT16(save_p, t);
|
|
|
|
if (!found)
|
|
{
|
|
lua_pushvalue(gL, myindex);
|
|
lua_rawseti(gL, TABLESINDEX, t);
|
|
return 1;
|
|
}
|
|
break;
|
|
}
|
|
case LUA_TUSERDATA:
|
|
switch (GetUserdataArchType())
|
|
{
|
|
case ARCH_MOBJINFO:
|
|
{
|
|
mobjinfo_t *info = *((mobjinfo_t **)lua_touserdata(gL, myindex));
|
|
WRITEUINT8(save_p, ARCH_MOBJINFO);
|
|
WRITEUINT16(save_p, info - mobjinfo);
|
|
break;
|
|
}
|
|
case ARCH_STATE:
|
|
{
|
|
state_t *state = *((state_t **)lua_touserdata(gL, myindex));
|
|
WRITEUINT8(save_p, ARCH_STATE);
|
|
WRITEUINT16(save_p, state - states);
|
|
break;
|
|
}
|
|
case ARCH_MOBJ:
|
|
{
|
|
mobj_t *mobj = *((mobj_t **)lua_touserdata(gL, myindex));
|
|
if (!mobj)
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
else {
|
|
WRITEUINT8(save_p, ARCH_MOBJ);
|
|
WRITEUINT32(save_p, mobj->mobjnum);
|
|
}
|
|
break;
|
|
}
|
|
case ARCH_PLAYER:
|
|
{
|
|
player_t *player = *((player_t **)lua_touserdata(gL, myindex));
|
|
if (!player)
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
else {
|
|
WRITEUINT8(save_p, ARCH_PLAYER);
|
|
WRITEUINT8(save_p, player - players);
|
|
}
|
|
break;
|
|
}
|
|
case ARCH_MAPTHING:
|
|
{
|
|
mapthing_t *mapthing = *((mapthing_t **)lua_touserdata(gL, myindex));
|
|
if (!mapthing)
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
else {
|
|
WRITEUINT8(save_p, ARCH_MAPTHING);
|
|
WRITEUINT16(save_p, mapthing - mapthings);
|
|
}
|
|
break;
|
|
}
|
|
case ARCH_VERTEX:
|
|
{
|
|
vertex_t *vertex = *((vertex_t **)lua_touserdata(gL, myindex));
|
|
if (!vertex)
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
else {
|
|
WRITEUINT8(save_p, ARCH_VERTEX);
|
|
WRITEUINT16(save_p, vertex - vertexes);
|
|
}
|
|
break;
|
|
}
|
|
case ARCH_LINE:
|
|
{
|
|
line_t *line = *((line_t **)lua_touserdata(gL, myindex));
|
|
if (!line)
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
else {
|
|
WRITEUINT8(save_p, ARCH_LINE);
|
|
WRITEUINT16(save_p, line - lines);
|
|
}
|
|
break;
|
|
}
|
|
case ARCH_SIDE:
|
|
{
|
|
side_t *side = *((side_t **)lua_touserdata(gL, myindex));
|
|
if (!side)
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
else {
|
|
WRITEUINT8(save_p, ARCH_SIDE);
|
|
WRITEUINT16(save_p, side - sides);
|
|
}
|
|
break;
|
|
}
|
|
case ARCH_SUBSECTOR:
|
|
{
|
|
subsector_t *subsector = *((subsector_t **)lua_touserdata(gL, myindex));
|
|
if (!subsector)
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
else {
|
|
WRITEUINT8(save_p, ARCH_SUBSECTOR);
|
|
WRITEUINT16(save_p, subsector - subsectors);
|
|
}
|
|
break;
|
|
}
|
|
case ARCH_SECTOR:
|
|
{
|
|
sector_t *sector = *((sector_t **)lua_touserdata(gL, myindex));
|
|
if (!sector)
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
else {
|
|
WRITEUINT8(save_p, ARCH_SECTOR);
|
|
WRITEUINT16(save_p, sector - sectors);
|
|
}
|
|
break;
|
|
}
|
|
case ARCH_MAPHEADER:
|
|
{
|
|
mapheader_t *header = *((mapheader_t **)lua_touserdata(gL, myindex));
|
|
if (!header)
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
else {
|
|
WRITEUINT8(save_p, ARCH_MAPHEADER);
|
|
WRITEUINT16(save_p, header - *mapheaderinfo);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
WRITEUINT8(save_p, ARCH_NULL);
|
|
return 2;
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ArchiveExtVars(void *pointer, const char *ptype)
|
|
{
|
|
int TABLESINDEX;
|
|
UINT16 i;
|
|
|
|
if (!gL) {
|
|
if (fastcmp(ptype,"player")) // players must always be included, even if no vars
|
|
WRITEUINT16(save_p, 0);
|
|
return;
|
|
}
|
|
|
|
TABLESINDEX = lua_gettop(gL);
|
|
|
|
lua_getfield(gL, LUA_REGISTRYINDEX, LREG_EXTVARS);
|
|
I_Assert(lua_istable(gL, -1));
|
|
lua_pushlightuserdata(gL, pointer);
|
|
lua_rawget(gL, -2);
|
|
lua_remove(gL, -2); // pop LREG_EXTVARS
|
|
|
|
if (!lua_istable(gL, -1))
|
|
{ // no extra values table
|
|
lua_pop(gL, 1);
|
|
if (fastcmp(ptype,"player")) // players must always be included, even if no vars
|
|
WRITEUINT16(save_p, 0);
|
|
return;
|
|
}
|
|
|
|
lua_pushnil(gL);
|
|
for (i = 0; lua_next(gL, -2); i++)
|
|
lua_pop(gL, 1);
|
|
|
|
if (i == 0 && !fastcmp(ptype,"player")) // skip anything that has an empty table and isn't a player.
|
|
return;
|
|
if (fastcmp(ptype,"mobj")) // mobjs must write their mobjnum as a header
|
|
WRITEUINT32(save_p, ((mobj_t *)pointer)->mobjnum);
|
|
WRITEUINT16(save_p, i);
|
|
lua_pushnil(gL);
|
|
while (lua_next(gL, -2))
|
|
{
|
|
I_Assert(lua_type(gL, -2) == LUA_TSTRING);
|
|
WRITESTRING(save_p, lua_tostring(gL, -2));
|
|
if (ArchiveValue(TABLESINDEX, -1) == 2)
|
|
CONS_Alert(CONS_ERROR, "Type of value for %s entry '%s' (%s) could not be archived!\n", ptype, lua_tostring(gL, -2), luaL_typename(gL, -1));
|
|
lua_pop(gL, 1);
|
|
}
|
|
|
|
lua_pop(gL, 1);
|
|
}
|
|
|
|
static int NetArchive(lua_State *L)
|
|
{
|
|
int TABLESINDEX = lua_upvalueindex(1);
|
|
int i, n = lua_gettop(L);
|
|
for (i = 1; i <= n; i++)
|
|
ArchiveValue(TABLESINDEX, i);
|
|
return n;
|
|
}
|
|
|
|
static void ArchiveTables(void)
|
|
{
|
|
int TABLESINDEX;
|
|
UINT16 i, n;
|
|
UINT8 e;
|
|
|
|
if (!gL)
|
|
return;
|
|
|
|
TABLESINDEX = lua_gettop(gL);
|
|
|
|
n = (UINT16)lua_objlen(gL, TABLESINDEX);
|
|
for (i = 1; i <= n; i++)
|
|
{
|
|
lua_rawgeti(gL, TABLESINDEX, i);
|
|
lua_pushnil(gL);
|
|
while (lua_next(gL, -2))
|
|
{
|
|
ArchiveValue(TABLESINDEX, -2); // key should be either a number or a string, ArchiveValue can handle this.
|
|
e = ArchiveValue(TABLESINDEX, -1);
|
|
if (e == 1)
|
|
n++; // the table contained a new table we'll have to archive. :(
|
|
else if (e == 2)
|
|
{
|
|
lua_pushvalue(gL, -2);
|
|
CONS_Alert(CONS_ERROR, "Type of value for table %d entry '%s' (%s) could not be archived!\n", i, lua_tostring(gL, -1), luaL_typename(gL, -1));
|
|
lua_pop(gL, 1);
|
|
}
|
|
lua_pop(gL, 1);
|
|
}
|
|
lua_pop(gL, 1);
|
|
WRITEUINT8(save_p, ARCH_TEND);
|
|
}
|
|
}
|
|
|
|
static UINT8 UnArchiveValue(int TABLESINDEX)
|
|
{
|
|
UINT8 type = READUINT8(save_p);
|
|
switch (type)
|
|
{
|
|
case ARCH_NULL:
|
|
lua_pushnil(gL);
|
|
break;
|
|
case ARCH_BOOLEAN:
|
|
lua_pushboolean(gL, READUINT8(save_p));
|
|
break;
|
|
case ARCH_SIGNED:
|
|
lua_pushinteger(gL, READFIXED(save_p));
|
|
break;
|
|
case ARCH_STRING:
|
|
{
|
|
char value[1024];
|
|
READSTRING(save_p, value);
|
|
lua_pushstring(gL, value);
|
|
break;
|
|
}
|
|
case ARCH_TABLE:
|
|
{
|
|
UINT16 tid = READUINT16(save_p);
|
|
lua_rawgeti(gL, TABLESINDEX, tid);
|
|
if (lua_isnil(gL, -1))
|
|
{
|
|
lua_pop(gL, 1);
|
|
lua_newtable(gL);
|
|
lua_pushvalue(gL, -1);
|
|
lua_rawseti(gL, TABLESINDEX, tid);
|
|
return 2;
|
|
}
|
|
break;
|
|
}
|
|
case ARCH_MOBJINFO:
|
|
LUA_PushUserdata(gL, &mobjinfo[READUINT16(save_p)], META_MOBJINFO);
|
|
break;
|
|
case ARCH_STATE:
|
|
LUA_PushUserdata(gL, &states[READUINT16(save_p)], META_STATE);
|
|
break;
|
|
case ARCH_MOBJ:
|
|
LUA_PushUserdata(gL, P_FindNewPosition(READUINT32(save_p)), META_MOBJ);
|
|
break;
|
|
case ARCH_PLAYER:
|
|
LUA_PushUserdata(gL, &players[READUINT8(save_p)], META_PLAYER);
|
|
break;
|
|
case ARCH_MAPTHING:
|
|
LUA_PushUserdata(gL, &mapthings[READUINT16(save_p)], META_MAPTHING);
|
|
break;
|
|
case ARCH_VERTEX:
|
|
LUA_PushUserdata(gL, &vertexes[READUINT16(save_p)], META_VERTEX);
|
|
break;
|
|
case ARCH_LINE:
|
|
LUA_PushUserdata(gL, &lines[READUINT16(save_p)], META_LINE);
|
|
break;
|
|
case ARCH_SIDE:
|
|
LUA_PushUserdata(gL, &sides[READUINT16(save_p)], META_SIDE);
|
|
break;
|
|
case ARCH_SUBSECTOR:
|
|
LUA_PushUserdata(gL, &subsectors[READUINT16(save_p)], META_SUBSECTOR);
|
|
break;
|
|
case ARCH_SECTOR:
|
|
LUA_PushUserdata(gL, §ors[READUINT16(save_p)], META_SECTOR);
|
|
break;
|
|
case ARCH_MAPHEADER:
|
|
LUA_PushUserdata(gL, §ors[READUINT16(save_p)], META_MAPHEADER);
|
|
break;
|
|
case ARCH_TEND:
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void UnArchiveExtVars(void *pointer)
|
|
{
|
|
int TABLESINDEX;
|
|
UINT16 field_count = READUINT16(save_p);
|
|
UINT16 i;
|
|
char field[1024];
|
|
|
|
if (field_count == 0)
|
|
return;
|
|
I_Assert(gL != NULL);
|
|
|
|
TABLESINDEX = lua_gettop(gL);
|
|
lua_createtable(gL, 0, field_count); // pointer's ext vars subtable
|
|
|
|
for (i = 0; i < field_count; i++)
|
|
{
|
|
READSTRING(save_p, field);
|
|
UnArchiveValue(TABLESINDEX);
|
|
lua_setfield(gL, -2, field);
|
|
}
|
|
|
|
lua_getfield(gL, LUA_REGISTRYINDEX, LREG_EXTVARS);
|
|
I_Assert(lua_istable(gL, -1));
|
|
lua_pushlightuserdata(gL, pointer);
|
|
lua_pushvalue(gL, -3); // pointer's ext vars subtable
|
|
lua_rawset(gL, -3);
|
|
lua_pop(gL, 2); // pop LREG_EXTVARS and pointer's subtable
|
|
}
|
|
|
|
static int NetUnArchive(lua_State *L)
|
|
{
|
|
int TABLESINDEX = lua_upvalueindex(1);
|
|
int i, n = lua_gettop(L);
|
|
for (i = 1; i <= n; i++)
|
|
UnArchiveValue(TABLESINDEX);
|
|
return n;
|
|
}
|
|
|
|
static void UnArchiveTables(void)
|
|
{
|
|
int TABLESINDEX;
|
|
UINT16 i, n;
|
|
|
|
if (!gL)
|
|
return;
|
|
|
|
TABLESINDEX = lua_gettop(gL);
|
|
|
|
n = (UINT16)lua_objlen(gL, TABLESINDEX);
|
|
for (i = 1; i <= n; i++)
|
|
{
|
|
lua_rawgeti(gL, TABLESINDEX, i);
|
|
while (true)
|
|
{
|
|
if (UnArchiveValue(TABLESINDEX) == 1)
|
|
break;
|
|
if (UnArchiveValue(TABLESINDEX) == 2)
|
|
n++;
|
|
lua_rawset(gL, -3);
|
|
}
|
|
lua_pop(gL, 1);
|
|
}
|
|
}
|
|
|
|
void LUA_Step(void)
|
|
{
|
|
if (!gL)
|
|
return;
|
|
lua_settop(gL, 0);
|
|
lua_gc(gL, LUA_GCSTEP, 1);
|
|
}
|
|
|
|
void LUA_Archive(void)
|
|
{
|
|
INT32 i;
|
|
thinker_t *th;
|
|
|
|
if (gL)
|
|
lua_newtable(gL); // tables to be archived.
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i])
|
|
continue;
|
|
// all players in game will be archived, even if they just add a 0.
|
|
ArchiveExtVars(&players[i], "player");
|
|
}
|
|
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
if (th->function.acp1 == (actionf_p1)P_MobjThinker)
|
|
{
|
|
// archive function will determine when to skip mobjs,
|
|
// and write mobjnum in otherwise.
|
|
ArchiveExtVars(th, "mobj");
|
|
}
|
|
WRITEUINT32(save_p, UINT32_MAX); // end of mobjs marker, replaces mobjnum.
|
|
|
|
LUAh_NetArchiveHook(NetArchive); // call the NetArchive hook in archive mode
|
|
ArchiveTables();
|
|
|
|
if (gL)
|
|
lua_pop(gL, 1); // pop tables
|
|
}
|
|
|
|
void LUA_UnArchive(void)
|
|
{
|
|
UINT32 mobjnum;
|
|
INT32 i;
|
|
thinker_t *th;
|
|
|
|
if (gL)
|
|
lua_newtable(gL); // tables to be read
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (!playeringame[i])
|
|
continue;
|
|
UnArchiveExtVars(&players[i]);
|
|
}
|
|
|
|
do {
|
|
mobjnum = READUINT32(save_p); // read a mobjnum
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
if (th->function.acp1 == (actionf_p1)P_MobjThinker
|
|
&& ((mobj_t *)th)->mobjnum == mobjnum) // find matching mobj
|
|
UnArchiveExtVars(th); // apply variables
|
|
} while(mobjnum != UINT32_MAX); // repeat until end of mobjs marker.
|
|
|
|
LUAh_NetArchiveHook(NetUnArchive); // call the NetArchive hook in unarchive mode
|
|
UnArchiveTables();
|
|
|
|
if (gL)
|
|
lua_pop(gL, 1); // pop tables
|
|
}
|
|
|
|
// For mobj_t, player_t, etc. to take custom variables.
|
|
int Lua_optoption(lua_State *L, int narg,
|
|
const char *def, const char *const lst[])
|
|
{
|
|
const char *name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg);
|
|
int i;
|
|
for (i=0; lst[i]; i++)
|
|
if (fastcmp(lst[i], name))
|
|
return i;
|
|
return -1;
|
|
}
|
|
|
|
#endif // HAVE_BLUA
|