mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-22 22:13:35 +02:00
612 lines
11 KiB
C++
612 lines
11 KiB
C++
/*
|
|
* LuaScriptingContext.cpp, part of VCMI engine
|
|
*
|
|
* Authors: listed in file AUTHORS in main folder
|
|
*
|
|
* License: GNU General Public License v2.0 or later
|
|
* Full text of license available in license.txt file, in main folder
|
|
*
|
|
*/
|
|
#include "StdInc.h"
|
|
|
|
#include "LuaScriptingContext.h"
|
|
|
|
#include <vstd/StringUtils.h>
|
|
#include <vcmi/events/EventBus.h>
|
|
#include <vcmi/ServerCallback.h>
|
|
#include "vcmi/Services.h"
|
|
|
|
#include "LuaStack.h"
|
|
|
|
#include "api/Registry.h"
|
|
|
|
#include "../../lib/json/JsonNode.h"
|
|
#include "../../lib/filesystem/Filesystem.h"
|
|
#include "../../lib/battle/IBattleInfoCallback.h"
|
|
#include "../../lib/CGameInfoCallback.h"
|
|
#include "../../lib/modding/ModScope.h"
|
|
|
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
namespace scripting
|
|
{
|
|
|
|
const std::string LuaContext::STATE_FIELD = "DATA";
|
|
|
|
LuaContext::LuaContext(const Script * source, const Environment * env_):
|
|
ContextBase(env_->logger()),
|
|
L(luaL_newstate()),
|
|
script(source),
|
|
env(env_)
|
|
{
|
|
static const std::vector<luaL_Reg> STD_LIBS =
|
|
{
|
|
{"", luaopen_base},
|
|
{LUA_TABLIBNAME, luaopen_table},
|
|
{LUA_STRLIBNAME, luaopen_string},
|
|
{LUA_MATHLIBNAME, luaopen_math},
|
|
{LUA_BITLIBNAME, luaopen_bit}
|
|
};
|
|
|
|
for(const luaL_Reg & lib : STD_LIBS)
|
|
{
|
|
lua_pushcfunction(L, lib.func);
|
|
lua_pushstring(L, lib.name);
|
|
lua_call(L, 1, 0);
|
|
}
|
|
|
|
popAll();
|
|
|
|
cleanupGlobals();
|
|
|
|
popAll();
|
|
|
|
lua_newtable(L);
|
|
modules = std::make_shared<LuaReference>(L);
|
|
popAll();
|
|
|
|
registerCore();
|
|
|
|
popAll();
|
|
|
|
LuaStack S(L);
|
|
|
|
S.push(env->game());
|
|
lua_setglobal(L, "GAME");
|
|
|
|
S.push(env->battle(BattleID::NONE));
|
|
lua_setglobal(L, "BATTLE");
|
|
|
|
S.push(env->eventBus());
|
|
lua_setglobal(L, "EVENT_BUS");
|
|
|
|
S.push(env->services());
|
|
lua_setglobal(L, "SERVICES");
|
|
|
|
popAll();
|
|
}
|
|
|
|
LuaContext::~LuaContext()
|
|
{
|
|
modules.reset();
|
|
scriptClosure.reset();
|
|
lua_close(L);
|
|
}
|
|
|
|
void LuaContext::cleanupGlobals()
|
|
{
|
|
LuaStack S(L);
|
|
S.clear();
|
|
S.pushNil();
|
|
lua_setglobal(L, "collectgarbage");
|
|
|
|
S.pushNil();
|
|
lua_setglobal(L, "dofile");
|
|
|
|
S.pushNil();
|
|
lua_setglobal(L, "load");
|
|
|
|
S.pushNil();
|
|
lua_setglobal(L, "loadfile");
|
|
|
|
S.pushNil();
|
|
lua_setglobal(L, "loadstring");
|
|
|
|
S.pushNil();
|
|
lua_setglobal(L, "print");
|
|
|
|
S.clear();
|
|
|
|
lua_getglobal(L, LUA_STRLIBNAME);
|
|
|
|
S.push("dump");
|
|
S.pushNil();
|
|
lua_rawset(L, -3);
|
|
S.clear();
|
|
|
|
lua_getglobal(L, LUA_MATHLIBNAME);
|
|
|
|
S.push("random");
|
|
S.pushNil();
|
|
lua_rawset(L, -3);
|
|
|
|
|
|
S.push("randomseed");
|
|
S.pushNil();
|
|
lua_rawset(L, -3);
|
|
S.clear();
|
|
}
|
|
|
|
void LuaContext::run(ServerCallback * server, const JsonNode & initialState)
|
|
{
|
|
{
|
|
LuaStack S(L);
|
|
S.push(server);
|
|
lua_setglobal(L, "SERVER");
|
|
S.clear();
|
|
}
|
|
|
|
run(initialState);
|
|
|
|
// {
|
|
// LuaStack S(L);
|
|
// S.pushNil();
|
|
// lua_setglobal(L, "SERVER");
|
|
// S.clear();
|
|
// }
|
|
}
|
|
|
|
void LuaContext::run(const JsonNode & initialState)
|
|
{
|
|
setGlobal(STATE_FIELD, initialState);
|
|
|
|
int ret = luaL_loadbuffer(L, script->getSource().c_str(), script->getSource().size(), script->getName().c_str());
|
|
|
|
if(ret)
|
|
{
|
|
logger->error("Script '%s' failed to load, error: %s", script->getName(), toStringRaw(-1));
|
|
popAll();
|
|
return;
|
|
}
|
|
|
|
scriptClosure = std::make_shared<LuaReference>(L);
|
|
popAll();
|
|
scriptClosure->push();
|
|
|
|
ret = lua_pcall(L, 0, 0, 0);
|
|
|
|
if(ret)
|
|
{
|
|
logger->error("Script '%s' failed to run, error: '%s'", script->getName(), toStringRaw(-1));
|
|
popAll();
|
|
}
|
|
}
|
|
|
|
int LuaContext::errorRetVoid(const std::string & message)
|
|
{
|
|
logger->error(message);
|
|
popAll();
|
|
return 0;
|
|
}
|
|
|
|
JsonNode LuaContext::callGlobal(const std::string & name, const JsonNode & parameters)
|
|
{
|
|
LuaStack S(L);
|
|
|
|
lua_getglobal(L, name.c_str());
|
|
|
|
if(!S.isFunction(-1))
|
|
{
|
|
boost::format fmt("%s is not a function");
|
|
fmt % name;
|
|
|
|
logger->error(fmt.str());
|
|
|
|
S.clear();
|
|
|
|
return JsonNode();
|
|
}
|
|
|
|
int argc = parameters.Vector().size();
|
|
|
|
for(int idx = 0; idx < argc; idx++)
|
|
S.push(parameters.Vector()[idx]);
|
|
|
|
if(lua_pcall(L, argc, 1, 0))
|
|
{
|
|
std::string error = lua_tostring(L, -1);
|
|
|
|
boost::format fmt("Lua function %s failed with message: %s");
|
|
fmt % name % error;
|
|
|
|
logger->error(fmt.str());
|
|
|
|
S.clear();
|
|
|
|
return JsonNode();
|
|
}
|
|
|
|
JsonNode ret;
|
|
|
|
pop(ret);
|
|
S.balance();
|
|
|
|
return ret;
|
|
}
|
|
|
|
JsonNode LuaContext::callGlobal(ServerCallback * cb, const std::string & name, const JsonNode & parameters)
|
|
{
|
|
LuaStack S(L);
|
|
S.push(cb);
|
|
lua_setglobal(L, "SERVER");
|
|
|
|
auto ret = callGlobal(name, parameters);
|
|
|
|
S.pushNil();
|
|
lua_setglobal(L, "SERVER");
|
|
|
|
return ret;
|
|
}
|
|
|
|
void LuaContext::getGlobal(const std::string & name, int & value)
|
|
{
|
|
LuaStack S(L);
|
|
|
|
lua_getglobal(L, name.c_str());
|
|
|
|
lua_Integer temp;
|
|
if(S.tryGetInteger(-1, temp))
|
|
value = static_cast<int>(temp);
|
|
else
|
|
value = 0;
|
|
S.balance();
|
|
}
|
|
|
|
void LuaContext::getGlobal(const std::string & name, std::string & value)
|
|
{
|
|
LuaStack S(L);
|
|
|
|
lua_getglobal(L, name.c_str());
|
|
|
|
if(!S.tryGet(-1, value))
|
|
value.clear();
|
|
|
|
S.balance();
|
|
}
|
|
|
|
void LuaContext::getGlobal(const std::string & name, double & value)
|
|
{
|
|
LuaStack S(L);
|
|
|
|
lua_getglobal(L, name.c_str());
|
|
|
|
if(!S.tryGet(-1, value))
|
|
value = 0.0;
|
|
|
|
S.balance();
|
|
}
|
|
|
|
void LuaContext::getGlobal(const std::string & name, JsonNode & value)
|
|
{
|
|
LuaStack S(L);
|
|
|
|
lua_getglobal(L, name.c_str());
|
|
|
|
pop(value);
|
|
|
|
S.balance();
|
|
}
|
|
|
|
void LuaContext::setGlobal(const std::string & name, int value)
|
|
{
|
|
lua_pushinteger(L, static_cast<lua_Integer>(value));
|
|
lua_setglobal(L, name.c_str());
|
|
}
|
|
|
|
void LuaContext::setGlobal(const std::string & name, const std::string & value)
|
|
{
|
|
lua_pushlstring(L, value.c_str(), value.size());
|
|
lua_setglobal(L, name.c_str());
|
|
}
|
|
|
|
void LuaContext::setGlobal(const std::string & name, double value)
|
|
{
|
|
lua_pushnumber(L, value);
|
|
lua_setglobal(L, name.c_str());
|
|
}
|
|
|
|
void LuaContext::setGlobal(const std::string & name, const JsonNode & value)
|
|
{
|
|
LuaStack S(L);
|
|
S.push(value);
|
|
lua_setglobal(L, name.c_str());
|
|
S.balance();
|
|
}
|
|
|
|
JsonNode LuaContext::saveState()
|
|
{
|
|
JsonNode data;
|
|
getGlobal(STATE_FIELD, data);
|
|
return data;
|
|
}
|
|
|
|
void LuaContext::pop(JsonNode & value)
|
|
{
|
|
auto type = lua_type(L, -1);
|
|
|
|
switch(type)
|
|
{
|
|
case LUA_TNUMBER:
|
|
value.Float() = lua_tonumber(L, -1);
|
|
break;
|
|
case LUA_TBOOLEAN:
|
|
value.Bool() = (lua_toboolean(L, -1) != 0);
|
|
break;
|
|
case LUA_TSTRING:
|
|
value.String() = toStringRaw(-1);
|
|
break;
|
|
case LUA_TTABLE:
|
|
{
|
|
JsonNode asVector;
|
|
JsonNode asStruct;
|
|
|
|
lua_pushnil(L); /* first key */
|
|
|
|
while(lua_next(L, -2) != 0)
|
|
{
|
|
/* 'key' (at index -2) and 'value' (at index -1) */
|
|
|
|
JsonNode fieldValue;
|
|
pop(fieldValue);
|
|
|
|
if(lua_type(L, -1) == LUA_TNUMBER)
|
|
{
|
|
auto key = lua_tointeger(L, -1);
|
|
|
|
if(key > 0)
|
|
{
|
|
if(asVector.Vector().size() < key)
|
|
asVector.Vector().resize(key);
|
|
--key;
|
|
asVector.Vector().at(key) = fieldValue;
|
|
}
|
|
}
|
|
else if(lua_isstring(L, -1))
|
|
{
|
|
auto key = toStringRaw(-1);
|
|
asStruct[key] = fieldValue;
|
|
}
|
|
}
|
|
|
|
if(!asVector.Vector().empty())
|
|
{
|
|
std::swap(value, asVector);
|
|
}
|
|
else
|
|
{
|
|
std::swap(value, asStruct);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
value.clear();
|
|
break;
|
|
}
|
|
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
void LuaContext::push(const std::string & value)
|
|
{
|
|
lua_pushlstring(L, value.c_str(), value.size());
|
|
}
|
|
|
|
void LuaContext::push(lua_CFunction f, void * opaque)
|
|
{
|
|
lua_pushlightuserdata(L, opaque);
|
|
lua_pushcclosure(L, f, 1);
|
|
}
|
|
|
|
void LuaContext::popAll()
|
|
{
|
|
lua_settop(L, 0);
|
|
}
|
|
|
|
std::string LuaContext::toStringRaw(int index)
|
|
{
|
|
size_t len = 0;
|
|
const auto *raw = lua_tolstring(L, index, &len);
|
|
return std::string(raw, len);
|
|
}
|
|
|
|
void LuaContext::registerCore()
|
|
{
|
|
push(&LuaContext::require, this);
|
|
lua_setglobal(L, "require");
|
|
|
|
push(&LuaContext::logError, this);
|
|
lua_setglobal(L, "logError");
|
|
|
|
popAll();//just in case
|
|
|
|
for(const auto & registar : api::Registry::get()->getCoreData())
|
|
{
|
|
registar.second->pushMetatable(L); //table
|
|
|
|
modules->push(); //table modules
|
|
push(registar.first); //table modules name
|
|
lua_pushvalue(L, -3); //table modules name table
|
|
lua_rawset(L, -3);
|
|
|
|
popAll();
|
|
}
|
|
}
|
|
|
|
int LuaContext::require(lua_State * L)
|
|
{
|
|
auto * self = static_cast<LuaContext *>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
|
|
if(!self)
|
|
{
|
|
lua_pushstring(L, "internal error");
|
|
lua_error(L);
|
|
return 0;
|
|
}
|
|
|
|
return self->loadModule();
|
|
}
|
|
|
|
int LuaContext::loadModule()
|
|
{
|
|
int argc = lua_gettop(L);
|
|
|
|
if(argc < 1)
|
|
return errorRetVoid("Module name required");
|
|
|
|
//if module is loaded already, assume that module name is valid
|
|
modules->push();
|
|
lua_pushvalue(L, -2);
|
|
lua_rawget(L, -2);
|
|
|
|
if(lua_istable(L, -1))
|
|
{
|
|
lua_replace(L, 1);
|
|
lua_settop(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
//continue with more checks
|
|
if(!lua_isstring(L, 1))
|
|
return errorRetVoid("Module name must be string");
|
|
|
|
std::string resourceName = toStringRaw(1);
|
|
|
|
if(resourceName.empty())
|
|
return errorRetVoid("Module name is empty");
|
|
|
|
auto temp = vstd::split(resourceName, ":");
|
|
|
|
std::string scope;
|
|
std::string modulePath;
|
|
|
|
if(temp.size() <= 1)
|
|
{
|
|
modulePath = temp.at(0);
|
|
}
|
|
else
|
|
{
|
|
scope = temp.at(0);
|
|
modulePath = temp.at(1);
|
|
}
|
|
|
|
if(scope.empty())
|
|
{
|
|
const auto *registar = api::Registry::get()->find(modulePath);
|
|
|
|
if(!registar)
|
|
{
|
|
return errorRetVoid("Module not found: "+modulePath);
|
|
}
|
|
|
|
registar->pushMetatable(L);
|
|
}
|
|
else if(scope == ModScope::scopeBuiltin())
|
|
{
|
|
|
|
// boost::algorithm::replace_all(modulePath, boost::is_any_of("\\/ "), "");
|
|
|
|
boost::algorithm::replace_all(modulePath, ".", "/");
|
|
|
|
auto *loader = CResourceHandler::get(ModScope::scopeBuiltin());
|
|
|
|
modulePath = "scripts/lib/" + modulePath;
|
|
|
|
ResourcePath id(modulePath, EResType::LUA);
|
|
|
|
if(!loader->existsResource(id))
|
|
return errorRetVoid("Module not found: "+modulePath);
|
|
|
|
auto rawData = loader->load(id)->readAll();
|
|
|
|
auto sourceText = std::string(reinterpret_cast<char *>(rawData.first.get()), rawData.second);
|
|
|
|
int ret = luaL_loadbuffer(L, sourceText.c_str(), sourceText.size(), modulePath.c_str());
|
|
|
|
if(ret)
|
|
return errorRetVoid(toStringRaw(-1));
|
|
|
|
ret = lua_pcall(L, 0, 1, 0);
|
|
|
|
if(ret)
|
|
{
|
|
logger->error("Module '%s' failed to run, error: %s", modulePath, toStringRaw(-1));
|
|
popAll();
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//todo: also allow loading scripts from same scope
|
|
return errorRetVoid("No access to scope "+scope);
|
|
}
|
|
|
|
modules->push(); //name table modules
|
|
lua_pushvalue(L, 1);//name table modules name
|
|
|
|
if(!lua_isstring(L, -1))
|
|
return errorRetVoid("Module name corrupted");
|
|
|
|
lua_pushvalue(L, -3);//name table modules name table
|
|
lua_rawset(L, -3);//name table modules
|
|
lua_pop(L, 1);//name table
|
|
|
|
lua_replace(L, 1);//table table
|
|
lua_settop(L, 1);//table
|
|
return 1;
|
|
}
|
|
|
|
int LuaContext::print(lua_State * L)
|
|
{
|
|
//TODO:
|
|
lua_settop(L, 0);
|
|
return 0;
|
|
}
|
|
|
|
int LuaContext::printImpl()
|
|
{
|
|
//TODO:
|
|
return 0;
|
|
}
|
|
|
|
int LuaContext::logError(lua_State * L)
|
|
{
|
|
auto * self = static_cast<LuaContext *>(lua_touserdata(L, lua_upvalueindex(1)));
|
|
|
|
if(!self)
|
|
{
|
|
lua_pushstring(L, "internal error");
|
|
lua_error(L);
|
|
return 0;
|
|
}
|
|
|
|
return self->logErrorImpl();
|
|
}
|
|
|
|
int LuaContext::logErrorImpl()
|
|
{
|
|
LuaStack S(L);
|
|
|
|
std::string message;
|
|
|
|
if(S.tryGet(1, message))
|
|
logger->error(message);
|
|
|
|
return S.retVoid();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
VCMI_LIB_NAMESPACE_END
|