Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 74 additions & 14 deletions src/xrScriptEngine/script_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,79 @@ struct luajit
}
};

// Custom `require` loader resolving modules through the engine file system.
// Unlike the default package.path loader, it also finds scripts packed inside game archives,
// with loose gamedata files taking priority over archived ones,
// consistently with how the engine resolves every other game asset.
//
// Example:
// `local example = require("scripts.folder.file")` imports `gamedata\scripts\folder\file.script`
static int gamedata_module_loader(lua_State* L)
{
pcstr moduleName = lua_tostring(L, 1);

string_path fileName;
xr_strcpy(fileName, moduleName);
for (char* c = fileName; *c; ++c)
{
if (*c == '.')
*c = _DELIMITER;
}
xr_strcat(fileName, ".script");

string_path filePath;
FS.update_path(filePath, "$game_data$", fileName);

IReader* reader = FS.r_open(filePath);
if (!reader)
{
// Not an error: report the candidate path and let the remaining searchers try
lua_pushfstring(L, "\n\tno file '%s' in engine file system", filePath);
return 1;
}

string_path chunkName;
strconcat(sizeof(chunkName), chunkName, "@", filePath);

const int errorCode = luaL_loadbuffer(L, static_cast<LPCSTR>(reader->pointer()), reader->length(), chunkName);
FS.r_close(reader);

if (errorCode)
{
return lua_error(L);
}

return 1;
}

// Adds gamedata folder as module root for lua `require` and allows usage of built-in lua module system.
//
// Example:
// `local example = require("scripts.folder.file")` tries to import `gamedata\scripts\folder\file.script`
static void setup_gamedata_module_loading(lua_State* L)
{
// Engine file system loader, resolves modules packed inside game archives as well.
// Registered after package.preload (index 1), before the default package.path loader:
// table.insert(package.loaders, 2, gamedata_module_loader)
lua_getglobal(L, "table");
lua_getfield(L, -1, "insert");
lua_remove(L, -2);
lua_getglobal(L, "package");
lua_getfield(L, -1, "loaders");
lua_remove(L, -2);
lua_pushinteger(L, 2);
lua_pushcfunction(L, gamedata_module_loader);
lua_call(L, 3, 0);

string_path gamedataPath;
string_path packagePath;

FS.update_path(gamedataPath, "$game_data$", "?.script;");
xr_sprintf(packagePath, "package.path = package.path .. [[%s]]", gamedataPath);

luaL_dostring(L, packagePath);
}

void CScriptEngine::init(export_func exporter, bool loadGlobalNamespace)
{
ZoneScoped;
Expand Down Expand Up @@ -851,20 +924,7 @@ void CScriptEngine::init(export_func exporter, bool loadGlobalNamespace)
luaL_dostring(lua(), mathRandom);
}

// Adds gamedata folder as module root for lua `require` and allows usage of built-in lua module system.
// Notes:
// - Does not resolve files inside archived game files
// Example:
// `local example = require("scripts.folder.file")` tries to import `gamedata\scripts\folder\file.script`
{
string_path gamedataPath;
string_path packagePath;

FS.update_path(gamedataPath, "$game_data$", "?.script;");
xr_sprintf(packagePath, "package.path = package.path .. [[%s]]", gamedataPath);

luaL_dostring(lua(), packagePath);
}
setup_gamedata_module_loading(lua());

// XXX nitrocaster: with vanilla scripts, '-nojit' option requires script profiler to be disabled. The reason
// is that lua hooks somehow make 'super' global unavailable (is's used all over the vanilla scripts).
Expand Down
Loading