diff --git a/dCommon/LevelFile.cpp b/dCommon/LevelFile.cpp index 326a15366..f3d7fa2ff 100644 --- a/dCommon/LevelFile.cpp +++ b/dCommon/LevelFile.cpp @@ -127,6 +127,42 @@ void LevelFile::ReadFileInfoChunk(std::istream& file, ChunkHeader& header) { BinaryIO::BinaryRead(file, header.fileInfo.particleChunkStart); } +void LevelFile::ApplyClientConfigFixups(SceneObject& obj, uint32_t version) { + // What ReadLvlObjectData (1.10.64, 0x0103ba20) changes in an object's config as it loads it + auto& settings = obj.settings; + const auto typeOf = [&settings](const std::u16string& key) { + const auto it = settings.find(key); + return it == settings.end() || !it->second ? LDF_TYPE_UNKNOWN : it->second->GetValueType(); + }; + const auto valueOf = [&settings](const std::u16string& key) { + return static_cast*>(settings.find(key)->second.get())->GetValue(); + }; + + // Before version 47 a respawn time was in milliseconds: a float over 100, or any u32, becomes seconds (a float) + if (version < 47) { + if (typeOf(u"respawn") == LDF_TYPE_FLOAT) { + const auto respawn = valueOf.operator()(u"respawn"); + if (respawn > 100.0f) settings.Insert(u"respawn", respawn * 0.001f); + } else if (typeOf(u"respawn") == LDF_TYPE_U32) { + settings.Insert(u"respawn", static_cast(valueOf.operator()(u"respawn") * 0.001)); + } + } + + // A model spawner (LOT 176 spawning template 14): its blueprint is its subkey unless it has one, its behaviors are + // off unless it says otherwise, and it never wraps its render + if (obj.lot == 176 && typeOf(u"spawntemplate") == LDF_TYPE_S32 && valueOf.operator()(u"spawntemplate") == 14) { + if (typeOf(u"blueprintid") == LDF_TYPE_UNKNOWN) { + const LWOOBJID subkey = typeOf(u"subkey") == LDF_TYPE_OBJID ? valueOf.operator()(u"subkey") : LWOOBJID_EMPTY; + settings.Insert(u"blueprintid", subkey); + } + if (typeOf(u"DisableModelBehaviors") == LDF_TYPE_UNKNOWN) settings.Insert(u"DisableModelBehaviors", true); + settings.Insert(u"preventRenderWrapping", true); + } + + // Before version 44 an object that overrides its scene goes to layer 0 of the scene it names + if (version < 44 && typeOf(u"sceneIDOverrideEnabled") != LDF_TYPE_UNKNOWN) settings.Insert(u"sceneLayerIDOverride", 0); +} + void LevelFile::ReadSceneObjectDataChunk(std::istream& file, uint32_t version) { uint32_t objectsCount = 0; BinaryIO::BinaryRead(file, objectsCount); @@ -165,6 +201,7 @@ void LevelFile::ReadSceneObjectDataChunk(std::istream& file, uint32_t version) { for (const auto& token : GeneralUtils::SplitString(GeneralUtils::UTF16ToWTF8(ldfString), '\n')) { obj.settings.ParseInsert(token); } + ApplyClientConfigFixups(obj, version); objects.push_back(std::move(obj)); } diff --git a/dCommon/LevelFile.h b/dCommon/LevelFile.h index 881517a45..98f992b29 100644 --- a/dCommon/LevelFile.h +++ b/dCommon/LevelFile.h @@ -65,4 +65,5 @@ private: static ChunkHeader ReadChunkHeader(std::istream& file, uint32_t start); void ReadFileInfoChunk(std::istream& file, ChunkHeader& header); void ReadSceneObjectDataChunk(std::istream& file, uint32_t version); + static void ApplyClientConfigFixups(SceneObject& obj, uint32_t version); }; diff --git a/tests/dCommonTests/ZoneFileTests.cpp b/tests/dCommonTests/ZoneFileTests.cpp index ff77ea421..67e05fe9e 100644 --- a/tests/dCommonTests/ZoneFileTests.cpp +++ b/tests/dCommonTests/ZoneFileTests.cpp @@ -505,6 +505,36 @@ TEST(LevelFileTests, SkipsEditorSettingsBySize) { EXPECT_TRUE(oldLevel.objects.empty()); } +// Config the client changes as it loads an object +TEST(LevelFileTests, FixesUpConfigAsTheClient) { + ZoneBytes w; + w.Put(30).Put(0); + w.data.append(48 + 12, '\0'); + w.Put(0).Put(4); + w.Object(30, 176, 0, "respawn=3:5000\nsceneIDOverrideEnabled=7:1"); + w.Object(30, 176, 0, "respawn=5:20000"); + w.Object(30, 176, 0, "respawn=3:20\nspawntemplate=1:14\nsubkey=9:123"); + w.Object(30, 176, 0, "spawntemplate=1:14\nblueprintid=9:5\nDisableModelBehaviors=7:0"); + + std::istringstream stream(w.Done()); + LevelFile level; + level.Read(stream); + ASSERT_EQ(level.objects.size(), 4u); + const auto value = [&level](size_t object, const std::u16string& key) { + const auto it = level.objects[object].settings.find(key); + return it == level.objects[object].settings.end() ? std::string("none") : it->second->GetString(); + }; + EXPECT_EQ(value(0, u"respawn"), "respawn=3:5.000000"); + EXPECT_EQ(value(0, u"sceneLayerIDOverride"), "sceneLayerIDOverride=5:0"); + EXPECT_EQ(value(1, u"respawn"), "respawn=3:20.000000"); + EXPECT_EQ(value(2, u"respawn"), "respawn=3:20.000000"); + EXPECT_EQ(value(2, u"blueprintid"), "blueprintid=9:123"); + EXPECT_EQ(value(2, u"DisableModelBehaviors"), "DisableModelBehaviors=7:1"); + EXPECT_EQ(value(2, u"preventRenderWrapping"), "preventRenderWrapping=7:1"); + EXPECT_EQ(value(3, u"blueprintid"), "blueprintid=9:5"); + EXPECT_EQ(value(3, u"DisableModelBehaviors"), "DisableModelBehaviors=7:0"); +} + TEST(LevelFileTests, DamagedFilesKeepWhatWasRead) { ZoneBytes w; w.Put(30).Put(0);