diff --git a/dCommon/dCommonVars.h b/dCommon/dCommonVars.h index cbecdd37..a52a1dc6 100644 --- a/dCommon/dCommonVars.h +++ b/dCommon/dCommonVars.h @@ -415,6 +415,12 @@ enum eReplicaComponentType : int32_t { COMPONENT_TYPE_MODEL = 5398484 //look man idk }; +enum class UseItemResponse : uint32_t { + NoImaginationForPet = 1, + FailedPrecondition, + MountsNotAllowed +}; + /** * Represents the different types of inventories an entity may have */ diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index cf17b0ac..e47f5bb5 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -24,6 +24,7 @@ #include "dZoneManager.h" #include "PropertyManagementComponent.h" #include "DestroyableComponent.h" +#include "dConfig.h" InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent) { @@ -1351,6 +1352,14 @@ void InventoryComponent::SpawnPet(Item* item) } } + // First check if we can summon the pet. You need 1 imagination to do so. + auto destroyableComponent = m_Parent->GetComponent(); + + if (Game::config->GetValue("pets_take_imagination") == "1" && destroyableComponent && destroyableComponent->GetImagination() <= 0) { + GameMessages::SendUseItemRequirementsResponse(m_Parent->GetObjectID(), m_Parent->GetSystemAddress(), UseItemResponse::NoImaginationForPet); + return; + } + EntityInfo info {}; info.lot = item->GetLot(); info.pos = m_Parent->GetPosition(); diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index a36d59e3..d54087aa 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -16,6 +16,7 @@ #include "../dWorldServer/ObjectIDManager.h" #include "Game.h" +#include "dConfig.h" #include "dChatFilter.h" #include "Database.h" @@ -81,6 +82,20 @@ PetComponent::PetComponent(Entity* parent, uint32_t componentId) : Component(par if (!checkPreconditions.empty()) { SetPreconditions(checkPreconditions); } + // Get the imagination drain rate from the CDClient + auto query = CDClientDatabase::CreatePreppedStmt("SELECT imaginationDrainRate FROM PetComponent WHERE id = ?;"); + + query.bind(1, static_cast(componentId)); + + auto result = query.execQuery(); + + // Should a result not exist for this pet default to 60 seconds. + if (!result.eof() && !result.fieldIsNull(0)) { + imaginationDrainRate = result.getFloatField(0, 60.0f); + } else { + imaginationDrainRate = 60.0f; + } + result.finalize(); } void PetComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) @@ -636,7 +651,7 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) inventoryComponent->SetDatabasePet(petSubKey, databasePet); - Activate(item, false); + Activate(item, false, true); m_Timer = 0; @@ -897,8 +912,10 @@ void PetComponent::Wander() m_Timer += (m_MovementAI->GetCurrentPosition().x - destination.x) / info.wanderSpeed; } -void PetComponent::Activate(Item* item, bool registerPet) +void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) { + AddDrainImaginationTimer(item, fromTaming); + m_ItemId = item->GetId(); m_DatabaseId = item->GetSubKey(); @@ -968,6 +985,44 @@ void PetComponent::Activate(Item* item, bool registerPet) GameMessages::SendShowPetActionButton(m_Owner, 3, true, owner->GetSystemAddress()); } +void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) { + if (Game::config->GetValue("pets_take_imagination") != "1") return; + + auto playerInventory = item->GetInventory(); + if (!playerInventory) return; + + auto playerInventoryComponent = playerInventory->GetComponent(); + if (!playerInventoryComponent) return; + + auto playerEntity = playerInventoryComponent->GetParent(); + if (!playerEntity) return; + + auto playerDestroyableComponent = playerEntity->GetComponent(); + if (!playerDestroyableComponent) return; + + // Drain by 1 when you summon pet or when this method is called, but not when we have just tamed this pet. + if (!fromTaming) playerDestroyableComponent->Imagine(-1); + + // Set this to a variable so when this is called back from the player the timer doesn't fire off. + m_Parent->AddCallbackTimer(imaginationDrainRate, [playerDestroyableComponent, this, item](){ + if (!playerDestroyableComponent) { + Game::logger->Log("PetComponent", "No petComponent and/or no playerDestroyableComponent\n"); + return; + } + + // If we are out of imagination despawn the pet. + if (playerDestroyableComponent->GetImagination() == 0) { + this->Deactivate(); + auto playerEntity = playerDestroyableComponent->GetParent(); + if (!playerEntity) return; + + GameMessages::SendUseItemRequirementsResponse(playerEntity->GetObjectID(), playerEntity->GetSystemAddress(), UseItemResponse::NoImaginationForPet); + } + + this->AddDrainImaginationTimer(item); + }); +} + void PetComponent::Deactivate() { GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), -1, u"despawn", "", LWOOBJID_EMPTY, 1, 1, true); diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index 845cfe31..913cbc56 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -82,7 +82,7 @@ public: * @param item the item to create the pet from * @param registerPet notifies the client that the pet was spawned, not necessary if this pet is being tamed */ - void Activate(Item* item, bool registerPet = true); + void Activate(Item* item, bool registerPet = true, bool fromTaming = false); /** * Despawns the pet @@ -203,6 +203,14 @@ public: */ static PetComponent* GetActivePet(LWOOBJID owner); + /** + * Adds the timer to the owner of this pet to drain imagination at the rate + * specified by the parameter imaginationDrainRate + * + * @param item The item that represents this pet in the inventory. + */ + void AddDrainImaginationTimer(Item* item, bool fromTaming = false); + private: /** @@ -346,4 +354,9 @@ private: * Preconditions that need to be met before an entity can tame this pet */ PreconditionExpression* m_Preconditions; + + /** + * The rate at which imagination is drained from the user for having the pet out. + */ + float imaginationDrainRate; }; \ No newline at end of file diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 58aba14d..6e6236a6 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1363,6 +1363,18 @@ void GameMessages::SendUseItemResult(Entity* entity, LOT templateID, bool useIte SEND_PACKET } +void GameMessages::SendUseItemRequirementsResponse(LWOOBJID objectID, const SystemAddress& sysAddr, UseItemResponse itemResponse) { + CBITSTREAM + CMSGHEADER + + bitStream.Write(objectID); + bitStream.Write(GAME_MSG::GAME_MSG_USE_ITEM_REQUIREMENTS_RESPONSE); + + bitStream.Write(itemResponse); + + SEND_PACKET +} + void GameMessages::SendMoveInventoryBatch(Entity* entity, uint32_t stackCount, int srcInv, int dstInv, const LWOOBJID& iObjID) { CBITSTREAM CMSGHEADER diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 602cb4b2..1d42eebc 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -372,6 +372,7 @@ namespace GameMessages { void SendActivityPause(LWOOBJID objectId, bool pause = false, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); void SendStartActivityTime(LWOOBJID objectId, float_t startTime, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); void SendRequestActivityEnter(LWOOBJID objectId, const SystemAddress& sysAddr, bool bStart, LWOOBJID userID); + void SendUseItemRequirementsResponse(LWOOBJID objectID, const SystemAddress& sysAddr, UseItemResponse itemResponse); // SG: diff --git a/dNet/dMessageIdentifiers.h b/dNet/dMessageIdentifiers.h index 8e20ab54..5b2ff639 100644 --- a/dNet/dMessageIdentifiers.h +++ b/dNet/dMessageIdentifiers.h @@ -376,6 +376,7 @@ enum GAME_MSG : unsigned short { GAME_MSG_NOTIFY_PET_TAMING_PUZZLE_SELECTED = 675, GAME_MSG_SHOW_PET_ACTION_BUTTON = 692, GAME_MSG_SET_EMOTE_LOCK_STATE = 693, + GAME_MSG_USE_ITEM_REQUIREMENTS_RESPONSE = 703, GAME_MSG_PLAY_EMBEDDED_EFFECT_ON_ALL_CLIENTS_NEAR_OBJECT = 713, GAME_MSG_DOWNLOAD_PROPERTY_DATA = 716, GAME_MSG_QUERY_PROPERTY_DATA = 717, diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index b1fec1a6..a665f059 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -57,3 +57,6 @@ check_fdb=0 # 0 or 1, DLU leaderboards will rate Avant Gardens Survival based on score by default. # This option should be set to 1 if you would like it to reflect the game when it was live (scoring based on time). classic_survival_scoring=0 + +# If this value is 1, pets will consume imagination as they did in live. if 0 they will not consume imagination at all. +pets_take_imagination=1