Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protocol lua API #3461

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions config.lua.dist
Expand Up @@ -34,6 +34,12 @@ statusTimeout = 5000
replaceKickOnLogin = true
maxPacketsPerSecond = 25

-- Lua API
-- NOTE: when changing luaApiIp to another IP then localhost, you'll still be able to connect through localhost
luaApiEnabled = true
luaApiIp = "127.0.0.1"
luaApiProtocolPort = 7179

-- Deaths
-- NOTE: Leave deathLosePercent as -1 if you want to use the default
-- death penalty formula. For the old formula, set it to 10. For
Expand Down
8 changes: 4 additions & 4 deletions data/globalevents/scripts/serversave.lua
@@ -1,4 +1,4 @@
local function ServerSave()
function ServerSave()
EvilHero90 marked this conversation as resolved.
Show resolved Hide resolved
if configManager.getBoolean(configKeys.SERVER_SAVE_CLEAN_MAP) then
cleanMap()
end
Expand All @@ -12,11 +12,11 @@ local function ServerSave()
end
end

local function ServerSaveWarning(time)
function ServerSaveWarning(time)
local remaningTime = tonumber(time) - 60000

if configManager.getBoolean(configKeys.SERVER_SAVE_NOTIFY_MESSAGE) then
Game.broadcastMessage("Server is saving game in " .. (remaningTime/60000) .." minute(s). Please logout.", MESSAGE_STATUS_WARNING)
Game.broadcastMessage("Server is saving game in " .. (remaningTime/60000) .." minute(s). Please logout.", MESSAGE_STATUS_WARNING)
end

if remaningTime > 60000 then
Expand All @@ -29,7 +29,7 @@ end
function onTime(interval)
local remaningTime = configManager.getNumber(configKeys.SERVER_SAVE_NOTIFY_DURATION) * 60000
if configManager.getBoolean(configKeys.SERVER_SAVE_NOTIFY_MESSAGE) then
Game.broadcastMessage("Server is saving game in " .. (remaningTime/60000) .." minute(s). Please logout.", MESSAGE_STATUS_WARNING)
Game.broadcastMessage("Server is saving game in " .. (remaningTime/60000) .." minute(s). Please logout.", MESSAGE_STATUS_WARNING)
end

addEvent(ServerSaveWarning, 60000, remaningTime)
Expand Down
4 changes: 4 additions & 0 deletions data/lib/compat/compat.lua
Expand Up @@ -213,6 +213,10 @@ do
self:type("record")
self:onRecord(value)
return
elseif key == "onHttpRequest" then
self:type("http")
self:onHttpRequest(value)
return
end
rawset(self, key, value)
end
Expand Down
111 changes: 111 additions & 0 deletions data/scripts/http/api.lua
@@ -0,0 +1,111 @@
local reloadTypes = {
["all"] = RELOAD_TYPE_ALL,

["action"] = RELOAD_TYPE_ACTIONS,
["actions"] = RELOAD_TYPE_ACTIONS,

["chat"] = RELOAD_TYPE_CHAT,
["channel"] = RELOAD_TYPE_CHAT,
["chatchannels"] = RELOAD_TYPE_CHAT,

["config"] = RELOAD_TYPE_CONFIG,
["configuration"] = RELOAD_TYPE_CONFIG,

["creaturescript"] = RELOAD_TYPE_CREATURESCRIPTS,
["creaturescripts"] = RELOAD_TYPE_CREATURESCRIPTS,

["events"] = RELOAD_TYPE_EVENTS,

["global"] = RELOAD_TYPE_GLOBAL,

["globalevent"] = RELOAD_TYPE_GLOBALEVENTS,
["globalevents"] = RELOAD_TYPE_GLOBALEVENTS,

["items"] = RELOAD_TYPE_ITEMS,

["monster"] = RELOAD_TYPE_MONSTERS,
["monsters"] = RELOAD_TYPE_MONSTERS,

["mount"] = RELOAD_TYPE_MOUNTS,
["mounts"] = RELOAD_TYPE_MOUNTS,

["move"] = RELOAD_TYPE_MOVEMENTS,
["movement"] = RELOAD_TYPE_MOVEMENTS,
["movements"] = RELOAD_TYPE_MOVEMENTS,

["npc"] = RELOAD_TYPE_NPCS,
["npcs"] = RELOAD_TYPE_NPCS,

["quest"] = RELOAD_TYPE_QUESTS,
["quests"] = RELOAD_TYPE_QUESTS,

["raid"] = RELOAD_TYPE_RAIDS,
["raids"] = RELOAD_TYPE_RAIDS,

["spell"] = RELOAD_TYPE_SPELLS,
["spells"] = RELOAD_TYPE_SPELLS,

["talk"] = RELOAD_TYPE_TALKACTIONS,
["talkaction"] = RELOAD_TYPE_TALKACTIONS,
["talkactions"] = RELOAD_TYPE_TALKACTIONS,

["weapon"] = RELOAD_TYPE_WEAPONS,
["weapons"] = RELOAD_TYPE_WEAPONS,

["scripts"] = RELOAD_TYPE_SCRIPTS,
["libs"] = RELOAD_TYPE_GLOBAL
}

local api = GlobalEvent("API")

function api.onHttpRequest(name, data)

-- reload
if data.reload then
local reloadType = reloadTypes[data.reload:lower()]
if not reloadType then
-- WIP
-- http:sendCallbackMessage("API", "Reload type not found.")
return
end

-- need to clear EventCallback.data or we end up having duplicated events on /reload scripts
if table.contains({RELOAD_TYPE_SCRIPTS, RELOAD_TYPE_ALL}, reloadType) then
EventCallback:clear()
end

Game.reload(reloadType)
print(string.format("Reloaded %s.", data.reload:lower()))
-- WIP
-- http:sendCallbackMessage("API", string.format("Reloaded %s.", name:lower()))
end

-- server save
if data.saveServer then
saveServer()
end

-- clean server
if data.cleanServer then
cleanMap()
end

-- close server
if data.closeServer then
Game.setGameState(GAME_STATE_SHUTDOWN)
end

-- start raid
if data.raid then
local returnValue = Game.startRaid(data.raid)
if returnValue ~= RETURNVALUE_NOERROR then
print("Raid: ".. Game.getReturnMessage(returnValue))
else
print("Raid: ".. data.raid .." started.")
end
end

return true
end

api:register()
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Expand Up @@ -54,6 +54,7 @@ set(tfs_SRC
${CMAKE_CURRENT_LIST_DIR}/protocollogin.cpp
${CMAKE_CURRENT_LIST_DIR}/protocolold.cpp
${CMAKE_CURRENT_LIST_DIR}/protocolstatus.cpp
${CMAKE_CURRENT_LIST_DIR}/protocolluaapi.cpp
${CMAKE_CURRENT_LIST_DIR}/quests.cpp
${CMAKE_CURRENT_LIST_DIR}/raids.cpp
${CMAKE_CURRENT_LIST_DIR}/rsa.cpp
Expand Down
3 changes: 3 additions & 0 deletions src/configmanager.cpp
Expand Up @@ -182,6 +182,7 @@ bool ConfigManager::load()
if (!loaded) { //info that must be loaded one time (unless we reset the modules involved)
boolean[BIND_ONLY_GLOBAL_ADDRESS] = getGlobalBoolean(L, "bindOnlyGlobalAddress", false);
boolean[OPTIMIZE_DATABASE] = getGlobalBoolean(L, "startupDatabaseOptimization", true);
boolean[LUA_API_ENABLED] = getGlobalBoolean(L, "luaApiEnabled", true);

if (string[IP] == "") {
string[IP] = getGlobalString(L, "ip", "127.0.0.1");
Expand All @@ -195,6 +196,7 @@ bool ConfigManager::load()
string[MYSQL_PASS] = getGlobalString(L, "mysqlPass", "");
string[MYSQL_DB] = getGlobalString(L, "mysqlDatabase", "forgottenserver");
string[MYSQL_SOCK] = getGlobalString(L, "mysqlSock", "");
string[LUA_API_IP] = getGlobalString(L, "luaApiIp", "127.0.0.1");

integer[SQL_PORT] = getGlobalNumber(L, "mysqlPort", 3306);

Expand All @@ -207,6 +209,7 @@ bool ConfigManager::load()
}

integer[STATUS_PORT] = getGlobalNumber(L, "statusProtocolPort", 7171);
integer[LUA_API_PORT] = getGlobalNumber(L, "luaApiProtocolPort", 7179);

integer[MARKET_OFFER_DURATION] = getGlobalNumber(L, "marketOfferDuration", 30 * 24 * 60 * 60);
}
Expand Down
5 changes: 4 additions & 1 deletion src/configmanager.h
Expand Up @@ -68,6 +68,8 @@ class ConfigManager
ONLY_INVITED_CAN_MOVE_HOUSE_ITEMS,
REMOVE_ON_DESPAWN,
PLAYER_CONSOLE_LOGS,
LUA_API_ENABLED,


LAST_BOOLEAN_CONFIG /* this must be the last one */
};
Expand All @@ -91,7 +93,7 @@ class ConfigManager
DEFAULT_PRIORITY,
MAP_AUTHOR,
CONFIG_FILE,

LUA_API_IP,
LAST_STRING_CONFIG /* this must be the last one */
};

Expand Down Expand Up @@ -133,6 +135,7 @@ class ConfigManager
VIP_PREMIUM_LIMIT,
DEPOT_FREE_LIMIT,
DEPOT_PREMIUM_LIMIT,
LUA_API_PORT,

LAST_INTEGER_CONFIG /* this must be the last one */
};
Expand Down
27 changes: 27 additions & 0 deletions src/globalevent.cpp
Expand Up @@ -228,6 +228,7 @@ GlobalEventMap GlobalEvents::getEventMap(GlobalEvent_t type)
case GLOBALEVENT_TIMER: return timerMap;
case GLOBALEVENT_STARTUP:
case GLOBALEVENT_SHUTDOWN:
case GLOBALEVENT_HTTPREQUEST:
case GLOBALEVENT_RECORD: {
GlobalEventMap retMap;
for (const auto& it : serverMap) {
Expand Down Expand Up @@ -305,6 +306,8 @@ bool GlobalEvent::configureEvent(const pugi::xml_node& node)
eventType = GLOBALEVENT_SHUTDOWN;
} else if (strcasecmp(value, "record") == 0) {
eventType = GLOBALEVENT_RECORD;
} else if (strcasecmp(value, "http") == 0) {
eventType = GLOBALEVENT_HTTPREQUEST;
} else {
std::cout << "[Error - GlobalEvent::configureEvent] No valid type \"" << attr.as_string() << "\" for globalevent with name " << name << std::endl;
return false;
Expand All @@ -326,6 +329,7 @@ std::string GlobalEvent::getScriptEventName() const
case GLOBALEVENT_SHUTDOWN: return "onShutdown";
case GLOBALEVENT_RECORD: return "onRecord";
case GLOBALEVENT_TIMER: return "onTime";
case GLOBALEVENT_HTTPREQUEST: return "onHttpRequest";
default: return "onThink";
}
}
Expand All @@ -349,6 +353,29 @@ bool GlobalEvent::executeRecord(uint32_t current, uint32_t old)
return scriptInterface->callFunction(2);
}

bool GlobalEvent::executeHttpRequest(std::string& name, std::unordered_map<std::string, std::string> data)
{
//onHttpRequest(name, data)
if (!scriptInterface->reserveScriptEnv()) {
std::cout << "[Error - GlobalEvent::executeHttpRequest] Call stack overflow" << std::endl;
return false;
}

ScriptEnvironment* env = scriptInterface->getScriptEnv();
env->setScriptId(scriptId, scriptInterface);

lua_State* L = scriptInterface->getLuaState();
scriptInterface->pushFunction(scriptId);

LuaScriptInterface::pushString(L, name);
lua_createtable(L, data.size(), 0);
for (auto& args : data) {
LuaScriptInterface::pushString(L, args.second);
lua_setfield(L, -2, args.first.c_str());
}
return scriptInterface->callFunction(2);
}

bool GlobalEvent::executeEvent() const
{
if (!scriptInterface->reserveScriptEnv()) {
Expand Down
2 changes: 2 additions & 0 deletions src/globalevent.h
Expand Up @@ -30,6 +30,7 @@ enum GlobalEvent_t {
GLOBALEVENT_STARTUP,
GLOBALEVENT_SHUTDOWN,
GLOBALEVENT_RECORD,
GLOBALEVENT_HTTPREQUEST,
};

class GlobalEvent;
Expand Down Expand Up @@ -84,6 +85,7 @@ class GlobalEvent final : public Event

bool executeRecord(uint32_t current, uint32_t old);
bool executeEvent() const;
bool executeHttpRequest(std::string& name, std::unordered_map<std::string, std::string> data);

GlobalEvent_t getEventType() const {
return eventType;
Expand Down
41 changes: 40 additions & 1 deletion src/luascript.cpp
Expand Up @@ -340,6 +340,42 @@ int32_t LuaScriptInterface::loadFile(const std::string& file, Npc* npc /* = null
return 0;
}

std::string LuaScriptInterface::loadString(const std::string& string, const std::string& fileName)
{
//loads string as a chunk at stack top
int ret = luaL_loadstring(luaState, string.c_str());
if (ret != 0) {
lastLuaError = popString(luaState);
return lastLuaError;
EvilHero90 marked this conversation as resolved.
Show resolved Hide resolved
}

//check that it is loaded as a function
if (!isFunction(luaState, -1)) {
return "not handled";
}

loadingFile = "lua api: " + fileName;

if (!reserveScriptEnv()) {
return "not handled";
}

ScriptEnvironment* env = getScriptEnv();
env->setScriptId(EVENT_ID_LOADING, this);

//execute it
ret = protectedCall(luaState, 0, 0);
if (ret != 0) {
lastLuaError = popString(luaState);
reportError(nullptr, lastLuaError);
resetScriptEnv();
return lastLuaError;
}

resetScriptEnv();
return "";
}

int32_t LuaScriptInterface::getEvent(const std::string& eventName)
{
//get our events table
Expand Down Expand Up @@ -2999,6 +3035,7 @@ void LuaScriptInterface::registerFunctions()
registerMethod("GlobalEvent", "onStartup", LuaScriptInterface::luaGlobalEventOnCallback);
registerMethod("GlobalEvent", "onShutdown", LuaScriptInterface::luaGlobalEventOnCallback);
registerMethod("GlobalEvent", "onRecord", LuaScriptInterface::luaGlobalEventOnCallback);
registerMethod("GlobalEvent", "onHttpRequest", LuaScriptInterface::luaGlobalEventOnCallback);

// Weapon
registerClass("Weapon", "", LuaScriptInterface::luaCreateWeapon);
Expand Down Expand Up @@ -15876,8 +15913,10 @@ int LuaScriptInterface::luaGlobalEventType(lua_State* L)
global->setEventType(GLOBALEVENT_SHUTDOWN);
} else if (tmpStr == "record") {
global->setEventType(GLOBALEVENT_RECORD);
} else if (tmpStr == "http") {
global->setEventType(GLOBALEVENT_HTTPREQUEST);
} else {
std::cout << "[Error - CreatureEvent::configureLuaEvent] Invalid type for global event: " << typeName << std::endl;
std::cout << "[Error - GlobalEvent::configureLuaEvent] Invalid type for global event: " << typeName << std::endl;
pushBoolean(L, false);
}
pushBoolean(L, true);
Expand Down
1 change: 1 addition & 0 deletions src/luascript.h
Expand Up @@ -213,6 +213,7 @@ class LuaScriptInterface
bool reInitState();

int32_t loadFile(const std::string& file, Npc* npc = nullptr);
std::string loadString(const std::string& string, const std::string& fileName);

const std::string& getFileById(int32_t scriptId);
int32_t getEvent(const std::string& eventName);
Expand Down
6 changes: 6 additions & 0 deletions src/otserv.cpp
Expand Up @@ -31,6 +31,7 @@
#include "protocolold.h"
#include "protocollogin.h"
#include "protocolstatus.h"
#include "protocolluaapi.h"
#include "databasemanager.h"
#include "scheduler.h"
#include "databasetasks.h"
Expand Down Expand Up @@ -303,6 +304,11 @@ void mainLoader(int, char*[], ServiceManager* services)
// Legacy login protocol
services->add<ProtocolOld>(static_cast<uint16_t>(g_config.getNumber(ConfigManager::LOGIN_PORT)));

// Lua API protocol
if (g_config.getBoolean(ConfigManager::LUA_API_ENABLED)) {
services->add<ProtocolLuaApi>(static_cast<uint16_t>(g_config.getNumber(ConfigManager::LUA_API_PORT)));
}

RentPeriod_t rentPeriod;
std::string strRentPeriod = asLowerCaseString(g_config.getString(ConfigManager::HOUSE_RENT_PERIOD));

Expand Down