Implement clientless transports, Lua reload and same-map portals
Add native transport recording and execution with grid lifecycle handling, clientless teleport acknowledgements, and regression tests. Support targeted same-map portal imports and preserve disabled taxi nodes. Document live transport and portal verification and the remaining portal inventory.
This commit is contained in:
1 parent
01a9ba4b0b
commit
1164a72ea3
40 files changed
+4533
-163
No files matched your search
@@ -1,5 +1,8 @@
|
||||
#include "ClientlessLuaEngine.h"
|
||||
#include "ClientlessPlayerMgr.h"
|
||||
#include "ClientlessActionExecutorMgr.h"
|
||||
#include "ClientlessTravelMgr.h"
|
||||
#include "Config.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include "Random.h"
|
||||
@@ -14,6 +17,15 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
// World-thread only. Script effects are committed only after a successful reload.
|
||||
struct ReloadEffects
|
||||
{
|
||||
std::vector<ClientlessAction> Actions;
|
||||
ClientlessIntent Intent;
|
||||
bool IntentChanged = false;
|
||||
};
|
||||
ReloadEffects* pendingReload = nullptr;
|
||||
|
||||
uint32 BoundGuid(lua_State* L) { return uint32(lua_tointeger(L, lua_upvalueindex(1))); }
|
||||
|
||||
std::unordered_map<std::string, std::string> ReadMap(lua_State* L, int index)
|
||||
@@ -48,12 +60,30 @@ int LuaIntend(lua_State* L)
|
||||
ClientlessIntent intent;
|
||||
intent.Name = luaL_checkstring(L, 1);
|
||||
if (lua_gettop(L) >= 2) intent.Parameters = ReadMap(L, 2);
|
||||
if (pendingReload)
|
||||
{
|
||||
bool valid = !intent.Empty();
|
||||
if (valid)
|
||||
{
|
||||
pendingReload->Intent = std::move(intent);
|
||||
pendingReload->IntentChanged = true;
|
||||
}
|
||||
lua_pushboolean(L, valid);
|
||||
return 1;
|
||||
}
|
||||
lua_pushboolean(L, sClientlessPlayerMgr->SetIntent(BoundGuid(L), std::move(intent)));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int LuaClearIntent(lua_State* L)
|
||||
{
|
||||
if (pendingReload)
|
||||
{
|
||||
pendingReload->Intent = ClientlessIntent();
|
||||
pendingReload->IntentChanged = true;
|
||||
lua_pushboolean(L, true);
|
||||
return 1;
|
||||
}
|
||||
lua_pushboolean(L, sClientlessPlayerMgr->ClearIntent(BoundGuid(L)));
|
||||
return 1;
|
||||
}
|
||||
@@ -63,6 +93,13 @@ int LuaQueueAction(lua_State* L)
|
||||
ClientlessAction action;
|
||||
action.Name = luaL_checkstring(L, 1);
|
||||
if (lua_gettop(L) >= 2) action.Parameters = ReadMap(L, 2);
|
||||
if (pendingReload)
|
||||
{
|
||||
bool valid = !action.Name.empty();
|
||||
if (valid) pendingReload->Actions.push_back(std::move(action));
|
||||
lua_pushboolean(L, valid);
|
||||
return 1;
|
||||
}
|
||||
lua_pushboolean(L, sClientlessPlayerMgr->QueueAction(BoundGuid(L), std::move(action)));
|
||||
return 1;
|
||||
}
|
||||
@@ -184,7 +221,10 @@ ClientlessLuaEngine* ClientlessLuaEngine::instance()
|
||||
|
||||
ClientlessLuaEngine::ClientlessLuaEngine()
|
||||
{
|
||||
_scriptRoot = std::getenv("VCORE_CLIENTLESS_SCRIPT_DIR") ? std::getenv("VCORE_CLIENTLESS_SCRIPT_DIR") : "scripts/clientless";
|
||||
_scriptRoot = sConfigMgr->GetStringDefault("Clientless.ScriptRoot", "scripts/clientless");
|
||||
if (char const* overrideRoot = std::getenv("VCORE_CLIENTLESS_SCRIPT_DIR"))
|
||||
if (*overrideRoot) _scriptRoot = overrideRoot;
|
||||
_scriptRoot = std::filesystem::absolute(_scriptRoot).lexically_normal().string();
|
||||
_state = luaL_newstate();
|
||||
if (!_state)
|
||||
{
|
||||
@@ -445,6 +485,61 @@ bool ClientlessLuaEngine::ExecuteAdventureScript(Player* player)
|
||||
return ExecuteScript(player, (root / "adventure" / "generic.lua").string(), "generic adventure");
|
||||
}
|
||||
|
||||
bool ClientlessLuaEngine::ReloadAdventureScript(Player* player, std::string& detail)
|
||||
{
|
||||
if (!player || !player->IsInWorld() || !sClientlessPlayerMgr->IsManaged(player->GetGUID().GetCounter()))
|
||||
{
|
||||
detail = "Reload requires an online managed clientless player.";
|
||||
return false;
|
||||
}
|
||||
uint32 guid = player->GetGUID().GetCounter();
|
||||
auto action = sClientlessActionExecutorMgr->GetStatus(guid);
|
||||
auto travel = sClientlessTravelMgr->GetStatus(guid);
|
||||
if (pendingReload || sClientlessPlayerMgr->GetQueuedActionCount(guid) ||
|
||||
(action && (action->State == ClientlessActionExecutionState::SelectingTarget ||
|
||||
action->State == ClientlessActionExecutionState::Moving)) ||
|
||||
(travel && travel->State == ClientlessTravelState::Moving) ||
|
||||
player->GetTransport() || player->IsInFlight() || player->IsBeingTeleported())
|
||||
{
|
||||
detail = "Reload refused: bot has queued actions, is travelling or is aboard a transport. Wait until idle.";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto previous = _profiles.find(guid);
|
||||
bool hadProfile = previous != _profiles.end();
|
||||
ClientlessAdventureProfile backup = hadProfile ? previous->second : ClientlessAdventureProfile();
|
||||
ReloadEffects effects;
|
||||
bool success;
|
||||
{
|
||||
struct Capture
|
||||
{
|
||||
explicit Capture(ReloadEffects& effects) { pendingReload = &effects; }
|
||||
~Capture() { pendingReload = nullptr; }
|
||||
} capture(effects);
|
||||
success = LoadAdventureProfile(player) && ExecuteAdventureScript(player);
|
||||
}
|
||||
if (!success)
|
||||
{
|
||||
if (hadProfile) _profiles[guid] = std::move(backup);
|
||||
else _profiles.erase(guid);
|
||||
detail = "Lua reload failed. Previous profile retained; no new actions or intent applied. See server log.";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (effects.IntentChanged)
|
||||
{
|
||||
if (effects.Intent.Empty()) sClientlessPlayerMgr->ClearIntent(guid);
|
||||
else sClientlessPlayerMgr->SetIntent(guid, std::move(effects.Intent));
|
||||
}
|
||||
for (auto& queued : effects.Actions)
|
||||
sClientlessPlayerMgr->QueueAction(guid, std::move(queued));
|
||||
detail = "Profile and adventure script reloaded; queued actions=" + std::to_string(effects.Actions.size()) + ".";
|
||||
}
|
||||
TC_LOG_INFO("server.adventurebot.navigation", "LUA_RELOAD guid=%u success=%u root='%s' detail='%s'",
|
||||
guid, success ? 1u : 0u, _scriptRoot.c_str(), detail.c_str());
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ClientlessLuaEngine::ExecuteTestScript(Player* player, std::string const& scriptName)
|
||||
{
|
||||
if (!player || scriptName.empty() || scriptName.find("..") != std::string::npos || scriptName.find('/') != std::string::npos || scriptName.find('\\') != std::string::npos)
|
||||
|
||||
Reference in new issue
Block a user