mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-05 20:33:44 +00:00
fix(dashboard): objects a client script hides on load aren't drawn in the 3D views
"Clear threat list Trigger Wall" (LOT 13632) on the NS medium property was
drawn as a big red wall: nothing in its data hides it, its client script
does (scripts/02_client/map/general/l_set_invisible.lua sets it invisible in
onRenderComponentReady). The scenery now reads each object's client script
once and treats an object as hidden, like renderDisabled, when a
self:SetVisible{visible = false} is directly in onStartup or
onRenderComponentReady (not under a condition). 12 LOTs match: trigger
volumes, effect containers, a scripted camera, dummies, booty chests (shown
once dug). They show with Hidden objects on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -144,8 +144,32 @@ namespace {
|
||||
std::string asset; // RenderComponent.render_asset as stored
|
||||
std::string type; // Objects.type
|
||||
int32_t shader{ -1 }; // mapShaders.gameValue of RenderComponent.shader_id (-1 fixed function or unknown)
|
||||
bool hidesOnLoad{}; // its client script hides it as soon as it loads (WorldScene::ClientScriptHidesOnLoad)
|
||||
};
|
||||
|
||||
// A client script's text, by its ScriptComponent path (backslashes, any case); empty when the client has none
|
||||
std::string ReadClientScript(const std::string& stored) {
|
||||
static const auto index = [] {
|
||||
std::unordered_map<std::string, std::filesystem::path> files;
|
||||
const auto res = ClientAssets::ResFolder();
|
||||
if (res.empty()) return files;
|
||||
std::error_code ec;
|
||||
for (const auto& top : std::filesystem::directory_iterator(res, ec)) {
|
||||
if (Lower(top.path().filename().string()) != "scripts" || !top.is_directory(ec)) continue;
|
||||
for (auto it = std::filesystem::recursive_directory_iterator(top.path(), ec); !ec && it != std::filesystem::recursive_directory_iterator(); it.increment(ec)) {
|
||||
if (it->is_regular_file(ec)) files.emplace(Lower(std::filesystem::relative(it->path(), res, ec).generic_string()), it->path());
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}();
|
||||
auto key = Lower(stored);
|
||||
std::replace(key.begin(), key.end(), '\\', '/');
|
||||
const auto it = index.find(key);
|
||||
if (it == index.end()) return {};
|
||||
std::ifstream in(it->second, std::ios::binary);
|
||||
return std::string(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
// mapShaders: id (what RenderComponent.shader_id and multishader tags name) -> gameValue (the shader drawn)
|
||||
std::map<int32_t, int32_t> g_ShaderValues;
|
||||
|
||||
@@ -161,6 +185,19 @@ namespace {
|
||||
}
|
||||
auto shaders = CDClientDatabase::ExecuteQuery("SELECT id, gameValue FROM mapShaders;");
|
||||
for (; !shaders.eof(); shaders.nextRow()) g_ShaderValues.try_emplace(shaders.getIntField(0), shaders.getIntField(1));
|
||||
// Objects whose client script hides them as soon as they load
|
||||
auto scripts = CDClientDatabase::ExecuteQuery(
|
||||
"SELECT cr.id, sc.client_script_name FROM ComponentsRegistry cr JOIN ScriptComponent sc ON sc.id = cr.component_id "
|
||||
"WHERE cr.component_type = " + std::to_string(static_cast<int32_t>(eReplicaComponentType::SCRIPT)) + " AND sc.client_script_name IS NOT NULL AND sc.client_script_name != '';");
|
||||
std::unordered_map<std::string, bool> hides; // by script, read once
|
||||
for (; !scripts.eof(); scripts.nextRow()) {
|
||||
const auto it = infos.find(static_cast<uint32_t>(scripts.getIntField(0)));
|
||||
if (it == infos.end()) continue;
|
||||
const std::string script = scripts.getStringField(1, "");
|
||||
auto [known, added] = hides.try_emplace(script, false);
|
||||
if (added) known->second = WorldScene::ClientScriptHidesOnLoad(ReadClientScript(script));
|
||||
it->second.hidesOnLoad = it->second.hidesOnLoad || known->second;
|
||||
}
|
||||
auto types = CDClientDatabase::ExecuteQuery("SELECT id, type FROM Objects;");
|
||||
for (; !types.eof(); types.nextRow()) {
|
||||
const auto it = infos.find(static_cast<uint32_t>(types.getIntField(0)));
|
||||
@@ -192,7 +229,8 @@ namespace {
|
||||
const auto& infos = RenderInfos();
|
||||
const auto info = infos.find(lot);
|
||||
if (info == infos.end()) return {};
|
||||
const auto draw = WorldScene::ClientDraws(object, info->second.type);
|
||||
auto draw = WorldScene::ClientDraws(object, info->second.type);
|
||||
if (draw == WorldScene::eClientDraw::DRAWN && info->second.hidesOnLoad) draw = WorldScene::eClientDraw::HIDDEN;
|
||||
if (draw == WorldScene::eClientDraw::NO_MODEL) return {};
|
||||
static std::mutex mutex;
|
||||
static std::unordered_map<std::string, std::string> resolved;
|
||||
|
||||
@@ -82,6 +82,38 @@ namespace WorldScene {
|
||||
DRAWN
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether a client script hides its object as soon as it loads: a self:SetVisible{visible = false ...} statement
|
||||
* directly in the body of onStartup or onRenderComponentReady, not inside a condition (as
|
||||
* scripts/02_client/map/general/l_set_invisible.lua on "Clear threat list Trigger Wall", or l_bootydig_client.lua,
|
||||
* which shows its object again once it's dug). The game runs the script; the viewers read it.
|
||||
*/
|
||||
inline bool ClientScriptHidesOnLoad(const std::string& script) {
|
||||
std::istringstream lines(script);
|
||||
std::string line;
|
||||
bool inLoadHandler = false;
|
||||
while (std::getline(lines, line)) {
|
||||
if (!line.empty() && line.back() == '\r') line.pop_back();
|
||||
if (line.starts_with("function ")) {
|
||||
const auto name = line.substr(9, line.find('(') == std::string::npos ? std::string::npos : line.find('(') - 9);
|
||||
inLoadHandler = name == "onStartup" || name == "onRenderComponentReady";
|
||||
continue;
|
||||
}
|
||||
if (line.starts_with("end")) {
|
||||
inLoadHandler = false;
|
||||
continue;
|
||||
}
|
||||
if (!inLoadHandler) continue;
|
||||
// One level in: a tab, or up to four spaces
|
||||
const auto indent = line.find_first_not_of(" \t");
|
||||
if (indent == std::string::npos || !(line.compare(0, indent, "\t") == 0 || (indent <= 4 && line.find('\t') >= indent))) continue;
|
||||
std::string statement;
|
||||
for (const char c : line.substr(indent)) if (c != ' ') statement += c;
|
||||
if (statement.starts_with("self:SetVisible{visible=false")) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the client draws an object's model, given its LOT's Objects.type. As the client's
|
||||
* ObjectLoader2::LoadRenderComponent (0x01053ca5 in client 1.10.64): nothing for renderDisabled or CreateNULLRender,
|
||||
|
||||
@@ -142,3 +142,16 @@ TEST(WorldSceneTests, ClientDrawsWhatTheGameDraws) {
|
||||
EXPECT_EQ(WorldScene::ClientDraws(read[4], "Smashables"), eClientDraw::NO_MODEL);
|
||||
EXPECT_EQ(WorldScene::ClientDraws(read[5], "Environmental"), eClientDraw::HIDDEN);
|
||||
}
|
||||
|
||||
// A client script that hides its object as soon as it loads (a top-level SetVisible false in onStartup or
|
||||
// onRenderComponentReady) makes the viewers treat the object as hidden; conditional ones don't
|
||||
TEST(WorldSceneTests, ClientScriptsThatHideOnLoad) {
|
||||
// scripts/02_client/map/general/l_set_invisible.lua
|
||||
EXPECT_TRUE(WorldScene::ClientScriptHidesOnLoad("-- comment\nfunction onRenderComponentReady(self,msg)\n\tself:SetVisible{visible = false, fadeTime = 0}\nend"));
|
||||
EXPECT_TRUE(WorldScene::ClientScriptHidesOnLoad("function onStartup(self)\r\n self:SetVisible{ visible = false }\r\nend\r\n"));
|
||||
// Only when a condition holds, in another handler, or shown
|
||||
EXPECT_FALSE(WorldScene::ClientScriptHidesOnLoad("function onStartup(self)\n\tif x then\n\t\tself:SetVisible{visible = false}\n\tend\nend"));
|
||||
EXPECT_FALSE(WorldScene::ClientScriptHidesOnLoad("function onTimerDone(self,msg)\n\tself:SetVisible{visible = false}\nend"));
|
||||
EXPECT_FALSE(WorldScene::ClientScriptHidesOnLoad("function onStartup(self)\n\tself:SetVisible{visible = true}\nend"));
|
||||
EXPECT_FALSE(WorldScene::ClientScriptHidesOnLoad(""));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user