Merge remote-tracking branch 'origin/main' into scripting-lua

This commit is contained in:
Wincent Holm 2022-04-29 09:38:05 +02:00
commit ac7823802e
27 changed files with 332 additions and 175 deletions

View File

@ -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();

View File

@ -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;
};

View File

@ -14,7 +14,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
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<DestroyableComponent>();
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<DestroyableComponent>();
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);
}
}

View File

@ -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);

View File

@ -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;

View File

@ -58,6 +58,8 @@ struct BehaviorContext
float skillTime = 0;
uint32_t skillID = 0;
uint32_t skillUId = 0;
bool failed = false;

View File

@ -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<ControllablePhysicsComponent>();
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<ControllablePhysicsComponent>();
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");
}

View File

@ -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;
};

View File

@ -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<SkillComponent>();
if (skillComponent == nullptr)
{
return;
}
if (skillComponent == nullptr) return;
skillComponent->CalculateBehavior(0, m_Action->m_behaviorId, branch.target, true, true);
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
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<CDSkillBehaviorTable>("SkillBehavior");
m_ActionBehaviorId = skillTable->GetSkillByID(m_Action).behaviorID;
m_Delay = GetFloat("delay");
m_NumIntervals = GetInt("num_intervals");
}

View File

@ -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;

View File

@ -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;
}
}
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);
}

View File

@ -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<float> 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<float> m_ActivePickupRadiusScales;
/**
* The active pickup radius for this entity
*/
float m_PickupRadius;
};
#endif // CONTROLLABLEPHYSICSCOMPONENT_H

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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.

View File

@ -1,116 +1,131 @@
#include "VendorComponent.h"
#include "Game.h"
#include "dServer.h"
#include <BitStream.h>
#include "Game.h"
#include "dServer.h"
VendorComponent::VendorComponent(Entity* parent) : Component(parent) {
auto* compRegistryTable = CDClientManager::Instance()->GetTable<CDComponentsRegistryTable>("ComponentsRegistry");
auto* vendorComponentTable = CDClientManager::Instance()->GetTable<CDVendorComponentTable>("VendorComponent");
auto* lootMatrixTable = CDClientManager::Instance()->GetTable<CDLootMatrixTable>("LootMatrix");
auto* lootTableTable = CDClientManager::Instance()->GetTable<CDLootTableTable>("LootTable");
int componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), COMPONENT_TYPE_VENDOR);
std::vector<CDVendorComponent> 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<CDLootMatrix> lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == lootMatrixID); });
if (lootMatrices.empty()) {
return;
}
for (const auto& lootMatrix : lootMatrices) {
int lootTableID = lootMatrix.LootTableIndex;
std::vector<CDLootTable> 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<int32_t>(lootMatrix.minToDrop, lootMatrix.maxToDrop);
for (size_t i = 0; i < randomCount; i++) {
if (vendorItems.empty()) {
break;
}
auto randomItemIndex = GeneralUtils::GenerateRandomNumber<int32_t>(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<int32_t>(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<LOT, int>& 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<CDLootMatrixTable>("LootMatrix");
std::vector<CDLootMatrix> lootMatrices = lootMatrixTable->Query([=](CDLootMatrix entry) { return (entry.LootMatrixIndex == m_LootMatrixID); });
if (lootMatrices.empty()) return;
// Done with lootMatrix table
auto* lootTableTable = CDClientManager::Instance()->GetTable<CDLootTableTable>("LootTable");
for (const auto& lootMatrix : lootMatrices) {
int lootTableID = lootMatrix.LootTableIndex;
std::vector<CDLootTable> 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<int32_t>(lootMatrix.minToDrop, lootMatrix.maxToDrop);
for (size_t i = 0; i < randomCount; i++) {
if (vendorItems.empty()) break;
auto randomItemIndex = GeneralUtils::GenerateRandomNumber<int32_t>(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<int32_t>(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<CDComponentsRegistryTable>("ComponentsRegistry");
int componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), COMPONENT_TYPE_VENDOR);
auto* vendorComponentTable = CDClientManager::Instance()->GetTable<CDVendorComponentTable>("VendorComponent");
std::vector<CDVendorComponent> 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;
}

View File

@ -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<LOT, int>& 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.
*/

View File

@ -285,7 +285,7 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System
auto* skillComponent = entity->GetComponent<SkillComponent>();
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<DestroyableComponent>();

View File

@ -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<uint32_t>(vendorItems.size()));
for (std::pair<LOT, int> item : vendorItems) {
@ -1273,6 +1272,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s
bitStream.Write(static_cast<int>(item.second));
}
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST
SEND_PACKET
}

View File

@ -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);

View File

@ -102,6 +102,10 @@ int32_t Inventory::FindEmptySlot()
{
newSize += 9u;
}
else
{
newSize += 10u;
}
if (newSize > GetSize())
{

View File

@ -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;
}

View File

@ -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,

View File

@ -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<uint32_t>(u"timesStunned", timesStunned + 1);
StunApe(self, false);

View File

@ -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();
}

View File

@ -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;
}