diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 495ece650..f4ef4fe3e 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -32,6 +32,7 @@ #include "ZoneMessages.h" #include "MovementMessages.h" #include "SkillComponent.h" +#include "Behavior.h" #include "Character.h" #include "EntityManager.h" #include "ItemSet.h" @@ -1694,6 +1695,20 @@ std::vector InventoryComponent::FindBuffs(Item* item, bool castOnEquip return buffs; } +std::vector InventoryComponent::FindEquippedJetPacks() const { + std::vector jetPacks; + for (const auto& equipped : m_Equipped | std::views::values) { + for (const auto behaviorID : FindBuffs(FindItemById(equipped.id), false)) { + if (Behavior::GetBehaviorTemplate(behaviorID) == BehaviorTemplate::JETPACK) jetPacks.push_back(behaviorID); + } + } + return jetPacks; +} + +void InventoryComponent::RecastEquippedJetPacks() const { + for (const auto behaviorID : FindEquippedJetPacks()) SkillComponent::HandleUnmanaged(behaviorID, m_Parent->GetObjectID()); +} + void InventoryComponent::SetNPCItems(const std::vector& items) { m_Equipped.clear(); diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 332ce2674..ebbff31e0 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -364,6 +364,17 @@ public: */ std::vector FindBuffs(Item* item, bool castOnEquip) const; + /** + * The jetpack behaviors among the equip skills of the equipped items + */ + std::vector FindEquippedJetPacks() const; + + /** + * Puts a player that wears a jetpack back in jetpack mode after a world load: live sent SetJetPackMode again + * shortly after the player's own construction in the next world, as the equip skill sends it + */ + void RecastEquippedJetPacks() const; + /** * One DeletionRestrictions row (CheckDeletionRestrictionIndex @ 00c94c20). visited stops rows that refer back to * themselves; the client lets such rows through. diff --git a/dGame/dGameMessages/ZoneMessages.cpp b/dGame/dGameMessages/ZoneMessages.cpp index 5d7b11804..45b3c0809 100644 --- a/dGame/dGameMessages/ZoneMessages.cpp +++ b/dGame/dGameMessages/ZoneMessages.cpp @@ -143,6 +143,9 @@ namespace GameMessages { character->OnZoneLoad(); } + // A jetpack that is worn works again in the new world (jetpack mode is not saved) + if (auto* const inventory = entity->GetComponent()) inventory->RecastEquippedJetPacks(); + // Moved here from another instance: put back what their save doesn't keep (the pet that was out) WorldMigration::OnPlayerLoaded(entity); diff --git a/tests/dGameTests/dComponentsTests/EquipMessagesTests.cpp b/tests/dGameTests/dComponentsTests/EquipMessagesTests.cpp index 773038b5a..f129a79a8 100644 --- a/tests/dGameTests/dComponentsTests/EquipMessagesTests.cpp +++ b/tests/dGameTests/dComponentsTests/EquipMessagesTests.cpp @@ -6,7 +6,10 @@ #include "CDClientDatabase.h" #include "CDComponentsRegistryTable.h" #include "CDItemComponentTable.h" +#include "CDBehaviorTemplateTable.h" #include "CDObjectSkillsTable.h" +#include "CDSkillBehaviorTable.h" +#include "BehaviorTemplate.h" #include "Character.h" #include "CharacterComponent.h" #include "EffectsMessages.h" @@ -210,3 +213,41 @@ TEST_F(EquipMessagesTests, UnequippingAndSlotsWithoutAnEffect) { sent = Messages(Capture([&] { rocket->Equip(true); })); EXPECT_EQ(sent, (std::vector{ { CHANGE_OBJECT_WORLD_STATE, rocket->GetId(), true, "ATTACHED" } })); } + +// A worn jetpack is found again after a world load, so jetpack mode can be sent again: live sent SetJetPackMode (use, +// bypass checks, effect 167) about 100 packets after the player's own construction in 12 of 28 world loads of a player +// with a jetpack, and constructed the player in jetpack mode in the other 16. +TEST_F(EquipMessagesTests, AWornJetPackIsFoundAfterAWorldLoad) { + constexpr LOT JETPACK = 1727; // skill 306, behavior 3569 on equip + RegisterLot(JETPACK, "back"); + CDClientManager::GetEntriesMutable().push_back({ JETPACK, 306, 1, 0 }); + CDClientManager::GetEntriesMutable().push_back({ SWORD, 400, 0, 0 }); // a use skill, not an equip one + auto& skills = CDClientManager::GetEntriesMutable(); + CDSkillBehavior jetPackSkill{}; + jetPackSkill.skillID = 306; + jetPackSkill.behaviorID = 3569; + skills.insert_or_assign(306, jetPackSkill); + CDSkillBehavior hoodSkill{}; + hoodSkill.skillID = 362; + hoodSkill.behaviorID = 4000; + skills.insert_or_assign(362, hoodSkill); + auto& templates = CDClientManager::GetEntriesMutable(); + CDBehaviorTemplate jetPackTemplate{}; + jetPackTemplate.behaviorID = 3569; + jetPackTemplate.templateID = static_cast(BehaviorTemplate::JETPACK); + templates.insert_or_assign(3569, jetPackTemplate); + CDBehaviorTemplate buffTemplate{}; + buffTemplate.behaviorID = 4000; + buffTemplate.templateID = static_cast(BehaviorTemplate::BUFF); + templates.insert_or_assign(4000, buffTemplate); + + EXPECT_TRUE(inventory->FindEquippedJetPacks().empty()); + // What a save puts back: the equipped items, without their equip skills cast + auto* const hood = Give(HOOD); + auto* const jetPack = Give(JETPACK); + Give(SWORD); + inventory->UpdateSlot("hair", { hood->GetId(), HOOD, 1, 0 }); + EXPECT_TRUE(inventory->FindEquippedJetPacks().empty()); + inventory->UpdateSlot("back", { jetPack->GetId(), JETPACK, 1, 1 }); + EXPECT_EQ(inventory->FindEquippedJetPacks(), std::vector{ 3569 }); +}