importet Areateleports as Nodes
This commit is contained in:
1 parent
93c3df1c95
commit
fe7b5e0f02
12 files changed
+2087
-218
No files matched your search
@@ -2,6 +2,7 @@
|
||||
#include "ClientlessPlayerMgr.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include "Random.h"
|
||||
#include <lua.hpp>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
@@ -86,10 +87,92 @@ void BoundFn(lua_State* L, int env, char const* name, lua_CFunction fn, uint32 g
|
||||
lua_setfield(L, env, name);
|
||||
}
|
||||
|
||||
std::string ReadStringField(lua_State* L, int table, char const* key, std::string const& fallback = {})
|
||||
{
|
||||
table = lua_absindex(L, table);
|
||||
lua_getfield(L, table, key);
|
||||
std::string value = lua_isstring(L, -1) ? lua_tostring(L, -1) : fallback;
|
||||
lua_pop(L, 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
bool ReadBoolField(lua_State* L, int table, char const* key, bool fallback)
|
||||
{
|
||||
table = lua_absindex(L, table);
|
||||
lua_getfield(L, table, key);
|
||||
bool value = lua_isboolean(L, -1) ? lua_toboolean(L, -1) != 0 : fallback;
|
||||
lua_pop(L, 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
float ReadFloatField(lua_State* L, int table, char const* key, float fallback)
|
||||
{
|
||||
table = lua_absindex(L, table);
|
||||
lua_getfield(L, table, key);
|
||||
float value = lua_isnumber(L, -1) ? float(lua_tonumber(L, -1)) : fallback;
|
||||
lua_pop(L, 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, float> ReadFloatDictionary(lua_State* L, int table, char const* key)
|
||||
{
|
||||
std::unordered_map<std::string, float> out;
|
||||
table = lua_absindex(L, table);
|
||||
lua_getfield(L, table, key);
|
||||
if (!lua_istable(L, -1))
|
||||
{
|
||||
lua_pop(L, 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
int dict = lua_absindex(L, -1);
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, dict) != 0)
|
||||
{
|
||||
if (lua_type(L, -2) == LUA_TSTRING && lua_isnumber(L, -1))
|
||||
out[lua_tostring(L, -2)] = float(lua_tonumber(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<ClientlessWeightedMount> ReadMountDictionary(lua_State* L, int movementTable)
|
||||
{
|
||||
std::vector<ClientlessWeightedMount> mounts;
|
||||
movementTable = lua_absindex(L, movementTable);
|
||||
lua_getfield(L, movementTable, "ground_mounts");
|
||||
if (!lua_istable(L, -1))
|
||||
{
|
||||
lua_pop(L, 1);
|
||||
return mounts;
|
||||
}
|
||||
|
||||
int dict = lua_absindex(L, -1);
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, dict) != 0)
|
||||
{
|
||||
if (lua_isinteger(L, -2) && lua_isnumber(L, -1))
|
||||
{
|
||||
lua_Integer id = lua_tointeger(L, -2);
|
||||
lua_Number weight = lua_tonumber(L, -1);
|
||||
if (id > 0 && id <= 0xFFFFFFFFll && weight > 0.0)
|
||||
mounts.push_back({ uint32(id), uint32(std::max<lua_Number>(1.0, weight)) });
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
return mounts;
|
||||
}
|
||||
|
||||
class ClientlessAdventureLuaController final : public ClientlessPlayerController
|
||||
{
|
||||
public:
|
||||
void OnPlayerOnline(Player* player) override { sClientlessLuaEngine->ExecuteAdventureScript(player); }
|
||||
void OnPlayerOnline(Player* player) override
|
||||
{
|
||||
sClientlessLuaEngine->LoadAdventureProfile(player);
|
||||
sClientlessLuaEngine->ExecuteAdventureScript(player);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -172,6 +255,184 @@ bool ClientlessLuaEngine::ExecuteScript(Player* player, std::string const& path,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientlessLuaEngine::LoadAdventureProfile(Player* player)
|
||||
{
|
||||
if (!_state || !player)
|
||||
return false;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
std::string lowerName = player->GetName();
|
||||
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(),
|
||||
[](unsigned char c) { return char(std::tolower(c)); });
|
||||
|
||||
fs::path root(_scriptRoot);
|
||||
fs::path specific = root / "adventure" / "profiles" / (lowerName + ".lua");
|
||||
fs::path generic = root / "adventure" / "profiles" / "generic.lua";
|
||||
fs::path chosen = fs::exists(specific) ? specific : generic;
|
||||
|
||||
int base = lua_gettop(_state);
|
||||
if (luaL_loadfile(_state, chosen.string().c_str()) != LUA_OK)
|
||||
{
|
||||
TC_LOG_ERROR("server.adventurebot",
|
||||
"[AdventureBot][Profile] guid=%u name='%s' load failed path='%s': %s",
|
||||
player->GetGUID().GetCounter(), player->GetName().c_str(),
|
||||
chosen.string().c_str(), lua_tostring(_state, -1));
|
||||
lua_settop(_state, base);
|
||||
return false;
|
||||
}
|
||||
|
||||
PushSandboxEnvironment(player);
|
||||
if (!lua_setupvalue(_state, base + 1, 1))
|
||||
{
|
||||
TC_LOG_ERROR("server.adventurebot",
|
||||
"[AdventureBot][Profile] guid=%u path='%s' has no _ENV upvalue",
|
||||
player->GetGUID().GetCounter(), chosen.string().c_str());
|
||||
lua_settop(_state, base);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lua_pcall(_state, 0, 1, 0) != LUA_OK)
|
||||
{
|
||||
TC_LOG_ERROR("server.adventurebot",
|
||||
"[AdventureBot][Profile] guid=%u runtime error path='%s': %s",
|
||||
player->GetGUID().GetCounter(), chosen.string().c_str(), lua_tostring(_state, -1));
|
||||
lua_settop(_state, base);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lua_istable(_state, -1))
|
||||
{
|
||||
TC_LOG_ERROR("server.adventurebot",
|
||||
"[AdventureBot][Profile] guid=%u path='%s' must return a table",
|
||||
player->GetGUID().GetCounter(), chosen.string().c_str());
|
||||
lua_settop(_state, base);
|
||||
return false;
|
||||
}
|
||||
|
||||
int rootTable = lua_absindex(_state, -1);
|
||||
ClientlessAdventureProfile profile;
|
||||
profile.CharacterName = player->GetName();
|
||||
profile.SourcePath = chosen.string();
|
||||
|
||||
lua_getfield(_state, rootTable, "identity");
|
||||
if (lua_istable(_state, -1))
|
||||
{
|
||||
profile.CharacterName = ReadStringField(_state, -1, "name", profile.CharacterName);
|
||||
profile.HomeRegion = ReadStringField(_state, -1, "home_region");
|
||||
}
|
||||
lua_pop(_state, 1);
|
||||
|
||||
lua_getfield(_state, rootTable, "movement");
|
||||
if (lua_istable(_state, -1))
|
||||
{
|
||||
profile.CanGroundMount = ReadBoolField(_state, -1, "can_ground_mount", false);
|
||||
profile.GroundMountMinDistance = std::max(0.0f,
|
||||
ReadFloatField(_state, -1, "ground_mount_min_distance", 60.0f));
|
||||
profile.GroundMountSpeedRate = std::max(1.0f,
|
||||
ReadFloatField(_state, -1, "ground_mount_speed_rate", 1.6f));
|
||||
profile.GroundMounts = ReadMountDictionary(_state, -1);
|
||||
}
|
||||
lua_pop(_state, 1);
|
||||
|
||||
profile.Personality = ReadFloatDictionary(_state, rootTable, "personality");
|
||||
profile.RegionAffinity = ReadFloatDictionary(_state, rootTable, "region_affinity");
|
||||
profile.ActivityAffinity = ReadFloatDictionary(_state, rootTable, "activity_affinity");
|
||||
|
||||
uint32 const guid = player->GetGUID().GetCounter();
|
||||
_profiles[guid] = std::move(profile);
|
||||
ClientlessAdventureProfile const& stored = _profiles.at(guid);
|
||||
|
||||
TC_LOG_INFO("server.adventurebot",
|
||||
"[AdventureBot][Profile] loaded guid=%u name='%s' source='%s' home='%s' "
|
||||
"canGroundMount=%u mounts=%u mountMinDistance=%.1f mountSpeedRate=%.2f "
|
||||
"personality=%u regionAffinity=%u activityAffinity=%u",
|
||||
guid, player->GetName().c_str(), stored.SourcePath.c_str(), stored.HomeRegion.c_str(),
|
||||
stored.CanGroundMount ? 1u : 0u, uint32(stored.GroundMounts.size()),
|
||||
stored.GroundMountMinDistance, stored.GroundMountSpeedRate,
|
||||
uint32(stored.Personality.size()), uint32(stored.RegionAffinity.size()),
|
||||
uint32(stored.ActivityAffinity.size()));
|
||||
|
||||
if (stored.CanGroundMount && stored.GroundMounts.empty())
|
||||
TC_LOG_WARN("server.adventurebot",
|
||||
"[AdventureBot][Profile] guid=%u enables ground mounts but defines no ground_mounts", guid);
|
||||
|
||||
lua_settop(_state, base);
|
||||
return true;
|
||||
}
|
||||
|
||||
ClientlessAdventureProfile const* ClientlessLuaEngine::GetAdventureProfile(uint32 characterGuid) const
|
||||
{
|
||||
auto itr = _profiles.find(characterGuid);
|
||||
return itr != _profiles.end() ? &itr->second : nullptr;
|
||||
}
|
||||
|
||||
uint32 ClientlessLuaEngine::SelectGroundMountDisplayId(uint32 characterGuid) const
|
||||
{
|
||||
ClientlessAdventureProfile const* profile = GetAdventureProfile(characterGuid);
|
||||
if (!profile || profile->GroundMounts.empty())
|
||||
return 0;
|
||||
|
||||
uint64 total = 0;
|
||||
for (ClientlessWeightedMount const& mount : profile->GroundMounts)
|
||||
total += mount.Weight;
|
||||
if (!total)
|
||||
return 0;
|
||||
|
||||
uint32 const cappedTotal = uint32(std::min<uint64>(total, 0xFFFFFFFFull));
|
||||
uint32 roll = urand(1u, cappedTotal);
|
||||
for (ClientlessWeightedMount const& mount : profile->GroundMounts)
|
||||
{
|
||||
if (roll <= mount.Weight)
|
||||
return mount.DisplayId;
|
||||
roll -= std::min(roll, mount.Weight);
|
||||
}
|
||||
return profile->GroundMounts.back().DisplayId;
|
||||
}
|
||||
|
||||
bool ClientlessLuaEngine::PrepareGroundMount(Player* player, float travelDistance, char const* context)
|
||||
{
|
||||
if (!player || !player->IsInWorld())
|
||||
return false;
|
||||
|
||||
ClientlessAdventureProfile const* profile = GetAdventureProfile(player->GetGUID().GetCounter());
|
||||
if (!profile || !profile->CanGroundMount || profile->GroundMounts.empty())
|
||||
return false;
|
||||
if (travelDistance < profile->GroundMountMinDistance || !player->IsOutdoors())
|
||||
return false;
|
||||
if (player->IsMounted())
|
||||
return true;
|
||||
|
||||
uint32 const displayId = SelectGroundMountDisplayId(player->GetGUID().GetCounter());
|
||||
if (!displayId)
|
||||
return false;
|
||||
|
||||
// The AdventureBot profile is authoritative for this capability. Intentionally
|
||||
// no level, riding-skill, spellbook, inventory or economy eligibility check.
|
||||
player->Mount(displayId);
|
||||
player->SetSpeedRate(MOVE_RUN, profile->GroundMountSpeedRate);
|
||||
|
||||
TC_LOG_INFO("server.adventurebot.navigation",
|
||||
"GROUND_MOUNT guid=%u name='%s' displayId=%u distance=%.2f speedRate=%.2f context='%s'",
|
||||
player->GetGUID().GetCounter(), player->GetName().c_str(), displayId,
|
||||
travelDistance, profile->GroundMountSpeedRate, context ? context : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientlessLuaEngine::DismountGroundMount(Player* player, char const* reason)
|
||||
{
|
||||
if (!player || !player->IsMounted())
|
||||
return;
|
||||
|
||||
uint32 const oldDisplay = player->GetMountID();
|
||||
player->Dismount();
|
||||
player->UpdateSpeed(MOVE_RUN);
|
||||
|
||||
TC_LOG_INFO("server.adventurebot.navigation",
|
||||
"GROUND_DISMOUNT guid=%u name='%s' displayId=%u reason='%s'",
|
||||
player->GetGUID().GetCounter(), player->GetName().c_str(), oldDisplay,
|
||||
reason ? reason : "");
|
||||
}
|
||||
|
||||
bool ClientlessLuaEngine::ExecuteAdventureScript(Player* player)
|
||||
{
|
||||
if (!player) return false;
|
||||
|
||||
Reference in new issue
Block a user