diff --git a/dGame/dBehaviors/AndBehavior.cpp b/dGame/dBehaviors/AndBehavior.cpp index 231b39c9..5fc1e113 100644 --- a/dGame/dBehaviors/AndBehavior.cpp +++ b/dGame/dBehaviors/AndBehavior.cpp @@ -19,6 +19,12 @@ void AndBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStre } } +void AndBehavior::UnCast(BehaviorContext* context, const BehaviorBranchContext branch) { + for (auto behavior : this->m_behaviors) { + behavior->UnCast(context, branch); + } +} + void AndBehavior::Load() { const auto parameters = GetParameterNames(); diff --git a/dGame/dBehaviors/AndBehavior.h b/dGame/dBehaviors/AndBehavior.h index 2b7d95e6..9cbce569 100644 --- a/dGame/dBehaviors/AndBehavior.h +++ b/dGame/dBehaviors/AndBehavior.h @@ -20,5 +20,7 @@ public: void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; + void UnCast(BehaviorContext* context, BehaviorBranchContext branch) override; + void Load() override; }; diff --git a/dGame/dBehaviors/BasicAttackBehavior.cpp b/dGame/dBehaviors/BasicAttackBehavior.cpp index 399efec1..a9a58245 100644 --- a/dGame/dBehaviors/BasicAttackBehavior.cpp +++ b/dGame/dBehaviors/BasicAttackBehavior.cpp @@ -14,7 +14,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi auto* destroyableComponent = entity->GetComponent(); if (destroyableComponent != nullptr) { PlayFx(u"onhit", entity->GetObjectID()); - destroyableComponent->Damage(this->m_maxDamage, context->originator); + destroyableComponent->Damage(this->m_maxDamage, context->originator, context->skillID); } this->m_onSuccess->Handle(context, bitStream, branch); @@ -56,7 +56,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi auto* destroyableComponent = entity->GetComponent(); if (destroyableComponent != nullptr) { PlayFx(u"onhit", entity->GetObjectID()); - destroyableComponent->Damage(damageDealt, context->originator); + destroyableComponent->Damage(damageDealt, context->originator, context->skillID); } } } @@ -113,7 +113,7 @@ void BasicAttackBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* auto* destroyableComponent = entity->GetComponent(); if (damage != 0 && destroyableComponent != nullptr) { PlayFx(u"onhit", entity->GetObjectID(), 1); - destroyableComponent->Damage(damage, context->originator, false); + destroyableComponent->Damage(damage, context->originator, context->skillID, false); context->ScheduleUpdate(branch.target); } } diff --git a/dGame/dBehaviors/Behavior.cpp b/dGame/dBehaviors/Behavior.cpp index 74153c11..db5bbf2f 100644 --- a/dGame/dBehaviors/Behavior.cpp +++ b/dGame/dBehaviors/Behavior.cpp @@ -18,6 +18,7 @@ #include "AreaOfEffectBehavior.h" #include "DurationBehavior.h" #include "TacArcBehavior.h" +#include "LootBuffBehavior.h" #include "AttackDelayBehavior.h" #include "BasicAttackBehavior.h" #include "ChainBehavior.h" @@ -172,7 +173,9 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId) behavior = new SpeedBehavior(behaviorId); break; case BehaviorTemplates::BEHAVIOR_DARK_INSPIRATION: break; - case BehaviorTemplates::BEHAVIOR_LOOT_BUFF: break; + case BehaviorTemplates::BEHAVIOR_LOOT_BUFF: + behavior = new LootBuffBehavior(behaviorId); + break; case BehaviorTemplates::BEHAVIOR_VENTURE_VISION: break; case BehaviorTemplates::BEHAVIOR_SPAWN_OBJECT: behavior = new SpawnBehavior(behaviorId); diff --git a/dGame/dBehaviors/BehaviorContext.cpp b/dGame/dBehaviors/BehaviorContext.cpp index fc67a82d..c7bf912f 100644 --- a/dGame/dBehaviors/BehaviorContext.cpp +++ b/dGame/dBehaviors/BehaviorContext.cpp @@ -64,8 +64,8 @@ void BehaviorContext::RegisterSyncBehavior(const uint32_t syncId, Behavior* beha void BehaviorContext::RegisterTimerBehavior(Behavior* behavior, const BehaviorBranchContext& branchContext, const LWOOBJID second) { - BehaviorTimerEntry entry -; + BehaviorTimerEntry entry; + entry.time = branchContext.duration; entry.behavior = behavior; entry.branchContext = branchContext; diff --git a/dGame/dBehaviors/BehaviorContext.h b/dGame/dBehaviors/BehaviorContext.h index 9f1d1621..f27889f1 100644 --- a/dGame/dBehaviors/BehaviorContext.h +++ b/dGame/dBehaviors/BehaviorContext.h @@ -58,6 +58,8 @@ struct BehaviorContext float skillTime = 0; + uint32_t skillID = 0; + uint32_t skillUId = 0; bool failed = false; diff --git a/dGame/dBehaviors/LootBuffBehavior.cpp b/dGame/dBehaviors/LootBuffBehavior.cpp new file mode 100644 index 00000000..fe46f7bb --- /dev/null +++ b/dGame/dBehaviors/LootBuffBehavior.cpp @@ -0,0 +1,38 @@ +#include "LootBuffBehavior.h" + +void LootBuffBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { + auto target = EntityManager::Instance()->GetEntity(context->caster); + if (!target) return; + + auto controllablePhysicsComponent = target->GetComponent(); + if (!controllablePhysicsComponent) return; + + controllablePhysicsComponent->AddPickupRadiusScale(m_Scale); + EntityManager::Instance()->SerializeEntity(target); + + if (branch.duration > 0) context->RegisterTimerBehavior(this, branch); + +} + +void LootBuffBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { + Handle(context, bitStream, branch); +} + +void LootBuffBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch) { + auto target = EntityManager::Instance()->GetEntity(context->caster); + if (!target) return; + + auto controllablePhysicsComponent = target->GetComponent(); + if (!controllablePhysicsComponent) return; + + controllablePhysicsComponent->RemovePickupRadiusScale(m_Scale); + EntityManager::Instance()->SerializeEntity(target); +} + +void LootBuffBehavior::Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) { + UnCast(context, branch); +} + +void LootBuffBehavior::Load() { + this->m_Scale = GetFloat("scale"); +} \ No newline at end of file diff --git a/dGame/dBehaviors/LootBuffBehavior.h b/dGame/dBehaviors/LootBuffBehavior.h new file mode 100644 index 00000000..0f85b3b0 --- /dev/null +++ b/dGame/dBehaviors/LootBuffBehavior.h @@ -0,0 +1,32 @@ +#pragma once +#include "Behavior.h" +#include "BehaviorBranchContext.h" +#include "BehaviorContext.h" +#include "ControllablePhysicsComponent.h" + +/** + * @brief This is the behavior class to be used for all Loot Buff behavior nodes in the Behavior tree. + * + */ +class LootBuffBehavior final : public Behavior +{ +public: + + float m_Scale; + + /* + * Inherited + */ + + explicit LootBuffBehavior(const uint32_t behaviorId) : Behavior(behaviorId) {} + + void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; + + void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; + + void UnCast(BehaviorContext* context, BehaviorBranchContext branch) override; + + void Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) override; + + void Load() override; +}; diff --git a/dGame/dBehaviors/OverTimeBehavior.cpp b/dGame/dBehaviors/OverTimeBehavior.cpp index dbee4c39..9e8618df 100644 --- a/dGame/dBehaviors/OverTimeBehavior.cpp +++ b/dGame/dBehaviors/OverTimeBehavior.cpp @@ -7,62 +7,26 @@ #include "SkillComponent.h" #include "DestroyableComponent.h" -/** - * The OverTime behavior is very inconsistent in how it appears in the skill tree vs. how it should behave. - * - * Items like "Doc in a Box" use an overtime behavior which you would expect have health & armor regen, but is only fallowed by a stun. - * - * Due to this inconsistency, we have to implement a special case for some items. - */ - void OverTimeBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { const auto originator = context->originator; auto* entity = EntityManager::Instance()->GetEntity(originator); - if (entity == nullptr) - { - return; - } + if (entity == nullptr) return; for (size_t i = 0; i < m_NumIntervals; i++) { entity->AddCallbackTimer((i + 1) * m_Delay, [originator, branch, this]() { auto* entity = EntityManager::Instance()->GetEntity(originator); - if (entity == nullptr) - { - return; - } + if (entity == nullptr) return; auto* skillComponent = entity->GetComponent(); - if (skillComponent == nullptr) - { - return; - } + if (skillComponent == nullptr) return; - skillComponent->CalculateBehavior(0, m_Action->m_behaviorId, branch.target, true, true); - - auto* destroyableComponent = entity->GetComponent(); - - if (destroyableComponent == nullptr) - { - return; - } - - /** - * Special cases for inconsistent behavior. - */ - - switch (m_behaviorId) - { - case 26253: // "Doc in a Box", heal up to 6 health and regen up to 18 armor. - destroyableComponent->Heal(1); - destroyableComponent->Repair(3); - break; - } + skillComponent->CalculateBehavior(m_Action, m_ActionBehaviorId, branch.target, true, true); }); } } @@ -74,7 +38,12 @@ void OverTimeBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bi void OverTimeBehavior::Load() { - m_Action = GetAction("action"); + m_Action = GetInt("action"); + // Since m_Action is a skillID and not a behavior, get is correlated behaviorID. + + CDSkillBehaviorTable* skillTable = CDClientManager::Instance()->GetTable("SkillBehavior"); + m_ActionBehaviorId = skillTable->GetSkillByID(m_Action).behaviorID; + m_Delay = GetFloat("delay"); m_NumIntervals = GetInt("num_intervals"); } diff --git a/dGame/dBehaviors/OverTimeBehavior.h b/dGame/dBehaviors/OverTimeBehavior.h index 6be675aa..5c177926 100644 --- a/dGame/dBehaviors/OverTimeBehavior.h +++ b/dGame/dBehaviors/OverTimeBehavior.h @@ -4,7 +4,8 @@ class OverTimeBehavior final : public Behavior { public: - Behavior* m_Action; + uint32_t m_Action; + uint32_t m_ActionBehaviorId; float m_Delay; int32_t m_NumIntervals; diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index 922ae55d..648b1471 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -29,6 +29,8 @@ ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : Com m_GravityScale = 1; m_DirtyCheats = false; m_IgnoreMultipliers = false; + m_PickupRadius = 0.0f; + m_DirtyPickupRadiusScale = true; if (entity->GetLOT() != 1) // Other physics entities we care about will be added by BaseCombatAI return; @@ -85,7 +87,13 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bo m_DirtyCheats = false; } - outBitStream->Write0(); + outBitStream->Write(m_DirtyPickupRadiusScale); + if (m_DirtyPickupRadiusScale) { + outBitStream->Write(m_PickupRadius); + outBitStream->Write0(); //No clue what this is so im leaving it false. + m_DirtyPickupRadiusScale = false; + } + outBitStream->Write0(); outBitStream->Write(m_DirtyPosition || bIsInitialUpdate); @@ -230,4 +238,32 @@ void ControllablePhysicsComponent::SetDirtyVelocity(bool val) { void ControllablePhysicsComponent::SetDirtyAngularVelocity(bool val) { m_DirtyAngularVelocity = val; -} \ No newline at end of file +} + +void ControllablePhysicsComponent::AddPickupRadiusScale(float value) { + m_ActivePickupRadiusScales.push_back(value); + if (value > m_PickupRadius) { + m_PickupRadius = value; + m_DirtyPickupRadiusScale = true; + } +} + +void ControllablePhysicsComponent::RemovePickupRadiusScale(float value) { + // Attempt to remove pickup radius from active radii + const auto pos = std::find(m_ActivePickupRadiusScales.begin(), m_ActivePickupRadiusScales.end(), value); + if (pos != m_ActivePickupRadiusScales.end()) { + m_ActivePickupRadiusScales.erase(pos); + } else { + Game::logger->Log("ControllablePhysicsComponent", "Warning: Could not find pickup radius %f in list of active radii. List has %i active radii.\n", value, m_ActivePickupRadiusScales.size()); + return; + } + + // Recalculate pickup radius since we removed one by now + m_PickupRadius = 0.0f; + m_DirtyPickupRadiusScale = true; + for (uint32_t i = 0; i < m_ActivePickupRadiusScales.size(); i++) { + auto candidateRadius = m_ActivePickupRadiusScales[i]; + if (m_PickupRadius < candidateRadius) m_PickupRadius = candidateRadius; + } + EntityManager::Instance()->SerializeEntity(m_Parent); +} diff --git a/dGame/dComponents/ControllablePhysicsComponent.h b/dGame/dComponents/ControllablePhysicsComponent.h index f0fe7461..c7acec62 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.h +++ b/dGame/dComponents/ControllablePhysicsComponent.h @@ -227,6 +227,24 @@ public: dpEntity* GetdpEntity() const { return m_dpEntity; } + /** + * I store this in a vector because if I have 2 separate pickup radii being applied to the player, I dont know which one is correctly active. + * This method adds the pickup radius to the vector of active radii and if its larger than the current one, is applied as the new pickup radius. + */ + void AddPickupRadiusScale(float value) ; + + /** + * Removes the provided pickup radius scale from our list of buffs + * The recalculates what our pickup radius is. + */ + void RemovePickupRadiusScale(float value) ; + + /** + * The pickup radii of this component. + * @return All active radii scales for this component. + */ + std::vector GetActivePickupRadiusScales() { return m_ActivePickupRadiusScales; }; + private: /** * The entity that owns this component @@ -322,6 +340,21 @@ private: * Whether this entity is static, making it unable to move */ bool m_Static; + + /** + * Whether the pickup scale is dirty. + */ + bool m_DirtyPickupRadiusScale; + + /** + * The list of pickup radius scales for this entity + */ + std::vector m_ActivePickupRadiusScales; + + /** + * The active pickup radius for this entity + */ + float m_PickupRadius; }; #endif // CONTROLLABLEPHYSICSCOMPONENT_H diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 2c906253..49b15b3f 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -593,7 +593,7 @@ void DestroyableComponent::Repair(const uint32_t armor) } -void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, bool echo) +void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32_t skillID, bool echo) { if (GetHealth() <= 0) { @@ -677,11 +677,10 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, bool e return; } - - Smash(source); + Smash(source, eKillType::VIOLENT, u"", skillID); } -void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType, const std::u16string& deathType) +void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType, const std::u16string& deathType, uint32_t skillID) { if (m_iHealth > 0) { @@ -727,31 +726,20 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType if (memberMissions == nullptr) continue; memberMissions->Progress(MissionTaskType::MISSION_TASK_TYPE_SMASH, m_Parent->GetLOT()); + memberMissions->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, m_Parent->GetLOT(), skillID); } } else { missions->Progress(MissionTaskType::MISSION_TASK_TYPE_SMASH, m_Parent->GetLOT()); + missions->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, m_Parent->GetLOT(), skillID); } } } const auto isPlayer = m_Parent->IsPlayer(); - GameMessages::SendDie( - m_Parent, - source, - source, - true, - killType, - deathType, - 0, - 0, - 0, - isPlayer, - false, - 1 - ); + GameMessages::SendDie(m_Parent, source, source, true, killType, deathType, 0, 0, 0, isPlayer, false, 1); //NANI?! if (!isPlayer) diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index d702897c..a1f57be4 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -377,17 +377,19 @@ public: * Attempt to damage this entity, handles everything from health and armor to absorption, immunity and callbacks. * @param damage the damage to attempt to apply * @param source the attacker that caused this damage + * @param skillID the skill that damaged this entity * @param echo whether or not to serialize the damage */ - void Damage(uint32_t damage, LWOOBJID source, bool echo = true); + void Damage(uint32_t damage, LWOOBJID source, uint32_t skillID = 0, bool echo = true); /** * Smashes this entity, notifying all clients * @param source the source that smashed this entity + * @param skillID the skill that killed this entity * @param killType the way this entity was killed, determines if a client animation is played * @param deathType the animation to play when killed */ - void Smash(LWOOBJID source, eKillType killType = eKillType::VIOLENT, const std::u16string& deathType = u""); + void Smash(LWOOBJID source, eKillType killType = eKillType::VIOLENT, const std::u16string& deathType = u"", uint32_t skillID = 0); /** * Pushes a layer of immunity to this entity, making it immune for longer diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index 0846f014..483e360f 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -25,12 +25,14 @@ ProjectileSyncEntry::ProjectileSyncEntry() { } -bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t skillUid, RakNet::BitStream* bitStream, const LWOOBJID target) +bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t skillUid, RakNet::BitStream* bitStream, const LWOOBJID target, uint32_t skillID) { auto* context = new BehaviorContext(this->m_Parent->GetObjectID()); context->caster = m_Parent->GetObjectID(); + context->skillID = skillID; + this->m_managedBehaviors.insert_or_assign(skillUid, context); auto* behavior = Behavior::CreateBehavior(behaviorId); diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index c0738efc..ad2449c3 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -92,7 +92,7 @@ public: * @param bitStream the bitSteam given by the client to determine the behavior path * @param target the explicit target of the skill */ - bool CastPlayerSkill(uint32_t behaviorId, uint32_t skillUid, RakNet::BitStream* bitStream, LWOOBJID target); + bool CastPlayerSkill(uint32_t behaviorId, uint32_t skillUid, RakNet::BitStream* bitStream, LWOOBJID target, uint32_t skillID = 0); /** * Continues a player skill. Should only be called when the server receives a sync message from the client. diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index b4346bb7..6a8e7356 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -1,116 +1,131 @@ #include "VendorComponent.h" -#include "Game.h" -#include "dServer.h" #include +#include "Game.h" +#include "dServer.h" + VendorComponent::VendorComponent(Entity* parent) : Component(parent) { - auto* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); - auto* vendorComponentTable = CDClientManager::Instance()->GetTable("VendorComponent"); - auto* lootMatrixTable = CDClientManager::Instance()->GetTable("LootMatrix"); - auto* lootTableTable = CDClientManager::Instance()->GetTable("LootTable"); - - int componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), COMPONENT_TYPE_VENDOR); - std::vector vendorComps = vendorComponentTable->Query([=](CDVendorComponent entry) { return (entry.id == componentID); }); - if (vendorComps.empty()) { - return; - } - m_BuyScalar = vendorComps[0].buyScalar; - m_SellScalar = vendorComps[0].sellScalar; - int lootMatrixID = vendorComps[0].LootMatrixIndex; - std::vector lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == lootMatrixID); }); - if (lootMatrices.empty()) { - return; - } - for (const auto& lootMatrix : lootMatrices) { - int lootTableID = lootMatrix.LootTableIndex; - std::vector vendorItems = lootTableTable->Query([=](CDLootTable entry) { return (entry.LootTableIndex == lootTableID); }); - if (lootMatrix.maxToDrop == 0 || lootMatrix.minToDrop == 0) { - for (CDLootTable item : vendorItems) { - m_Inventory.insert({item.itemid, item.sortPriority}); - } - } else { - auto randomCount = GeneralUtils::GenerateRandomNumber(lootMatrix.minToDrop, lootMatrix.maxToDrop); - - for (size_t i = 0; i < randomCount; i++) { - if (vendorItems.empty()) { - break; - } - - auto randomItemIndex = GeneralUtils::GenerateRandomNumber(0, vendorItems.size() - 1); - - const auto& randomItem = vendorItems[randomItemIndex]; - - vendorItems.erase(vendorItems.begin() + randomItemIndex); - - m_Inventory.insert({randomItem.itemid, randomItem.sortPriority}); - } - } - } - - //Because I want a vendor to sell these cameras - if (parent->GetLOT() == 13569) { - auto randomCamera = GeneralUtils::GenerateRandomNumber(0, 2); - - switch (randomCamera) { - case 0: - m_Inventory.insert({16253, 0}); //Grungagroid - break; - case 1: - m_Inventory.insert({16254, 0}); //Hipstabrick - break; - case 2: - m_Inventory.insert({16204, 0}); //Megabrixel snapshot - break; - default: - break; - } - } - - //Custom code for Max vanity NPC - if (parent->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) { - m_Inventory.clear(); - m_Inventory.insert({11909, 0}); //Top hat w frog - m_Inventory.insert({7785, 0}); //Flash bulb - m_Inventory.insert({12764, 0}); //Big fountain soda - m_Inventory.insert({12241, 0}); //Hot cocoa (from fb) - } + SetupConstants(); + RefreshInventory(true); } VendorComponent::~VendorComponent() = default; void VendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { - outBitStream->Write1(); - outBitStream->Write1(); // this bit is REQUIRED for vendor + mission multiinteract - outBitStream->Write(HasCraftingStation()); + outBitStream->Write1(); + outBitStream->Write1(); // Has standard items (Required for vendors with missions.) + outBitStream->Write(HasCraftingStation()); // Has multi use items } void VendorComponent::OnUse(Entity* originator) { - GameMessages::SendVendorOpenWindow(m_Parent, originator->GetSystemAddress()); - GameMessages::SendVendorStatusUpdate(m_Parent, originator->GetSystemAddress()); + GameMessages::SendVendorOpenWindow(m_Parent, originator->GetSystemAddress()); + GameMessages::SendVendorStatusUpdate(m_Parent, originator->GetSystemAddress()); } float VendorComponent::GetBuyScalar() const { - return m_BuyScalar; + return m_BuyScalar; } float VendorComponent::GetSellScalar() const { - return m_SellScalar; + return m_SellScalar; } void VendorComponent::SetBuyScalar(float value) { - m_BuyScalar = value; + m_BuyScalar = value; } void VendorComponent::SetSellScalar(float value) { - m_SellScalar = value; + m_SellScalar = value; } std::map& VendorComponent::GetInventory() { - return m_Inventory; + return m_Inventory; } bool VendorComponent::HasCraftingStation() { - // As far as we know, only Umami has a crafting station - return m_Parent->GetLOT() == 13800; + // As far as we know, only Umami has a crafting station + return m_Parent->GetLOT() == 13800; } + +void VendorComponent::RefreshInventory(bool isCreation) { + //Custom code for Max vanity NPC + if (m_Parent->GetLOT() == 9749 && Game::server->GetZoneID() == 1201) { + if (!isCreation) return; + m_Inventory.insert({11909, 0}); //Top hat w frog + m_Inventory.insert({7785, 0}); //Flash bulb + m_Inventory.insert({12764, 0}); //Big fountain soda + m_Inventory.insert({12241, 0}); //Hot cocoa (from fb) + return; + } + m_Inventory.clear(); + auto* lootMatrixTable = CDClientManager::Instance()->GetTable("LootMatrix"); + std::vector lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == m_LootMatrixID); }); + + if (lootMatrices.empty()) return; + // Done with lootMatrix table + + auto* lootTableTable = CDClientManager::Instance()->GetTable("LootTable"); + + for (const auto& lootMatrix : lootMatrices) { + int lootTableID = lootMatrix.LootTableIndex; + std::vector vendorItems = lootTableTable->Query([=](CDLootTable entry) { return (entry.LootTableIndex == lootTableID); }); + if (lootMatrix.maxToDrop == 0 || lootMatrix.minToDrop == 0) { + for (CDLootTable item : vendorItems) { + m_Inventory.insert({item.itemid, item.sortPriority}); + } + } else { + auto randomCount = GeneralUtils::GenerateRandomNumber(lootMatrix.minToDrop, lootMatrix.maxToDrop); + + for (size_t i = 0; i < randomCount; i++) { + if (vendorItems.empty()) break; + + auto randomItemIndex = GeneralUtils::GenerateRandomNumber(0, vendorItems.size() - 1); + + const auto& randomItem = vendorItems[randomItemIndex]; + + vendorItems.erase(vendorItems.begin() + randomItemIndex); + + m_Inventory.insert({randomItem.itemid, randomItem.sortPriority}); + } + } + } + + //Because I want a vendor to sell these cameras + if (m_Parent->GetLOT() == 13569) { + auto randomCamera = GeneralUtils::GenerateRandomNumber(0, 2); + + switch (randomCamera) { + case 0: + m_Inventory.insert({16253, 0}); //Grungagroid + break; + case 1: + m_Inventory.insert({16254, 0}); //Hipstabrick + break; + case 2: + m_Inventory.insert({16204, 0}); //Megabrixel snapshot + break; + default: + break; + } + } + + // Callback timer to refresh this inventory. + m_Parent->AddCallbackTimer(m_RefreshTimeSeconds, [this]() { + RefreshInventory(); + }); + GameMessages::SendVendorStatusUpdate(m_Parent, UNASSIGNED_SYSTEM_ADDRESS); +} + +void VendorComponent::SetupConstants() { + auto* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); + int componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), COMPONENT_TYPE_VENDOR); + + auto* vendorComponentTable = CDClientManager::Instance()->GetTable("VendorComponent"); + std::vector vendorComps = vendorComponentTable->Query([=](CDVendorComponent entry) { return (entry.id == componentID); }); + if (vendorComps.empty()) return; + m_BuyScalar = vendorComps[0].buyScalar; + m_SellScalar = vendorComps[0].sellScalar; + m_RefreshTimeSeconds = vendorComps[0].refreshTimeSeconds; + m_LootMatrixID = vendorComps[0].LootMatrixIndex; +} \ No newline at end of file diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index fb9dbf6b..c037d875 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -1,11 +1,12 @@ +#pragma once #ifndef VENDORCOMPONENT_H #define VENDORCOMPONENT_H -#include "RakNetTypes.h" -#include "Entity.h" -#include "GameMessages.h" #include "CDClientManager.h" #include "Component.h" +#include "Entity.h" +#include "GameMessages.h" +#include "RakNetTypes.h" /** * A component for vendor NPCs. A vendor sells items to the player. @@ -56,17 +57,36 @@ public: */ std::map& GetInventory(); + /** + * Refresh the inventory of this vendor. + */ + void RefreshInventory(bool isCreation = false); + + /** + * Called on startup of vendor to setup the variables for the component. + */ + void SetupConstants(); private: /** - * The buy scaler. + * The buy scalar. */ float m_BuyScalar; /** - * The sell scaler. + * The sell scalar. */ float m_SellScalar; + /** + * The refresh time of this vendors' inventory. + */ + float m_RefreshTimeSeconds; + + /** + * Loot matrix id of this vendor. + */ + uint32_t m_LootMatrixID; + /** * The list of items the vendor sells. */ diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 45760cc9..cdaae38c 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -285,7 +285,7 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System auto* skillComponent = entity->GetComponent(); - success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID); + success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID, startSkill.skillID); if (success && entity->GetCharacter()) { DestroyableComponent* destComp = entity->GetComponent(); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index a1fbdb47..e8e84931 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1252,8 +1252,7 @@ void GameMessages::SendVendorOpenWindow(Entity* entity, const SystemAddress& sys SEND_PACKET } -// ah yes, impl code in a send function, beautiful! -void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr) { +void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly) { CBITSTREAM CMSGHEADER @@ -1265,7 +1264,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s bitStream.Write(entity->GetObjectID()); bitStream.Write(GAME_MSG::GAME_MSG_VENDOR_STATUS_UPDATE); - bitStream.Write(false); + bitStream.Write(bUpdateOnly); bitStream.Write(static_cast(vendorItems.size())); for (std::pair item : vendorItems) { @@ -1273,6 +1272,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s bitStream.Write(static_cast(item.second)); } + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST SEND_PACKET } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index f3997200..602cb4b2 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -109,7 +109,7 @@ namespace GameMessages { void SendModularBuildEnd(Entity* entity); void SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr); - void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr); + void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly = false); void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr); void SendRemoveItemFromInventory(Entity* entity, const SystemAddress& sysAddr, LWOOBJID iObjID, LOT templateID, int inventoryType, uint32_t stackCount, uint32_t stackRemaining); diff --git a/dGame/dInventory/Inventory.cpp b/dGame/dInventory/Inventory.cpp index ae1715d7..6e8be6aa 100644 --- a/dGame/dInventory/Inventory.cpp +++ b/dGame/dInventory/Inventory.cpp @@ -102,6 +102,10 @@ int32_t Inventory::FindEmptySlot() { newSize += 9u; } + else + { + newSize += 10u; + } if (newSize > GetSize()) { diff --git a/dGame/dMission/MissionTask.cpp b/dGame/dMission/MissionTask.cpp index 30cd58a1..2dd59887 100644 --- a/dGame/dMission/MissionTask.cpp +++ b/dGame/dMission/MissionTask.cpp @@ -326,10 +326,12 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& case MissionTaskType::MISSION_TASK_TYPE_SKILL: { - if (!InParameters(value)) break; - - AddProgress(count); - + // This is a complicated check because for some missions we need to check for the associate being in the parameters instead of the value being in the parameters. + if (associate == LWOOBJID_EMPTY && GetAllTargets().size() == 1 && GetAllTargets()[0] == -1) { + if (InParameters(value)) AddProgress(count); + } else { + if (InParameters(associate) && InAllTargets(value)) AddProgress(count); + } break; } diff --git a/dNet/dMessageIdentifiers.h b/dNet/dMessageIdentifiers.h index eae3f88a..8e20ab54 100644 --- a/dNet/dMessageIdentifiers.h +++ b/dNet/dMessageIdentifiers.h @@ -324,6 +324,7 @@ enum GAME_MSG : unsigned short { GAME_MSG_ACTIVITY_STOP = 408, GAME_MSG_SHOOTING_GALLERY_CLIENT_AIM_UPDATE = 409, GAME_MSG_SHOOTING_GALLERY_FIRE = 411, + GAME_MSG_REQUEST_VENDOR_STATUS_UPDATE = 416, GAME_MSG_VENDOR_STATUS_UPDATE = 417, GAME_MSG_NOTIFY_CLIENT_SHOOTING_GALLERY_SCORE = 425, GAME_MSG_CONSUME_CLIENT_ITEM = 427, diff --git a/dScripts/BaseEnemyApe.cpp b/dScripts/BaseEnemyApe.cpp index 3419b2c4..9aa391d1 100644 --- a/dScripts/BaseEnemyApe.cpp +++ b/dScripts/BaseEnemyApe.cpp @@ -49,7 +49,7 @@ void BaseEnemyApe::OnTimerDone(Entity *self, std::string timerName) { if (destroyableComponent != nullptr) { destroyableComponent->SetArmor(destroyableComponent->GetMaxArmor() / timesStunned); } - + EntityManager::Instance()->SerializeEntity(self); self->SetVar(u"timesStunned", timesStunned + 1); StunApe(self, false); diff --git a/dScripts/NsConcertInstrument.cpp b/dScripts/NsConcertInstrument.cpp index 92341ee9..bfd35083 100644 --- a/dScripts/NsConcertInstrument.cpp +++ b/dScripts/NsConcertInstrument.cpp @@ -195,7 +195,7 @@ void NsConcertInstrument::EquipInstruments(Entity *self, Entity *player) { // Equip the left hand instrument const auto leftInstrumentLot = instrumentLotLeft.find(GetInstrumentLot(self))->second; if (leftInstrumentLot != LOT_NULL) { - inventory->AddItem(leftInstrumentLot, 1, eLootSourceType::LOOT_SOURCE_ACTIVITY); + inventory->AddItem(leftInstrumentLot, 1, eLootSourceType::LOOT_SOURCE_NONE, TEMP_ITEMS, {}, LWOOBJID_EMPTY, false); auto* leftInstrument = inventory->FindItemByLot(leftInstrumentLot, TEMP_ITEMS); leftInstrument->Equip(); } @@ -203,7 +203,7 @@ void NsConcertInstrument::EquipInstruments(Entity *self, Entity *player) { // Equip the right hand instrument const auto rightInstrumentLot = instrumentLotRight.find(GetInstrumentLot(self))->second; if (rightInstrumentLot != LOT_NULL) { - inventory->AddItem(rightInstrumentLot, 1, eLootSourceType::LOOT_SOURCE_ACTIVITY); + inventory->AddItem(rightInstrumentLot, 1, eLootSourceType::LOOT_SOURCE_NONE, TEMP_ITEMS, {}, LWOOBJID_EMPTY, false); auto* rightInstrument = inventory->FindItemByLot(rightInstrumentLot, TEMP_ITEMS); rightInstrument->Equip(); } diff --git a/dScripts/NtParadoxPanelServer.cpp b/dScripts/NtParadoxPanelServer.cpp index c1c9dd3f..556002fe 100644 --- a/dScripts/NtParadoxPanelServer.cpp +++ b/dScripts/NtParadoxPanelServer.cpp @@ -38,10 +38,11 @@ void NtParadoxPanelServer::OnUse(Entity* self, Entity* user) GameMessages::SendPlayAnimation(player, u"rebuild-celebrate"); GameMessages::SendNotifyClientObject(self->GetObjectID(), u"SparkStop", 0, 0, player->GetObjectID(), "", player->GetSystemAddress()); - + GameMessages::SendSetStunned(player->GetObjectID(), eStunState::POP, player->GetSystemAddress(), LWOOBJID_EMPTY, false, false, true, false, true, true, false, false, true); self->SetVar(u"bActive", false); }); - + GameMessages::SendPlayAnimation(user, u"nexus-powerpanel", 6.0f); + GameMessages::SendSetStunned(user->GetObjectID(), eStunState::PUSH, user->GetSystemAddress(), LWOOBJID_EMPTY, false, false, true, false, true, true, false, false, true); return; }