[Feature] Add parcel container support (#4305)

* Add parcel container support

This allows sending containers with items as a parcel
When sending a item via a parcel, if the player had multiples of that item, the wrong item would be removed from inventory.

* Rebase updates
This commit is contained in:
Mitch Freeman 2024-05-17 02:58:26 -03:00 committed by GitHub
parent c87aadbf0c
commit a80ab75260
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 740 additions and 18 deletions

View File

@ -178,6 +178,7 @@ SET(repositories
repositories/base/base_character_material_repository.h
repositories/base/base_character_memmed_spells_repository.h
repositories/base/base_character_parcels_repository.h
repositories/base/base_character_parcels_containers_repository.h
repositories/base/base_character_peqzone_flags_repository.h
repositories/base/base_character_pet_buffs_repository.h
repositories/base/base_character_pet_info_repository.h
@ -359,6 +360,7 @@ SET(repositories
repositories/character_material_repository.h
repositories/character_memmed_spells_repository.h
repositories/character_parcels_repository.h
repositories/character_parcels_containers_repository.h
repositories/character_peqzone_flags_repository.h
repositories/character_pet_buffs_repository.h
repositories/character_pet_info_repository.h

View File

@ -5578,6 +5578,35 @@ ADD COLUMN `extra_haste` int(11) NOT NULL DEFAULT 0 AFTER `wis`;
.sql = R"(
ALTER TABLE `guild_bank`
CHANGE COLUMN `qty` `qty` INT(10) NOT NULL DEFAULT '0' AFTER `itemid`;
)"
},
ManifestEntry{
.version = 9277,
.description = "2024_05_09_parcel_enable_containers.sql",
.check = "SHOW TABLES LIKE 'character_parcels_containers'",
.condition = "empty",
.match = "",
.sql = R"(
CREATE TABLE `character_parcels_containers` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`parcels_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`slot_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`item_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`aug_slot_1` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`aug_slot_2` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`aug_slot_3` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`aug_slot_4` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`aug_slot_5` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`aug_slot_6` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`quantity` INT(10) UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE,
INDEX `fk_character_parcels_id` (`parcels_id`) USING BTREE,
CONSTRAINT `fk_character_parcels_id` FOREIGN KEY (`parcels_id`) REFERENCES `character_parcels` (`id`) ON UPDATE NO ACTION ON DELETE CASCADE
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1
;
)"
}
// -- template; copy/paste this when you need to create a new entry

View File

@ -60,6 +60,7 @@ namespace DatabaseSchema {
{"character_material", "id"},
{"character_memmed_spells", "id"},
{"character_parcels", "char_id"},
{"character_parcels_containers", "id"},
{"character_pet_buffs", "char_id"},
{"character_pet_info", "char_id"},
{"character_pet_inventory", "char_id"},
@ -130,6 +131,7 @@ namespace DatabaseSchema {
"character_material",
"character_memmed_spells",
"character_parcels",
"character_parcels_containers",
"character_pet_buffs",
"character_pet_info",
"character_pet_inventory",

View File

@ -0,0 +1,499 @@
/**
* DO NOT MODIFY THIS FILE
*
* This repository was automatically generated and is NOT to be modified directly.
* Any repository modifications are meant to be made to the repository extending the base.
* Any modifications to base repositories are to be made by the generator only
*
* @generator ./utils/scripts/generators/repository-generator.pl
* @docs https://docs.eqemu.io/developer/repositories
*/
#ifndef EQEMU_BASE_CHARACTER_PARCELS_CONTAINERS_REPOSITORY_H
#define EQEMU_BASE_CHARACTER_PARCELS_CONTAINERS_REPOSITORY_H
#include "../../database.h"
#include "../../strings.h"
#include <ctime>
class BaseCharacterParcelsContainersRepository {
public:
struct CharacterParcelsContainers {
uint32_t id;
uint32_t parcels_id;
uint32_t slot_id;
uint32_t item_id;
uint32_t aug_slot_1;
uint32_t aug_slot_2;
uint32_t aug_slot_3;
uint32_t aug_slot_4;
uint32_t aug_slot_5;
uint32_t aug_slot_6;
uint32_t quantity;
};
static std::string PrimaryKey()
{
return std::string("id");
}
static std::vector<std::string> Columns()
{
return {
"id",
"parcels_id",
"slot_id",
"item_id",
"aug_slot_1",
"aug_slot_2",
"aug_slot_3",
"aug_slot_4",
"aug_slot_5",
"aug_slot_6",
"quantity",
};
}
static std::vector<std::string> SelectColumns()
{
return {
"id",
"parcels_id",
"slot_id",
"item_id",
"aug_slot_1",
"aug_slot_2",
"aug_slot_3",
"aug_slot_4",
"aug_slot_5",
"aug_slot_6",
"quantity",
};
}
static std::string ColumnsRaw()
{
return std::string(Strings::Implode(", ", Columns()));
}
static std::string SelectColumnsRaw()
{
return std::string(Strings::Implode(", ", SelectColumns()));
}
static std::string TableName()
{
return std::string("character_parcels_containers");
}
static std::string BaseSelect()
{
return fmt::format(
"SELECT {} FROM {}",
SelectColumnsRaw(),
TableName()
);
}
static std::string BaseInsert()
{
return fmt::format(
"INSERT INTO {} ({}) ",
TableName(),
ColumnsRaw()
);
}
static CharacterParcelsContainers NewEntity()
{
CharacterParcelsContainers e{};
e.id = 0;
e.parcels_id = 0;
e.slot_id = 0;
e.item_id = 0;
e.aug_slot_1 = 0;
e.aug_slot_2 = 0;
e.aug_slot_3 = 0;
e.aug_slot_4 = 0;
e.aug_slot_5 = 0;
e.aug_slot_6 = 0;
e.quantity = 0;
return e;
}
static CharacterParcelsContainers GetCharacterParcelsContainers(
const std::vector<CharacterParcelsContainers> &character_parcels_containerss,
int character_parcels_containers_id
)
{
for (auto &character_parcels_containers : character_parcels_containerss) {
if (character_parcels_containers.id == character_parcels_containers_id) {
return character_parcels_containers;
}
}
return NewEntity();
}
static CharacterParcelsContainers FindOne(
Database& db,
int character_parcels_containers_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE {} = {} LIMIT 1",
BaseSelect(),
PrimaryKey(),
character_parcels_containers_id
)
);
auto row = results.begin();
if (results.RowCount() == 1) {
CharacterParcelsContainers e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.parcels_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_1 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_2 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_3 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_4 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_5 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.aug_slot_6 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.quantity = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
return e;
}
return NewEntity();
}
static int DeleteOne(
Database& db,
int character_parcels_containers_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {} = {}",
TableName(),
PrimaryKey(),
character_parcels_containers_id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int UpdateOne(
Database& db,
const CharacterParcelsContainers &e
)
{
std::vector<std::string> v;
auto columns = Columns();
v.push_back(columns[1] + " = " + std::to_string(e.parcels_id));
v.push_back(columns[2] + " = " + std::to_string(e.slot_id));
v.push_back(columns[3] + " = " + std::to_string(e.item_id));
v.push_back(columns[4] + " = " + std::to_string(e.aug_slot_1));
v.push_back(columns[5] + " = " + std::to_string(e.aug_slot_2));
v.push_back(columns[6] + " = " + std::to_string(e.aug_slot_3));
v.push_back(columns[7] + " = " + std::to_string(e.aug_slot_4));
v.push_back(columns[8] + " = " + std::to_string(e.aug_slot_5));
v.push_back(columns[9] + " = " + std::to_string(e.aug_slot_6));
v.push_back(columns[10] + " = " + std::to_string(e.quantity));
auto results = db.QueryDatabase(
fmt::format(
"UPDATE {} SET {} WHERE {} = {}",
TableName(),
Strings::Implode(", ", v),
PrimaryKey(),
e.id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static CharacterParcelsContainers InsertOne(
Database& db,
CharacterParcelsContainers e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.parcels_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back(std::to_string(e.quantity));
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES ({})",
BaseInsert(),
Strings::Implode(",", v)
)
);
if (results.Success()) {
e.id = results.LastInsertedID();
return e;
}
e = NewEntity();
return e;
}
static int InsertMany(
Database& db,
const std::vector<CharacterParcelsContainers> &entries
)
{
std::vector<std::string> insert_chunks;
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.parcels_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back(std::to_string(e.quantity));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
std::vector<std::string> v;
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseInsert(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static std::vector<CharacterParcelsContainers> All(Database& db)
{
std::vector<CharacterParcelsContainers> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{}",
BaseSelect()
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterParcelsContainers e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.parcels_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_1 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_2 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_3 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_4 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_5 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.aug_slot_6 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.quantity = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
all_entries.push_back(e);
}
return all_entries;
}
static std::vector<CharacterParcelsContainers> GetWhere(Database& db, const std::string &where_filter)
{
std::vector<CharacterParcelsContainers> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE {}",
BaseSelect(),
where_filter
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterParcelsContainers e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.parcels_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.slot_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.item_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.aug_slot_1 = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.aug_slot_2 = row[5] ? static_cast<uint32_t>(strtoul(row[5], nullptr, 10)) : 0;
e.aug_slot_3 = row[6] ? static_cast<uint32_t>(strtoul(row[6], nullptr, 10)) : 0;
e.aug_slot_4 = row[7] ? static_cast<uint32_t>(strtoul(row[7], nullptr, 10)) : 0;
e.aug_slot_5 = row[8] ? static_cast<uint32_t>(strtoul(row[8], nullptr, 10)) : 0;
e.aug_slot_6 = row[9] ? static_cast<uint32_t>(strtoul(row[9], nullptr, 10)) : 0;
e.quantity = row[10] ? static_cast<uint32_t>(strtoul(row[10], nullptr, 10)) : 0;
all_entries.push_back(e);
}
return all_entries;
}
static int DeleteWhere(Database& db, const std::string &where_filter)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {}",
TableName(),
where_filter
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int Truncate(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"TRUNCATE TABLE {}",
TableName()
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int64 GetMaxId(Database& db)
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COALESCE(MAX({}), 0) FROM {}",
PrimaryKey(),
TableName()
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
static int64 Count(Database& db, const std::string &where_filter = "")
{
auto results = db.QueryDatabase(
fmt::format(
"SELECT COUNT(*) FROM {} {}",
TableName(),
(where_filter.empty() ? "" : "WHERE " + where_filter)
)
);
return (results.Success() && results.begin()[0] ? strtoll(results.begin()[0], nullptr, 10) : 0);
}
static std::string BaseReplace()
{
return fmt::format(
"REPLACE INTO {} ({}) ",
TableName(),
ColumnsRaw()
);
}
static int ReplaceOne(
Database& db,
const CharacterParcelsContainers &e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.parcels_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back(std::to_string(e.quantity));
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES ({})",
BaseReplace(),
Strings::Implode(",", v)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int ReplaceMany(
Database& db,
const std::vector<CharacterParcelsContainers> &entries
)
{
std::vector<std::string> insert_chunks;
for (auto &e: entries) {
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.parcels_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.aug_slot_1));
v.push_back(std::to_string(e.aug_slot_2));
v.push_back(std::to_string(e.aug_slot_3));
v.push_back(std::to_string(e.aug_slot_4));
v.push_back(std::to_string(e.aug_slot_5));
v.push_back(std::to_string(e.aug_slot_6));
v.push_back(std::to_string(e.quantity));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
std::vector<std::string> v;
auto results = db.QueryDatabase(
fmt::format(
"{} VALUES {}",
BaseReplace(),
Strings::Implode(",", insert_chunks)
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
};
#endif //EQEMU_BASE_CHARACTER_PARCELS_CONTAINERS_REPOSITORY_H

View File

@ -0,0 +1,50 @@
#ifndef EQEMU_CHARACTER_PARCELS_CONTAINERS_REPOSITORY_H
#define EQEMU_CHARACTER_PARCELS_CONTAINERS_REPOSITORY_H
#include "../database.h"
#include "../strings.h"
#include "base/base_character_parcels_containers_repository.h"
class CharacterParcelsContainersRepository: public BaseCharacterParcelsContainersRepository {
public:
/**
* This file was auto generated and can be modified and extended upon
*
* Base repository methods are automatically
* generated in the "base" version of this repository. The base repository
* is immutable and to be left untouched, while methods in this class
* are used as extension methods for more specific persistence-layer
* accessors or mutators.
*
* Base Methods (Subject to be expanded upon in time)
*
* Note: Not all tables are designed appropriately to fit functionality with all base methods
*
* InsertOne
* UpdateOne
* DeleteOne
* FindOne
* GetWhere(std::string where_filter)
* DeleteWhere(std::string where_filter)
* InsertMany
* All
*
* Example custom methods in a repository
*
* CharacterParcelsContainersRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* CharacterParcelsContainersRepository::GetWhereNeverExpires()
* CharacterParcelsContainersRepository::GetWhereXAndY()
* CharacterParcelsContainersRepository::DeleteWhereXAndY()
*
* Most of the above could be covered by base methods, but if you as a developer
* find yourself re-using logic for other parts of the code, its best to just make a
* method that can be re-used easily elsewhere especially if it can use a base repository
* method and encapsulate filters there
*/
// Custom extended repository methods here
};
#endif //EQEMU_CHARACTER_PARCELS_CONTAINERS_REPOSITORY_H

View File

@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9276
#define CURRENT_BINARY_DATABASE_VERSION 9277
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9044
#endif

View File

@ -12435,3 +12435,43 @@ uint16 Client::GetSkill(EQ::skills::SkillType skill_id) const
}
return 0;
}
void Client::RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity)
{
EQ::ItemInstance *item = nullptr;
static const int16 slots[][2] = {
{ EQ::invslot::POSSESSIONS_BEGIN, EQ::invslot::POSSESSIONS_END },
{ EQ::invbag::GENERAL_BAGS_BEGIN, EQ::invbag::GENERAL_BAGS_END },
{ EQ::invbag::CURSOR_BAG_BEGIN, EQ::invbag::CURSOR_BAG_END},
{ EQ::invslot::BANK_BEGIN, EQ::invslot::BANK_END },
{ EQ::invslot::GUILD_TRIBUTE_BEGIN, EQ::invslot::GUILD_TRIBUTE_END },
{ EQ::invbag::BANK_BAGS_BEGIN, EQ::invbag::BANK_BAGS_END },
{ EQ::invslot::SHARED_BANK_BEGIN, EQ::invslot::SHARED_BANK_END },
{ EQ::invbag::SHARED_BANK_BAGS_BEGIN, EQ::invbag::SHARED_BANK_BAGS_END },
};
int16 removed_count = 0;
const size_t slot_index_count = sizeof(slots) / sizeof(slots[0]);
for (int slot_index = 0; slot_index < slot_index_count; ++slot_index) {
for (int slot_id = slots[slot_index][0]; slot_id <= slots[slot_index][1]; ++slot_id) {
if (removed_count == quantity) {
break;
}
item = GetInv().GetItem(slot_id);
if (item && item->GetSerialNumber() == serial_number) {
int16 charges = item->IsStackable() ? item->GetCharges() : 0;
int16 stack_size = std::max(charges, static_cast<int16>(1));
if ((removed_count + stack_size) <= quantity) {
removed_count += stack_size;
DeleteItemInInventory(slot_id, charges, true);
} else {
int16 amount_left = (quantity - removed_count);
if (amount_left > 0 && stack_size >= amount_left) {
removed_count += amount_left;
DeleteItemInInventory(slot_id, amount_left, true);
}
}
}
}
}
}

View File

@ -1042,6 +1042,7 @@ public:
void SetItemCooldown(uint32 item_id, bool use_saved_timer = false, uint32 in_seconds = 1);
uint32 GetItemCooldown(uint32 item_id);
void RemoveItem(uint32 item_id, uint32 quantity = 1);
void RemoveItemBySerialNumber(uint32 serial_number, uint32 quantity = 1);
bool SwapItem(MoveItem_Struct* move_in);
void SwapItemResync(MoveItem_Struct* move_slots);
void QSSwapItemAuditor(MoveItem_Struct* move_in, bool postaction_call = false);

View File

@ -20,6 +20,7 @@
#include "../common/events/player_event_logs.h"
#include "../common/repositories/trader_repository.h"
#include "../common/repositories/character_parcels_repository.h"
#include "../common/repositories/character_parcels_containers_repository.h"
#include "worldserver.h"
#include "string_ids.h"
#include "client.h"
@ -403,7 +404,30 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
return;
}
RemoveItem(parcel_out.item_id, parcel_out.quantity);
std::vector<CharacterParcelsContainersRepository::CharacterParcelsContainers> all_entries{};
if (inst->IsNoneEmptyContainer()) {
CharacterParcelsContainersRepository::CharacterParcelsContainers cpc{};
for (auto const &kv: *inst->GetContents()) {
cpc.parcels_id = result.id;
cpc.slot_id = kv.first;
cpc.item_id = kv.second->GetID();
if (kv.second->IsAugmented()) {
auto augs = kv.second->GetAugmentIDs();
cpc.aug_slot_1 = augs.at(0);
cpc.aug_slot_2 = augs.at(1);
cpc.aug_slot_3 = augs.at(2);
cpc.aug_slot_4 = augs.at(3);
cpc.aug_slot_5 = augs.at(4);
cpc.aug_slot_6 = augs.at(5);
}
cpc.quantity = kv.second->GetCharges() > 0 ? kv.second->GetCharges() : 1;
all_entries.push_back(cpc);
}
CharacterParcelsContainersRepository::InsertMany(database, all_entries);
}
RemoveItemBySerialNumber(inst->GetSerialNumber(), parcel_out.quantity);
std::unique_ptr<EQApplicationPacket> outapp(new EQApplicationPacket(OP_ShopSendParcel));
QueuePacket(outapp.get());
@ -435,6 +459,23 @@ void Client::DoParcelSend(const Parcel_Struct *parcel_in)
e.sent_date = parcel_out.sent_date;
RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e);
if (!all_entries.empty()) {
for (auto const &i: all_entries) {
e.from_player_name = parcel_out.from_name;
e.to_player_name = send_to_client.at(0).character_name;
e.item_id = i.item_id;
e.aug_slot_1 = i.aug_slot_1;
e.aug_slot_2 = i.aug_slot_2;
e.aug_slot_3 = i.aug_slot_3;
e.aug_slot_4 = i.aug_slot_4;
e.aug_slot_5 = i.aug_slot_5;
e.aug_slot_6 = i.aug_slot_6;
e.quantity = i.quantity;
e.sent_date = parcel_out.sent_date;
RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e);
}
}
}
Parcel_Struct ps{};
@ -663,8 +704,54 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
}
}
else if (free_id != INVALID_INDEX) {
std::vector<CharacterParcelsContainersRepository::CharacterParcelsContainers> results{};
if (inst->IsClassBag() && inst->GetItem()->BagSlots > 0) {
results = CharacterParcelsContainersRepository::GetWhere(database, fmt::format("`parcels_id` = {}", p->second.id));
for (auto const &i : results) {
std::unique_ptr<EQ::ItemInstance> item(
database.CreateItem(
i.item_id,
i.quantity,
i.aug_slot_1,
i.aug_slot_2,
i.aug_slot_3,
i.aug_slot_4,
i.aug_slot_5,
i.aug_slot_6
)
);
if (CheckLoreConflict(item->GetItem())) {
Message(
Chat::Yellow,
fmt::format("Lore Item Found in Inventory: {}", item->GetItem()->Name).c_str());
MessageString(Chat::Yellow, DUP_LORE);
Message(Chat::Red, "Unable to retrieve parcel.");
SendParcelRetrieveAck();
return;
}
}
}
inst->SetCharges(item_quantity > 0 ? item_quantity : 1);
if (PutItemInInventory(free_id, *inst.get(), true)) {
if (inst->IsClassBag() && inst->GetItem()->BagSlots > 0) {
for (auto const &i: results) {
std::unique_ptr<EQ::ItemInstance> item(
database.CreateItem(
i.item_id,
i.quantity,
i.aug_slot_1,
i.aug_slot_2,
i.aug_slot_3,
i.aug_slot_4,
i.aug_slot_5,
i.aug_slot_6
)
);
auto bag_slot = EQ::InventoryProfile::CalcSlotId(free_id, i.slot_id);
PutItemInInventory(bag_slot, *item.get(), true);
}
}
MessageString(
Chat::Yellow,
PARCEL_DELIVERED,
@ -672,6 +759,34 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
inst->GetItem()->Name,
p->second.from_name.c_str()
);
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_RETRIEVE)) {
PlayerEvent::ParcelRetrieve e{};
e.from_player_name = p->second.from_name;
e.item_id = p->second.item_id;
e.aug_slot_1 = p->second.aug_slot_1;
e.aug_slot_2 = p->second.aug_slot_2;
e.aug_slot_3 = p->second.aug_slot_3;
e.aug_slot_4 = p->second.aug_slot_4;
e.aug_slot_5 = p->second.aug_slot_5;
e.aug_slot_6 = p->second.aug_slot_6;
e.quantity = p->second.quantity;
e.sent_date = p->second.sent_date;
RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e);
for (auto const &i:results) {
e.from_player_name = p->second.from_name;
e.item_id = i.item_id;
e.aug_slot_1 = i.aug_slot_1;
e.aug_slot_2 = i.aug_slot_2;
e.aug_slot_3 = i.aug_slot_3;
e.aug_slot_4 = i.aug_slot_4;
e.aug_slot_5 = i.aug_slot_5;
e.aug_slot_6 = i.aug_slot_6;
e.quantity = i.quantity;
e.sent_date = p->second.sent_date;
RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e);
}
}
}
else {
MessageString(Chat::Yellow, PARCEL_INV_FULL, merchant->GetCleanName());
@ -687,22 +802,6 @@ void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
}
}
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_RETRIEVE)) {
PlayerEvent::ParcelRetrieve e{};
e.from_player_name = p->second.from_name;
e.item_id = p->second.item_id;
e.aug_slot_1 = p->second.aug_slot_1;
e.aug_slot_2 = p->second.aug_slot_2;
e.aug_slot_3 = p->second.aug_slot_3;
e.aug_slot_4 = p->second.aug_slot_4;
e.aug_slot_5 = p->second.aug_slot_5;
e.aug_slot_6 = p->second.aug_slot_6;
e.quantity = p->second.quantity;
e.sent_date = p->second.sent_date;
RecordPlayerEventLog(PlayerEvent::PARCEL_RETRIEVE, e);
}
DeleteParcel(p->second.id);
SendParcelDelete(parcel_in);
m_parcels.erase(p);