Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Inventory Brick and Model groups #1587

Merged
merged 11 commits into from
Aug 2, 2024
9 changes: 9 additions & 0 deletions dCommon/dEnums/eInventoryType.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#define __EINVENTORYTYPE__H__

#include <cstdint>

#include "magic_enum.hpp"

static const uint8_t NUMBER_OF_INVENTORIES = 17;
/**
* Represents the different types of inventories an entity may have
Expand Down Expand Up @@ -56,4 +59,10 @@ class InventoryType {
};
};

template <>
struct magic_enum::customize::enum_range<eInventoryType> {
static constexpr int min = 0;
static constexpr int max = 16;
};

#endif //!__EINVENTORYTYPE__H__
137 changes: 130 additions & 7 deletions dGame/dComponents/InventoryComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
#include "CDScriptComponentTable.h"
#include "CDObjectSkillsTable.h"
#include "CDSkillBehaviorTable.h"
#include "StringifiedEnum.h"

#include <ranges>

InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) {
this->m_Dirty = true;
Expand Down Expand Up @@ -492,6 +495,11 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) {
return;
}

auto* const groups = inventoryElement->FirstChildElement("grps");
if (groups) {
LoadGroupXml(*groups);
}

m_Consumable = inventoryElement->IntAttribute("csl", LOT_NULL);

auto* bag = bags->FirstChildElement();
Expand Down Expand Up @@ -640,6 +648,15 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) {
bags->LinkEndChild(bag);
}

auto* groups = inventoryElement->FirstChildElement("grps");
if (groups) {
groups->DeleteChildren();
} else {
groups = inventoryElement->InsertNewChildElement("grps");
}

UpdateGroupXml(*groups);

auto* items = inventoryElement->FirstChildElement("items");

if (items == nullptr) {
Expand Down Expand Up @@ -1600,18 +1617,18 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) {
}


bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId){
bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId) {
BehaviorSlot behaviorSlot = BehaviorSlot::Invalid;
if (slot == 1 ) behaviorSlot = BehaviorSlot::Primary;
else if (slot == 2 ) behaviorSlot = BehaviorSlot::Offhand;
else if (slot == 3 ) behaviorSlot = BehaviorSlot::Neck;
else if (slot == 4 ) behaviorSlot = BehaviorSlot::Head;
else if (slot == 5 ) behaviorSlot = BehaviorSlot::Consumable;
if (slot == 1) behaviorSlot = BehaviorSlot::Primary;
else if (slot == 2) behaviorSlot = BehaviorSlot::Offhand;
else if (slot == 3) behaviorSlot = BehaviorSlot::Neck;
else if (slot == 4) behaviorSlot = BehaviorSlot::Head;
else if (slot == 5) behaviorSlot = BehaviorSlot::Consumable;
else return false;
return SetSkill(behaviorSlot, skillId);
}

bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){
bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) {
if (skillId == 0) return false;
const auto index = m_Skills.find(slot);
if (index != m_Skills.end()) {
Expand All @@ -1624,3 +1641,109 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){
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");
}
}
43 changes: 42 additions & 1 deletion dGame/dComponents/InventoryComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,35 @@ enum class eItemType : int32_t;
*/
class InventoryComponent final : public Component {
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;
InventoryComponent(Entity* parent);

Expand Down Expand Up @@ -367,14 +396,23 @@ class InventoryComponent final : public Component {
*/
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(BehaviorSlot slot, uint32_t skillId);

void UpdateGroup(const GroupUpdate& groupUpdate);
void RemoveGroup(const std::string& groupId);

~InventoryComponent() override;

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
*/
Expand Down Expand Up @@ -477,6 +515,9 @@ class InventoryComponent final : public Component {
* @param document the xml doc to load from
*/
void UpdatePetXml(tinyxml2::XMLDocument& document);

void LoadGroupXml(const tinyxml2::XMLElement& groups);
void UpdateGroupXml(tinyxml2::XMLElement& groups) const;
};

#endif
7 changes: 7 additions & 0 deletions dGame/dGameMessages/GameMessageHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,13 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
case eGameMessageType::REQUEST_VENDOR_STATUS_UPDATE:
GameMessages::SendVendorStatusUpdate(entity, sysAddr, true);
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:
LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
break;
Expand Down
63 changes: 63 additions & 0 deletions dGame/dGameMessages/GameMessages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6209,3 +6209,66 @@ void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string t
auto sysAddr = entity->GetSystemAddress();
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);
}
3 changes: 3 additions & 0 deletions dGame/dGameMessages/GameMessages.h
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,9 @@ namespace GameMessages {
void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity);

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

#endif // GAMEMESSAGES_H
Loading