mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-08-09 20:24:16 +00:00
Merge branch 'main' into moreMovementAi
This commit is contained in:
@@ -82,6 +82,7 @@
|
||||
#include "CollectibleComponent.h"
|
||||
#include "ItemComponent.h"
|
||||
#include "GhostComponent.h"
|
||||
#include "AchievementVendorComponent.h"
|
||||
|
||||
// Table includes
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
@@ -615,6 +616,8 @@ void Entity::Initialize() {
|
||||
AddComponent<VendorComponent>();
|
||||
} else if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::DONATION_VENDOR, -1) != -1)) {
|
||||
AddComponent<DonationVendorComponent>();
|
||||
} else if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ACHIEVEMENT_VENDOR, -1) != -1)) {
|
||||
AddComponent<AchievementVendorComponent>();
|
||||
}
|
||||
|
||||
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_VENDOR, -1) != -1) {
|
||||
@@ -1185,6 +1188,11 @@ void Entity::WriteComponents(RakNet::BitStream* outBitStream, eReplicaPacketType
|
||||
donationVendorComponent->Serialize(outBitStream, bIsInitialUpdate);
|
||||
}
|
||||
|
||||
AchievementVendorComponent* achievementVendorComponent;
|
||||
if (TryGetComponent(eReplicaComponentType::ACHIEVEMENT_VENDOR, achievementVendorComponent)) {
|
||||
achievementVendorComponent->Serialize(outBitStream, bIsInitialUpdate);
|
||||
}
|
||||
|
||||
BouncerComponent* bouncerComponent;
|
||||
if (TryGetComponent(eReplicaComponentType::BOUNCER, bouncerComponent)) {
|
||||
bouncerComponent->Serialize(outBitStream, bIsInitialUpdate);
|
||||
@@ -2179,3 +2187,9 @@ void Entity::SetRespawnRot(const NiQuaternion& rotation) {
|
||||
auto* characterComponent = GetComponent<CharacterComponent>();
|
||||
if (characterComponent) characterComponent->SetRespawnRot(rotation);
|
||||
}
|
||||
|
||||
void Entity::SetScale(const float scale) {
|
||||
if (scale == m_Scale) return;
|
||||
m_Scale = scale;
|
||||
Game::entityManager->SerializeEntity(this);
|
||||
}
|
@@ -295,6 +295,8 @@ public:
|
||||
|
||||
void ProcessPositionUpdate(PositionUpdate& update);
|
||||
|
||||
void SetScale(const float scale);
|
||||
|
||||
protected:
|
||||
LWOOBJID m_ObjectID;
|
||||
|
||||
|
@@ -243,13 +243,13 @@ bool BehaviorContext::CalculateUpdate(const float deltaTime) {
|
||||
echo.uiBehaviorHandle = entry.handle;
|
||||
echo.uiSkillHandle = this->skillUId;
|
||||
|
||||
auto* bitStream = new RakNet::BitStream();
|
||||
RakNet::BitStream bitStream{};
|
||||
|
||||
// Calculate sync
|
||||
entry.behavior->SyncCalculation(this, bitStream, entry.branchContext);
|
||||
entry.behavior->SyncCalculation(this, &bitStream, entry.branchContext);
|
||||
|
||||
if (!clientInitalized) {
|
||||
echo.sBitStream.assign(reinterpret_cast<char*>(bitStream->GetData()), bitStream->GetNumberOfBytesUsed());
|
||||
echo.sBitStream.assign(reinterpret_cast<char*>(bitStream.GetData()), bitStream.GetNumberOfBytesUsed());
|
||||
|
||||
// Write message
|
||||
RakNet::BitStream message;
|
||||
@@ -262,8 +262,6 @@ bool BehaviorContext::CalculateUpdate(const float deltaTime) {
|
||||
}
|
||||
|
||||
ExecuteUpdates();
|
||||
|
||||
delete bitStream;
|
||||
}
|
||||
|
||||
std::vector<BehaviorSyncEntry> valid;
|
||||
|
72
dGame/dComponents/AchievementVendorComponent.cpp
Normal file
72
dGame/dComponents/AchievementVendorComponent.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "AchievementVendorComponent.h"
|
||||
#include "MissionComponent.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "eMissionState.h"
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDItemComponentTable.h"
|
||||
#include "eVendorTransactionResult.h"
|
||||
#include "CheatDetection.h"
|
||||
#include "UserManager.h"
|
||||
#include "CDMissionsTable.h"
|
||||
|
||||
bool AchievementVendorComponent::SellsItem(Entity* buyer, const LOT lot) {
|
||||
auto* missionComponent = buyer->GetComponent<MissionComponent>();
|
||||
if (!missionComponent) return false;
|
||||
|
||||
if (m_PlayerPurchasableItems[buyer->GetObjectID()].contains(lot)){
|
||||
return true;
|
||||
}
|
||||
|
||||
CDMissionsTable* missionsTable = CDClientManager::GetTable<CDMissionsTable>();
|
||||
const auto missions = missionsTable->GetMissionsForReward(lot);
|
||||
for (const auto mission : missions) {
|
||||
if (missionComponent->GetMissionState(mission) == eMissionState::COMPLETE) {
|
||||
m_PlayerPurchasableItems[buyer->GetObjectID()].insert(lot);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
|
||||
// get the item Comp from the item LOT
|
||||
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
||||
CDItemComponentTable* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
|
||||
int itemCompID = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::ITEM);
|
||||
CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID);
|
||||
uint32_t costLOT = itemComp.commendationLOT;
|
||||
|
||||
if (costLOT == -1 || !SellsItem(buyer, lot)) {
|
||||
auto* user = UserManager::Instance()->GetUser(buyer->GetSystemAddress());
|
||||
CheatDetection::ReportCheat(user, buyer->GetSystemAddress(), "Attempted to buy item %i from achievement vendor %i that is not purchasable", lot, m_Parent->GetLOT());
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* inventoryComponent = buyer->GetComponent<InventoryComponent>();
|
||||
if (!inventoryComponent) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (costLOT == 13763) { // Faction Token Proxy
|
||||
auto* missionComponent = buyer->GetComponent<MissionComponent>();
|
||||
if (!missionComponent) return;
|
||||
|
||||
if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) costLOT = 8318; // "Assembly Token"
|
||||
if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) costLOT = 8321; // "Venture League Token"
|
||||
if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) costLOT = 8319; // "Sentinels Token"
|
||||
if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) costLOT = 8320; // "Paradox Token"
|
||||
}
|
||||
|
||||
const uint32_t altCurrencyCost = itemComp.commendationCost * count;
|
||||
if (inventoryComponent->GetLotCount(costLOT) < altCurrencyCost) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
inventoryComponent->RemoveItem(costLOT, altCurrencyCost);
|
||||
inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR);
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
|
||||
}
|
23
dGame/dComponents/AchievementVendorComponent.h
Normal file
23
dGame/dComponents/AchievementVendorComponent.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef __ACHIEVEMENTVENDORCOMPONENT__H__
|
||||
#define __ACHIEVEMENTVENDORCOMPONENT__H__
|
||||
|
||||
#include "VendorComponent.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
class Entity;
|
||||
|
||||
class AchievementVendorComponent final : public VendorComponent {
|
||||
public:
|
||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ACHIEVEMENT_VENDOR;
|
||||
AchievementVendorComponent(Entity* parent) : VendorComponent(parent) {};
|
||||
bool SellsItem(Entity* buyer, const LOT lot);
|
||||
void Buy(Entity* buyer, LOT lot, uint32_t count);
|
||||
|
||||
private:
|
||||
std::map<LWOOBJID,std::set<LOT>> m_PlayerPurchasableItems;
|
||||
};
|
||||
|
||||
|
||||
#endif //!__ACHIEVEMENTVENDORCOMPONENT__H__
|
@@ -208,9 +208,8 @@ void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOO
|
||||
void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity, bool ignoreRefCount) {
|
||||
const auto& iter = m_Buffs.find(id);
|
||||
|
||||
if (iter == m_Buffs.end()) {
|
||||
return;
|
||||
}
|
||||
// If the buff is already scheduled to be removed, don't do it again
|
||||
if (iter == m_Buffs.end() || m_BuffsToRemove.contains(id)) return;
|
||||
|
||||
if (!ignoreRefCount && !iter->second.cancelOnRemoveBuff) {
|
||||
iter->second.refCount--;
|
||||
@@ -222,7 +221,7 @@ void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity
|
||||
|
||||
GameMessages::SendRemoveBuff(m_Parent, fromUnEquip, removeImmunity, id);
|
||||
|
||||
m_BuffsToRemove.push_back(id);
|
||||
m_BuffsToRemove.insert(id);
|
||||
|
||||
RemoveBuffEffect(id);
|
||||
}
|
||||
|
@@ -141,7 +141,7 @@ private:
|
||||
std::map<int32_t, Buff> m_Buffs;
|
||||
|
||||
// Buffs to remove at the end of the update frame.
|
||||
std::vector<int32_t> m_BuffsToRemove;
|
||||
std::set<int32_t> m_BuffsToRemove;
|
||||
|
||||
/**
|
||||
* Parameters (=effects) for each buff
|
||||
|
@@ -1,4 +1,5 @@
|
||||
set(DGAME_DCOMPONENTS_SOURCES
|
||||
"AchievementVendorComponent.cpp"
|
||||
"ActivityComponent.cpp"
|
||||
"BaseCombatAIComponent.cpp"
|
||||
"BouncerComponent.cpp"
|
||||
|
@@ -252,7 +252,7 @@ bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LW
|
||||
|
||||
|
||||
SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, const uint32_t behaviorId, const LWOOBJID target, const bool ignoreTarget, const bool clientInitalized, const LWOOBJID originatorOverride) {
|
||||
auto* bitStream = new RakNet::BitStream();
|
||||
RakNet::BitStream bitStream{};
|
||||
|
||||
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
||||
|
||||
@@ -266,14 +266,13 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c
|
||||
|
||||
context->foundTarget = target != LWOOBJID_EMPTY || ignoreTarget || clientInitalized;
|
||||
|
||||
behavior->Calculate(context, bitStream, { target, 0 });
|
||||
behavior->Calculate(context, &bitStream, { target, 0 });
|
||||
|
||||
for (auto* script : CppScripts::GetEntityScripts(m_Parent)) {
|
||||
script->OnSkillCast(m_Parent, skillId);
|
||||
}
|
||||
|
||||
if (!context->foundTarget) {
|
||||
delete bitStream;
|
||||
delete context;
|
||||
|
||||
// Invalid attack
|
||||
@@ -299,7 +298,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c
|
||||
}
|
||||
//start.optionalTargetID = target;
|
||||
|
||||
start.sBitStream.assign(reinterpret_cast<char*>(bitStream->GetData()), bitStream->GetNumberOfBytesUsed());
|
||||
start.sBitStream.assign(reinterpret_cast<char*>(bitStream.GetData()), bitStream.GetNumberOfBytesUsed());
|
||||
|
||||
// Write message
|
||||
RakNet::BitStream message;
|
||||
@@ -313,8 +312,6 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c
|
||||
|
||||
context->ExecuteUpdates();
|
||||
|
||||
delete bitStream;
|
||||
|
||||
// Valid attack
|
||||
return { true, context->skillTime };
|
||||
}
|
||||
@@ -424,13 +421,13 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
|
||||
|
||||
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
||||
|
||||
auto* bitStream = new RakNet::BitStream();
|
||||
RakNet::BitStream bitStream{};
|
||||
|
||||
behavior->Calculate(entry.context, bitStream, entry.branchContext);
|
||||
behavior->Calculate(entry.context, &bitStream, entry.branchContext);
|
||||
|
||||
DoClientProjectileImpact projectileImpact;
|
||||
|
||||
projectileImpact.sBitStream.assign(reinterpret_cast<char*>(bitStream->GetData()), bitStream->GetNumberOfBytesUsed());
|
||||
projectileImpact.sBitStream.assign(reinterpret_cast<char*>(bitStream.GetData()), bitStream.GetNumberOfBytesUsed());
|
||||
projectileImpact.i64OwnerID = this->m_Parent->GetObjectID();
|
||||
projectileImpact.i64OrgID = entry.id;
|
||||
projectileImpact.i64TargetID = entry.branchContext.target;
|
||||
@@ -444,37 +441,29 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
|
||||
Game::server->Send(&message, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
|
||||
entry.context->ExecuteUpdates();
|
||||
|
||||
delete bitStream;
|
||||
}
|
||||
|
||||
void SkillComponent::HandleUnmanaged(const uint32_t behaviorId, const LWOOBJID target, LWOOBJID source) {
|
||||
auto* context = new BehaviorContext(source);
|
||||
BehaviorContext context{ source };
|
||||
|
||||
context->unmanaged = true;
|
||||
context->caster = target;
|
||||
context.unmanaged = true;
|
||||
context.caster = target;
|
||||
|
||||
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
||||
|
||||
auto* bitStream = new RakNet::BitStream();
|
||||
RakNet::BitStream bitStream{};
|
||||
|
||||
behavior->Handle(context, bitStream, { target });
|
||||
|
||||
delete bitStream;
|
||||
|
||||
delete context;
|
||||
behavior->Handle(&context, &bitStream, { target });
|
||||
}
|
||||
|
||||
void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID target) {
|
||||
auto* context = new BehaviorContext(target);
|
||||
BehaviorContext context{ target };
|
||||
|
||||
context->caster = target;
|
||||
context.caster = target;
|
||||
|
||||
auto* behavior = Behavior::CreateBehavior(behaviorId);
|
||||
|
||||
behavior->UnCast(context, { target });
|
||||
|
||||
delete context;
|
||||
behavior->UnCast(&context, { target });
|
||||
}
|
||||
|
||||
SkillComponent::SkillComponent(Entity* parent): Component(parent) {
|
||||
|
@@ -8,6 +8,11 @@
|
||||
#include "CDLootMatrixTable.h"
|
||||
#include "CDLootTableTable.h"
|
||||
#include "CDItemComponentTable.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "Character.h"
|
||||
#include "eVendorTransactionResult.h"
|
||||
#include "UserManager.h"
|
||||
#include "CheatDetection.h"
|
||||
|
||||
VendorComponent::VendorComponent(Entity* parent) : Component(parent) {
|
||||
m_HasStandardCostItems = false;
|
||||
@@ -151,3 +156,60 @@ void VendorComponent::HandleMrReeCameras(){
|
||||
m_Inventory.push_back(SoldItem(camera, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void VendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
|
||||
|
||||
if (!SellsItem(lot)) {
|
||||
auto* user = UserManager::Instance()->GetUser(buyer->GetSystemAddress());
|
||||
CheatDetection::ReportCheat(user, buyer->GetSystemAddress(), "Attempted to buy item %i from achievement vendor %i that is not purchasable", lot, m_Parent->GetLOT());
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* inventoryComponent = buyer->GetComponent<InventoryComponent>();
|
||||
if (!inventoryComponent) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
||||
CDItemComponentTable* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
|
||||
int itemCompID = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::ITEM);
|
||||
CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID);
|
||||
|
||||
// Extra currency that needs to be deducted in case of crafting
|
||||
auto craftingCurrencies = CDItemComponentTable::ParseCraftingCurrencies(itemComp);
|
||||
for (const auto& [crafintCurrencyLOT, crafintCurrencyCount]: craftingCurrencies) {
|
||||
if (inventoryComponent->GetLotCount(crafintCurrencyLOT) < (crafintCurrencyCount * count)) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const auto& [crafintCurrencyLOT, crafintCurrencyCount]: craftingCurrencies) {
|
||||
inventoryComponent->RemoveItem(crafintCurrencyLOT, crafintCurrencyCount * count);
|
||||
}
|
||||
|
||||
|
||||
float buyScalar = GetBuyScalar();
|
||||
const auto coinCost = static_cast<uint32_t>(std::floor((itemComp.baseValue * buyScalar) * count));
|
||||
|
||||
Character* character = buyer->GetCharacter();
|
||||
if (!character || character->GetCoins() < coinCost) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Inventory::IsValidItem(itemComp.currencyLOT)) {
|
||||
const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * buyScalar) * count;
|
||||
if (inventoryComponent->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) {
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
inventoryComponent->RemoveItem(itemComp.currencyLOT, altCurrencyCost);
|
||||
}
|
||||
|
||||
character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR);
|
||||
inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR);
|
||||
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
|
||||
}
|
@@ -47,6 +47,7 @@ public:
|
||||
m_DirtyVendor = true;
|
||||
}
|
||||
|
||||
void Buy(Entity* buyer, LOT lot, uint32_t count);
|
||||
|
||||
private:
|
||||
void SetupMaxCustomVendor();
|
||||
|
@@ -269,11 +269,9 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System
|
||||
auto* skill_component = entity->GetComponent<SkillComponent>();
|
||||
|
||||
if (skill_component != nullptr) {
|
||||
auto* bs = new RakNet::BitStream(reinterpret_cast<unsigned char*>(const_cast<char*>(message.sBitStream.c_str())), message.sBitStream.size(), false);
|
||||
auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&message.sBitStream[0]), message.sBitStream.size(), false);
|
||||
|
||||
skill_component->SyncPlayerProjectile(message.i64LocalID, bs, message.i64TargetID);
|
||||
|
||||
delete bs;
|
||||
skill_component->SyncPlayerProjectile(message.i64LocalID, &bs, message.i64TargetID);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -296,18 +294,16 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System
|
||||
bool success = false;
|
||||
|
||||
if (behaviorId > 0) {
|
||||
RakNet::BitStream* bs = new RakNet::BitStream(reinterpret_cast<unsigned char*>(const_cast<char*>(startSkill.sBitStream.c_str())), startSkill.sBitStream.size(), false);
|
||||
auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&startSkill.sBitStream[0]), startSkill.sBitStream.size(), false);
|
||||
|
||||
auto* skillComponent = entity->GetComponent<SkillComponent>();
|
||||
|
||||
success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID, startSkill.skillID);
|
||||
success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, &bs, startSkill.optionalTargetID, startSkill.skillID);
|
||||
|
||||
if (success && entity->GetCharacter()) {
|
||||
DestroyableComponent* destComp = entity->GetComponent<DestroyableComponent>();
|
||||
destComp->SetImagination(destComp->GetImagination() - skillTable->GetSkillByID(startSkill.skillID).imaginationcost);
|
||||
}
|
||||
|
||||
delete bs;
|
||||
}
|
||||
|
||||
if (Game::server->GetZoneID() == 1302) {
|
||||
@@ -353,13 +349,11 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System
|
||||
}
|
||||
|
||||
if (usr != nullptr) {
|
||||
RakNet::BitStream* bs = new RakNet::BitStream(reinterpret_cast<unsigned char*>(const_cast<char*>(sync.sBitStream.c_str())), sync.sBitStream.size(), false);
|
||||
auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&sync.sBitStream[0]), sync.sBitStream.size(), false);
|
||||
|
||||
auto* skillComponent = entity->GetComponent<SkillComponent>();
|
||||
|
||||
skillComponent->SyncPlayerSkill(sync.uiSkillHandle, sync.uiBehaviorHandle, bs);
|
||||
|
||||
delete bs;
|
||||
skillComponent->SyncPlayerSkill(sync.uiSkillHandle, sync.uiBehaviorHandle, &bs);
|
||||
}
|
||||
|
||||
EchoSyncSkill echo = EchoSyncSkill();
|
||||
|
@@ -78,6 +78,7 @@
|
||||
#include "LevelProgressionComponent.h"
|
||||
#include "DonationVendorComponent.h"
|
||||
#include "GhostComponent.h"
|
||||
#include "AchievementVendorComponent.h"
|
||||
|
||||
// Message includes:
|
||||
#include "dZoneManager.h"
|
||||
@@ -97,6 +98,7 @@
|
||||
#include "ePetAbilityType.h"
|
||||
#include "ActivityManager.h"
|
||||
#include "PlayerManager.h"
|
||||
#include "eVendorTransactionResult.h"
|
||||
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDObjectsTable.h"
|
||||
@@ -1323,15 +1325,14 @@ void GameMessages::SendVendorStatusUpdate(Entity* entity, const SystemAddress& s
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void GameMessages::SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr) {
|
||||
void GameMessages::SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr, eVendorTransactionResult result) {
|
||||
CBITSTREAM;
|
||||
CMSGHEADER;
|
||||
|
||||
int iResult = 0x02; // success, seems to be the only relevant one
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(eGameMessageType::VENDOR_TRANSACTION_RESULT);
|
||||
bitStream.Write(iResult);
|
||||
bitStream.Write(result);
|
||||
|
||||
SEND_PACKET;
|
||||
}
|
||||
@@ -4664,94 +4665,27 @@ void GameMessages::HandleBuyFromVendor(RakNet::BitStream* inStream, Entity* enti
|
||||
if (!user) return;
|
||||
Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar());
|
||||
if (!player) return;
|
||||
|
||||
// handle buying normal items
|
||||
auto* vendorComponent = entity->GetComponent<VendorComponent>();
|
||||
if (vendorComponent) {
|
||||
vendorComponent->Buy(player, item, count);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* propertyVendorComponent = static_cast<PropertyVendorComponent*>(entity->GetComponent(eReplicaComponentType::PROPERTY_VENDOR));
|
||||
// handle buying achievement items
|
||||
auto* achievementVendorComponent = entity->GetComponent<AchievementVendorComponent>();
|
||||
if (achievementVendorComponent) {
|
||||
achievementVendorComponent->Buy(player, item, count);
|
||||
return;
|
||||
}
|
||||
|
||||
if (propertyVendorComponent != nullptr) {
|
||||
// Handle buying properties
|
||||
auto* propertyVendorComponent = entity->GetComponent<PropertyVendorComponent>();
|
||||
if (propertyVendorComponent) {
|
||||
propertyVendorComponent->OnBuyFromVendor(player, bConfirmed, item, count);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const auto isCommendationVendor = entity->GetLOT() == 13806;
|
||||
|
||||
auto* vend = entity->GetComponent<VendorComponent>();
|
||||
if (!vend && !isCommendationVendor) return;
|
||||
|
||||
auto* inv = player->GetComponent<InventoryComponent>();
|
||||
if (!inv) return;
|
||||
|
||||
if (!isCommendationVendor && !vend->SellsItem(item)) {
|
||||
LOG("User %llu %s tried to buy an item %i from a vendor when they do not sell said item", player->GetObjectID(), user->GetUsername().c_str(), item);
|
||||
return;
|
||||
}
|
||||
|
||||
CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
||||
CDItemComponentTable* itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
|
||||
|
||||
int itemCompID = compRegistryTable->GetByIDAndType(item, eReplicaComponentType::ITEM);
|
||||
CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID);
|
||||
|
||||
Character* character = player->GetCharacter();
|
||||
if (!character) return;
|
||||
|
||||
// Extra currency that needs to be deducted in case of crafting
|
||||
auto craftingCurrencies = CDItemComponentTable::ParseCraftingCurrencies(itemComp);
|
||||
for (const auto& craftingCurrency : craftingCurrencies) {
|
||||
inv->RemoveItem(craftingCurrency.first, craftingCurrency.second * count);
|
||||
}
|
||||
|
||||
if (isCommendationVendor) {
|
||||
if (itemComp.commendationLOT != 13763) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* missionComponent = player->GetComponent<MissionComponent>();
|
||||
|
||||
if (missionComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOT tokenId = -1;
|
||||
|
||||
if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) tokenId = 8318; // "Assembly Token"
|
||||
if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) tokenId = 8321; // "Venture League Token"
|
||||
if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) tokenId = 8319; // "Sentinels Token"
|
||||
if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) tokenId = 8320; // "Paradox Token"
|
||||
|
||||
const uint32_t altCurrencyCost = itemComp.commendationCost * count;
|
||||
|
||||
if (inv->GetLotCount(tokenId) < altCurrencyCost) {
|
||||
return;
|
||||
}
|
||||
|
||||
inv->RemoveItem(tokenId, altCurrencyCost);
|
||||
|
||||
inv->AddItem(item, count, eLootSourceType::VENDOR);
|
||||
} else {
|
||||
float buyScalar = vend->GetBuyScalar();
|
||||
|
||||
const auto coinCost = static_cast<uint32_t>(std::floor((itemComp.baseValue * buyScalar) * count));
|
||||
|
||||
if (character->GetCoins() < coinCost) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Inventory::IsValidItem(itemComp.currencyLOT)) {
|
||||
const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * buyScalar) * count;
|
||||
|
||||
if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) {
|
||||
return;
|
||||
}
|
||||
|
||||
inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost);
|
||||
}
|
||||
|
||||
character->SetCoins(character->GetCoins() - (coinCost), eLootSourceType::VENDOR);
|
||||
inv->AddItem(item, count, eLootSourceType::VENDOR);
|
||||
}
|
||||
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr);
|
||||
}
|
||||
|
||||
void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
@@ -4785,7 +4719,10 @@ void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entit
|
||||
CDItemComponent itemComp = itemComponentTable->GetItemComponentByID(itemCompID);
|
||||
|
||||
// Items with a base value of 0 or max int are special items that should not be sold if they're not sub items
|
||||
if (itemComp.baseValue == 0 || itemComp.baseValue == UINT_MAX) return;
|
||||
if (itemComp.baseValue == 0 || itemComp.baseValue == UINT_MAX) {
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::SELL_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
float sellScalar = vend->GetSellScalar();
|
||||
if (Inventory::IsValidItem(itemComp.currencyLOT)) {
|
||||
@@ -4793,11 +4730,9 @@ void GameMessages::HandleSellToVendor(RakNet::BitStream* inStream, Entity* entit
|
||||
inv->AddItem(itemComp.currencyLOT, std::floor(altCurrency), eLootSourceType::VENDOR); // Return alt currencies like faction tokens.
|
||||
}
|
||||
|
||||
//inv->RemoveItem(count, -1, iObjID);
|
||||
inv->MoveItemToInventory(item, eInventoryType::VENDOR_BUYBACK, count, true, false, true);
|
||||
character->SetCoins(std::floor(character->GetCoins() + (static_cast<uint32_t>(itemComp.baseValue * sellScalar) * count)), eLootSourceType::VENDOR);
|
||||
//Game::entityManager->SerializeEntity(player); // so inventory updates
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr);
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::SELL_SUCCESS);
|
||||
}
|
||||
|
||||
void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
@@ -4839,16 +4774,16 @@ void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity*
|
||||
const auto cost = static_cast<uint32_t>(std::floor(((itemComp.baseValue * sellScalar) * count)));
|
||||
|
||||
if (character->GetCoins() < cost) {
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Inventory::IsValidItem(itemComp.currencyLOT)) {
|
||||
const uint32_t altCurrencyCost = std::floor(itemComp.altCurrencyCost * sellScalar) * count;
|
||||
|
||||
if (inv->GetLotCount(itemComp.currencyLOT) < altCurrencyCost) {
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
inv->RemoveItem(itemComp.currencyLOT, altCurrencyCost);
|
||||
}
|
||||
|
||||
@@ -4856,7 +4791,7 @@ void GameMessages::HandleBuybackFromVendor(RakNet::BitStream* inStream, Entity*
|
||||
inv->MoveItemToInventory(item, Inventory::FindInventoryTypeForLot(item->GetLot()), count, true, false);
|
||||
character->SetCoins(character->GetCoins() - cost, eLootSourceType::VENDOR);
|
||||
//Game::entityManager->SerializeEntity(player); // so inventory updates
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr);
|
||||
GameMessages::SendVendorTransactionResult(entity, sysAddr, eVendorTransactionResult::PURCHASE_SUCCESS);
|
||||
}
|
||||
|
||||
void GameMessages::HandleParseChatMessage(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
|
@@ -38,6 +38,7 @@ enum class eUseItemResponse : uint32_t;
|
||||
enum class eQuickBuildFailReason : uint32_t;
|
||||
enum class eQuickBuildState : uint32_t;
|
||||
enum class BehaviorSlot : int32_t;
|
||||
enum class eVendorTransactionResult : uint32_t;
|
||||
|
||||
namespace GameMessages {
|
||||
class PropertyDataMessage;
|
||||
@@ -135,7 +136,7 @@ namespace GameMessages {
|
||||
|
||||
void SendVendorOpenWindow(Entity* entity, const SystemAddress& sysAddr);
|
||||
void SendVendorStatusUpdate(Entity* entity, const SystemAddress& sysAddr, bool bUpdateOnly = false);
|
||||
void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr);
|
||||
void SendVendorTransactionResult(Entity* entity, const SystemAddress& sysAddr, eVendorTransactionResult result);
|
||||
|
||||
void SendRemoveItemFromInventory(Entity* entity, const SystemAddress& sysAddr, LWOOBJID iObjID, LOT templateID, int inventoryType, uint32_t stackCount, uint32_t stackRemaining);
|
||||
void SendConsumeClientItem(Entity* entity, bool bSuccess, LWOOBJID item);
|
||||
|
@@ -22,143 +22,13 @@
|
||||
|
||||
#include <fstream>
|
||||
|
||||
std::vector<VanityNPC> VanityUtilities::m_NPCs = {};
|
||||
std::vector<VanityParty> VanityUtilities::m_Parties = {};
|
||||
std::vector<std::string> VanityUtilities::m_PartyPhrases = {};
|
||||
std::vector<VanityObject> VanityUtilities::m_Objects = {};
|
||||
std::set<std::string> VanityUtilities::m_LoadedFiles = {};
|
||||
|
||||
|
||||
void VanityUtilities::SpawnVanity() {
|
||||
if (Game::config->GetValue("disable_vanity") == "1") {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t zoneID = Game::server->GetZoneID();
|
||||
|
||||
for (const auto& npc : m_NPCs) {
|
||||
if (npc.m_ID == LWOOBJID_EMPTY) continue;
|
||||
if (npc.m_LOT == 176){
|
||||
Game::zoneManager->RemoveSpawner(npc.m_ID);
|
||||
} else{
|
||||
auto* entity = Game::entityManager->GetEntity(npc.m_ID);
|
||||
if (!entity) continue;
|
||||
entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT);
|
||||
}
|
||||
}
|
||||
|
||||
m_NPCs.clear();
|
||||
m_Parties.clear();
|
||||
m_PartyPhrases.clear();
|
||||
|
||||
ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/NPC.xml").string());
|
||||
|
||||
// Loop through all parties
|
||||
for (const auto& party : m_Parties) {
|
||||
const auto chance = party.m_Chance;
|
||||
const auto zone = party.m_Zone;
|
||||
|
||||
if (zone != Game::server->GetZoneID()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
|
||||
if (chance < rate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Copy m_NPCs into a new vector
|
||||
std::vector<VanityNPC> npcList = m_NPCs;
|
||||
std::vector<uint32_t> taken = {};
|
||||
|
||||
LOG("Spawning party with %i locations", party.m_Locations.size());
|
||||
|
||||
// Loop through all locations
|
||||
for (const auto& location : party.m_Locations) {
|
||||
rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
|
||||
if (0.75f < rate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get a random NPC
|
||||
auto npcIndex = GeneralUtils::GenerateRandomNumber<uint32_t>(0, npcList.size() - 1);
|
||||
|
||||
while (std::find(taken.begin(), taken.end(), npcIndex) != taken.end()) {
|
||||
npcIndex = GeneralUtils::GenerateRandomNumber<uint32_t>(0, npcList.size() - 1);
|
||||
}
|
||||
|
||||
auto& npc = npcList[npcIndex];
|
||||
// Skip spawners
|
||||
if (npc.m_LOT == 176) continue;
|
||||
|
||||
taken.push_back(npcIndex);
|
||||
|
||||
LOG("ldf size is %i", npc.ldf.size());
|
||||
if (npc.ldf.empty()) {
|
||||
npc.ldf = {
|
||||
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
|
||||
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
|
||||
};
|
||||
}
|
||||
|
||||
// Spawn the NPC
|
||||
if (npc.m_LOT == 176){
|
||||
npc.m_ID = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf);
|
||||
} else {
|
||||
auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf);
|
||||
if (!npc.m_Phrases.empty()) {
|
||||
npcEntity->SetVar<std::vector<std::string>>(u"chats", m_PartyPhrases);
|
||||
SetupNPCTalk(npcEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through all NPCs
|
||||
for (auto& npc : m_NPCs) {
|
||||
if (npc.m_Locations.find(Game::server->GetZoneID()) == npc.m_Locations.end())
|
||||
continue;
|
||||
|
||||
const std::vector<VanityNPCLocation>& locations = npc.m_Locations.at(Game::server->GetZoneID());
|
||||
|
||||
// Pick a random location
|
||||
const auto& location = locations[GeneralUtils::GenerateRandomNumber<int>(
|
||||
static_cast<size_t>(0), static_cast<size_t>(locations.size() - 1))];
|
||||
|
||||
float rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
|
||||
if (location.m_Chance < rate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (npc.ldf.empty()) {
|
||||
npc.ldf = {
|
||||
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
|
||||
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
|
||||
};
|
||||
}
|
||||
if (npc.m_LOT == 176){
|
||||
npc.m_ID = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf);
|
||||
} else {
|
||||
// Spawn the NPC
|
||||
auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf);
|
||||
if (!npcEntity) continue;
|
||||
npc.m_ID = npcEntity->GetObjectID();
|
||||
if (!npc.m_Phrases.empty()){
|
||||
npcEntity->SetVar<std::vector<std::string>>(u"chats", npc.m_Phrases);
|
||||
|
||||
auto* scriptComponent = npcEntity->GetComponent<ScriptComponent>();
|
||||
|
||||
if (scriptComponent && !npc.m_Script.empty()) {
|
||||
scriptComponent->SetScript(npc.m_Script);
|
||||
scriptComponent->SetSerialized(false);
|
||||
|
||||
for (const auto& npc : npc.m_Flags) {
|
||||
npcEntity->SetVar<bool>(GeneralUtils::ASCIIToUTF16(npc.first), npc.second);
|
||||
}
|
||||
}
|
||||
SetupNPCTalk(npcEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (zoneID == 1200) {
|
||||
{
|
||||
EntityInfo info;
|
||||
@@ -175,38 +45,99 @@ void VanityUtilities::SpawnVanity() {
|
||||
Game::entityManager->ConstructEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (Game::config->GetValue("disable_vanity") == "1") {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& npc : m_Objects) {
|
||||
if (npc.m_ID == LWOOBJID_EMPTY) continue;
|
||||
if (npc.m_LOT == 176){
|
||||
Game::zoneManager->RemoveSpawner(npc.m_ID);
|
||||
} else{
|
||||
auto* entity = Game::entityManager->GetEntity(npc.m_ID);
|
||||
if (!entity) continue;
|
||||
entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT);
|
||||
}
|
||||
}
|
||||
|
||||
m_Objects.clear();
|
||||
m_LoadedFiles.clear();
|
||||
|
||||
ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/root.xml").string());
|
||||
|
||||
// Loop through all objects
|
||||
for (auto& object : m_Objects) {
|
||||
if (object.m_Locations.find(Game::server->GetZoneID()) == object.m_Locations.end()) continue;
|
||||
|
||||
const std::vector<VanityObjectLocation>& locations = object.m_Locations.at(Game::server->GetZoneID());
|
||||
|
||||
// Pick a random location
|
||||
const auto& location = locations[GeneralUtils::GenerateRandomNumber<int>(
|
||||
static_cast<size_t>(0), static_cast<size_t>(locations.size() - 1))];
|
||||
|
||||
float rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
|
||||
if (location.m_Chance < rate) continue;
|
||||
|
||||
if (object.m_Config.empty()) {
|
||||
object.m_Config = {
|
||||
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
|
||||
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
|
||||
};
|
||||
}
|
||||
if (object.m_LOT == 176){
|
||||
object.m_ID = SpawnSpawner(object, location);
|
||||
} else {
|
||||
// Spawn the NPC
|
||||
auto* objectEntity = SpawnObject(object, location);
|
||||
if (!objectEntity) continue;
|
||||
object.m_ID = objectEntity->GetObjectID();
|
||||
if (!object.m_Phrases.empty()){
|
||||
objectEntity->SetVar<std::vector<std::string>>(u"chats", object.m_Phrases);
|
||||
|
||||
auto* scriptComponent = objectEntity->GetComponent<ScriptComponent>();
|
||||
|
||||
if (scriptComponent && !object.m_Script.empty()) {
|
||||
scriptComponent->SetScript(object.m_Script);
|
||||
scriptComponent->SetSerialized(false);
|
||||
}
|
||||
SetupNPCTalk(objectEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LWOOBJID VanityUtilities::SpawnSpawner(LOT lot, const NiPoint3& position, const NiQuaternion& rotation, const std::vector<LDFBaseData*>& ldf){
|
||||
LWOOBJID VanityUtilities::SpawnSpawner(const VanityObject& object, const VanityObjectLocation& location) {
|
||||
SceneObject obj;
|
||||
obj.lot = lot;
|
||||
obj.lot = object.m_LOT;
|
||||
// guratantee we have no collisions
|
||||
do {
|
||||
obj.id = ObjectIDManager::GenerateObjectID();
|
||||
} while(Game::zoneManager->GetSpawner(obj.id));
|
||||
obj.position = position;
|
||||
obj.rotation = rotation;
|
||||
obj.settings = ldf;
|
||||
obj.position = location.m_Position;
|
||||
obj.rotation = location.m_Rotation;
|
||||
obj.settings = object.m_Config;
|
||||
Level::MakeSpawner(obj);
|
||||
return obj.id;
|
||||
}
|
||||
|
||||
Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoint3& position, const NiQuaternion& rotation, const std::vector<LOT>& inventory, const std::vector<LDFBaseData*>& ldf) {
|
||||
Entity* VanityUtilities::SpawnObject(const VanityObject& object, const VanityObjectLocation& location) {
|
||||
EntityInfo info;
|
||||
info.lot = lot;
|
||||
info.pos = position;
|
||||
info.rot = rotation;
|
||||
info.lot = object.m_LOT;
|
||||
info.pos = location.m_Position;
|
||||
info.rot = location.m_Rotation;
|
||||
info.scale = location.m_Scale;
|
||||
info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID();
|
||||
info.settings = ldf;
|
||||
info.settings = object.m_Config;
|
||||
|
||||
auto* entity = Game::entityManager->CreateEntity(info);
|
||||
entity->SetVar(u"npcName", name);
|
||||
entity->SetVar(u"npcName", object.m_Name);
|
||||
if (entity->GetVar<bool>(u"noGhosting")) entity->SetIsGhostingCandidate(false);
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent && !inventory.empty()) {
|
||||
inventoryComponent->SetNPCItems(inventory);
|
||||
if (inventoryComponent && !object.m_Equipment.empty()) {
|
||||
inventoryComponent->SetNPCItems(object.m_Equipment);
|
||||
}
|
||||
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
@@ -223,6 +154,11 @@ Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoin
|
||||
}
|
||||
|
||||
void VanityUtilities::ParseXML(const std::string& file) {
|
||||
if (m_LoadedFiles.contains(file)){
|
||||
LOG("Trying to load vanity file %s twice!!!", file.c_str());
|
||||
return;
|
||||
}
|
||||
m_LoadedFiles.insert(file);
|
||||
// Read the entire file
|
||||
std::ifstream xmlFile(file);
|
||||
std::string xml((std::istreambuf_iterator<char>(xmlFile)), std::istreambuf_iterator<char>());
|
||||
@@ -231,210 +167,112 @@ void VanityUtilities::ParseXML(const std::string& file) {
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.Parse(xml.c_str(), xml.size());
|
||||
|
||||
// Read the NPCs
|
||||
auto* npcs = doc.FirstChildElement("npcs");
|
||||
|
||||
if (npcs == nullptr) {
|
||||
LOG("Failed to parse NPCs");
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto* party = npcs->FirstChildElement("party"); party != nullptr; party = party->NextSiblingElement("party")) {
|
||||
// Get 'zone' as uint32_t and 'chance' as float
|
||||
uint32_t zone = 0;
|
||||
float chance = 0.0f;
|
||||
|
||||
if (party->Attribute("zone") != nullptr) {
|
||||
zone = std::stoul(party->Attribute("zone"));
|
||||
}
|
||||
|
||||
if (party->Attribute("chance") != nullptr) {
|
||||
chance = std::stof(party->Attribute("chance"));
|
||||
}
|
||||
|
||||
VanityParty partyInfo;
|
||||
partyInfo.m_Zone = zone;
|
||||
partyInfo.m_Chance = chance;
|
||||
|
||||
auto* locations = party->FirstChildElement("locations");
|
||||
|
||||
if (locations == nullptr) {
|
||||
LOG("Failed to parse party locations");
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto* location = locations->FirstChildElement("location"); location != nullptr;
|
||||
location = location->NextSiblingElement("location")) {
|
||||
// Get the location data
|
||||
auto* x = location->Attribute("x");
|
||||
auto* y = location->Attribute("y");
|
||||
auto* z = location->Attribute("z");
|
||||
auto* rw = location->Attribute("rw");
|
||||
auto* rx = location->Attribute("rx");
|
||||
auto* ry = location->Attribute("ry");
|
||||
auto* rz = location->Attribute("rz");
|
||||
|
||||
if (x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr
|
||||
|| rz == nullptr) {
|
||||
LOG("Failed to parse party location data");
|
||||
// Read the objects
|
||||
auto* files = doc.FirstChildElement("files");
|
||||
if (files) {
|
||||
for (auto* file = files->FirstChildElement("file"); file != nullptr; file = file->NextSiblingElement("file")) {
|
||||
std::string enabled = file->Attribute("enabled");
|
||||
std::string filename = file->Attribute("name");
|
||||
if (enabled != "1") {
|
||||
continue;
|
||||
}
|
||||
|
||||
VanityNPCLocation locationData;
|
||||
locationData.m_Position = { std::stof(x), std::stof(y), std::stof(z) };
|
||||
locationData.m_Rotation = { std::stof(rw), std::stof(rx), std::stof(ry), std::stof(rz) };
|
||||
locationData.m_Chance = 1.0f;
|
||||
|
||||
partyInfo.m_Locations.push_back(locationData);
|
||||
}
|
||||
|
||||
m_Parties.push_back(partyInfo);
|
||||
}
|
||||
|
||||
auto* partyPhrases = npcs->FirstChildElement("partyphrases");
|
||||
|
||||
if (partyPhrases == nullptr) {
|
||||
LOG("No party phrases found");
|
||||
} else {
|
||||
for (auto* phrase = partyPhrases->FirstChildElement("phrase"); phrase != nullptr;
|
||||
phrase = phrase->NextSiblingElement("phrase")) {
|
||||
// Get the phrase
|
||||
auto* text = phrase->GetText();
|
||||
|
||||
if (text == nullptr) {
|
||||
LOG("Failed to parse party phrase");
|
||||
continue;
|
||||
}
|
||||
|
||||
m_PartyPhrases.push_back(text);
|
||||
ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity" / filename).string());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* npc = npcs->FirstChildElement("npc"); npc != nullptr; npc = npc->NextSiblingElement("npc")) {
|
||||
// Get the NPC name
|
||||
auto* name = npc->Attribute("name");
|
||||
// Read the objects
|
||||
auto* objects = doc.FirstChildElement("objects");
|
||||
|
||||
if (!name) name = "";
|
||||
if (objects) {
|
||||
for (auto* object = objects->FirstChildElement("object"); object != nullptr; object = object->NextSiblingElement("object")) {
|
||||
// Get the NPC name
|
||||
auto* name = object->Attribute("name");
|
||||
|
||||
// Get the NPC lot
|
||||
auto* lot = npc->Attribute("lot");
|
||||
if (!name) name = "";
|
||||
|
||||
if (lot == nullptr) {
|
||||
LOG("Failed to parse NPC lot");
|
||||
continue;
|
||||
}
|
||||
// Get the NPC lot
|
||||
auto* lot = object->Attribute("lot");
|
||||
|
||||
// Get the equipment
|
||||
auto* equipment = npc->FirstChildElement("equipment");
|
||||
std::vector<LOT> inventory;
|
||||
|
||||
if (equipment) {
|
||||
auto* text = equipment->GetText();
|
||||
|
||||
if (text != nullptr) {
|
||||
std::string equipmentString(text);
|
||||
|
||||
std::vector<std::string> splitEquipment = GeneralUtils::SplitString(equipmentString, ',');
|
||||
|
||||
for (auto& item : splitEquipment) {
|
||||
inventory.push_back(std::stoi(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get the phrases
|
||||
auto* phrases = npc->FirstChildElement("phrases");
|
||||
|
||||
std::vector<std::string> phraseList = {};
|
||||
|
||||
if (phrases) {
|
||||
for (auto* phrase = phrases->FirstChildElement("phrase"); phrase != nullptr;
|
||||
phrase = phrase->NextSiblingElement("phrase")) {
|
||||
// Get the phrase
|
||||
auto* text = phrase->GetText();
|
||||
if (text == nullptr) {
|
||||
LOG("Failed to parse NPC phrase");
|
||||
continue;
|
||||
}
|
||||
phraseList.push_back(text);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the script
|
||||
auto* scriptElement = npc->FirstChildElement("script");
|
||||
|
||||
std::string scriptName = "";
|
||||
|
||||
if (scriptElement != nullptr) {
|
||||
auto* scriptNameAttribute = scriptElement->Attribute("name");
|
||||
if (scriptNameAttribute) scriptName = scriptNameAttribute;
|
||||
}
|
||||
|
||||
auto* ldfElement = npc->FirstChildElement("ldf");
|
||||
std::vector<std::u16string> keys = {};
|
||||
|
||||
std::vector<LDFBaseData*> ldf = {};
|
||||
if(ldfElement) {
|
||||
for (auto* entry = ldfElement->FirstChildElement("entry"); entry != nullptr;
|
||||
entry = entry->NextSiblingElement("entry")) {
|
||||
// Get the ldf data
|
||||
auto* data = entry->Attribute("data");
|
||||
if (!data) continue;
|
||||
|
||||
LDFBaseData* ldfData = LDFBaseData::DataFromString(data);
|
||||
keys.push_back(ldfData->GetKey());
|
||||
ldf.push_back(ldfData);
|
||||
}
|
||||
}
|
||||
if (!keys.empty()) ldf.push_back(new LDFData<std::vector<std::u16string>>(u"syncLDF", keys));
|
||||
|
||||
VanityNPC npcData;
|
||||
npcData.m_Name = name;
|
||||
npcData.m_LOT = std::stoi(lot);
|
||||
npcData.m_Equipment = inventory;
|
||||
npcData.m_Phrases = phraseList;
|
||||
npcData.m_Script = scriptName;
|
||||
npcData.ldf = ldf;
|
||||
|
||||
// Get flags
|
||||
auto* flags = npc->FirstChildElement("flags");
|
||||
|
||||
if (flags != nullptr) {
|
||||
for (auto* flag = flags->FirstChildElement("flag"); flag != nullptr;
|
||||
flag = flag->NextSiblingElement("flag")) {
|
||||
// Get the flag name
|
||||
auto* name = flag->Attribute("name");
|
||||
|
||||
if (name == nullptr) {
|
||||
LOG("Failed to parse NPC flag name");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the flag value
|
||||
auto* value = flag->Attribute("value");
|
||||
|
||||
if (value == nullptr) {
|
||||
LOG("Failed to parse NPC flag value");
|
||||
continue;
|
||||
}
|
||||
|
||||
npcData.m_Flags[name] = std::stoi(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the zones
|
||||
for (auto* zone = npc->FirstChildElement("zone"); zone != nullptr; zone = zone->NextSiblingElement("zone")) {
|
||||
// Get the zone ID
|
||||
auto* zoneID = zone->Attribute("id");
|
||||
|
||||
if (zoneID == nullptr) {
|
||||
LOG("Failed to parse NPC zone ID");
|
||||
if (lot == nullptr) {
|
||||
LOG("Failed to parse object lot");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the equipment
|
||||
auto* equipment = object->FirstChildElement("equipment");
|
||||
std::vector<LOT> inventory;
|
||||
|
||||
if (equipment) {
|
||||
auto* text = equipment->GetText();
|
||||
|
||||
if (text != nullptr) {
|
||||
std::string equipmentString(text);
|
||||
|
||||
std::vector<std::string> splitEquipment = GeneralUtils::SplitString(equipmentString, ',');
|
||||
|
||||
for (auto& item : splitEquipment) {
|
||||
inventory.push_back(std::stoi(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get the phrases
|
||||
auto* phrases = object->FirstChildElement("phrases");
|
||||
|
||||
std::vector<std::string> phraseList = {};
|
||||
|
||||
if (phrases) {
|
||||
for (auto* phrase = phrases->FirstChildElement("phrase"); phrase != nullptr;
|
||||
phrase = phrase->NextSiblingElement("phrase")) {
|
||||
// Get the phrase
|
||||
auto* text = phrase->GetText();
|
||||
if (text == nullptr) {
|
||||
LOG("Failed to parse NPC phrase");
|
||||
continue;
|
||||
}
|
||||
phraseList.push_back(text);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the script
|
||||
auto* scriptElement = object->FirstChildElement("script");
|
||||
|
||||
std::string scriptName = "";
|
||||
|
||||
if (scriptElement != nullptr) {
|
||||
auto* scriptNameAttribute = scriptElement->Attribute("name");
|
||||
if (scriptNameAttribute) scriptName = scriptNameAttribute;
|
||||
}
|
||||
|
||||
auto* configElement = object->FirstChildElement("config");
|
||||
std::vector<std::u16string> keys = {};
|
||||
|
||||
std::vector<LDFBaseData*> config = {};
|
||||
if(configElement) {
|
||||
for (auto* key = configElement->FirstChildElement("key"); key != nullptr;
|
||||
key = key->NextSiblingElement("key")) {
|
||||
// Get the config data
|
||||
auto* data = key->Attribute("data");
|
||||
if (!data) continue;
|
||||
|
||||
LDFBaseData* configData = LDFBaseData::DataFromString(data);
|
||||
keys.push_back(configData->GetKey());
|
||||
config.push_back(configData);
|
||||
}
|
||||
}
|
||||
if (!keys.empty()) config.push_back(new LDFData<std::vector<std::u16string>>(u"syncLDF", keys));
|
||||
|
||||
VanityObject objectData;
|
||||
objectData.m_Name = name;
|
||||
objectData.m_LOT = std::stoi(lot);
|
||||
objectData.m_Equipment = inventory;
|
||||
objectData.m_Phrases = phraseList;
|
||||
objectData.m_Script = scriptName;
|
||||
objectData.m_Config = config;
|
||||
|
||||
// Get the locations
|
||||
auto* locations = zone->FirstChildElement("locations");
|
||||
auto* locations = object->FirstChildElement("locations");
|
||||
|
||||
if (locations == nullptr) {
|
||||
LOG("Failed to parse NPC locations");
|
||||
@@ -443,7 +281,9 @@ void VanityUtilities::ParseXML(const std::string& file) {
|
||||
|
||||
for (auto* location = locations->FirstChildElement("location"); location != nullptr;
|
||||
location = location->NextSiblingElement("location")) {
|
||||
|
||||
// Get the location data
|
||||
auto* zoneID = location->Attribute("zone");
|
||||
auto* x = location->Attribute("x");
|
||||
auto* y = location->Attribute("y");
|
||||
auto* z = location->Attribute("z");
|
||||
@@ -452,41 +292,52 @@ void VanityUtilities::ParseXML(const std::string& file) {
|
||||
auto* ry = location->Attribute("ry");
|
||||
auto* rz = location->Attribute("rz");
|
||||
|
||||
if (x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr
|
||||
if (zoneID == nullptr || x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr
|
||||
|| rz == nullptr) {
|
||||
LOG("Failed to parse NPC location data");
|
||||
continue;
|
||||
}
|
||||
|
||||
VanityNPCLocation locationData;
|
||||
VanityObjectLocation locationData;
|
||||
locationData.m_Position = { std::stof(x), std::stof(y), std::stof(z) };
|
||||
locationData.m_Rotation = { std::stof(rw), std::stof(rx), std::stof(ry), std::stof(rz) };
|
||||
locationData.m_Chance = 1.0f;
|
||||
|
||||
if (location->Attribute("chance") != nullptr) {
|
||||
if (location->Attribute("chance")) {
|
||||
locationData.m_Chance = std::stof(location->Attribute("chance"));
|
||||
}
|
||||
|
||||
const auto& it = npcData.m_Locations.find(std::stoi(zoneID));
|
||||
if (location->Attribute("scale")) {
|
||||
locationData.m_Scale = std::stof(location->Attribute("scale"));
|
||||
}
|
||||
|
||||
if (it != npcData.m_Locations.end()) {
|
||||
|
||||
const auto& it = objectData.m_Locations.find(std::stoi(zoneID));
|
||||
|
||||
if (it != objectData.m_Locations.end()) {
|
||||
it->second.push_back(locationData);
|
||||
} else {
|
||||
std::vector<VanityNPCLocation> locations;
|
||||
std::vector<VanityObjectLocation> locations;
|
||||
locations.push_back(locationData);
|
||||
npcData.m_Locations.insert(std::make_pair(std::stoi(zoneID), locations));
|
||||
objectData.m_Locations.insert(std::make_pair(std::stoi(zoneID), locations));
|
||||
}
|
||||
|
||||
if (!(std::find(keys.begin(), keys.end(), u"teleport") != keys.end())) {
|
||||
m_Objects.push_back(objectData);
|
||||
objectData.m_Locations.clear();
|
||||
}
|
||||
}
|
||||
if (std::find(keys.begin(), keys.end(), u"teleport") != keys.end()) {
|
||||
m_Objects.push_back(objectData);
|
||||
}
|
||||
}
|
||||
|
||||
m_NPCs.push_back(npcData);
|
||||
}
|
||||
}
|
||||
|
||||
VanityNPC* VanityUtilities::GetNPC(const std::string& name) {
|
||||
for (size_t i = 0; i < m_NPCs.size(); i++) {
|
||||
if (m_NPCs[i].m_Name == name) {
|
||||
return &m_NPCs[i];
|
||||
VanityObject* VanityUtilities::GetObject(const std::string& name) {
|
||||
for (size_t i = 0; i < m_Objects.size(); i++) {
|
||||
if (m_Objects[i].m_Name == name) {
|
||||
return &m_Objects[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,10 +349,13 @@ std::string VanityUtilities::ParseMarkdown(const std::string& file) {
|
||||
|
||||
// Read the file into a string
|
||||
std::ifstream t(file);
|
||||
|
||||
std::stringstream output;
|
||||
// If the file does not exist, return an empty string.
|
||||
if (!t.good()) {
|
||||
return "";
|
||||
output << "File ";
|
||||
output << file.substr(file.rfind("/") + 1);
|
||||
output << " not found!\nContact your DarkflameServer admin\nor find the server source at https://github.com/DarkflameUniverse/DarkflameServer";
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
@@ -511,7 +365,6 @@ std::string VanityUtilities::ParseMarkdown(const std::string& file) {
|
||||
// Loop through all lines in the file.
|
||||
// Replace all instances of the markdown syntax with the corresponding HTML.
|
||||
// Only care about headers
|
||||
std::stringstream output;
|
||||
std::string line;
|
||||
std::stringstream ss;
|
||||
ss << fileContents;
|
||||
|
@@ -3,15 +3,17 @@
|
||||
#include "dCommonVars.h"
|
||||
#include "Entity.h"
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
struct VanityNPCLocation
|
||||
struct VanityObjectLocation
|
||||
{
|
||||
float m_Chance = 1.0f;
|
||||
NiPoint3 m_Position;
|
||||
NiQuaternion m_Rotation;
|
||||
float m_Scale = 1.0f;
|
||||
};
|
||||
|
||||
struct VanityNPC
|
||||
struct VanityObject
|
||||
{
|
||||
LWOOBJID m_ID = LWOOBJID_EMPTY;
|
||||
std::string m_Name;
|
||||
@@ -19,37 +21,24 @@ struct VanityNPC
|
||||
std::vector<LOT> m_Equipment;
|
||||
std::vector<std::string> m_Phrases;
|
||||
std::string m_Script;
|
||||
std::map<std::string, bool> m_Flags;
|
||||
std::map<uint32_t, std::vector<VanityNPCLocation>> m_Locations;
|
||||
std::vector<LDFBaseData*> ldf;
|
||||
std::map<uint32_t, std::vector<VanityObjectLocation>> m_Locations;
|
||||
std::vector<LDFBaseData*> m_Config;
|
||||
};
|
||||
|
||||
struct VanityParty
|
||||
{
|
||||
uint32_t m_Zone;
|
||||
float m_Chance = 1.0f;
|
||||
std::vector<VanityNPCLocation> m_Locations;
|
||||
};
|
||||
|
||||
class VanityUtilities
|
||||
{
|
||||
public:
|
||||
static void SpawnVanity();
|
||||
|
||||
static Entity* SpawnNPC(
|
||||
LOT lot,
|
||||
const std::string& name,
|
||||
const NiPoint3& position,
|
||||
const NiQuaternion& rotation,
|
||||
const std::vector<LOT>& inventory,
|
||||
const std::vector<LDFBaseData*>& ldf
|
||||
static Entity* SpawnObject(
|
||||
const VanityObject& object,
|
||||
const VanityObjectLocation& location
|
||||
);
|
||||
|
||||
static LWOOBJID SpawnSpawner(
|
||||
LOT lot,
|
||||
const NiPoint3& position,
|
||||
const NiQuaternion& rotation,
|
||||
const std::vector<LDFBaseData*>& ldf
|
||||
const VanityObject& object,
|
||||
const VanityObjectLocation& location
|
||||
);
|
||||
|
||||
static std::string ParseMarkdown(
|
||||
@@ -60,16 +49,14 @@ public:
|
||||
const std::string& file
|
||||
);
|
||||
|
||||
static VanityNPC* GetNPC(const std::string& name);
|
||||
static VanityObject* GetObject(const std::string& name);
|
||||
|
||||
private:
|
||||
static void SetupNPCTalk(Entity* npc);
|
||||
|
||||
static void NPCTalk(Entity* npc);
|
||||
|
||||
static std::vector<VanityNPC> m_NPCs;
|
||||
|
||||
static std::vector<VanityParty> m_Parties;
|
||||
|
||||
static std::vector<std::string> m_PartyPhrases;
|
||||
static std::vector<VanityObject> m_Objects;
|
||||
|
||||
static std::set<std::string> m_LoadedFiles;
|
||||
};
|
||||
|
Reference in New Issue
Block a user