mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-12-22 13:33:35 +00:00
feat: Add Inventory Brick and Model groups (#1587)
* Added feature grouping logic * Add saving of brick buckets * Add edge case check for max group count * Use vector for storing groups * Update InventoryComponent.cpp * Update InventoryComponent.h * Update InventoryComponent.h * fix string log format * Update GameMessages.cpp
This commit is contained in:
parent
2c70f1503c
commit
aaf446fe6e
@ -4,6 +4,9 @@
|
|||||||
#define __EINVENTORYTYPE__H__
|
#define __EINVENTORYTYPE__H__
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "magic_enum.hpp"
|
||||||
|
|
||||||
static const uint8_t NUMBER_OF_INVENTORIES = 17;
|
static const uint8_t NUMBER_OF_INVENTORIES = 17;
|
||||||
/**
|
/**
|
||||||
* Represents the different types of inventories an entity may have
|
* Represents the different types of inventories an entity may have
|
||||||
@ -56,4 +59,10 @@ public:
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct magic_enum::customize::enum_range<eInventoryType> {
|
||||||
|
static constexpr int min = 0;
|
||||||
|
static constexpr int max = 16;
|
||||||
|
};
|
||||||
|
|
||||||
#endif //!__EINVENTORYTYPE__H__
|
#endif //!__EINVENTORYTYPE__H__
|
||||||
|
@ -37,6 +37,9 @@
|
|||||||
#include "CDScriptComponentTable.h"
|
#include "CDScriptComponentTable.h"
|
||||||
#include "CDObjectSkillsTable.h"
|
#include "CDObjectSkillsTable.h"
|
||||||
#include "CDSkillBehaviorTable.h"
|
#include "CDSkillBehaviorTable.h"
|
||||||
|
#include "StringifiedEnum.h"
|
||||||
|
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
|
InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
|
||||||
this->m_Dirty = true;
|
this->m_Dirty = true;
|
||||||
@ -492,6 +495,11 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto* const groups = inventoryElement->FirstChildElement("grps");
|
||||||
|
if (groups) {
|
||||||
|
LoadGroupXml(*groups);
|
||||||
|
}
|
||||||
|
|
||||||
m_Consumable = inventoryElement->IntAttribute("csl", LOT_NULL);
|
m_Consumable = inventoryElement->IntAttribute("csl", LOT_NULL);
|
||||||
|
|
||||||
auto* bag = bags->FirstChildElement();
|
auto* bag = bags->FirstChildElement();
|
||||||
@ -630,6 +638,15 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) {
|
|||||||
bags->LinkEndChild(bag);
|
bags->LinkEndChild(bag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto* groups = inventoryElement->FirstChildElement("grps");
|
||||||
|
if (groups) {
|
||||||
|
groups->DeleteChildren();
|
||||||
|
} else {
|
||||||
|
groups = inventoryElement->InsertNewChildElement("grps");
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateGroupXml(*groups);
|
||||||
|
|
||||||
auto* items = inventoryElement->FirstChildElement("items");
|
auto* items = inventoryElement->FirstChildElement("items");
|
||||||
|
|
||||||
if (items == nullptr) {
|
if (items == nullptr) {
|
||||||
@ -1603,3 +1620,110 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) {
|
|||||||
m_Skills.insert_or_assign(slot, skillId);
|
m_Skills.insert_or_assign(slot, skillId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InventoryComponent::UpdateGroup(const GroupUpdate& groupUpdate) {
|
||||||
|
if (groupUpdate.groupId.empty()) return;
|
||||||
|
|
||||||
|
if (groupUpdate.inventory != eInventoryType::BRICKS && groupUpdate.inventory != eInventoryType::MODELS) {
|
||||||
|
LOG("Invalid inventory type for grouping %s", StringifiedEnum::ToString(groupUpdate.inventory).data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& groups = m_Groups[groupUpdate.inventory];
|
||||||
|
auto groupItr = std::ranges::find_if(groups, [&groupUpdate](const Group& group) {
|
||||||
|
return group.groupId == groupUpdate.groupId;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (groupUpdate.command != GroupUpdateCommand::ADD && groupItr == groups.end()) {
|
||||||
|
LOG("Group %s not found in inventory %s. Cannot process command.", groupUpdate.groupId.c_str(), StringifiedEnum::ToString(groupUpdate.inventory).data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupUpdate.command == GroupUpdateCommand::ADD && groups.size() >= MaximumGroupCount) {
|
||||||
|
LOG("Cannot add group to inventory %s. Maximum group count reached.", StringifiedEnum::ToString(groupUpdate.inventory).data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (groupUpdate.command) {
|
||||||
|
case GroupUpdateCommand::ADD: {
|
||||||
|
auto& group = groups.emplace_back();
|
||||||
|
group.groupId = groupUpdate.groupId;
|
||||||
|
group.groupName = groupUpdate.groupName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GroupUpdateCommand::ADD_LOT: {
|
||||||
|
groupItr->lots.insert(groupUpdate.lot);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GroupUpdateCommand::REMOVE: {
|
||||||
|
groups.erase(groupItr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GroupUpdateCommand::REMOVE_LOT: {
|
||||||
|
groupItr->lots.erase(groupUpdate.lot);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GroupUpdateCommand::MODIFY: {
|
||||||
|
groupItr->groupName = groupUpdate.groupName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
LOG("Invalid group update command %i", groupUpdate.command);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InventoryComponent::UpdateGroupXml(tinyxml2::XMLElement& groups) const {
|
||||||
|
for (const auto& [inventory, groupsData] : m_Groups) {
|
||||||
|
for (const auto& group : groupsData) {
|
||||||
|
auto* const groupElement = groups.InsertNewChildElement("grp");
|
||||||
|
|
||||||
|
groupElement->SetAttribute("id", group.groupId.c_str());
|
||||||
|
groupElement->SetAttribute("n", group.groupName.c_str());
|
||||||
|
groupElement->SetAttribute("t", static_cast<uint32_t>(inventory));
|
||||||
|
groupElement->SetAttribute("u", 0);
|
||||||
|
std::ostringstream lots;
|
||||||
|
bool first = true;
|
||||||
|
for (const auto lot : group.lots) {
|
||||||
|
if (!first) lots << ' ';
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
lots << lot;
|
||||||
|
}
|
||||||
|
groupElement->SetAttribute("l", lots.str().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InventoryComponent::LoadGroupXml(const tinyxml2::XMLElement& groups) {
|
||||||
|
auto* groupElement = groups.FirstChildElement("grp");
|
||||||
|
|
||||||
|
while (groupElement) {
|
||||||
|
const char* groupId = nullptr;
|
||||||
|
const char* groupName = nullptr;
|
||||||
|
const char* lots = nullptr;
|
||||||
|
uint32_t inventory = eInventoryType::INVALID;
|
||||||
|
|
||||||
|
groupElement->QueryStringAttribute("id", &groupId);
|
||||||
|
groupElement->QueryStringAttribute("n", &groupName);
|
||||||
|
groupElement->QueryStringAttribute("l", &lots);
|
||||||
|
groupElement->QueryAttribute("t", &inventory);
|
||||||
|
|
||||||
|
if (!groupId || !groupName || !lots) {
|
||||||
|
LOG("Failed to load group from xml id %i name %i lots %i",
|
||||||
|
groupId == nullptr, groupName == nullptr, lots == nullptr);
|
||||||
|
} else {
|
||||||
|
auto& group = m_Groups[static_cast<eInventoryType>(inventory)].emplace_back();
|
||||||
|
group.groupId = groupId;
|
||||||
|
group.groupName = groupName;
|
||||||
|
|
||||||
|
for (const auto& lotStr : GeneralUtils::SplitString(lots, ' ')) {
|
||||||
|
auto lot = GeneralUtils::TryParse<LOT>(lotStr);
|
||||||
|
if (lot) group.lots.insert(*lot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupElement = groupElement->NextSiblingElement("grp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -37,6 +37,35 @@ enum class eItemType : int32_t;
|
|||||||
*/
|
*/
|
||||||
class InventoryComponent final : public Component {
|
class InventoryComponent final : public Component {
|
||||||
public:
|
public:
|
||||||
|
struct Group {
|
||||||
|
// Generated ID for the group. The ID is sent by the client and has the format user_group + Math.random() * UINT_MAX.
|
||||||
|
std::string groupId;
|
||||||
|
// Custom name assigned by the user.
|
||||||
|
std::string groupName;
|
||||||
|
// All the lots the user has in the group.
|
||||||
|
std::set<LOT> lots;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class GroupUpdateCommand {
|
||||||
|
ADD,
|
||||||
|
ADD_LOT,
|
||||||
|
MODIFY,
|
||||||
|
REMOVE,
|
||||||
|
REMOVE_LOT,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Based on the command, certain fields will be used or not used.
|
||||||
|
// for example, ADD_LOT wont use groupName, MODIFY wont use lots, etc.
|
||||||
|
struct GroupUpdate {
|
||||||
|
std::string groupId;
|
||||||
|
std::string groupName;
|
||||||
|
LOT lot;
|
||||||
|
eInventoryType inventory;
|
||||||
|
GroupUpdateCommand command;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr uint32_t MaximumGroupCount = 50;
|
||||||
|
|
||||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY;
|
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY;
|
||||||
InventoryComponent(Entity* parent);
|
InventoryComponent(Entity* parent);
|
||||||
|
|
||||||
@ -367,14 +396,23 @@ public:
|
|||||||
*/
|
*/
|
||||||
void UnequipScripts(Item* unequippedItem);
|
void UnequipScripts(Item* unequippedItem);
|
||||||
|
|
||||||
std::map<BehaviorSlot, uint32_t> GetSkills(){ return m_Skills; };
|
std::map<BehaviorSlot, uint32_t> GetSkills() { return m_Skills; };
|
||||||
|
|
||||||
bool SetSkill(int slot, uint32_t skillId);
|
bool SetSkill(int slot, uint32_t skillId);
|
||||||
bool SetSkill(BehaviorSlot slot, uint32_t skillId);
|
bool SetSkill(BehaviorSlot slot, uint32_t skillId);
|
||||||
|
|
||||||
|
void UpdateGroup(const GroupUpdate& groupUpdate);
|
||||||
|
void RemoveGroup(const std::string& groupId);
|
||||||
|
|
||||||
~InventoryComponent() override;
|
~InventoryComponent() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* The key is the inventory the group belongs to, the value maps' key is the id for the group.
|
||||||
|
* This is only used for bricks and model inventories.
|
||||||
|
*/
|
||||||
|
std::map<eInventoryType, std::vector<Group>> m_Groups{ { eInventoryType::BRICKS, {} }, { eInventoryType::MODELS, {} } };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All the inventory this entity possesses
|
* All the inventory this entity possesses
|
||||||
*/
|
*/
|
||||||
@ -477,6 +515,9 @@ private:
|
|||||||
* @param document the xml doc to load from
|
* @param document the xml doc to load from
|
||||||
*/
|
*/
|
||||||
void UpdatePetXml(tinyxml2::XMLDocument& document);
|
void UpdatePetXml(tinyxml2::XMLDocument& document);
|
||||||
|
|
||||||
|
void LoadGroupXml(const tinyxml2::XMLElement& groups);
|
||||||
|
void UpdateGroupXml(tinyxml2::XMLElement& groups) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -685,6 +685,13 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
|||||||
case eGameMessageType::REQUEST_VENDOR_STATUS_UPDATE:
|
case eGameMessageType::REQUEST_VENDOR_STATUS_UPDATE:
|
||||||
GameMessages::SendVendorStatusUpdate(entity, sysAddr, true);
|
GameMessages::SendVendorStatusUpdate(entity, sysAddr, true);
|
||||||
break;
|
break;
|
||||||
|
case eGameMessageType::UPDATE_INVENTORY_GROUP:
|
||||||
|
GameMessages::HandleUpdateInventoryGroup(inStream, entity, sysAddr);
|
||||||
|
break;
|
||||||
|
case eGameMessageType::UPDATE_INVENTORY_GROUP_CONTENTS:
|
||||||
|
GameMessages::HandleUpdateInventoryGroupContents(inStream, entity, sysAddr);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
|
LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
|
||||||
break;
|
break;
|
||||||
|
@ -6251,6 +6251,69 @@ void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string t
|
|||||||
SEND_PACKET;
|
SEND_PACKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameMessages::HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||||
|
std::string action;
|
||||||
|
std::u16string groupName;
|
||||||
|
InventoryComponent::GroupUpdate groupUpdate;
|
||||||
|
bool locked{}; // All groups are locked by default
|
||||||
|
|
||||||
|
uint32_t size{};
|
||||||
|
if (!inStream.Read(size)) return;
|
||||||
|
action.resize(size);
|
||||||
|
if (!inStream.Read(action.data(), size)) return;
|
||||||
|
|
||||||
|
if (!inStream.Read(size)) return;
|
||||||
|
groupUpdate.groupId.resize(size);
|
||||||
|
if (!inStream.Read(groupUpdate.groupId.data(), size)) return;
|
||||||
|
|
||||||
|
if (!inStream.Read(size)) return;
|
||||||
|
groupName.resize(size);
|
||||||
|
if (!inStream.Read(reinterpret_cast<char*>(groupName.data()), size * 2)) return;
|
||||||
|
|
||||||
|
if (!inStream.Read(groupUpdate.inventory)) return;
|
||||||
|
if (!inStream.Read(locked)) return;
|
||||||
|
|
||||||
|
groupUpdate.groupName = GeneralUtils::UTF16ToWTF8(groupName);
|
||||||
|
|
||||||
|
if (action == "ADD") groupUpdate.command = InventoryComponent::GroupUpdateCommand::ADD;
|
||||||
|
else if (action == "MODIFY") groupUpdate.command = InventoryComponent::GroupUpdateCommand::MODIFY;
|
||||||
|
else if (action == "REMOVE") groupUpdate.command = InventoryComponent::GroupUpdateCommand::REMOVE;
|
||||||
|
else {
|
||||||
|
LOG("Invalid action %s", action.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||||
|
if (inventoryComponent) inventoryComponent->UpdateGroup(groupUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameMessages::HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||||
|
std::string action;
|
||||||
|
InventoryComponent::GroupUpdate groupUpdate;
|
||||||
|
|
||||||
|
uint32_t size{};
|
||||||
|
if (!inStream.Read(size)) return;
|
||||||
|
action.resize(size);
|
||||||
|
if (!inStream.Read(action.data(), size)) return;
|
||||||
|
|
||||||
|
if (action == "ADD") groupUpdate.command = InventoryComponent::GroupUpdateCommand::ADD_LOT;
|
||||||
|
else if (action == "REMOVE") groupUpdate.command = InventoryComponent::GroupUpdateCommand::REMOVE_LOT;
|
||||||
|
else {
|
||||||
|
LOG("Invalid action %s", action.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inStream.Read(size)) return;
|
||||||
|
groupUpdate.groupId.resize(size);
|
||||||
|
if (!inStream.Read(groupUpdate.groupId.data(), size)) return;
|
||||||
|
|
||||||
|
if (!inStream.Read(groupUpdate.inventory)) return;
|
||||||
|
if (!inStream.Read(groupUpdate.lot)) return;
|
||||||
|
|
||||||
|
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||||
|
if (inventoryComponent) inventoryComponent->UpdateGroup(groupUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID) {
|
void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID) {
|
||||||
CBITSTREAM;
|
CBITSTREAM;
|
||||||
CMSGHEADER;
|
CMSGHEADER;
|
||||||
|
@ -673,6 +673,9 @@ namespace GameMessages {
|
|||||||
void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity);
|
void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity);
|
||||||
|
|
||||||
void SendSlashCommandFeedbackText(Entity* entity, std::u16string text);
|
void SendSlashCommandFeedbackText(Entity* entity, std::u16string text);
|
||||||
|
|
||||||
|
void HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||||
|
void HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||||
void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID);
|
void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user