From 1328850a8d225d969784fb6b59fe282e4e019915 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 24 Feb 2024 23:01:28 -0800 Subject: [PATCH 1/5] buffRemoval (#1464) Update BuffComponent.cpp --- dGame/dComponents/BuffComponent.cpp | 7 +++---- dGame/dComponents/BuffComponent.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index cdf1d5bc..4122a800 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -208,9 +208,8 @@ void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOO void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity, bool ignoreRefCount) { const auto& iter = m_Buffs.find(id); - if (iter == m_Buffs.end()) { - return; - } + // If the buff is already scheduled to be removed, don't do it again + if (iter == m_Buffs.end() || m_BuffsToRemove.contains(id)) return; if (!ignoreRefCount && !iter->second.cancelOnRemoveBuff) { iter->second.refCount--; @@ -222,7 +221,7 @@ void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity GameMessages::SendRemoveBuff(m_Parent, fromUnEquip, removeImmunity, id); - m_BuffsToRemove.push_back(id); + m_BuffsToRemove.insert(id); RemoveBuffEffect(id); } diff --git a/dGame/dComponents/BuffComponent.h b/dGame/dComponents/BuffComponent.h index 18aa7a42..f509faa2 100644 --- a/dGame/dComponents/BuffComponent.h +++ b/dGame/dComponents/BuffComponent.h @@ -141,7 +141,7 @@ private: std::map m_Buffs; // Buffs to remove at the end of the update frame. - std::vector m_BuffsToRemove; + std::set m_BuffsToRemove; /** * Parameters (=effects) for each buff From e729c7f84659029a0777f409f2f146697797ab81 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sun, 25 Feb 2024 01:47:05 -0600 Subject: [PATCH 2/5] feat: achievement vendor and vendor feedback (#1461) * Groundwork * movie buying logic out of gm handler make transaction result more useful * Full implementation Cleanup and fix some calls in gamemessages * Load the component in the entity Patch Auth * new line at eof * cache lookups * remove sort * fix includes --- dCommon/dEnums/eReplicaComponentType.h | 2 +- dCommon/dEnums/eVendorTransactionResult.h | 15 +++ .../CDClientTables/CDMissionsTable.cpp | 10 +- .../CDClientTables/CDMissionsTable.h | 3 + dGame/Entity.cpp | 8 ++ .../AchievementVendorComponent.cpp | 72 +++++++++++ .../dComponents/AchievementVendorComponent.h | 23 ++++ dGame/dComponents/CMakeLists.txt | 1 + dGame/dComponents/VendorComponent.cpp | 62 +++++++++ dGame/dComponents/VendorComponent.h | 1 + dGame/dGameMessages/GameMessages.cpp | 121 ++++-------------- dGame/dGameMessages/GameMessages.h | 3 +- dNet/AuthPackets.cpp | 2 +- 13 files changed, 226 insertions(+), 97 deletions(-) create mode 100644 dCommon/dEnums/eVendorTransactionResult.h create mode 100644 dGame/dComponents/AchievementVendorComponent.cpp create mode 100644 dGame/dComponents/AchievementVendorComponent.h diff --git a/dCommon/dEnums/eReplicaComponentType.h b/dCommon/dEnums/eReplicaComponentType.h index 83acbf89..2b991dfb 100644 --- a/dCommon/dEnums/eReplicaComponentType.h +++ b/dCommon/dEnums/eReplicaComponentType.h @@ -106,7 +106,7 @@ enum class eReplicaComponentType : uint32_t { INTERACTION_MANAGER, DONATION_VENDOR, COMBAT_MEDIATOR, - COMMENDATION_VENDOR, + ACHIEVEMENT_VENDOR, GATE_RUSH_CONTROL, RAIL_ACTIVATOR, ROLLER, diff --git a/dCommon/dEnums/eVendorTransactionResult.h b/dCommon/dEnums/eVendorTransactionResult.h new file mode 100644 index 00000000..e61ee0ee --- /dev/null +++ b/dCommon/dEnums/eVendorTransactionResult.h @@ -0,0 +1,15 @@ +#ifndef __EVENDORTRANSACTIONRESULT__ +#define __EVENDORTRANSACTIONRESULT__ + +#include + +enum class eVendorTransactionResult : uint32_t { + SELL_SUCCESS = 0, + SELL_FAIL, + PURCHASE_SUCCESS, + PURCHASE_FAIL, + DONATION_FAIL, + DONATION_FULL +}; + +#endif // !__EVENDORTRANSACTIONRESULT__ diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp index 8862b1db..97dcde9f 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp @@ -79,7 +79,6 @@ void CDMissionsTable::LoadValuesFromDatabase() { entries.push_back(entry); tableData.nextRow(); } - tableData.finalize(); Default.id = -1; @@ -118,3 +117,12 @@ const CDMissions& CDMissionsTable::GetByMissionID(uint32_t missionID, bool& foun return Default; } +const std::set CDMissionsTable::GetMissionsForReward(LOT lot) { + std::set toReturn {}; + for (const auto& entry : GetEntries()) { + if (lot == entry.reward_item1 || lot == entry.reward_item2 || lot == entry.reward_item3 || lot == entry.reward_item4) { + toReturn.insert(entry.id); + } + } + return toReturn; +} diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h index 6ba7b19e..5067f2e2 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h +++ b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h @@ -70,6 +70,9 @@ public: const CDMissions& GetByMissionID(uint32_t missionID, bool& found) const; + const std::set GetMissionsForReward(LOT lot); + + static CDMissions Default; }; diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 269b4cc4..bb932991 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -82,6 +82,7 @@ #include "CollectibleComponent.h" #include "ItemComponent.h" #include "GhostComponent.h" +#include "AchievementVendorComponent.h" // Table includes #include "CDComponentsRegistryTable.h" @@ -615,6 +616,8 @@ void Entity::Initialize() { AddComponent(); } else if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::DONATION_VENDOR, -1) != -1)) { AddComponent(); + } else if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ACHIEVEMENT_VENDOR, -1) != -1)) { + AddComponent(); } if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_VENDOR, -1) != -1) { @@ -1191,6 +1194,11 @@ void Entity::WriteComponents(RakNet::BitStream* outBitStream, eReplicaPacketType donationVendorComponent->Serialize(outBitStream, bIsInitialUpdate); } + AchievementVendorComponent* achievementVendorComponent; + if (TryGetComponent(eReplicaComponentType::ACHIEVEMENT_VENDOR, achievementVendorComponent)) { + achievementVendorComponent->Serialize(outBitStream, bIsInitialUpdate); + } + BouncerComponent* bouncerComponent; if (TryGetComponent(eReplicaComponentType::BOUNCER, bouncerComponent)) { bouncerComponent->Serialize(outBitStream, bIsInitialUpdate); diff --git a/dGame/dComponents/AchievementVendorComponent.cpp b/dGame/dComponents/AchievementVendorComponent.cpp new file mode 100644 index 00000000..10a0ca29 --- /dev/null +++ b/dGame/dComponents/AchievementVendorComponent.cpp @@ -0,0 +1,72 @@ +#include "AchievementVendorComponent.h" +#include "MissionComponent.h" +#include "InventoryComponent.h" +#include "eMissionState.h" +#include "CDComponentsRegistryTable.h" +#include "CDItemComponentTable.h" +#include "eVendorTransactionResult.h" +#include "CheatDetection.h" +#include "UserManager.h" +#include "CDMissionsTable.h" + +bool AchievementVendorComponent::SellsItem(Entity* buyer, const LOT lot) { + auto* missionComponent = buyer->GetComponent(); + if (!missionComponent) return false; + + if (m_PlayerPurchasableItems[buyer->GetObjectID()].contains(lot)){ + return true; + } + + CDMissionsTable* missionsTable = CDClientManager::GetTable(); + const auto missions = missionsTable->GetMissionsForReward(lot); + for (const auto mission : missions) { + if (missionComponent->GetMissionState(mission) == eMissionState::COMPLETE) { + m_PlayerPurchasableItems[buyer->GetObjectID()].insert(lot); + return true; + } + } + return false; +} + +void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { + // get the item Comp from the item LOT + CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); + CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); + int itemCompID = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::ITEM); + CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID); + uint32_t costLOT = itemComp.commendationLOT; + + if (costLOT == -1 || !SellsItem(buyer, lot)) { + auto* user = UserManager::Instance()->GetUser(buyer->GetSystemAddress()); + CheatDetection::ReportCheat(user, buyer->GetSystemAddress(), "Attempted to buy item %i from achievement vendor %i that is not purchasable", lot, m_Parent->GetLOT()); + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + + auto* inventoryComponent = buyer->GetComponent(); + if (!inventoryComponent) { + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + + if (costLOT == 13763) { // Faction Token Proxy + auto* missionComponent = buyer->GetComponent(); + if (!missionComponent) return; + + if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) costLOT = 8318; // "Assembly Token" + if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) costLOT = 8321; // "Venture League Token" + if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) costLOT = 8319; // "Sentinels Token" + if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) costLOT = 8320; // "Paradox Token" + } + + const uint32_t altCurrencyCost = itemComp.commendationCost * count; + if (inventoryComponent->GetLotCount(costLOT) < altCurrencyCost) { + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + + inventoryComponent->RemoveItem(costLOT, altCurrencyCost); + inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR); + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS); + +} \ No newline at end of file diff --git a/dGame/dComponents/AchievementVendorComponent.h b/dGame/dComponents/AchievementVendorComponent.h new file mode 100644 index 00000000..bffd3983 --- /dev/null +++ b/dGame/dComponents/AchievementVendorComponent.h @@ -0,0 +1,23 @@ +#ifndef __ACHIEVEMENTVENDORCOMPONENT__H__ +#define __ACHIEVEMENTVENDORCOMPONENT__H__ + +#include "VendorComponent.h" +#include "eReplicaComponentType.h" +#include +#include + +class Entity; + +class AchievementVendorComponent final : public VendorComponent { +public: + static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ACHIEVEMENT_VENDOR; + AchievementVendorComponent(Entity* parent) : VendorComponent(parent) {}; + bool SellsItem(Entity* buyer, const LOT lot); + void Buy(Entity* buyer, LOT lot, uint32_t count); + +private: + std::map> m_PlayerPurchasableItems; +}; + + +#endif //!__ACHIEVEMENTVENDORCOMPONENT__H__ diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index ac509e11..21fe9207 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -1,4 +1,5 @@ set(DGAME_DCOMPONENTS_SOURCES + "AchievementVendorComponent.cpp" "ActivityComponent.cpp" "BaseCombatAIComponent.cpp" "BouncerComponent.cpp" diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index afa3d013..9e9428f7 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -8,6 +8,11 @@ #include "CDLootMatrixTable.h" #include "CDLootTableTable.h" #include "CDItemComponentTable.h" +#include "InventoryComponent.h" +#include "Character.h" +#include "eVendorTransactionResult.h" +#include "UserManager.h" +#include "CheatDetection.h" VendorComponent::VendorComponent(Entity* parent) : Component(parent) { m_HasStandardCostItems = false; @@ -151,3 +156,60 @@ void VendorComponent::HandleMrReeCameras(){ m_Inventory.push_back(SoldItem(camera, 0)); } } + + +void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) { + + if (!SellsItem(lot)) { + auto* user = UserManager::Instance()->GetUser(buyer->GetSystemAddress()); + CheatDetection::ReportCheat(user, buyer->GetSystemAddress(), "Attempted to buy item %i from achievement vendor %i that is not purchasable", lot, m_Parent->GetLOT()); + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + + auto* inventoryComponent = buyer->GetComponent(); + if (!inventoryComponent) { + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); + CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); + int itemCompID = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::ITEM); + CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID); + + // Extra currency that needs to be deducted in case of crafting + auto craftingCurrencies = CDItemComponentTable::ParseCraftingCurrencies(itemComp); + for (const auto& [crafintCurrencyLOT, crafintCurrencyCount]: craftingCurrencies) { + if (inventoryComponent->GetLotCount(crafintCurrencyLOT) < (crafintCurrencyCount * count)) { + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + } + for (const auto& [crafintCurrencyLOT, crafintCurrencyCount]: craftingCurrencies) { + inventoryComponent->RemoveItem(crafintCurrencyLOT, crafintCurrencyCount * count); + } + + + float buyScalar = GetBuyScalar(); + const auto coinCost = static_cast(std::floor((itemComp.baseValue * buyScalar) * count)); + + Character* character = buyer->GetCharacter(); + if (!character || character->GetCoins() < coinCost) { + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + + if (Inventory::IsValidItem(itemComp.currencyLOT)) { + const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * buyScalar) * count; + if (inventoryComponent->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) { + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL); + return; + } + inventoryComponent->RemoveItem(itemComp.currencyLOT, altCurrencyCost); + } + + character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR); + inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR); + GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS); + +} \ No newline at end of file diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index 48b766d2..9025148b 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -47,6 +47,7 @@ public: m_DirtyVendor = true; } + void Buy(Entity* buyer, LOT lot, uint32_t count); private: void SetupMaxCustomVendor(); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index c38ef880..a62bf382 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -78,6 +78,7 @@ #include "LevelProgressionComponent.h" #include "DonationVendorComponent.h" #include "GhostComponent.h" +#include "AchievementVendorComponent.h" // Message includes: #include "dZoneManager.h" @@ -97,6 +98,7 @@ #include "ePetAbilityType.h" #include "ActivityManager.h" #include "PlayerManager.h" +#include "eVendorTransactionResult.h" #include "CDComponentsRegistryTable.h" #include "CDObjectsTable.h" @@ -1323,15 +1325,14 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s SEND_PACKET; } -void GameMessages::SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr) { +void GameMessages::SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr, eVendorTransactionResult result) { CBITSTREAM; CMSGHEADER; - int iResult = 0x02; // success, seems to be the only relevant one bitStream.Write(entity->GetObjectID()); bitStream.Write(eGameMessageType::VENDOR_TRANSACTION_RESULT); - bitStream.Write(iResult); + bitStream.Write(result); SEND_PACKET; } @@ -4664,94 +4665,27 @@ void GameMessages::HandleBuyFromVendor(RakNet::BitStream* inStream, Entity* enti if (!user) return; Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar()); if (!player) return; + + // handle buying normal items + auto* vendorComponent = entity->GetComponent(); + if (vendorComponent) { + vendorComponent->Buy(player, item, count); + return; + } - auto* propertyVendorComponent = static_cast(entity->GetComponent(eReplicaComponentType::PROPERTY_VENDOR)); + // handle buying achievement items + auto* achievementVendorComponent = entity->GetComponent(); + if (achievementVendorComponent) { + achievementVendorComponent->Buy(player, item, count); + return; + } - if (propertyVendorComponent != nullptr) { + // Handle buying properties + auto* propertyVendorComponent = entity->GetComponent(); + if (propertyVendorComponent) { propertyVendorComponent->OnBuyFromVendor(player, bConfirmed, item, count); - return; } - - const auto isCommendationVendor = entity->GetLOT() == 13806; - - auto* vend = entity->GetComponent(); - if (!vend && !isCommendationVendor) return; - - auto* inv = player->GetComponent(); - if (!inv) return; - - if (!isCommendationVendor && !vend->SellsItem(item)) { - LOG("User %llu %s tried to buy an item %i from a vendor when they do not sell said item", player->GetObjectID(), user->GetUsername().c_str(), item); - return; - } - - CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable(); - CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); - - int itemCompID = compRegistryTable->GetByIDAndType(item, eReplicaComponentType::ITEM); - CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID); - - Character* character = player->GetCharacter(); - if (!character) return; - - // Extra currency that needs to be deducted in case of crafting - auto craftingCurrencies = CDItemComponentTable::ParseCraftingCurrencies(itemComp); - for (const auto& craftingCurrency : craftingCurrencies) { - inv->RemoveItem(craftingCurrency.first, craftingCurrency.second * count); - } - - if (isCommendationVendor) { - if (itemComp.commendationLOT != 13763) { - return; - } - - auto* missionComponent = player->GetComponent(); - - if (missionComponent == nullptr) { - return; - } - - LOT tokenId = -1; - - if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) tokenId = 8318; // "Assembly Token" - if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) tokenId = 8321; // "Venture League Token" - if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) tokenId = 8319; // "Sentinels Token" - if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) tokenId = 8320; // "Paradox Token" - - const uint32_t altCurrencyCost = itemComp.commendationCost * count; - - if (inv->GetLotCount(tokenId) < altCurrencyCost) { - return; - } - - inv->RemoveItem(tokenId, altCurrencyCost); - - inv->AddItem(item, count, eLootSourceType::VENDOR); - } else { - float buyScalar = vend->GetBuyScalar(); - - const auto coinCost = static_cast(std::floor((itemComp.baseValue * buyScalar) * count)); - - if (character->GetCoins() < coinCost) { - return; - } - - if (Inventory::IsValidItem(itemComp.currencyLOT)) { - const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * buyScalar) * count; - - if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) { - return; - } - - inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost); - } - - character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR); - inv->AddItem(item, count, eLootSourceType::VENDOR); - } - - GameMessages::SendVendorTransactionResult(entity, sysAddr); } void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { @@ -4785,7 +4719,10 @@ void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entit CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID); // Items with a base value of 0 or max int are special items that should not be sold if they're not sub items - if (itemComp.baseValue == 0 || itemComp.baseValue == UINT_MAX) return; + if (itemComp.baseValue == 0 || itemComp.baseValue == UINT_MAX) { + GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::SELL_FAIL); + return; + } float sellScalar = vend->GetSellScalar(); if (Inventory::IsValidItem(itemComp.currencyLOT)) { @@ -4793,11 +4730,9 @@ void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entit inv->AddItem(itemComp.currencyLOT, std::floor(altCurrency), eLootSourceType::VENDOR); // Return alt currencies like faction tokens. } - //inv->RemoveItem(count, -1, iObjID); inv->MoveItemToInventory(item, eInventoryType::VENDOR_BUYBACK, count, true, false, true); character->SetCoins(std::floor(character->GetCoins() + (static_cast(itemComp.baseValue * sellScalar) * count)), eLootSourceType::VENDOR); - //Game::entityManager->SerializeEntity(player); // so inventory updates - GameMessages::SendVendorTransactionResult(entity, sysAddr); + GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::SELL_SUCCESS); } void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { @@ -4839,16 +4774,16 @@ void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity* const auto cost = static_cast(std::floor(((itemComp.baseValue * sellScalar) * count))); if (character->GetCoins() < cost) { + GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_FAIL); return; } if (Inventory::IsValidItem(itemComp.currencyLOT)) { const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * sellScalar) * count; - if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) { + GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_FAIL); return; } - inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost); } @@ -4856,7 +4791,7 @@ void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity* inv->MoveItemToInventory(item, Inventory::FindInventoryTypeForLot(item->GetLot()), count, true, false); character->SetCoins(character->GetCoins() - cost, eLootSourceType::VENDOR); //Game::entityManager->SerializeEntity(player); // so inventory updates - GameMessages::SendVendorTransactionResult(entity, sysAddr); + GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_SUCCESS); } void GameMessages::HandleParseChatMessage(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index a96bbf60..9604d261 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -38,6 +38,7 @@ enum class eUseItemResponse : uint32_t; enum class eQuickBuildFailReason : uint32_t; enum class eQuickBuildState : uint32_t; enum class BehaviorSlot : int32_t; +enum class eVendorTransactionResult : uint32_t; namespace GameMessages { class PropertyDataMessage; @@ -135,7 +136,7 @@ namespace GameMessages { void SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr); void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly = false); - void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr); + void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr, eVendorTransactionResult result); void SendRemoveItemFromInventory(Entity* entity, const SystemAddress& sysAddr, LWOOBJID iObjID, LOT templateID, int inventoryType, uint32_t stackCount, uint32_t stackRemaining); void SendConsumeClientItem(Entity* entity, bool bSuccess, LWOOBJID item); diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index 25ccc902..54c28299 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -82,7 +82,7 @@ void AuthPackets::SendHandshake(dServer* server, const SystemAddress& sysAddr, c if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth); else if (serverType == ServerType::World) bitStream.Write(ServiceId::World); else bitStream.Write(ServiceId::General); - bitStream.Write(774909490); + bitStream.Write(215523405360); server->Send(&bitStream, sysAddr, false); } From cf706d4974199c053addb708525195081d3b711f Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 25 Feb 2024 05:56:01 -0800 Subject: [PATCH 3/5] Remove ag special case patch (#1462) Tested that revision was never the poison value in any lvl file when starting zone 1100. --- dZoneManager/Level.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/dZoneManager/Level.cpp b/dZoneManager/Level.cpp index 9524d908..5f35b629 100644 --- a/dZoneManager/Level.cpp +++ b/dZoneManager/Level.cpp @@ -200,9 +200,6 @@ void Level::ReadFileInfoChunk(std::istream& file, Header& header) { BinaryIO::BinaryRead(file, header.fileInfo.enviromentChunkStart); BinaryIO::BinaryRead(file, header.fileInfo.objectChunkStart); BinaryIO::BinaryRead(file, header.fileInfo.particleChunkStart); - - //PATCH FOR AG: (messed up file?) - if (header.fileInfo.revision == 0xCDCDCDCD && m_ParentZone->GetZoneID().GetMapID() == 1100) header.fileInfo.revision = 26; } void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) { From 192c8cf974b46b03b5604457274797733a73d330 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sun, 25 Feb 2024 16:59:10 -0600 Subject: [PATCH 4/5] feat: refactor vanity (#1477) * feat: refactor vanity cleanup code to be generalized for objects remove unused party feature add fallback to data to text Allow for better organizing data in multiple files remove special case flag values in favor of config data general cleanup and fixes * newline at eof's --- CMakeLists.txt | 2 +- dGame/Entity.cpp | 6 + dGame/Entity.h | 2 + dGame/dUtilities/VanityUtilities.cpp | 559 +++++++----------- dGame/dUtilities/VanityUtilities.h | 43 +- dScripts/02_server/DLU/CMakeLists.txt | 2 +- ...NPC.cpp => DLUVanityTeleportingObject.cpp} | 26 +- ...nityNPC.h => DLUVanityTeleportingObject.h} | 7 +- dScripts/CppScripts.cpp | 6 +- vanity/atm.xml | 23 + vanity/{NPC.xml => dev-tribute.xml} | 345 +++++------ vanity/root.xml | 4 + 12 files changed, 414 insertions(+), 611 deletions(-) rename dScripts/02_server/DLU/{DLUVanityNPC.cpp => DLUVanityTeleportingObject.cpp} (51%) rename dScripts/02_server/DLU/{DLUVanityNPC.h => DLUVanityTeleportingObject.h} (54%) create mode 100644 vanity/atm.xml rename vanity/{NPC.xml => dev-tribute.xml} (65%) create mode 100644 vanity/root.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index e085bfe7..b36bdb29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,7 +179,7 @@ file(ARCHIVE_EXTRACT INPUT ${PROJECT_BINARY_DIR}/navmeshes.zip DESTINATION ${PRO file(REMOVE ${PROJECT_BINARY_DIR}/navmeshes.zip) # Copy vanity files on first build -set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "NPC.xml") +set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "root.xml" "dev-tribute.xml" "atm.xml") foreach(file ${VANITY_FILES}) configure_file("${CMAKE_SOURCE_DIR}/vanity/${file}" "${CMAKE_BINARY_DIR}/vanity/${file}" COPYONLY) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index bb932991..dd15f69d 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -2197,3 +2197,9 @@ void Entity::SetRespawnRot(const NiQuaternion& rotation) { auto* characterComponent = GetComponent(); if (characterComponent) characterComponent->SetRespawnRot(rotation); } + +void Entity::SetScale(const float scale) { + if (scale == m_Scale) return; + m_Scale = scale; + Game::entityManager->SerializeEntity(this); +} \ No newline at end of file diff --git a/dGame/Entity.h b/dGame/Entity.h index 6546e458..7d5e24c9 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -295,6 +295,8 @@ public: void ProcessPositionUpdate(PositionUpdate& update); + void SetScale(const float scale); + protected: LWOOBJID m_ObjectID; diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index fa1a3eac..3e93f830 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -22,143 +22,13 @@ #include -std::vector VanityUtilities::m_NPCs = {}; -std::vector VanityUtilities::m_Parties = {}; -std::vector VanityUtilities::m_PartyPhrases = {}; +std::vector VanityUtilities::m_Objects = {}; +std::set VanityUtilities::m_LoadedFiles = {}; + void VanityUtilities::SpawnVanity() { - if (Game::config->GetValue("disable_vanity") == "1") { - return; - } - const uint32_t zoneID = Game::server->GetZoneID(); - for (const auto& npc : m_NPCs) { - if (npc.m_ID == LWOOBJID_EMPTY) continue; - if (npc.m_LOT == 176){ - Game::zoneManager->RemoveSpawner(npc.m_ID); - } else{ - auto* entity = Game::entityManager->GetEntity(npc.m_ID); - if (!entity) continue; - entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT); - } - } - - m_NPCs.clear(); - m_Parties.clear(); - m_PartyPhrases.clear(); - - ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/NPC.xml").string()); - - // Loop through all parties - for (const auto& party : m_Parties) { - const auto chance = party.m_Chance; - const auto zone = party.m_Zone; - - if (zone != Game::server->GetZoneID()) { - continue; - } - - float rate = GeneralUtils::GenerateRandomNumber(0, 1); - if (chance < rate) { - continue; - } - - // Copy m_NPCs into a new vector - std::vector npcList = m_NPCs; - std::vector taken = {}; - - LOG("Spawning party with %i locations", party.m_Locations.size()); - - // Loop through all locations - for (const auto& location : party.m_Locations) { - rate = GeneralUtils::GenerateRandomNumber(0, 1); - if (0.75f < rate) { - continue; - } - - // Get a random NPC - auto npcIndex = GeneralUtils::GenerateRandomNumber(0, npcList.size() - 1); - - while (std::find(taken.begin(), taken.end(), npcIndex) != taken.end()) { - npcIndex = GeneralUtils::GenerateRandomNumber(0, npcList.size() - 1); - } - - auto& npc = npcList[npcIndex]; - // Skip spawners - if (npc.m_LOT == 176) continue; - - taken.push_back(npcIndex); - - LOG("ldf size is %i", npc.ldf.size()); - if (npc.ldf.empty()) { - npc.ldf = { - new LDFData>(u"syncLDF", { u"custom_script_client" }), - new LDFData(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") - }; - } - - // Spawn the NPC - if (npc.m_LOT == 176){ - npc.m_ID = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf); - } else { - auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf); - if (!npc.m_Phrases.empty()) { - npcEntity->SetVar>(u"chats", m_PartyPhrases); - SetupNPCTalk(npcEntity); - } - } - } - return; - } - - // Loop through all NPCs - for (auto& npc : m_NPCs) { - if (npc.m_Locations.find(Game::server->GetZoneID()) == npc.m_Locations.end()) - continue; - - const std::vector& locations = npc.m_Locations.at(Game::server->GetZoneID()); - - // Pick a random location - const auto& location = locations[GeneralUtils::GenerateRandomNumber( - static_cast(0), static_cast(locations.size() - 1))]; - - float rate = GeneralUtils::GenerateRandomNumber(0, 1); - if (location.m_Chance < rate) { - continue; - } - - if (npc.ldf.empty()) { - npc.ldf = { - new LDFData>(u"syncLDF", { u"custom_script_client" }), - new LDFData(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") - }; - } - if (npc.m_LOT == 176){ - npc.m_ID = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf); - } else { - // Spawn the NPC - auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf); - if (!npcEntity) continue; - npc.m_ID = npcEntity->GetObjectID(); - if (!npc.m_Phrases.empty()){ - npcEntity->SetVar>(u"chats", npc.m_Phrases); - - auto* scriptComponent = npcEntity->GetComponent(); - - if (scriptComponent && !npc.m_Script.empty()) { - scriptComponent->SetScript(npc.m_Script); - scriptComponent->SetSerialized(false); - - for (const auto& npc : npc.m_Flags) { - npcEntity->SetVar(GeneralUtils::ASCIIToUTF16(npc.first), npc.second); - } - } - SetupNPCTalk(npcEntity); - } - } - } - if (zoneID == 1200) { { EntityInfo info; @@ -175,38 +45,99 @@ void VanityUtilities::SpawnVanity() { Game::entityManager->ConstructEntity(entity); } } + + if (Game::config->GetValue("disable_vanity") == "1") { + return; + } + + for (const auto& npc : m_Objects) { + if (npc.m_ID == LWOOBJID_EMPTY) continue; + if (npc.m_LOT == 176){ + Game::zoneManager->RemoveSpawner(npc.m_ID); + } else{ + auto* entity = Game::entityManager->GetEntity(npc.m_ID); + if (!entity) continue; + entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT); + } + } + + m_Objects.clear(); + m_LoadedFiles.clear(); + + ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/root.xml").string()); + + // Loop through all objects + for (auto& object : m_Objects) { + if (object.m_Locations.find(Game::server->GetZoneID()) == object.m_Locations.end()) continue; + + const std::vector& locations = object.m_Locations.at(Game::server->GetZoneID()); + + // Pick a random location + const auto& location = locations[GeneralUtils::GenerateRandomNumber( + static_cast(0), static_cast(locations.size() - 1))]; + + float rate = GeneralUtils::GenerateRandomNumber(0, 1); + if (location.m_Chance < rate) continue; + + if (object.m_Config.empty()) { + object.m_Config = { + new LDFData>(u"syncLDF", { u"custom_script_client" }), + new LDFData(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") + }; + } + if (object.m_LOT == 176){ + object.m_ID = SpawnSpawner(object, location); + } else { + // Spawn the NPC + auto* objectEntity = SpawnObject(object, location); + if (!objectEntity) continue; + object.m_ID = objectEntity->GetObjectID(); + if (!object.m_Phrases.empty()){ + objectEntity->SetVar>(u"chats", object.m_Phrases); + + auto* scriptComponent = objectEntity->GetComponent(); + + if (scriptComponent && !object.m_Script.empty()) { + scriptComponent->SetScript(object.m_Script); + scriptComponent->SetSerialized(false); + } + SetupNPCTalk(objectEntity); + } + } + } } -LWOOBJID VanityUtilities::SpawnSpawner(LOT lot, const NiPoint3& position, const NiQuaternion& rotation, const std::vector& ldf){ +LWOOBJID VanityUtilities::SpawnSpawner(const VanityObject& object, const VanityObjectLocation& location) { SceneObject obj; - obj.lot = lot; + obj.lot = object.m_LOT; // guratantee we have no collisions do { obj.id = ObjectIDManager::GenerateObjectID(); } while(Game::zoneManager->GetSpawner(obj.id)); - obj.position = position; - obj.rotation = rotation; - obj.settings = ldf; + obj.position = location.m_Position; + obj.rotation = location.m_Rotation; + obj.settings = object.m_Config; Level::MakeSpawner(obj); return obj.id; } -Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoint3& position, const NiQuaternion& rotation, const std::vector& inventory, const std::vector& ldf) { +Entity* VanityUtilities::SpawnObject(const VanityObject& object, const VanityObjectLocation& location) { EntityInfo info; - info.lot = lot; - info.pos = position; - info.rot = rotation; + info.lot = object.m_LOT; + info.pos = location.m_Position; + info.rot = location.m_Rotation; + info.scale = location.m_Scale; info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID(); - info.settings = ldf; + info.settings = object.m_Config; auto* entity = Game::entityManager->CreateEntity(info); - entity->SetVar(u"npcName", name); + entity->SetVar(u"npcName", object.m_Name); if (entity->GetVar(u"noGhosting")) entity->SetIsGhostingCandidate(false); auto* inventoryComponent = entity->GetComponent(); - if (inventoryComponent && !inventory.empty()) { - inventoryComponent->SetNPCItems(inventory); + if (inventoryComponent && !object.m_Equipment.empty()) { + inventoryComponent->SetNPCItems(object.m_Equipment); } auto* destroyableComponent = entity->GetComponent(); @@ -223,6 +154,11 @@ Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoin } void VanityUtilities::ParseXML(const std::string& file) { + if (m_LoadedFiles.contains(file)){ + LOG("Trying to load vanity file %s twice!!!", file.c_str()); + return; + } + m_LoadedFiles.insert(file); // Read the entire file std::ifstream xmlFile(file); std::string xml((std::istreambuf_iterator(xmlFile)), std::istreambuf_iterator()); @@ -231,210 +167,112 @@ void VanityUtilities::ParseXML(const std::string& file) { tinyxml2::XMLDocument doc; doc.Parse(xml.c_str(), xml.size()); - // Read the NPCs - auto* npcs = doc.FirstChildElement("npcs"); - - if (npcs == nullptr) { - LOG("Failed to parse NPCs"); - return; - } - - for (auto* party = npcs->FirstChildElement("party"); party != nullptr; party = party->NextSiblingElement("party")) { - // Get 'zone' as uint32_t and 'chance' as float - uint32_t zone = 0; - float chance = 0.0f; - - if (party->Attribute("zone") != nullptr) { - zone = std::stoul(party->Attribute("zone")); - } - - if (party->Attribute("chance") != nullptr) { - chance = std::stof(party->Attribute("chance")); - } - - VanityParty partyInfo; - partyInfo.m_Zone = zone; - partyInfo.m_Chance = chance; - - auto* locations = party->FirstChildElement("locations"); - - if (locations == nullptr) { - LOG("Failed to parse party locations"); - continue; - } - - for (auto* location = locations->FirstChildElement("location"); location != nullptr; - location = location->NextSiblingElement("location")) { - // Get the location data - auto* x = location->Attribute("x"); - auto* y = location->Attribute("y"); - auto* z = location->Attribute("z"); - auto* rw = location->Attribute("rw"); - auto* rx = location->Attribute("rx"); - auto* ry = location->Attribute("ry"); - auto* rz = location->Attribute("rz"); - - if (x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr - || rz == nullptr) { - LOG("Failed to parse party location data"); + // Read the objects + auto* files = doc.FirstChildElement("files"); + if (files) { + for (auto* file = files->FirstChildElement("file"); file != nullptr; file = file->NextSiblingElement("file")) { + std::string enabled = file->Attribute("enabled"); + std::string filename = file->Attribute("name"); + if (enabled != "1") { continue; } - - VanityNPCLocation locationData; - locationData.m_Position = { std::stof(x), std::stof(y), std::stof(z) }; - locationData.m_Rotation = { std::stof(rw), std::stof(rx), std::stof(ry), std::stof(rz) }; - locationData.m_Chance = 1.0f; - - partyInfo.m_Locations.push_back(locationData); - } - - m_Parties.push_back(partyInfo); - } - - auto* partyPhrases = npcs->FirstChildElement("partyphrases"); - - if (partyPhrases == nullptr) { - LOG("No party phrases found"); - } else { - for (auto* phrase = partyPhrases->FirstChildElement("phrase"); phrase != nullptr; - phrase = phrase->NextSiblingElement("phrase")) { - // Get the phrase - auto* text = phrase->GetText(); - - if (text == nullptr) { - LOG("Failed to parse party phrase"); - continue; - } - - m_PartyPhrases.push_back(text); + ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity" / filename).string()); } } - for (auto* npc = npcs->FirstChildElement("npc"); npc != nullptr; npc = npc->NextSiblingElement("npc")) { - // Get the NPC name - auto* name = npc->Attribute("name"); + // Read the objects + auto* objects = doc.FirstChildElement("objects"); - if (!name) name = ""; + if (objects) { + for (auto* object = objects->FirstChildElement("object"); object != nullptr; object = object->NextSiblingElement("object")) { + // Get the NPC name + auto* name = object->Attribute("name"); - // Get the NPC lot - auto* lot = npc->Attribute("lot"); + if (!name) name = ""; - if (lot == nullptr) { - LOG("Failed to parse NPC lot"); - continue; - } + // Get the NPC lot + auto* lot = object->Attribute("lot"); - // Get the equipment - auto* equipment = npc->FirstChildElement("equipment"); - std::vector inventory; - - if (equipment) { - auto* text = equipment->GetText(); - - if (text != nullptr) { - std::string equipmentString(text); - - std::vector splitEquipment = GeneralUtils::SplitString(equipmentString, ','); - - for (auto& item : splitEquipment) { - inventory.push_back(std::stoi(item)); - } - } - } - - - // Get the phrases - auto* phrases = npc->FirstChildElement("phrases"); - - std::vector phraseList = {}; - - if (phrases) { - for (auto* phrase = phrases->FirstChildElement("phrase"); phrase != nullptr; - phrase = phrase->NextSiblingElement("phrase")) { - // Get the phrase - auto* text = phrase->GetText(); - if (text == nullptr) { - LOG("Failed to parse NPC phrase"); - continue; - } - phraseList.push_back(text); - } - } - - // Get the script - auto* scriptElement = npc->FirstChildElement("script"); - - std::string scriptName = ""; - - if (scriptElement != nullptr) { - auto* scriptNameAttribute = scriptElement->Attribute("name"); - if (scriptNameAttribute) scriptName = scriptNameAttribute; - } - - auto* ldfElement = npc->FirstChildElement("ldf"); - std::vector keys = {}; - - std::vector ldf = {}; - if(ldfElement) { - for (auto* entry = ldfElement->FirstChildElement("entry"); entry != nullptr; - entry = entry->NextSiblingElement("entry")) { - // Get the ldf data - auto* data = entry->Attribute("data"); - if (!data) continue; - - LDFBaseData* ldfData = LDFBaseData::DataFromString(data); - keys.push_back(ldfData->GetKey()); - ldf.push_back(ldfData); - } - } - if (!keys.empty()) ldf.push_back(new LDFData>(u"syncLDF", keys)); - - VanityNPC npcData; - npcData.m_Name = name; - npcData.m_LOT = std::stoi(lot); - npcData.m_Equipment = inventory; - npcData.m_Phrases = phraseList; - npcData.m_Script = scriptName; - npcData.ldf = ldf; - - // Get flags - auto* flags = npc->FirstChildElement("flags"); - - if (flags != nullptr) { - for (auto* flag = flags->FirstChildElement("flag"); flag != nullptr; - flag = flag->NextSiblingElement("flag")) { - // Get the flag name - auto* name = flag->Attribute("name"); - - if (name == nullptr) { - LOG("Failed to parse NPC flag name"); - continue; - } - - // Get the flag value - auto* value = flag->Attribute("value"); - - if (value == nullptr) { - LOG("Failed to parse NPC flag value"); - continue; - } - - npcData.m_Flags[name] = std::stoi(value); - } - } - - // Get the zones - for (auto* zone = npc->FirstChildElement("zone"); zone != nullptr; zone = zone->NextSiblingElement("zone")) { - // Get the zone ID - auto* zoneID = zone->Attribute("id"); - - if (zoneID == nullptr) { - LOG("Failed to parse NPC zone ID"); + if (lot == nullptr) { + LOG("Failed to parse object lot"); continue; } + // Get the equipment + auto* equipment = object->FirstChildElement("equipment"); + std::vector inventory; + + if (equipment) { + auto* text = equipment->GetText(); + + if (text != nullptr) { + std::string equipmentString(text); + + std::vector splitEquipment = GeneralUtils::SplitString(equipmentString, ','); + + for (auto& item : splitEquipment) { + inventory.push_back(std::stoi(item)); + } + } + } + + + // Get the phrases + auto* phrases = object->FirstChildElement("phrases"); + + std::vector phraseList = {}; + + if (phrases) { + for (auto* phrase = phrases->FirstChildElement("phrase"); phrase != nullptr; + phrase = phrase->NextSiblingElement("phrase")) { + // Get the phrase + auto* text = phrase->GetText(); + if (text == nullptr) { + LOG("Failed to parse NPC phrase"); + continue; + } + phraseList.push_back(text); + } + } + + // Get the script + auto* scriptElement = object->FirstChildElement("script"); + + std::string scriptName = ""; + + if (scriptElement != nullptr) { + auto* scriptNameAttribute = scriptElement->Attribute("name"); + if (scriptNameAttribute) scriptName = scriptNameAttribute; + } + + auto* configElement = object->FirstChildElement("config"); + std::vector keys = {}; + + std::vector config = {}; + if(configElement) { + for (auto* key = configElement->FirstChildElement("key"); key != nullptr; + key = key->NextSiblingElement("key")) { + // Get the config data + auto* data = key->Attribute("data"); + if (!data) continue; + + LDFBaseData* configData = LDFBaseData::DataFromString(data); + keys.push_back(configData->GetKey()); + config.push_back(configData); + } + } + if (!keys.empty()) config.push_back(new LDFData>(u"syncLDF", keys)); + + VanityObject objectData; + objectData.m_Name = name; + objectData.m_LOT = std::stoi(lot); + objectData.m_Equipment = inventory; + objectData.m_Phrases = phraseList; + objectData.m_Script = scriptName; + objectData.m_Config = config; + // Get the locations - auto* locations = zone->FirstChildElement("locations"); + auto* locations = object->FirstChildElement("locations"); if (locations == nullptr) { LOG("Failed to parse NPC locations"); @@ -443,7 +281,9 @@ void VanityUtilities::ParseXML(const std::string& file) { for (auto* location = locations->FirstChildElement("location"); location != nullptr; location = location->NextSiblingElement("location")) { + // Get the location data + auto* zoneID = location->Attribute("zone"); auto* x = location->Attribute("x"); auto* y = location->Attribute("y"); auto* z = location->Attribute("z"); @@ -452,41 +292,52 @@ void VanityUtilities::ParseXML(const std::string& file) { auto* ry = location->Attribute("ry"); auto* rz = location->Attribute("rz"); - if (x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr + if (zoneID == nullptr || x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr || rz == nullptr) { LOG("Failed to parse NPC location data"); continue; } - VanityNPCLocation locationData; + VanityObjectLocation locationData; locationData.m_Position = { std::stof(x), std::stof(y), std::stof(z) }; locationData.m_Rotation = { std::stof(rw), std::stof(rx), std::stof(ry), std::stof(rz) }; locationData.m_Chance = 1.0f; - if (location->Attribute("chance") != nullptr) { + if (location->Attribute("chance")) { locationData.m_Chance = std::stof(location->Attribute("chance")); } - const auto& it = npcData.m_Locations.find(std::stoi(zoneID)); + if (location->Attribute("scale")) { + locationData.m_Scale = std::stof(location->Attribute("scale")); + } - if (it != npcData.m_Locations.end()) { + + const auto& it = objectData.m_Locations.find(std::stoi(zoneID)); + + if (it != objectData.m_Locations.end()) { it->second.push_back(locationData); } else { - std::vector locations; + std::vector locations; locations.push_back(locationData); - npcData.m_Locations.insert(std::make_pair(std::stoi(zoneID), locations)); + objectData.m_Locations.insert(std::make_pair(std::stoi(zoneID), locations)); + } + + if (!(std::find(keys.begin(), keys.end(), u"teleport") != keys.end())) { + m_Objects.push_back(objectData); + objectData.m_Locations.clear(); } } + if (std::find(keys.begin(), keys.end(), u"teleport") != keys.end()) { + m_Objects.push_back(objectData); + } } - - m_NPCs.push_back(npcData); } } -VanityNPC* VanityUtilities::GetNPC(const std::string& name) { - for (size_t i = 0; i < m_NPCs.size(); i++) { - if (m_NPCs[i].m_Name == name) { - return &m_NPCs[i]; +VanityObject* VanityUtilities::GetObject(const std::string& name) { + for (size_t i = 0; i < m_Objects.size(); i++) { + if (m_Objects[i].m_Name == name) { + return &m_Objects[i]; } } @@ -498,10 +349,13 @@ std::string VanityUtilities::ParseMarkdown(const std::string& file) { // Read the file into a string std::ifstream t(file); - + std::stringstream output; // If the file does not exist, return an empty string. if (!t.good()) { - return ""; + output << "File "; + output << file.substr(file.rfind("/") + 1); + output << " not found!\nContact your DarkflameServer admin\nor find the server source at https://github.com/DarkflameUniverse/DarkflameServer"; + return output.str(); } std::stringstream buffer; @@ -511,7 +365,6 @@ std::string VanityUtilities::ParseMarkdown(const std::string& file) { // Loop through all lines in the file. // Replace all instances of the markdown syntax with the corresponding HTML. // Only care about headers - std::stringstream output; std::string line; std::stringstream ss; ss << fileContents; diff --git a/dGame/dUtilities/VanityUtilities.h b/dGame/dUtilities/VanityUtilities.h index cff73bce..49bd23ab 100644 --- a/dGame/dUtilities/VanityUtilities.h +++ b/dGame/dUtilities/VanityUtilities.h @@ -3,15 +3,17 @@ #include "dCommonVars.h" #include "Entity.h" #include +#include -struct VanityNPCLocation +struct VanityObjectLocation { float m_Chance = 1.0f; NiPoint3 m_Position; NiQuaternion m_Rotation; + float m_Scale = 1.0f; }; -struct VanityNPC +struct VanityObject { LWOOBJID m_ID = LWOOBJID_EMPTY; std::string m_Name; @@ -19,37 +21,24 @@ struct VanityNPC std::vector m_Equipment; std::vector m_Phrases; std::string m_Script; - std::map m_Flags; - std::map> m_Locations; - std::vector ldf; + std::map> m_Locations; + std::vector m_Config; }; -struct VanityParty -{ - uint32_t m_Zone; - float m_Chance = 1.0f; - std::vector m_Locations; -}; class VanityUtilities { public: static void SpawnVanity(); - static Entity* SpawnNPC( - LOT lot, - const std::string& name, - const NiPoint3& position, - const NiQuaternion& rotation, - const std::vector& inventory, - const std::vector& ldf + static Entity* SpawnObject( + const VanityObject& object, + const VanityObjectLocation& location ); static LWOOBJID SpawnSpawner( - LOT lot, - const NiPoint3& position, - const NiQuaternion& rotation, - const std::vector& ldf + const VanityObject& object, + const VanityObjectLocation& location ); static std::string ParseMarkdown( @@ -60,16 +49,14 @@ public: const std::string& file ); - static VanityNPC* GetNPC(const std::string& name); + static VanityObject* GetObject(const std::string& name); private: static void SetupNPCTalk(Entity* npc); static void NPCTalk(Entity* npc); - static std::vector m_NPCs; - - static std::vector m_Parties; - - static std::vector m_PartyPhrases; + static std::vector m_Objects; + + static std::set m_LoadedFiles; }; diff --git a/dScripts/02_server/DLU/CMakeLists.txt b/dScripts/02_server/DLU/CMakeLists.txt index 64d4cbbd..fb257d3e 100644 --- a/dScripts/02_server/DLU/CMakeLists.txt +++ b/dScripts/02_server/DLU/CMakeLists.txt @@ -1,3 +1,3 @@ set(DSCRIPTS_SOURCES_02_SERVER_DLU - "DLUVanityNPC.cpp" + "DLUVanityTeleportingObject.cpp" PARENT_SCOPE) diff --git a/dScripts/02_server/DLU/DLUVanityNPC.cpp b/dScripts/02_server/DLU/DLUVanityTeleportingObject.cpp similarity index 51% rename from dScripts/02_server/DLU/DLUVanityNPC.cpp rename to dScripts/02_server/DLU/DLUVanityTeleportingObject.cpp index ba2c6604..8aff1995 100644 --- a/dScripts/02_server/DLU/DLUVanityNPC.cpp +++ b/dScripts/02_server/DLU/DLUVanityTeleportingObject.cpp @@ -1,22 +1,22 @@ -#include "DLUVanityNPC.h" +#include "DLUVanityTeleportingObject.h" #include "GameMessages.h" #include "dServer.h" #include "VanityUtilities.h" #include "RenderComponent.h" -void DLUVanityNPC::OnStartup(Entity* self) { - m_NPC = VanityUtilities::GetNPC("averysumner - Destroyer of Worlds"); +void DLUVanityTeleportingObject::OnStartup(Entity* self) { + if (!self->HasVar(u"npcName") || !self->HasVar(u"teleport")) return; + m_Object = VanityUtilities::GetObject(self->GetVarAsString(u"npcName")); - if (m_NPC == nullptr) { - return; - } + if (!m_Object) return; + if (self->HasVar(u"teleportInterval")) m_TeleportInterval = self->GetVar(u"teleportInterval"); if (self->GetVar(u"teleport")) { - self->AddTimer("setupTeleport", 15.0f); + self->AddTimer("setupTeleport", m_TeleportInterval); } } -void DLUVanityNPC::OnTimerDone(Entity* self, std::string timerName) { +void DLUVanityTeleportingObject::OnTimerDone(Entity* self, std::string timerName) { if (timerName == "setupTeleport") { RenderComponent::PlayAnimation(self, u"interact"); GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportBeam", "teleportBeam"); @@ -28,20 +28,22 @@ void DLUVanityNPC::OnTimerDone(Entity* self, std::string timerName) { GameMessages::SendStopFXEffect(self, true, "teleportBeam"); GameMessages::SendStopFXEffect(self, true, "teleportRings"); } else if (timerName == "teleport") { - std::vector& locations = m_NPC->m_Locations[Game::server->GetZoneID()]; + std::vector& locations = m_Object->m_Locations[Game::server->GetZoneID()]; selectLocation: - VanityNPCLocation& newLocation = locations[GeneralUtils::GenerateRandomNumber(0, locations.size() - 1)]; + VanityObjectLocation& newLocation = locations[GeneralUtils::GenerateRandomNumber(0, locations.size() - 1)]; + // try to get not the same position, but if we get the same one twice, it's fine if (self->GetPosition() == newLocation.m_Position) { - goto selectLocation; // cry about it + VanityObjectLocation& newLocation = locations[GeneralUtils::GenerateRandomNumber(0, locations.size() - 1)]; } self->SetPosition(newLocation.m_Position); self->SetRotation(newLocation.m_Rotation); + self->SetScale(newLocation.m_Scale); GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportBeam", "teleportBeam"); GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportRings", "teleportRings"); self->AddTimer("stopFX", 2.0f); - self->AddTimer("setupTeleport", 15.0f); + self->AddTimer("setupTeleport", m_TeleportInterval); } } diff --git a/dScripts/02_server/DLU/DLUVanityNPC.h b/dScripts/02_server/DLU/DLUVanityTeleportingObject.h similarity index 54% rename from dScripts/02_server/DLU/DLUVanityNPC.h rename to dScripts/02_server/DLU/DLUVanityTeleportingObject.h index aeb8e051..a13ba901 100644 --- a/dScripts/02_server/DLU/DLUVanityNPC.h +++ b/dScripts/02_server/DLU/DLUVanityTeleportingObject.h @@ -1,13 +1,14 @@ #pragma once #include "CppScripts.h" -class VanityNPC; -class DLUVanityNPC : public CppScripts::Script +class VanityObject; +class DLUVanityTeleportingObject : public CppScripts::Script { public: void OnStartup(Entity* self) override; void OnTimerDone(Entity* self, std::string timerName) override; private: - VanityNPC* m_NPC; + VanityObject* m_Object; + float m_TeleportInterval = 15.0f; }; diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 071bd7a3..7cb853e6 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -216,7 +216,7 @@ #include "NtNaomiBreadcrumbServer.h" // DLU Scripts -#include "DLUVanityNPC.h" +#include "DLUVanityTeleportingObject.h" // AM Scripts #include "AmConsoleTeleportServer.h" @@ -834,8 +834,8 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new NjNyaMissionitems(); //DLU: - else if (scriptName == "scripts\\02_server\\DLU\\DLUVanityNPC.lua") - script = new DLUVanityNPC(); + else if (scriptName == "scripts\\02_server\\DLU\\DLUVanityTeleportingObject.lua") + script = new DLUVanityTeleportingObject(); // Survival minigame else if (scriptName == "scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_STROMBIE.lua") diff --git a/vanity/atm.xml b/vanity/atm.xml new file mode 100644 index 00000000..96ed1a2b --- /dev/null +++ b/vanity/atm.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vanity/NPC.xml b/vanity/dev-tribute.xml similarity index 65% rename from vanity/NPC.xml rename to vanity/dev-tribute.xml index 2311ab46..d20e31a6 100644 --- a/vanity/NPC.xml +++ b/vanity/dev-tribute.xml @@ -1,5 +1,5 @@ - - + + 6802, 2519, 2623, 14806 Sorry for the mess. @@ -11,39 +11,33 @@ Everything is awesome! I hope my behaviors are behaving themselves. - - - - - - - + + + + + 12947, 12949, 12962, 12963 I hope quickbulds are still working! Be careful crossing the gap! Have The Maelstrom stopped going invisible? - - - - - - - + + + + + 9950, 9944, 14102, 14092 Hello Explorer! It's great to see you made it! Have you heard about Darkflame? I've traveled across this entire system, but nothing beats the view here. - - - - - - - + + + + + cmerw[acowipaejio;fawjioefasdl;kfjm; @@ -51,20 +45,18 @@ zxnpoasdfiopwemsadf'kawpfo[ekasdf;'s *teleports behind you* -