diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index 08a2464c6..9c42910b4 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -16,6 +16,7 @@ set(DGAME_DCOMPONENTS_SOURCES "Character/LevelProgressionComponent.cpp" "LUPExhibitComponent.cpp" "Character/MissionComponent.cpp" + "Character/MountControlComponent.cpp" "MissionOfferComponent.cpp" "ModelComponent.cpp" "ModuleAssemblyComponent.cpp" diff --git a/dGame/dComponents/Character/MountControlComponent.cpp b/dGame/dComponents/Character/MountControlComponent.cpp new file mode 100644 index 000000000..2360433e6 --- /dev/null +++ b/dGame/dComponents/Character/MountControlComponent.cpp @@ -0,0 +1,23 @@ +#include "MountControlComponent.h" + +#include "Entity.h" +#include "PossessorComponent.h" + +#include "tinyxml2.h" + +void MountControlComponent::LoadFromSaveData(const tinyxml2::XMLDocument& doc) { + const auto* const obj = doc.FirstChildElement("obj"); + const auto* const mount = obj ? obj->FirstChildElement("mnt") : nullptr; + if (mount) mount->QueryInt64Attribute("a", &m_SavedMountItemID); +} + +void MountControlComponent::WriteSaveData(tinyxml2::XMLDocument& doc) { + auto* const obj = doc.FirstChildElement("obj"); + if (!obj) return; + + auto* mount = obj->FirstChildElement("mnt"); + if (!mount) mount = obj->InsertNewChildElement("mnt"); + + const auto* const possessor = m_Parent->GetComponent(); + mount->SetAttribute("a", possessor ? possessor->GetMountItemID() : LWOOBJID_EMPTY); +} diff --git a/dGame/dComponents/Character/MountControlComponent.h b/dGame/dComponents/Character/MountControlComponent.h index 00cf101f2..02494f7cf 100644 --- a/dGame/dComponents/Character/MountControlComponent.h +++ b/dGame/dComponents/Character/MountControlComponent.h @@ -2,17 +2,31 @@ #define MOUNTCONTROLCOMPONENT_H #include "Component.h" +#include "dCommonVars.h" #include "eReplicaComponentType.h" /** * Mount control (111): the character's riding of mounts, as the client's LWOMountControlComponent: the client makes it (componentID -1) with every character - * (LWOCharacterComponent::Factory). Nothing of it is sent or done on the server yet. + * (LWOCharacterComponent::Factory). It keeps the mount item in the character's save, as live did (, + * 0 when not riding: all 450 live saves looked at were 0). */ class MountControlComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MOUNT_CONTROL; MountControlComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {} + + // LWOMountControlComponent::LoadFromSaveData (0x00bf99c0): , the mount item of the save + void LoadFromSaveData(const tinyxml2::XMLDocument& doc) override; + + // Writes : the item of the mount being ridden (PossessorComponent's), 0 when on foot + void WriteSaveData(tinyxml2::XMLDocument& doc) override; + + // The mount item the save named. Riding does not go on over a world change, so nothing is mounted from it. + LWOOBJID GetSavedMountItemID() const { return m_SavedMountItemID; } + +private: + LWOOBJID m_SavedMountItemID = LWOOBJID_EMPTY; }; #endif // MOUNTCONTROLCOMPONENT_H diff --git a/tests/dGameTests/dComponentsTests/CMakeLists.txt b/tests/dGameTests/dComponentsTests/CMakeLists.txt index fb9cd3edd..45d4e2242 100644 --- a/tests/dGameTests/dComponentsTests/CMakeLists.txt +++ b/tests/dGameTests/dComponentsTests/CMakeLists.txt @@ -6,6 +6,7 @@ set(DCOMPONENTS_TESTS "DestroyableLevelConfigTests.cpp" "ProximityMonitorComponentTests.cpp" "RailActivatorComponentTests.cpp" + "MountControlComponentTests.cpp" "ConstructionOnlyComponentTests.cpp" "PropertyVendorComponentTests.cpp" "VendorBuybackTests.cpp" diff --git a/tests/dGameTests/dComponentsTests/MountControlComponentTests.cpp b/tests/dGameTests/dComponentsTests/MountControlComponentTests.cpp new file mode 100644 index 000000000..cf930a084 --- /dev/null +++ b/tests/dGameTests/dComponentsTests/MountControlComponentTests.cpp @@ -0,0 +1,62 @@ +// The mount item in a character's save: LWOMountControlComponent::LoadFromSaveData (0x00bf99c0) reads , and +// every live save had the element ( on the 450 looked at). +#include "GameDependencies.h" + +#include "Entity.h" +#include "MountControlComponent.h" +#include "PossessorComponent.h" + +#include "tinyxml2.h" + +#include +#include + +class MountControlComponentTests : public GameDependenciesTest { +protected: + void SetUp() override { SetUpDependencies(); } + void TearDown() override { TearDownDependencies(); } + + static std::string Print(const tinyxml2::XMLDocument& doc) { + tinyxml2::XMLPrinter printer(nullptr, true); + doc.Print(&printer); + return printer.CStr(); + } +}; + +// A save from before the element was written gains it, and nothing else changes +TEST_F(MountControlComponentTests, OldSaveGainsTheElement) { + tinyxml2::XMLDocument doc; + ASSERT_EQ(doc.Parse(R"()"), tinyxml2::XML_SUCCESS); + Entity player(70, info); + auto* const mountControl = player.AddComponent(-1); + mountControl->LoadFromSaveData(doc); + EXPECT_EQ(mountControl->GetSavedMountItemID(), LWOOBJID_EMPTY); + mountControl->WriteSaveData(doc); + EXPECT_EQ(Print(doc), R"()"); + // Saving again changes nothing + mountControl->WriteSaveData(doc); + EXPECT_EQ(Print(doc), R"()"); +} + +// The item of the mount being ridden is saved, in the element's own place; on foot it is 0 again +TEST_F(MountControlComponentTests, TheRiddenMountIsSaved) { + tinyxml2::XMLDocument doc; + ASSERT_EQ(doc.Parse(R"()"), tinyxml2::XML_SUCCESS); + Entity player(71, info); + auto* const possessor = player.AddComponent(-1); + auto* const mountControl = player.AddComponent(-1); + mountControl->LoadFromSaveData(doc); + EXPECT_EQ(mountControl->GetSavedMountItemID(), 1152921510436607007LL); + + // Nothing is ridden after a load + mountControl->WriteSaveData(doc); + EXPECT_EQ(Print(doc), R"()"); + + possessor->SetMountItemID(1152921510436607123LL); + mountControl->WriteSaveData(doc); + EXPECT_EQ(Print(doc), R"()"); + + possessor->SetMountItemID(LWOOBJID_EMPTY); + mountControl->WriteSaveData(doc); + EXPECT_EQ(Print(doc), R"()"); +}