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() void AndBehavior::Load()
{ {
const auto parameters = GetParameterNames(); const auto parameters = GetParameterNames();

View File

@ -20,5 +20,7 @@ public:
void Calculate(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 Load() override; void Load() override;
}; };

View File

@ -14,7 +14,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) { if (destroyableComponent != nullptr) {
PlayFx(u"onhit", entity->GetObjectID()); 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); this->m_onSuccess->Handle(context, bitStream, branch);
@ -56,7 +56,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) { if (destroyableComponent != nullptr) {
PlayFx(u"onhit", entity->GetObjectID()); 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>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (damage != 0 && destroyableComponent != nullptr) { if (damage != 0 && destroyableComponent != nullptr) {
PlayFx(u"onhit", entity->GetObjectID(), 1); PlayFx(u"onhit", entity->GetObjectID(), 1);
destroyableComponent->Damage(damage, context->originator, false); destroyableComponent->Damage(damage, context->originator, context->skillID, false);
context->ScheduleUpdate(branch.target); context->ScheduleUpdate(branch.target);
} }
} }

View File

@ -18,6 +18,7 @@
#include "AreaOfEffectBehavior.h" #include "AreaOfEffectBehavior.h"
#include "DurationBehavior.h" #include "DurationBehavior.h"
#include "TacArcBehavior.h" #include "TacArcBehavior.h"
#include "LootBuffBehavior.h"
#include "AttackDelayBehavior.h" #include "AttackDelayBehavior.h"
#include "BasicAttackBehavior.h" #include "BasicAttackBehavior.h"
#include "ChainBehavior.h" #include "ChainBehavior.h"
@ -172,7 +173,9 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId)
behavior = new SpeedBehavior(behaviorId); behavior = new SpeedBehavior(behaviorId);
break; break;
case BehaviorTemplates::BEHAVIOR_DARK_INSPIRATION: 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_VENTURE_VISION: break;
case BehaviorTemplates::BEHAVIOR_SPAWN_OBJECT: case BehaviorTemplates::BEHAVIOR_SPAWN_OBJECT:
behavior = new SpawnBehavior(behaviorId); 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) void BehaviorContext::RegisterTimerBehavior(Behavior* behavior, const BehaviorBranchContext& branchContext, const LWOOBJID second)
{ {
BehaviorTimerEntry entry BehaviorTimerEntry entry;
;
entry.time = branchContext.duration; entry.time = branchContext.duration;
entry.behavior = behavior; entry.behavior = behavior;
entry.branchContext = branchContext; entry.branchContext = branchContext;

View File

@ -58,6 +58,8 @@ struct BehaviorContext
float skillTime = 0; float skillTime = 0;
uint32_t skillID = 0;
uint32_t skillUId = 0; uint32_t skillUId = 0;
bool failed = false; 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 "SkillComponent.h"
#include "DestroyableComponent.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) void OverTimeBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch)
{ {
const auto originator = context->originator; const auto originator = context->originator;
auto* entity = EntityManager::Instance()->GetEntity(originator); auto* entity = EntityManager::Instance()->GetEntity(originator);
if (entity == nullptr) if (entity == nullptr) return;
{
return;
}
for (size_t i = 0; i < m_NumIntervals; i++) for (size_t i = 0; i < m_NumIntervals; i++)
{ {
entity->AddCallbackTimer((i + 1) * m_Delay, [originator, branch, this]() { entity->AddCallbackTimer((i + 1) * m_Delay, [originator, branch, this]() {
auto* entity = EntityManager::Instance()->GetEntity(originator); auto* entity = EntityManager::Instance()->GetEntity(originator);
if (entity == nullptr) if (entity == nullptr) return;
{
return;
}
auto* skillComponent = entity->GetComponent<SkillComponent>(); auto* skillComponent = entity->GetComponent<SkillComponent>();
if (skillComponent == nullptr) if (skillComponent == nullptr) return;
{
return;
}
skillComponent->CalculateBehavior(0, m_Action->m_behaviorId, branch.target, true, true); skillComponent->CalculateBehavior(m_Action, m_ActionBehaviorId, 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;
}
}); });
} }
} }
@ -74,7 +38,12 @@ void OverTimeBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bi
void OverTimeBehavior::Load() 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_Delay = GetFloat("delay");
m_NumIntervals = GetInt("num_intervals"); m_NumIntervals = GetInt("num_intervals");
} }

View File

@ -4,7 +4,8 @@
class OverTimeBehavior final : public Behavior class OverTimeBehavior final : public Behavior
{ {
public: public:
Behavior* m_Action; uint32_t m_Action;
uint32_t m_ActionBehaviorId;
float m_Delay; float m_Delay;
int32_t m_NumIntervals; int32_t m_NumIntervals;

View File

@ -29,6 +29,8 @@ ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : Com
m_GravityScale = 1; m_GravityScale = 1;
m_DirtyCheats = false; m_DirtyCheats = false;
m_IgnoreMultipliers = 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 if (entity->GetLOT() != 1) // Other physics entities we care about will be added by BaseCombatAI
return; return;
@ -85,7 +87,13 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bo
m_DirtyCheats = false; 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->Write0();
outBitStream->Write(m_DirtyPosition || bIsInitialUpdate); outBitStream->Write(m_DirtyPosition || bIsInitialUpdate);
@ -231,3 +239,31 @@ void ControllablePhysicsComponent::SetDirtyVelocity(bool val) {
void ControllablePhysicsComponent::SetDirtyAngularVelocity(bool val) { void ControllablePhysicsComponent::SetDirtyAngularVelocity(bool val) {
m_DirtyAngularVelocity = 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; } 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: private:
/** /**
* The entity that owns this component * The entity that owns this component
@ -322,6 +340,21 @@ private:
* Whether this entity is static, making it unable to move * Whether this entity is static, making it unable to move
*/ */
bool m_Static; 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 #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) if (GetHealth() <= 0)
{ {
@ -677,11 +677,10 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, bool e
return; return;
} }
Smash(source, eKillType::VIOLENT, u"", skillID);
Smash(source);
} }
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) if (m_iHealth > 0)
{ {
@ -727,31 +726,20 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
if (memberMissions == nullptr) continue; if (memberMissions == nullptr) continue;
memberMissions->Progress(MissionTaskType::MISSION_TASK_TYPE_SMASH, m_Parent->GetLOT()); memberMissions->Progress(MissionTaskType::MISSION_TASK_TYPE_SMASH, m_Parent->GetLOT());
memberMissions->Progress(MissionTaskType::MISSION_TASK_TYPE_SKILL, m_Parent->GetLOT(), skillID);
} }
} }
else else
{ {
missions->Progress(MissionTaskType::MISSION_TASK_TYPE_SMASH, m_Parent->GetLOT()); 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(); const auto isPlayer = m_Parent->IsPlayer();
GameMessages::SendDie( GameMessages::SendDie(m_Parent, source, source, true, killType, deathType, 0, 0, 0, isPlayer, false, 1);
m_Parent,
source,
source,
true,
killType,
deathType,
0,
0,
0,
isPlayer,
false,
1
);
//NANI?! //NANI?!
if (!isPlayer) 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. * 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 damage the damage to attempt to apply
* @param source the attacker that caused this damage * @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 * @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 * Smashes this entity, notifying all clients
* @param source the source that smashed this entity * @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 killType the way this entity was killed, determines if a client animation is played
* @param deathType the animation to play when killed * @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 * 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()); auto* context = new BehaviorContext(this->m_Parent->GetObjectID());
context->caster = m_Parent->GetObjectID(); context->caster = m_Parent->GetObjectID();
context->skillID = skillID;
this->m_managedBehaviors.insert_or_assign(skillUid, context); this->m_managedBehaviors.insert_or_assign(skillUid, context);
auto* behavior = Behavior::CreateBehavior(behaviorId); 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 bitStream the bitSteam given by the client to determine the behavior path
* @param target the explicit target of the skill * @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. * 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 "VendorComponent.h"
#include "Game.h"
#include "dServer.h"
#include <BitStream.h> #include <BitStream.h>
#include "Game.h"
#include "dServer.h"
VendorComponent::VendorComponent(Entity* parent) : Component(parent) { VendorComponent::VendorComponent(Entity* parent) : Component(parent) {
auto* compRegistryTable = CDClientManager::Instance()->GetTable<CDComponentsRegistryTable>("ComponentsRegistry"); SetupConstants();
auto* vendorComponentTable = CDClientManager::Instance()->GetTable<CDVendorComponentTable>("VendorComponent"); RefreshInventory(true);
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)
}
} }
VendorComponent::~VendorComponent() = default; VendorComponent::~VendorComponent() = default;
void VendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) { void VendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write1(); outBitStream->Write1();
outBitStream->Write1(); // this bit is REQUIRED for vendor + mission multiinteract outBitStream->Write1(); // Has standard items (Required for vendors with missions.)
outBitStream->Write(HasCraftingStation()); outBitStream->Write(HasCraftingStation()); // Has multi use items
} }
void VendorComponent::OnUse(Entity* originator) { void VendorComponent::OnUse(Entity* originator) {
GameMessages::SendVendorOpenWindow(m_Parent, originator->GetSystemAddress()); GameMessages::SendVendorOpenWindow(m_Parent, originator->GetSystemAddress());
GameMessages::SendVendorStatusUpdate(m_Parent, originator->GetSystemAddress()); GameMessages::SendVendorStatusUpdate(m_Parent, originator->GetSystemAddress());
} }
float VendorComponent::GetBuyScalar() const { float VendorComponent::GetBuyScalar() const {
return m_BuyScalar; return m_BuyScalar;
} }
float VendorComponent::GetSellScalar() const { float VendorComponent::GetSellScalar() const {
return m_SellScalar; return m_SellScalar;
} }
void VendorComponent::SetBuyScalar(float value) { void VendorComponent::SetBuyScalar(float value) {
m_BuyScalar = value; m_BuyScalar = value;
} }
void VendorComponent::SetSellScalar(float value) { void VendorComponent::SetSellScalar(float value) {
m_SellScalar = value; m_SellScalar = value;
} }
std::map<LOT, int>& VendorComponent::GetInventory() { std::map<LOT, int>& VendorComponent::GetInventory() {
return m_Inventory; return m_Inventory;
} }
bool VendorComponent::HasCraftingStation() { bool VendorComponent::HasCraftingStation() {
// As far as we know, only Umami has a crafting station // As far as we know, only Umami has a crafting station
return m_Parent->GetLOT() == 13800; 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 #ifndef VENDORCOMPONENT_H
#define VENDORCOMPONENT_H #define VENDORCOMPONENT_H
#include "RakNetTypes.h"
#include "Entity.h"
#include "GameMessages.h"
#include "CDClientManager.h" #include "CDClientManager.h"
#include "Component.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. * A component for vendor NPCs. A vendor sells items to the player.
@ -56,17 +57,36 @@ public:
*/ */
std::map<LOT, int>& GetInventory(); 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: private:
/** /**
* The buy scaler. * The buy scalar.
*/ */
float m_BuyScalar; float m_BuyScalar;
/** /**
* The sell scaler. * The sell scalar.
*/ */
float m_SellScalar; 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. * 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>(); 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()) { if (success && entity->GetCharacter()) {
DestroyableComponent* destComp = entity->GetComponent<DestroyableComponent>(); DestroyableComponent* destComp = entity->GetComponent<DestroyableComponent>();

View File

@ -1252,8 +1252,7 @@ void GameMessages::SendVendorOpenWindow(Entity* entity, const SystemAddress& sys
SEND_PACKET SEND_PACKET
} }
// ah yes, impl code in a send function, beautiful! void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly) {
void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr) {
CBITSTREAM CBITSTREAM
CMSGHEADER CMSGHEADER
@ -1265,7 +1264,7 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s
bitStream.Write(entity->GetObjectID()); bitStream.Write(entity->GetObjectID());
bitStream.Write(GAME_MSG::GAME_MSG_VENDOR_STATUS_UPDATE); bitStream.Write(GAME_MSG::GAME_MSG_VENDOR_STATUS_UPDATE);
bitStream.Write(false); bitStream.Write(bUpdateOnly);
bitStream.Write(static_cast<uint32_t>(vendorItems.size())); bitStream.Write(static_cast<uint32_t>(vendorItems.size()));
for (std::pair<LOT, int> item : vendorItems) { 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)); bitStream.Write(static_cast<int>(item.second));
} }
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST
SEND_PACKET SEND_PACKET
} }

View File

@ -109,7 +109,7 @@ namespace GameMessages {
void SendModularBuildEnd(Entity* entity); void SendModularBuildEnd(Entity* entity);
void SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr); 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 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); 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; newSize += 9u;
} }
else
{
newSize += 10u;
}
if (newSize > GetSize()) 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: case MissionTaskType::MISSION_TASK_TYPE_SKILL:
{ {
if (!InParameters(value)) break; // 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) {
AddProgress(count); if (InParameters(value)) AddProgress(count);
} else {
if (InParameters(associate) && InAllTargets(value)) AddProgress(count);
}
break; break;
} }

View File

@ -324,6 +324,7 @@ enum GAME_MSG : unsigned short {
GAME_MSG_ACTIVITY_STOP = 408, GAME_MSG_ACTIVITY_STOP = 408,
GAME_MSG_SHOOTING_GALLERY_CLIENT_AIM_UPDATE = 409, GAME_MSG_SHOOTING_GALLERY_CLIENT_AIM_UPDATE = 409,
GAME_MSG_SHOOTING_GALLERY_FIRE = 411, GAME_MSG_SHOOTING_GALLERY_FIRE = 411,
GAME_MSG_REQUEST_VENDOR_STATUS_UPDATE = 416,
GAME_MSG_VENDOR_STATUS_UPDATE = 417, GAME_MSG_VENDOR_STATUS_UPDATE = 417,
GAME_MSG_NOTIFY_CLIENT_SHOOTING_GALLERY_SCORE = 425, GAME_MSG_NOTIFY_CLIENT_SHOOTING_GALLERY_SCORE = 425,
GAME_MSG_CONSUME_CLIENT_ITEM = 427, GAME_MSG_CONSUME_CLIENT_ITEM = 427,

View File

@ -49,7 +49,7 @@ void BaseEnemyApe::OnTimerDone(Entity *self, std::string timerName) {
if (destroyableComponent != nullptr) { if (destroyableComponent != nullptr) {
destroyableComponent->SetArmor(destroyableComponent->GetMaxArmor() / timesStunned); destroyableComponent->SetArmor(destroyableComponent->GetMaxArmor() / timesStunned);
} }
EntityManager::Instance()->SerializeEntity(self);
self->SetVar<uint32_t>(u"timesStunned", timesStunned + 1); self->SetVar<uint32_t>(u"timesStunned", timesStunned + 1);
StunApe(self, false); StunApe(self, false);

View File

@ -195,7 +195,7 @@ void NsConcertInstrument::EquipInstruments(Entity *self, Entity *player) {
// Equip the left hand instrument // Equip the left hand instrument
const auto leftInstrumentLot = instrumentLotLeft.find(GetInstrumentLot(self))->second; const auto leftInstrumentLot = instrumentLotLeft.find(GetInstrumentLot(self))->second;
if (leftInstrumentLot != LOT_NULL) { 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); auto* leftInstrument = inventory->FindItemByLot(leftInstrumentLot, TEMP_ITEMS);
leftInstrument->Equip(); leftInstrument->Equip();
} }
@ -203,7 +203,7 @@ void NsConcertInstrument::EquipInstruments(Entity *self, Entity *player) {
// Equip the right hand instrument // Equip the right hand instrument
const auto rightInstrumentLot = instrumentLotRight.find(GetInstrumentLot(self))->second; const auto rightInstrumentLot = instrumentLotRight.find(GetInstrumentLot(self))->second;
if (rightInstrumentLot != LOT_NULL) { 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); auto* rightInstrument = inventory->FindItemByLot(rightInstrumentLot, TEMP_ITEMS);
rightInstrument->Equip(); rightInstrument->Equip();
} }

View File

@ -38,10 +38,11 @@ void NtParadoxPanelServer::OnUse(Entity* self, Entity* user)
GameMessages::SendPlayAnimation(player, u"rebuild-celebrate"); GameMessages::SendPlayAnimation(player, u"rebuild-celebrate");
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"SparkStop", 0, 0, player->GetObjectID(), "", player->GetSystemAddress()); 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); 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; return;
} }