Files
Viano-Core/tests/clientless/test_lua_reload.py
T
Nico 1164a72ea3 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.
2026-10-26 11:27:56 +01:00

146 lines
6.5 KiB
Python

"""Compile the production reload/bindings with real Lua and deterministic server doubles."""
import pathlib
import subprocess
import tempfile
ROOT = pathlib.Path(__file__).resolve().parents[2]
SOURCE = (ROOT / "src/server/game/World/ClientlessLuaEngine.cpp").read_text()
def function(signature):
start = SOURCE.index(signature)
opening = SOURCE.index("{", start)
depth = 1
end = opening + 1
while depth:
depth += (SOURCE[end] == "{") - (SOURCE[end] == "}")
end += 1
return SOURCE[start:end]
HARNESS = r'''
#include <lua.hpp>
#include <cassert>
#include <cstdint>
#include <string>
#include <vector>
#include <unordered_map>
#include <utility>
using uint32 = uint32_t;
#define TC_LOG_INFO(...) ((void)0)
struct ClientlessIntent {
std::string Name;
std::unordered_map<std::string,std::string> Parameters;
bool Empty() const { return Name.empty(); }
};
using ClientlessAction = ClientlessIntent;
struct ClientlessAdventureProfile { int Revision = 0; };
struct Guid { uint32 GetCounter() const { return 26; } };
struct Player {
bool aboard = false, flying = false, teleport = false;
Guid GetGUID() { return {}; }
bool IsInWorld() { return true; }
bool GetTransport() { return aboard; }
bool IsInFlight() { return flying; }
bool IsBeingTeleported() { return teleport; }
};
enum class ClientlessActionExecutionState { Idle, SelectingTarget, Moving, Completed, Failed };
enum class ClientlessTravelState { Idle, Moving, Arrived };
struct ActionStatus { ClientlessActionExecutionState State = ClientlessActionExecutionState::Idle; };
struct TravelStatus { ClientlessTravelState State = ClientlessTravelState::Idle; };
struct Manager {
bool managed = true;
std::vector<ClientlessAction> actions;
ClientlessIntent intent;
bool IsManaged(uint32) { return managed; }
unsigned GetQueuedActionCount(uint32) { return actions.size(); }
bool QueueAction(uint32, ClientlessAction a) { actions.push_back(std::move(a)); return true; }
bool SetIntent(uint32, ClientlessIntent i) { intent = std::move(i); return true; }
bool ClearIntent(uint32) { intent = {}; return true; }
} manager;
struct Executor { ActionStatus status; ActionStatus* GetStatus(uint32) { return &status; } } executor;
struct Travel { TravelStatus status; TravelStatus* GetStatus(uint32) { return &status; } } travel;
auto* sClientlessPlayerMgr = &manager;
auto* sClientlessActionExecutorMgr = &executor;
auto* sClientlessTravelMgr = &travel;
class ClientlessLuaEngine {
public:
std::unordered_map<uint32,ClientlessAdventureProfile> _profiles;
std::string _scriptRoot = "/test";
std::string script;
bool profileOK = true;
bool LoadAdventureProfile(Player*) {
if (!profileOK) return false;
_profiles[26].Revision++;
return true;
}
bool ExecuteAdventureScript(Player*);
bool ReloadAdventureScript(Player*, std::string&);
};
'''
HARNESS += SOURCE[SOURCE.index("struct ReloadEffects"):SOURCE.index("uint32 BoundGuid")]
for signature in ["uint32 BoundGuid", "std::unordered_map<std::string, std::string> ReadMap",
"int LuaIntend", "int LuaClearIntent", "int LuaQueueAction"]:
HARNESS += function(signature) + "\n"
HARNESS += r'''
bool ClientlessLuaEngine::ExecuteAdventureScript(Player*) {
lua_State* L = luaL_newstate();
luaL_openlibs(L);
for (auto entry : {std::pair{"intend", LuaIntend}, {"clear_intent", LuaClearIntent}, {"queue_action", LuaQueueAction}}) {
lua_pushinteger(L,26); lua_pushcclosure(L,entry.second,1); lua_setglobal(L,entry.first);
}
bool success = luaL_loadstring(L, script.c_str()) == LUA_OK && lua_pcall(L,0,0,0) == LUA_OK;
lua_close(L);
return success;
}
'''
HARNESS += function("bool ClientlessLuaEngine::ReloadAdventureScript")
HARNESS += r'''
int main() {
Player p;
ClientlessLuaEngine engine;
std::string detail;
engine._profiles[26].Revision = 4;
manager.intent.Name = "old";
engine.script = "queue_action('move', {node='tram_if'}); intend('new'); error('failure')";
assert(!engine.ReloadAdventureScript(&p,detail));
assert(manager.actions.empty() && manager.intent.Name == "old");
assert(engine._profiles.at(26).Revision == 4 && !pendingReload);
engine.script = "this is invalid lua";
assert(!engine.ReloadAdventureScript(&p,detail));
assert(engine._profiles.at(26).Revision == 4 && manager.actions.empty());
engine.profileOK = false;
assert(!engine.ReloadAdventureScript(&p,detail));
engine.profileOK = true;
engine.script = "assert(not queue_action('')); queue_action('move', {node='tram_if'}); clear_intent(); intend('new')";
assert(engine.ReloadAdventureScript(&p,detail));
assert(manager.actions.size() == 1 && manager.actions[0].Parameters.at("node") == "tram_if");
assert(manager.intent.Name == "new" && engine._profiles.at(26).Revision == 5);
assert(!engine.ReloadAdventureScript(&p,detail)); // queued actions must not duplicate
manager.actions.clear();
executor.status.State = ClientlessActionExecutionState::Moving;
assert(!engine.ReloadAdventureScript(&p,detail));
executor.status.State = ClientlessActionExecutionState::Completed;
travel.status.State = ClientlessTravelState::Moving;
assert(!engine.ReloadAdventureScript(&p,detail));
travel.status.State = ClientlessTravelState::Arrived;
p.aboard = true; assert(!engine.ReloadAdventureScript(&p,detail)); p.aboard = false;
p.flying = true; assert(!engine.ReloadAdventureScript(&p,detail)); p.flying = false;
p.teleport = true; assert(!engine.ReloadAdventureScript(&p,detail)); p.teleport = false;
assert(!engine.ReloadAdventureScript(nullptr,detail));
manager.managed = false; assert(!engine.ReloadAdventureScript(&p,detail)); manager.managed = true;
engine._profiles.clear(); engine.script = "error('failure')";
assert(!engine.ReloadAdventureScript(&p,detail) && engine._profiles.empty());
engine.script = "clear_intent()";
assert(engine.ReloadAdventureScript(&p,detail) && manager.intent.Empty());
}
'''
with tempfile.TemporaryDirectory(prefix="vcore-lua-reload-") as folder:
source = pathlib.Path(folder) / "test.cpp"
binary = pathlib.Path(folder) / "test"
source.write_text(HARNESS)
flags = subprocess.check_output(["pkg-config", "--cflags", "--libs", "lua5.4"], text=True).split()
subprocess.run(["c++", "-std=c++17", str(source), "-o", str(binary), *flags], check=True)
subprocess.run([str(binary)], check=True)
print("Lua reload regression tests passed (production reload/bindings, real Lua 5.4).")