From 4117ceb6c1721ae1dd277bbf42f2075bc712a1e0 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 25 Apr 2022 03:25:07 -0700 Subject: [PATCH 01/11] Skill Tasks changes Addressed an issue where the Spinjitzu Initiate achievement would not progress. This also allows mission tasks that specify that the player must get a kill on an enemy with a skill to progress. Tested mission 1935 and 1139 and both missions progressed and completed as intended. --- dGame/dBehaviors/BasicAttackBehavior.cpp | 6 +++--- dGame/dBehaviors/BehaviorContext.h | 2 ++ dGame/dComponents/DestroyableComponent.cpp | 24 ++++++---------------- dGame/dComponents/DestroyableComponent.h | 6 ++++-- dGame/dComponents/SkillComponent.cpp | 4 +++- dGame/dComponents/SkillComponent.h | 2 +- dGame/dGameMessages/GameMessageHandler.cpp | 2 +- dGame/dMission/MissionTask.cpp | 10 +++++---- 8 files changed, 26 insertions(+), 30 deletions(-) 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/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/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/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/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; } From 7d233a04c0eb0c168ee1a9250aa0c0e049b58ea2 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 25 Apr 2022 16:56:40 -0700 Subject: [PATCH 02/11] Corrected action for OverTimeBehavior --- dGame/dBehaviors/OverTimeBehavior.cpp | 51 ++++++--------------------- dGame/dBehaviors/OverTimeBehavior.h | 3 +- 2 files changed, 12 insertions(+), 42 deletions(-) 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; From 27edf5da1d8e668e605bea3a29c2beb4d7cbd9d3 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 25 Apr 2022 18:16:30 -0700 Subject: [PATCH 03/11] Typo in NsConcertInstruments --- dScripts/NsConcertInstrument.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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(); } From 5afeb265cdbefffa8fce46675a1073fbaf25f448 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 26 Apr 2022 03:41:16 -0700 Subject: [PATCH 04/11] Updated vendor component Fixed a few issues in VendorComponent. - Corrected serialization to only happen on construction. - Added functionality to refresh the vendor based on info from the vendor component table - some whitespaceing inconsistencies. - Sorted includes. Tested the vendor in Nimbus Station and when the player re-enters the world, the vendor inventory refreshes, as opposed to previously where the world would need to reset in order to refresh the inventory. --- dGame/dComponents/VendorComponent.cpp | 187 ++++++++++++++------------ dGame/dComponents/VendorComponent.h | 30 ++++- dGame/dGameMessages/GameMessages.cpp | 6 +- dGame/dGameMessages/GameMessages.h | 2 +- dNet/dMessageIdentifiers.h | 1 + 5 files changed, 133 insertions(+), 93 deletions(-) diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index b4346bb7..4dd88e71 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -1,116 +1,135 @@ #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()); + // Only serialize this entity on construction. + // [bool] hasVendorInfo + // [bool] hasStandardItems (always true?) + // [bool] hasMulticostItems (only true for umami with their cooking for now.) + if (!bIsInitialUpdate) return; + outBitStream->Write1(); + outBitStream->Write1(); + outBitStream->Write(HasCraftingStation()); } 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(); + }); +} + +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/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/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, From cacf4fcd977da998a4a608d0af2493c27727d76f Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 26 Apr 2022 15:23:29 -0700 Subject: [PATCH 05/11] corrected serialization --- dGame/dComponents/VendorComponent.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index 4dd88e71..d2df1ef1 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -13,11 +13,6 @@ VendorComponent::VendorComponent(Entity* parent) : Component(parent) { VendorComponent::~VendorComponent() = default; void VendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { - // Only serialize this entity on construction. - // [bool] hasVendorInfo - // [bool] hasStandardItems (always true?) - // [bool] hasMulticostItems (only true for umami with their cooking for now.) - if (!bIsInitialUpdate) return; outBitStream->Write1(); outBitStream->Write1(); outBitStream->Write(HasCraftingStation()); From fa7c4d9c27df717e379d0ff07cb3bc472e476639 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 26 Apr 2022 15:26:30 -0700 Subject: [PATCH 06/11] added comments --- dGame/dComponents/VendorComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index d2df1ef1..39558774 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -14,8 +14,8 @@ VendorComponent::~VendorComponent() = default; void VendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { outBitStream->Write1(); - outBitStream->Write1(); - outBitStream->Write(HasCraftingStation()); + outBitStream->Write1(); // Has standard items (Required for vendors with missions.) + outBitStream->Write(HasCraftingStation()); // Has multi use items } void VendorComponent::OnUse(Entity* originator) { From 843a5b39c08f9cdccfabb05d6ea514354cb9954f Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 27 Apr 2022 01:35:46 -0700 Subject: [PATCH 07/11] Vendor refresh change Changed vendor refresh to happen as soon as it is able to. --- dGame/dComponents/VendorComponent.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index 39558774..6a8e7356 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -114,6 +114,7 @@ void VendorComponent::RefreshInventory(bool isCreation) { m_Parent->AddCallbackTimer(m_RefreshTimeSeconds, [this]() { RefreshInventory(); }); + GameMessages::SendVendorStatusUpdate(m_Parent, UNASSIGNED_SYSTEM_ADDRESS); } void VendorComponent::SetupConstants() { From b713b948418fd64909319667ab0a455b94bc6a7b Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 26 Apr 2022 21:44:35 -0700 Subject: [PATCH 08/11] Added Loot Buff Behavior Added functions and methods to support the pickup radius. Functionality includes: Corrected serialization for ControllablePhysicsComponent which correctly serializes the pickup radius to the client. A method to add the pickup radius to the list of active pickup radii the component has. A method to remove and re-calculate the largest active radii the component currently has. Tested equipping all variations of the LootBuff behavior (passive skills, items, item skills) and all functioned as intended. Tested equipping multiple items with a loot buff and then unequipping them in different orders. Tested adding pickup radii of different values and the server correctly adjusted the pickup radius to the largest one currently equipped. --- dGame/dBehaviors/AndBehavior.cpp | 6 +++ dGame/dBehaviors/AndBehavior.h | 2 + dGame/dBehaviors/Behavior.cpp | 5 ++- dGame/dBehaviors/BehaviorContext.cpp | 4 +- dGame/dBehaviors/LootBuffBehavior.cpp | 38 ++++++++++++++++++ dGame/dBehaviors/LootBuffBehavior.h | 32 +++++++++++++++ .../ControllablePhysicsComponent.cpp | 40 ++++++++++++++++++- .../ControllablePhysicsComponent.h | 33 +++++++++++++++ 8 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 dGame/dBehaviors/LootBuffBehavior.cpp create mode 100644 dGame/dBehaviors/LootBuffBehavior.h 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/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/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/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 From 2f598dad09d30b2ef8c3fe3ed18c7cdcd252232d Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 28 Apr 2022 00:13:57 -0700 Subject: [PATCH 09/11] Serialize ape when changing armor value --- dScripts/BaseEnemyApe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From 59be7d86354e3aa058287aad0ce2c0efeb49b084 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 28 Apr 2022 14:36:55 -0700 Subject: [PATCH 10/11] Added back brick inventory resizing --- dGame/dInventory/Inventory.cpp | 4 ++++ 1 file changed, 4 insertions(+) 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()) { From 2435870ec532ee6dcaa19ed75d6e9c828f78b988 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 28 Apr 2022 19:51:49 -0700 Subject: [PATCH 11/11] Added animation Added the GM animation and stun for the Nexus Tower Paradox Panels for mission 1281. Tested on local instance and animation played correctly when having the mission and did not play at all before or after completing the mission --- dScripts/NtParadoxPanelServer.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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; }