mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-25 15:07:28 +00:00
3cfbc9d3df
May not do the recursive restriction cause they aren't used in the live game
613 lines
16 KiB
C++
613 lines
16 KiB
C++
#include "Item.h"
|
|
|
|
#include <sstream>
|
|
|
|
#include "ObjectIDManager.h"
|
|
#include "GeneralUtils.h"
|
|
#include "GameMessages.h"
|
|
#include "Entity.h"
|
|
#include "Game.h"
|
|
#include "Logger.h"
|
|
#include "EntityManager.h"
|
|
#include "RenderComponent.h"
|
|
#include "PossessableComponent.h"
|
|
#include "CharacterComponent.h"
|
|
#include "eItemType.h"
|
|
#include "AssetManager.h"
|
|
#include "InventoryComponent.h"
|
|
#include "Loot.h"
|
|
#include "eObjectBits.h"
|
|
#include "eReplicaComponentType.h"
|
|
#include "eUseItemResponse.h"
|
|
#include "dZoneManager.h"
|
|
#include "ChatPackets.h"
|
|
#include "eDeletionRestrictionsCheckType.h"
|
|
|
|
#include "CDBrickIDTableTable.h"
|
|
#include "CDObjectSkillsTable.h"
|
|
#include "CDComponentsRegistryTable.h"
|
|
#include "CDPackageComponentTable.h"
|
|
#include "CDDeletionRestrictionsTable.h"
|
|
|
|
namespace {
|
|
const std::map<std::string, std::string> ExtraSettingAbbreviations = {
|
|
{ "assemblyPartLOTs", "ma" },
|
|
{ "blueprintID", "b" },
|
|
{ "userModelID", "ui" },
|
|
{ "userModelName", "un" },
|
|
{ "userModelDesc", "ud" },
|
|
{ "userModelHasBhvr", "ub" },
|
|
{ "userModelBehaviors", "ubh" },
|
|
{ "userModelBehaviorSourceID", "ubs" },
|
|
{ "userModelPhysicsType", "up" },
|
|
{ "userModelMod", "um" },
|
|
{ "userModelOpt", "uo" },
|
|
{ "reforgedLOT", "rl" },
|
|
};
|
|
}
|
|
|
|
Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const std::vector<LDFBaseData*>& config, const LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType) {
|
|
if (!Inventory::IsValidItem(lot)) {
|
|
return;
|
|
}
|
|
|
|
this->id = id;
|
|
this->lot = lot;
|
|
this->inventory = inventory;
|
|
this->slot = slot;
|
|
this->count = count;
|
|
this->bound = bound;
|
|
this->config = config;
|
|
this->parent = parent;
|
|
this->info = &Inventory::FindItemComponent(lot);
|
|
this->preconditions = new PreconditionExpression(this->info->reqPrecondition);
|
|
this->subKey = subKey;
|
|
|
|
inventory->AddManagedItem(this);
|
|
}
|
|
|
|
Item::Item(
|
|
const LOT lot,
|
|
Inventory* inventory,
|
|
const uint32_t slot,
|
|
const uint32_t count,
|
|
const std::vector<LDFBaseData*>& config,
|
|
const LWOOBJID parent,
|
|
bool showFlyingLoot,
|
|
bool isModMoveAndEquip,
|
|
LWOOBJID subKey,
|
|
bool bound,
|
|
eLootSourceType lootSourceType) {
|
|
if (!Inventory::IsValidItem(lot)) {
|
|
return;
|
|
}
|
|
|
|
if (isModMoveAndEquip) {
|
|
showFlyingLoot = false;
|
|
}
|
|
|
|
this->lot = lot;
|
|
this->inventory = inventory;
|
|
this->slot = slot;
|
|
this->count = count;
|
|
this->config = config;
|
|
this->parent = parent;
|
|
this->id = LWOOBJID_EMPTY;
|
|
this->info = &Inventory::FindItemComponent(lot);
|
|
this->bound = info->isBOP || bound;
|
|
this->preconditions = new PreconditionExpression(this->info->reqPrecondition);
|
|
this->subKey = subKey;
|
|
|
|
LWOOBJID id = ObjectIDManager::GenerateRandomObjectID();
|
|
|
|
GeneralUtils::SetBit(id, eObjectBits::CHARACTER);
|
|
GeneralUtils::SetBit(id, eObjectBits::PERSISTENT);
|
|
|
|
const auto type = static_cast<eItemType>(info->itemType);
|
|
|
|
if (type == eItemType::MOUNT) {
|
|
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
|
|
}
|
|
|
|
this->id = id;
|
|
|
|
inventory->AddManagedItem(this);
|
|
|
|
auto* entity = inventory->GetComponent()->GetParent();
|
|
GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, static_cast<int>(this->count), subKey, lootSourceType);
|
|
|
|
if (isModMoveAndEquip) {
|
|
Equip();
|
|
|
|
LOG("Move and equipped (%i) from (%i)", this->lot, this->inventory->GetType());
|
|
|
|
Game::entityManager->SerializeEntity(inventory->GetComponent()->GetParent());
|
|
}
|
|
}
|
|
|
|
LWOOBJID Item::GetId() const {
|
|
return id;
|
|
}
|
|
|
|
LOT Item::GetLot() const {
|
|
return lot;
|
|
}
|
|
|
|
uint32_t Item::GetCount() const {
|
|
return count;
|
|
}
|
|
|
|
uint32_t Item::GetSlot() const {
|
|
return slot;
|
|
}
|
|
|
|
std::vector<LDFBaseData*> Item::GetConfig() const {
|
|
return config;
|
|
}
|
|
|
|
std::vector<LDFBaseData*>& Item::GetConfig() {
|
|
return config;
|
|
}
|
|
|
|
const CDItemComponent& Item::GetInfo() const {
|
|
return *info;
|
|
}
|
|
|
|
bool Item::GetBound() const {
|
|
return bound;
|
|
}
|
|
|
|
Inventory* Item::GetInventory() const {
|
|
return inventory;
|
|
}
|
|
|
|
LWOOBJID Item::GetParent() const {
|
|
return parent;
|
|
}
|
|
|
|
LWOOBJID Item::GetSubKey() const {
|
|
return subKey;
|
|
}
|
|
|
|
PreconditionExpression* Item::GetPreconditionExpression() const {
|
|
return preconditions;
|
|
}
|
|
|
|
void Item::SetCount(const uint32_t value, const bool silent, const bool disassemble, const bool showFlyingLoot, eLootSourceType lootSourceType) {
|
|
if (value == count) {
|
|
return;
|
|
}
|
|
|
|
const auto delta = std::abs(static_cast<int32_t>(value) - static_cast<int32_t>(count));
|
|
|
|
const auto type = static_cast<eItemType>(info->itemType);
|
|
|
|
if (disassemble) {
|
|
if (value < count) {
|
|
for (auto i = 0; i < delta; ++i) {
|
|
Disassemble();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!silent) {
|
|
auto* entity = inventory->GetComponent()->GetParent();
|
|
|
|
if (value > count) {
|
|
GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, delta, LWOOBJID_EMPTY, lootSourceType);
|
|
} else {
|
|
GameMessages::SendRemoveItemFromInventory(entity, entity->GetSystemAddress(), id, lot, inventory->GetType(), delta, value);
|
|
}
|
|
}
|
|
|
|
count = value;
|
|
|
|
if (count == 0) {
|
|
RemoveFromInventory();
|
|
}
|
|
}
|
|
|
|
void Item::SetSlot(const uint32_t value) {
|
|
if (slot == value) {
|
|
return;
|
|
}
|
|
|
|
for (const auto& pair : inventory->GetItems()) {
|
|
auto* item = pair.second;
|
|
|
|
if (item->slot == value) {
|
|
item->slot = slot;
|
|
}
|
|
}
|
|
|
|
slot = value;
|
|
}
|
|
|
|
void Item::SetBound(const bool value) {
|
|
bound = value;
|
|
}
|
|
|
|
void Item::SetSubKey(LWOOBJID value) {
|
|
subKey = value;
|
|
}
|
|
|
|
void Item::SetInventory(Inventory* value) {
|
|
inventory->RemoveManagedItem(this);
|
|
|
|
inventory = value;
|
|
|
|
inventory->AddManagedItem(this);
|
|
}
|
|
|
|
void Item::Equip(const bool skipChecks) {
|
|
if (IsEquipped()) {
|
|
return;
|
|
}
|
|
|
|
inventory->GetComponent()->EquipItem(this, skipChecks);
|
|
}
|
|
|
|
void Item::UnEquip() {
|
|
if (!IsEquipped()) {
|
|
return;
|
|
}
|
|
|
|
inventory->GetComponent()->UnEquipItem(this);
|
|
}
|
|
|
|
bool Item::IsEquipped() const {
|
|
auto* component = inventory->GetComponent();
|
|
|
|
for (const auto& pair : component->GetEquippedItems()) {
|
|
const auto item = pair.second;
|
|
|
|
if (item.id == id) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Item::Consume() {
|
|
auto* skillsTable = CDClientManager::GetTable<CDObjectSkillsTable>();
|
|
|
|
auto skills = skillsTable->Query([this](const CDObjectSkills entry) {
|
|
return entry.objectTemplate == static_cast<uint32_t>(lot);
|
|
});
|
|
|
|
auto success = false;
|
|
|
|
for (auto& skill : skills) {
|
|
if (skill.castOnType == 3) // Consumable type
|
|
{
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
LOG_DEBUG("Consumed LOT (%i) itemID (%llu). Success=(%d)", lot, id, success);
|
|
|
|
GameMessages::SendUseItemResult(inventory->GetComponent()->GetParent(), lot, success);
|
|
|
|
if (success) {
|
|
inventory->GetComponent()->RemoveItem(lot, 1);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
void Item::UseNonEquip(Item* item) {
|
|
LOT thisLot = this->GetLot();
|
|
if (!GetInventory()) {
|
|
LOG_DEBUG("item %i has no inventory??", this->GetLot());
|
|
return;
|
|
}
|
|
|
|
auto* playerInventoryComponent = GetInventory()->GetComponent();
|
|
if (!playerInventoryComponent) {
|
|
LOG_DEBUG("no inventory component attached to item id %llu lot %i", this->GetId(), this->GetLot());
|
|
return;
|
|
}
|
|
|
|
auto* playerEntity = playerInventoryComponent->GetParent();
|
|
if (!playerEntity) {
|
|
LOG_DEBUG("no player entity attached to inventory? item id is %llu", this->GetId());
|
|
return;
|
|
}
|
|
|
|
const auto type = static_cast<eItemType>(info->itemType);
|
|
if (type == eItemType::MOUNT) {
|
|
if (Game::zoneManager->GetMountsAllowed()) {
|
|
playerInventoryComponent->HandlePossession(this);
|
|
} else {
|
|
ChatPackets::SendSystemMessage(playerEntity->GetSystemAddress(), u"Mounts are not allowed in this zone");
|
|
}
|
|
} else if (type == eItemType::PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY) {
|
|
if (Game::zoneManager->GetPetsAllowed()) {
|
|
const auto& databasePet = playerInventoryComponent->GetDatabasePet(subKey);
|
|
if (databasePet.lot != LOT_NULL) {
|
|
playerInventoryComponent->SpawnPet(this);
|
|
}
|
|
} else {
|
|
ChatPackets::SendSystemMessage(playerEntity->GetSystemAddress(), u"Pets are not allowed in this zone");
|
|
}
|
|
// This precondition response is taken care of in SpawnPet().
|
|
} else {
|
|
bool success = false;
|
|
auto inventory = item->GetInventory();
|
|
if (inventory && inventory->GetType() == eInventoryType::ITEMS) {
|
|
auto* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
|
const auto packageComponentId = compRegistryTable->GetByIDAndType(lot, eReplicaComponentType::PACKAGE);
|
|
|
|
if (packageComponentId == 0) return;
|
|
|
|
auto* packCompTable = CDClientManager::GetTable<CDPackageComponentTable>();
|
|
auto packages = packCompTable->Query([=](const CDPackageComponent entry) {return entry.id == static_cast<uint32_t>(packageComponentId); });
|
|
|
|
auto success = !packages.empty();
|
|
if (success) {
|
|
if (this->GetPreconditionExpression()->Check(playerInventoryComponent->GetParent())) {
|
|
auto* entityParent = playerInventoryComponent->GetParent();
|
|
// Roll the loot for all the packages then see if it all fits. If it fits, give it to the player, otherwise don't.
|
|
std::unordered_map<LOT, int32_t> rolledLoot{};
|
|
for (auto& pack : packages) {
|
|
auto thisPackage = Loot::RollLootMatrix(entityParent, pack.LootMatrixIndex);
|
|
for (auto& loot : thisPackage) {
|
|
// If we already rolled this lot, add it to the existing one, otherwise create a new entry.
|
|
auto existingLoot = rolledLoot.find(loot.first);
|
|
if (existingLoot == rolledLoot.end()) {
|
|
rolledLoot.insert(loot);
|
|
} else {
|
|
existingLoot->second += loot.second;
|
|
}
|
|
}
|
|
}
|
|
if (playerInventoryComponent->HasSpaceForLoot(rolledLoot)) {
|
|
Loot::GiveLoot(playerInventoryComponent->GetParent(), rolledLoot, eLootSourceType::CONSUMPTION);
|
|
item->SetCount(item->GetCount() - 1);
|
|
} else {
|
|
success = false;
|
|
}
|
|
} else {
|
|
GameMessages::SendUseItemRequirementsResponse(
|
|
playerInventoryComponent->GetParent()->GetObjectID(),
|
|
playerInventoryComponent->GetParent()->GetSystemAddress(),
|
|
eUseItemResponse::FailedPrecondition
|
|
);
|
|
success = false;
|
|
}
|
|
}
|
|
}
|
|
LOG_DEBUG("Player %llu %s used item %i", playerEntity->GetObjectID(), success ? "successfully" : "unsuccessfully", thisLot);
|
|
GameMessages::SendUseItemResult(playerInventoryComponent->GetParent(), thisLot, success);
|
|
}
|
|
}
|
|
|
|
void Item::Disassemble(const eInventoryType inventoryType) {
|
|
for (auto* data : config) {
|
|
if (data->GetKey() == u"assemblyPartLOTs") {
|
|
auto modStr = data->GetValueAsString();
|
|
|
|
// This shouldn't be null but always check your pointers.
|
|
if (GetInventory()) {
|
|
auto inventoryComponent = GetInventory()->GetComponent();
|
|
if (inventoryComponent) {
|
|
auto entity = inventoryComponent->GetParent();
|
|
if (entity) entity->SetVar<std::string>(u"currentModifiedBuild", modStr);
|
|
}
|
|
}
|
|
|
|
std::vector<LOT> modArray;
|
|
|
|
std::stringstream ssData(modStr);
|
|
|
|
std::string token;
|
|
|
|
const auto deliminator = '+';
|
|
|
|
while (std::getline(ssData, token, deliminator)) {
|
|
const auto modLot = std::stoi(token.substr(2, token.size() - 1));
|
|
|
|
modArray.push_back(modLot);
|
|
}
|
|
|
|
for (const auto mod : modArray) {
|
|
inventory->GetComponent()->AddItem(mod, 1, eLootSourceType::DELETION, inventoryType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Item::DisassembleModel(uint32_t numToDismantle) {
|
|
auto* table = CDClientManager::GetTable<CDComponentsRegistryTable>();
|
|
|
|
const auto componentId = table->GetByIDAndType(GetLot(), eReplicaComponentType::RENDER);
|
|
|
|
auto query = CDClientDatabase::CreatePreppedStmt("SELECT render_asset, LXFMLFolder FROM RenderComponent WHERE id = ?;");
|
|
query.bind(1, static_cast<int>(componentId));
|
|
|
|
auto result = query.execQuery();
|
|
|
|
if (result.eof() || result.fieldIsNull("render_asset")) {
|
|
return;
|
|
}
|
|
|
|
std::string renderAsset = std::string(result.getStringField("render_asset"));
|
|
|
|
// normalize path slashes
|
|
for (auto& c : renderAsset) {
|
|
if (c == '\\') c = '/';
|
|
}
|
|
|
|
std::string lxfmlFolderName = std::string(result.getStringField("LXFMLFolder"));
|
|
if (!lxfmlFolderName.empty()) lxfmlFolderName.insert(0, "/");
|
|
|
|
std::vector<std::string> renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/');
|
|
if (renderAssetSplit.empty()) return;
|
|
|
|
std::string lxfmlPath = "BrickModels" + lxfmlFolderName + "/" + GeneralUtils::SplitString(renderAssetSplit.back(), '.').at(0) + ".lxfml";
|
|
auto file = Game::assetManager->GetFile(lxfmlPath.c_str());
|
|
|
|
if (!file) {
|
|
LOG("Failed to load %s to disassemble model into bricks, check that this file exists", lxfmlPath.c_str());
|
|
return;
|
|
}
|
|
|
|
std::stringstream data;
|
|
data << file.rdbuf();
|
|
|
|
uint32_t fileSize;
|
|
file.seekg(0, std::ios::end);
|
|
fileSize = static_cast<uint32_t>(file.tellg());
|
|
file.seekg(0, std::ios::beg);
|
|
|
|
if (fileSize == 0) return;
|
|
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
if (doc.Parse(data.str().c_str(), data.str().size()) != tinyxml2::XML_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
auto* lxfml = doc.FirstChildElement("LXFML");
|
|
if (!lxfml) return;
|
|
auto* bricks = lxfml->FirstChildElement("Bricks");
|
|
std::string searchTerm = "Brick";
|
|
|
|
if (!bricks) {
|
|
searchTerm = "Part";
|
|
auto* scene = lxfml->FirstChildElement("Scene");
|
|
if (!scene) return;
|
|
|
|
auto* model = scene->FirstChildElement("Model");
|
|
if (!model) return;
|
|
|
|
bricks = model->FirstChildElement("Group");
|
|
if (!bricks) return;
|
|
}
|
|
|
|
auto* currentBrick = bricks->FirstChildElement(searchTerm.c_str());
|
|
|
|
// First iteration gets the count
|
|
std::map<int32_t, int32_t> parts;
|
|
while (currentBrick) {
|
|
const char* const designID = currentBrick->Attribute("designID");
|
|
if (designID) {
|
|
const auto designId = GeneralUtils::TryParse<uint32_t>(designID);
|
|
if (!designId) {
|
|
LOG("Failed to parse designID %s", designID);
|
|
continue;
|
|
}
|
|
parts[designId.value()]++;
|
|
}
|
|
|
|
currentBrick = currentBrick->NextSiblingElement(searchTerm.c_str());
|
|
}
|
|
|
|
auto* brickIDTable = CDClientManager::GetTable<CDBrickIDTableTable>();
|
|
|
|
// Second iteration actually distributes the bricks
|
|
for (const auto& [part, count] : parts) {
|
|
const auto partLocal = part;
|
|
const auto brickID = brickIDTable->Query([&](const CDBrickIDTable& entry) {
|
|
return entry.LEGOBrickID == partLocal;
|
|
});
|
|
|
|
if (brickID.empty()) continue;
|
|
|
|
GetInventory()->GetComponent()->AddItem(brickID[0].NDObjectID, count * numToDismantle, eLootSourceType::DELETION);
|
|
}
|
|
}
|
|
|
|
void Item::RemoveFromInventory() {
|
|
UnEquip();
|
|
|
|
count = 0;
|
|
|
|
inventory->RemoveManagedItem(this);
|
|
|
|
delete this;
|
|
}
|
|
|
|
Item::~Item() {
|
|
delete preconditions;
|
|
|
|
for (auto* value : config) {
|
|
delete value;
|
|
}
|
|
|
|
config.clear();
|
|
}
|
|
|
|
void Item::SaveConfigXml(tinyxml2::XMLElement& i) const {
|
|
tinyxml2::XMLElement* x = nullptr;
|
|
|
|
for (const auto* config : this->config) {
|
|
const auto& key = GeneralUtils::UTF16ToWTF8(config->GetKey());
|
|
const auto saveKey = ExtraSettingAbbreviations.find(key);
|
|
if (saveKey == ExtraSettingAbbreviations.end()) {
|
|
continue;
|
|
}
|
|
|
|
if (!x) {
|
|
x = i.InsertNewChildElement("x");
|
|
}
|
|
|
|
const auto dataToSave = config->GetString(false);
|
|
x->SetAttribute(saveKey->second.c_str(), dataToSave.c_str());
|
|
}
|
|
}
|
|
|
|
void Item::LoadConfigXml(const tinyxml2::XMLElement& i) {
|
|
const auto* x = i.FirstChildElement("x");
|
|
if (!x) return;
|
|
|
|
for (const auto& pair : ExtraSettingAbbreviations) {
|
|
const auto* data = x->Attribute(pair.second.c_str());
|
|
if (!data) continue;
|
|
|
|
const auto value = pair.first + "=" + data;
|
|
config.push_back(LDFBaseData::DataFromString(value));
|
|
}
|
|
}
|
|
|
|
bool Item::CanDeleteItem(Item* item) {
|
|
if (!item) return false;
|
|
// TODO:
|
|
// Check if item is being possessed
|
|
// Check if the owner is mounting item? (how is this different than the above)
|
|
// Allow GM 9 to freely delete
|
|
// Finally, check Deletion Restriction
|
|
const auto& itemComponent = item->inventory->FindItemComponent(item->lot);
|
|
if (itemComponent.delResIndex == -1) return true;
|
|
return CheckDeletionRestriction(itemComponent.delResIndex, item->lot);
|
|
}
|
|
|
|
bool Item::CheckDeletionRestriction(uint32_t delResIndex, LOT item) {
|
|
auto* delresTable = CDClientManager::GetTable<CDDeletionRestrictionsTable>();
|
|
const auto restriction = delresTable->GetByID(delResIndex);
|
|
|
|
switch(restriction.checkType) {
|
|
case eDeletionRestrictionsCheckType::INCLUDE_LOTS:
|
|
if (std::ranges::find(restriction.ids, item) != restriction.ids.end()) return false;
|
|
else return true;
|
|
case eDeletionRestrictionsCheckType::EXCLUDE_LOTS:
|
|
if (std::ranges::find(restriction.ids, item) != restriction.ids.end()) return true;
|
|
else return false;
|
|
case eDeletionRestrictionsCheckType::ANY_OF_THESE:
|
|
// TODO: Implement
|
|
return true;
|
|
case eDeletionRestrictionsCheckType::ALL_OF_THESE:
|
|
// TODO: Implement
|
|
return true;
|
|
case eDeletionRestrictionsCheckType::WHILE_IN_ZONE:
|
|
if (std::ranges::find(restriction.ids, Game::zoneManager->GetZoneID().GetMapID()) != restriction.ids.end()) return false;
|
|
else return true;
|
|
case eDeletionRestrictionsCheckType::ALWAYS_RESTRICTED:
|
|
return false;
|
|
case eDeletionRestrictionsCheckType::MAX:
|
|
default:
|
|
return true;
|
|
}
|
|
}
|