[Feature] Add Parcel Feature for RoF2 Clients (#4198)

* Add Parcel Feature

Add the parcel system for RoF2 client

* Fixed a duplicate define

* Reformat

reformating and review changes

* Further Formatting

* Memory Mgmt Updates

Refactored to using unique_ptr/make_unique/etc to avoid manual memory mgmt.

Other format changes

* Refactor db structure

Refactor for db structure of parcels to character_parcels
Removal of parcel_merchants
Addition of npc_types.is_parcel_merchant
Cleanup as a result

* Refactor to use item id 99990 for money transfers.  Removed the money string function as a result, though simplified the messaging related to money.  Other updates based on feedback.

* Move prune routine out of scheduler and into a world process.
Removed RuleI from #define

* Update

* Update database.cpp

* Update database_update_manifest.cpp

* Update main.cpp

* Update client_process.cpp

* Update parcels.cpp

* Remove parcel merchant content to optional sql instead of manifest.

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
This commit is contained in:
Mitch Freeman
2024-04-20 23:15:56 -03:00
committed by GitHub
parent 64fefaebe4
commit fcffc6b3d4
58 changed files with 2505 additions and 230 deletions
+2
View File
@@ -177,6 +177,7 @@ SET(repositories
repositories/base/base_character_leadership_abilities_repository.h
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_peqzone_flags_repository.h
repositories/base/base_character_pet_buffs_repository.h
repositories/base/base_character_pet_info_repository.h
@@ -357,6 +358,7 @@ SET(repositories
repositories/character_leadership_abilities_repository.h
repositories/character_material_repository.h
repositories/character_memmed_spells_repository.h
repositories/character_parcels_repository.h
repositories/character_peqzone_flags_repository.h
repositories/character_pet_buffs_repository.h
repositories/character_pet_info_repository.h
+41
View File
@@ -35,6 +35,7 @@
#include "../common/repositories/character_data_repository.h"
#include "../common/repositories/character_languages_repository.h"
#include "../common/repositories/character_leadership_abilities_repository.h"
#include "../common/repositories/character_parcels_repository.h"
#include "../common/repositories/character_skills_repository.h"
#include "../common/repositories/data_buckets_repository.h"
#include "../common/repositories/group_id_repository.h"
@@ -49,6 +50,7 @@
#include "../common/repositories/raid_members_repository.h"
#include "../common/repositories/reports_repository.h"
#include "../common/repositories/variables_repository.h"
#include "../common/events/player_event_logs.h"
// Disgrace: for windows compile
#ifdef _WINDOWS
@@ -2042,3 +2044,42 @@ void Database::Decode(std::string &in)
in.at(i) -= char('0');
}
};
void Database::PurgeCharacterParcels()
{
auto filter = fmt::format("sent_date < (NOW() - INTERVAL {} DAY)", RuleI(Parcel, ParcelPruneDelay));
auto results = CharacterParcelsRepository::GetWhere(*this, filter);
auto prune = CharacterParcelsRepository::DeleteWhere(*this, filter);
PlayerEvent::ParcelDelete pd{};
PlayerEventLogsRepository::PlayerEventLogs pel{};
pel.event_type_id = PlayerEvent::PARCEL_DELETE;
pel.event_type_name = PlayerEvent::EventName[pel.event_type_id];
std::stringstream ss;
for (auto const &r: results) {
pd.from_name = r.from_name;
pd.item_id = r.item_id;
pd.note = r.note;
pd.quantity = r.quantity;
pd.sent_date = r.sent_date;
pd.char_id = r.char_id;
{
cereal::JSONOutputArchiveSingleLine ar(ss);
pd.serialize(ar);
}
pel.event_data = ss.str();
pel.created_at = std::time(nullptr);
player_event_logs.AddToQueue(pel);
ss.str("");
ss.clear();
}
LogInfo(
"Purged <yellow>[{}] parcels that were over <yellow>[{}] days old.",
results.size(),
RuleI(Parcel, ParcelPruneDelay)
);
}
+1
View File
@@ -267,6 +267,7 @@ public:
void SourceDatabaseTableFromUrl(const std::string& table_name, const std::string& url);
void SourceSqlFromUrl(const std::string& url);
void PurgeCharacterParcels();
void Encode(std::string &in);
void Decode(std::string &in);
@@ -5494,6 +5494,32 @@ ALTER TABLE `lootdrop_entries` ADD `content_flags` varchar(100) NULL;
ALTER TABLE `lootdrop_entries` ADD `content_flags_disabled` varchar(100) NULL;
)",
.content_schema_update = true
},
ManifestEntry{
.version = 9271,
.description = "2024_03_10_parcel_implementation.sql",
.check = "SHOW TABLES LIKE 'character_parcels'",
.condition = "empty",
.match = "",
.sql = R"(CREATE TABLE `character_parcels` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`char_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`item_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`slot_id` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`quantity` INT(10) UNSIGNED NOT NULL DEFAULT '0',
`from_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
`note` VARCHAR(1024) NULL DEFAULT NULL COLLATE 'latin1_swedish_ci',
`sent_date` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `data_constraint` (`slot_id`, `char_id`) USING BTREE
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;
ALTER TABLE `npc_types`
ADD COLUMN `is_parcel_merchant` TINYINT(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `keeps_sold_items`;
)"
}
// -- template; copy/paste this when you need to create a new entry
// ManifestEntry{
+2
View File
@@ -59,6 +59,7 @@ namespace DatabaseSchema {
{"character_leadership_abilities", "id"},
{"character_material", "id"},
{"character_memmed_spells", "id"},
{"character_parcels", "char_id"},
{"character_pet_buffs", "char_id"},
{"character_pet_info", "char_id"},
{"character_pet_inventory", "char_id"},
@@ -128,6 +129,7 @@ namespace DatabaseSchema {
"character_leadership_abilities",
"character_material",
"character_memmed_spells",
"character_parcels",
"character_pet_buffs",
"character_pet_info",
"character_pet_inventory",
+5
View File
@@ -510,6 +510,11 @@ N(OP_ShopEndConfirm),
N(OP_ShopItem),
N(OP_ShopPlayerBuy),
N(OP_ShopPlayerSell),
N(OP_ShopSendParcel),
N(OP_ShopDeleteParcel),
N(OP_ShopRespondParcel),
N(OP_ShopRetrieveParcel),
N(OP_ShopParcelIcon),
N(OP_ShopRequest),
N(OP_SimpleMessage),
N(OP_SkillUpdate),
+6
View File
@@ -1131,4 +1131,10 @@ namespace LeadershipAbilitySlot {
constexpr uint16 HealthOfTargetsTarget = 14;
}
#define PARCEL_SEND_ITEMS 0
#define PARCEL_SEND_MONEY 1
#define PARCEL_MONEY_ITEM_ID 99990 // item id of money
#define PARCEL_LIMIT 5
#define PARCEL_BEGIN_SLOT 1
#endif /*COMMON_EQ_CONSTANTS_H*/
+96 -19
View File
@@ -27,6 +27,8 @@
#include "../common/version.h"
#include "emu_constants.h"
#include "textures.h"
#include "../cereal/include/cereal/archives/binary.hpp"
#include "../cereal/include/cereal/types/string.hpp"
static const uint32 BUFF_COUNT = 42;
@@ -1533,20 +1535,32 @@ struct ExpUpdate_Struct
** Packet Types: See ItemPacketType enum
**
*/
enum ItemPacketType
{
ItemPacketViewLink = 0x00,
ItemPacketMerchant = 0x64,
ItemPacketTradeView = 0x65,
ItemPacketLoot = 0x66,
ItemPacketTrade = 0x67,
ItemPacketCharInventory = 0x69,
ItemPacketLimbo = 0x6A,
ItemPacketWorldContainer = 0x6B,
ItemPacketTributeItem = 0x6C,
ItemPacketGuildTribute = 0x6D,
ItemPacketCharmUpdate = 0x6E, // noted as incorrect
ItemPacketInvalid = 0xFF
enum ItemPacketType {
ItemPacketViewLink = 0x00,
ItemPacketMerchant = 0x64,
ItemPacketTradeView = 0x65,
ItemPacketLoot = 0x66,
ItemPacketTrade = 0x67,
ItemPacketCharInventory = 0x69,
ItemPacketLimbo = 0x6A,
ItemPacketWorldContainer = 0x6B,
ItemPacketTributeItem = 0x6C,
ItemPacketGuildTribute = 0x6D,
ItemPacketCharmUpdate = 0x6E, // noted as incorrect
ItemPacketRecovery = 0x71,
ItemPacketParcel = 0x73,
ItemPacketInvalid = 0xFF
};
enum MerchantWindowTabDisplay {
None = 0x00,
SellBuy = 0x01,
Recover = 0x02,
SellBuyRecover = 0x03,
Parcel = 0x04,
SellBuyParcel = 0x05,
RecoverParcel = 0x06,
SellBuyRecoverParcel = 0x07
};
//enum ItemPacketType
@@ -2057,12 +2071,75 @@ struct TimeOfDay_Struct {
};
// Darvik: shopkeeper structs
struct Merchant_Click_Struct {
/*000*/ uint32 npcid; // Merchant NPC's entity id
/*004*/ uint32 playerid;
/*008*/ uint32 command; //1=open, 0=cancel/close
/*012*/ float rate; //cost multiplier, dosent work anymore
struct MerchantClick_Struct
{
/*000*/ uint32 npc_id; // Merchant NPC's entity id
/*004*/ uint32 player_id;
/*008*/ uint32 command; // 1=open, 0=cancel/close
/*012*/ float rate; // cost multiplier, dosent work anymore
/*016*/ int32 tab_display; // bitmask b000 none, b001 Purchase/Sell, b010 Recover, b100 Parcels
/*020*/ int32 unknown020; // Seen 2592000 from Server or -1 from Client
/*024*/
};
enum MerchantActions {
Close = 0,
Open = 1
};
struct Parcel_Struct
{
/*000*/ uint32 npc_id;
/*004*/ uint32 item_slot;
/*008*/ uint32 quantity;
/*012*/ uint32 money_flag;
/*016*/ char send_to[64];
/*080*/ char note[128];
/*208*/ uint32 unknown_208;
/*212*/ uint32 unknown_212;
/*216*/ uint32 unknown_216;
};
struct ParcelRetrieve_Struct
{
uint32 merchant_entity_id;
uint32 player_entity_id;
uint32 parcel_slot_id;
uint32 parcel_item_id;
};
struct ParcelMessaging_Struct {
ItemPacketType packet_type;
std::string serialized_item;
uint32 sent_time;
std::string player_name;
std::string note;
uint32 slot_id;
template<class Archive>
void serialize(Archive &archive)
{
archive(
CEREAL_NVP(packet_type),
CEREAL_NVP(serialized_item),
CEREAL_NVP(sent_time),
CEREAL_NVP(player_name),
CEREAL_NVP(note),
CEREAL_NVP(slot_id)
);
}
};
struct ParcelIcon_Struct {
uint32 status; //0 off 1 on 2 overlimit
};
enum ParcelIconActions {
IconOff = 0,
IconOn = 1,
Overlimit = 2
};
/*
Unknowns:
0 is e7 from 01 to // MAYBE SLOT IN PURCHASE
+5 -2
View File
@@ -697,8 +697,11 @@ void PlayerEventLogs::SetSettingsDefaults()
m_settings[PlayerEvent::KILLED_NAMED_NPC].event_enabled = 1;
m_settings[PlayerEvent::KILLED_RAID_NPC].event_enabled = 1;
m_settings[PlayerEvent::ITEM_CREATION].event_enabled = 1;
m_settings[PlayerEvent::GUILD_TRIBUTE_DONATE_ITEM].event_enabled = 1;
m_settings[PlayerEvent::GUILD_TRIBUTE_DONATE_PLAT].event_enabled = 1;
m_settings[PlayerEvent::GUILD_TRIBUTE_DONATE_ITEM].event_enabled = 1;
m_settings[PlayerEvent::GUILD_TRIBUTE_DONATE_PLAT].event_enabled = 1;
m_settings[PlayerEvent::PARCEL_SEND].event_enabled = 1;
m_settings[PlayerEvent::PARCEL_RETRIEVE].event_enabled = 1;
m_settings[PlayerEvent::PARCEL_DELETE].event_enabled = 1;
for (int i = PlayerEvent::GM_COMMAND; i != PlayerEvent::MAX; i++) {
m_settings[i].retention_days = RETENTION_DAYS_DEFAULT;
+70 -2
View File
@@ -58,6 +58,9 @@ namespace PlayerEvent {
ITEM_CREATION,
GUILD_TRIBUTE_DONATE_ITEM,
GUILD_TRIBUTE_DONATE_PLAT,
PARCEL_SEND,
PARCEL_RETRIEVE,
PARCEL_DELETE,
MAX // dont remove
};
@@ -66,7 +69,7 @@ namespace PlayerEvent {
// If event is unimplemented just tag (Unimplemented) in the name
// Events don't get saved to the database if unimplemented or deprecated
// Events tagged as deprecated will get automatically removed
static const char *EventName[PlayerEvent::MAX] = {
static const char *EventName[EventType::MAX] = {
"None",
"GM Command",
"Zoning",
@@ -116,7 +119,10 @@ namespace PlayerEvent {
"Killed Raid NPC",
"Item Creation",
"Guild Tribute Donate Item",
"Guild Tribute Donate Platinum"
"Guild Tribute Donate Platinum",
"Parcel Item Sent",
"Parcel Item Retrieved",
"Parcel Prune Routine"
};
// Generic struct used by all events
@@ -976,6 +982,68 @@ namespace PlayerEvent {
);
}
};
struct ParcelRetrieve {
uint32 item_id;
uint32 quantity;
std::string from_player_name;
uint32 sent_date;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(quantity),
CEREAL_NVP(from_player_name),
CEREAL_NVP(sent_date)
);
}
};
struct ParcelSend {
uint32 item_id;
uint32 quantity;
std::string from_player_name;
std::string to_player_name;
uint32 sent_date;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(quantity),
CEREAL_NVP(from_player_name),
CEREAL_NVP(to_player_name),
CEREAL_NVP(sent_date)
);
}
};
struct ParcelDelete {
uint32 item_id;
uint32 quantity;
uint32 char_id;
std::string from_name;
std::string note;
uint32 sent_date;
// cereal
template<class Archive>
void serialize(Archive &ar)
{
ar(
CEREAL_NVP(item_id),
CEREAL_NVP(quantity),
CEREAL_NVP(char_id),
CEREAL_NVP(from_name),
CEREAL_NVP(note),
CEREAL_NVP(sent_date));
}
};
}
#endif //EQEMU_PLAYER_EVENTS_H
-28
View File
@@ -3089,21 +3089,6 @@ namespace RoF
FINISH_ENCODE();
}
ENCODE(OP_ShopRequest)
{
ENCODE_LENGTH_EXACT(Merchant_Click_Struct);
SETUP_DIRECT_ENCODE(Merchant_Click_Struct, structs::Merchant_Click_Struct);
OUT(npcid);
OUT(playerid);
OUT(command);
OUT(rate);
eq->unknown01 = 3; // Not sure what these values do yet, but list won't display without them
eq->unknown02 = 2592000;
FINISH_ENCODE();
}
ENCODE(OP_SkillUpdate)
{
ENCODE_LENGTH_EXACT(SkillUpdate_Struct);
@@ -5047,19 +5032,6 @@ namespace RoF
FINISH_DIRECT_DECODE();
}
DECODE(OP_ShopRequest)
{
DECODE_LENGTH_EXACT(structs::Merchant_Click_Struct);
SETUP_DIRECT_DECODE(Merchant_Click_Struct, structs::Merchant_Click_Struct);
IN(npcid);
IN(playerid);
IN(command);
IN(rate);
FINISH_DIRECT_DECODE();
}
DECODE(OP_Trader)
{
uint32 psize = __packet->size;
+111 -62
View File
@@ -1579,30 +1579,75 @@ namespace RoF2
*p = nullptr;
//store away the emu struct
uchar* __emu_buffer = in->pBuffer;
uchar *__emu_buffer = in->pBuffer;
ItemPacket_Struct *old_item_pkt = (ItemPacket_Struct *) __emu_buffer;
ItemPacket_Struct* old_item_pkt = (ItemPacket_Struct*)__emu_buffer;
EQ::InternalSerializedItem_Struct* int_struct = (EQ::InternalSerializedItem_Struct*)(&__emu_buffer[4]);
switch(old_item_pkt->PacketType)
{
case ItemPacketParcel: {
ParcelMessaging_Struct pms{};
EQ::Util::MemoryStreamReader ss(reinterpret_cast<char *>(in->pBuffer), in->size);
cereal::BinaryInputArchive ar(ss);
ar(pms);
EQ::OutBuffer ob;
EQ::OutBuffer::pos_type last_pos = ob.tellp();
uint32 player_name_length = pms.player_name.length();
uint32 note_length = pms.note.length();
ob.write((const char*)__emu_buffer, 4);
auto *int_struct = (EQ::InternalSerializedItem_Struct *) pms.serialized_item.data();
SerializeItem(ob, (const EQ::ItemInstance*)int_struct->inst, int_struct->slot_id, 0, old_item_pkt->PacketType);
if (ob.tellp() == last_pos) {
LogNetcode("RoF2::ENCODE(OP_ItemPacket) Serialization failed on item slot [{}]", int_struct->slot_id);
delete in;
return;
}
EQ::OutBuffer ob;
EQ::OutBuffer::pos_type last_pos = ob.tellp();
ob.write(reinterpret_cast<const char *>(&pms.packet_type), 4);
in->size = ob.size();
in->pBuffer = ob.detach();
SerializeItem(ob, (const EQ::ItemInstance *) int_struct->inst, pms.slot_id, 0, ItemPacketParcel);
delete[] __emu_buffer;
if (ob.tellp() == last_pos) {
LogNetcode("RoF2::ENCODE(OP_ItemPacket) Serialization failed on item slot [{}]", pms.slot_id);
safe_delete_array(__emu_buffer);
safe_delete(in);
return;
}
dest->FastQueuePacket(&in, ack_req);
}
ob.write((const char *) &pms.sent_time, 4);
ob.write((const char *) &player_name_length, 4);
ob.write(pms.player_name.c_str(), pms.player_name.length());
ob.write((const char *) &note_length, 4);
ob.write(pms.note.c_str(), pms.note.length());
in->size = ob.size();
in->pBuffer = ob.detach();
safe_delete_array(__emu_buffer);
dest->FastQueuePacket(&in, ack_req);
break;
}
default: {
EQ::InternalSerializedItem_Struct *int_struct = (EQ::InternalSerializedItem_Struct *)(&__emu_buffer[4]);
EQ::OutBuffer ob;
EQ::OutBuffer::pos_type last_pos = ob.tellp();
ob.write((const char *)__emu_buffer, 4);
SerializeItem(ob, (const EQ::ItemInstance *)int_struct->inst, int_struct->slot_id, 0,
old_item_pkt->PacketType);
if (ob.tellp() == last_pos) {
LogNetcode("RoF2::ENCODE(OP_ItemPacket) Serialization failed on item slot [{}]",
int_struct->slot_id);
safe_delete_array(__emu_buffer);
safe_delete(in);
return;
}
in->size = ob.size();
in->pBuffer = ob.detach();
safe_delete_array(__emu_buffer);
dest->FastQueuePacket(&in, ack_req);
}
}
}
ENCODE(OP_ItemVerifyReply)
{
@@ -3163,21 +3208,6 @@ namespace RoF2
FINISH_ENCODE();
}
ENCODE(OP_ShopRequest)
{
ENCODE_LENGTH_EXACT(Merchant_Click_Struct);
SETUP_DIRECT_ENCODE(Merchant_Click_Struct, structs::Merchant_Click_Struct);
OUT(npcid);
OUT(playerid);
OUT(command);
OUT(rate);
eq->unknown01 = 3; // Not sure what these values do yet, but list won't display without them
eq->unknown02 = 2592000;
FINISH_ENCODE();
}
ENCODE(OP_SkillUpdate)
{
ENCODE_LENGTH_EXACT(SkillUpdate_Struct);
@@ -5307,15 +5337,17 @@ namespace RoF2
FINISH_DIRECT_DECODE();
}
DECODE(OP_ShopRequest)
DECODE(OP_ShopSendParcel)
{
DECODE_LENGTH_EXACT(structs::Merchant_Click_Struct);
SETUP_DIRECT_DECODE(Merchant_Click_Struct, structs::Merchant_Click_Struct);
DECODE_LENGTH_EXACT(structs::Parcel_Struct);
SETUP_DIRECT_DECODE(Parcel_Struct, structs::Parcel_Struct);
IN(npcid);
IN(playerid);
IN(command);
IN(rate);
IN(npc_id);
IN(quantity);
IN(money_flag);
emu->item_slot = RoF2ToServerTypelessSlot(eq->inventory_slot, invtype::typePossessions);
strn0cpy(emu->send_to, eq->send_to, sizeof(emu->send_to));
strn0cpy(emu->note, eq->note, sizeof(emu->note));
FINISH_DIRECT_DECODE();
}
@@ -5534,11 +5566,24 @@ namespace RoF2
//sprintf(hdr.unknown000, "06e0002Y1W00");
snprintf(hdr.unknown000, sizeof(hdr.unknown000), "%016d", item->ID);
if (packet_type == ItemPacketParcel) {
strn0cpy(
hdr.unknown000,
fmt::format(
"{:03}PAR{:010}\0",
inst->GetMerchantSlot(),
item->ID
).c_str(),
sizeof(hdr.unknown000)
);
}
hdr.stacksize = (inst->IsStackable() ? ((inst->GetCharges() > 1000) ? 0xFFFFFFFF : inst->GetCharges()) : 1);
hdr.stacksize =
item->ID == PARCEL_MONEY_ITEM_ID ? inst->GetPrice() : (inst->IsStackable() ? ((inst->GetCharges() > 1000)
? 0xFFFFFFFF : inst->GetCharges()) : 1);
hdr.unknown004 = 0;
structs::InventorySlot_Struct slot_id;
structs::InventorySlot_Struct slot_id{};
switch (packet_type) {
case ItemPacketLoot:
slot_id = ServerToRoF2CorpseSlot(slot_id_in);
@@ -5548,22 +5593,24 @@ namespace RoF2
break;
}
hdr.slot_type = (inst->GetMerchantSlot() ? invtype::typeMerchant : slot_id.Type);
hdr.main_slot = (inst->GetMerchantSlot() ? inst->GetMerchantSlot() : slot_id.Slot);
hdr.sub_slot = (inst->GetMerchantSlot() ? 0xffff : slot_id.SubIndex);
hdr.aug_slot = (inst->GetMerchantSlot() ? 0xffff : slot_id.AugIndex);
hdr.price = inst->GetPrice();
hdr.merchant_slot = (inst->GetMerchantSlot() ? inst->GetMerchantCount() : 1);
hdr.scaled_value = (inst->IsScaling() ? (inst->GetExp() / 100) : 0);
hdr.instance_id = (inst->GetMerchantSlot() ? inst->GetMerchantSlot() : inst->GetSerialNumber());
hdr.unknown028 = 0;
hdr.slot_type = (inst->GetMerchantSlot() ? invtype::typeMerchant : slot_id.Type);
hdr.main_slot = (inst->GetMerchantSlot() ? inst->GetMerchantSlot() : slot_id.Slot);
hdr.sub_slot = (inst->GetMerchantSlot() ? 0xffff : slot_id.SubIndex);
hdr.aug_slot = (inst->GetMerchantSlot() ? 0xffff : slot_id.AugIndex);
hdr.price = inst->GetPrice();
hdr.merchant_slot = ((inst->GetMerchantSlot() ? inst->GetMerchantCount() : 1));
hdr.scaled_value = (inst->IsScaling() ? (inst->GetExp() / 100) : 0);
hdr.instance_id = (inst->GetMerchantSlot() ? inst->GetMerchantSlot() : inst->GetSerialNumber());
hdr.parcel_item_id = packet_type == ItemPacketParcel ? inst->GetID() : 0;
hdr.last_cast_time = inst->GetRecastTimestamp();
hdr.charges = (inst->IsStackable() ? (item->MaxCharges ? 1 : 0) : ((inst->GetCharges() > 254) ? 0xFFFFFFFF : inst->GetCharges()));
hdr.inst_nodrop = (inst->IsAttuned() ? 1 : 0);
hdr.unknown044 = 0;
hdr.unknown048 = 0;
hdr.unknown052 = 0;
hdr.isEvolving = item->EvolvingItem;
hdr.charges = (inst->IsStackable() ? (item->MaxCharges ? 1 : 0) : ((inst->GetCharges() > 254)
? 0xFFFFFFFF
: inst->GetCharges()));
hdr.inst_nodrop = (inst->IsAttuned() ? 1 : 0);
hdr.unknown044 = 0;
hdr.unknown048 = 0;
hdr.unknown052 = 0;
hdr.isEvolving = item->EvolvingItem;
ob.write((const char*)&hdr, sizeof(RoF2::structs::ItemSerializationHeader));
@@ -5621,9 +5668,10 @@ namespace RoF2
ob.write((const char*)&hdrf, sizeof(RoF2::structs::ItemSerializationHeaderFinish));
if (strlen(item->Name) > 0)
if (strlen(item->Name) > 0) {
ob.write(item->Name, strlen(item->Name));
ob.write("\0", 1);
ob.write("\0", 1);
}
if (strlen(item->Lore) > 0)
ob.write(item->Lore, strlen(item->Lore));
@@ -5784,10 +5832,11 @@ namespace RoF2
itbs.unknown5 = 0;
itbs.potion_belt_enabled = item->PotionBelt;
itbs.potion_belt_slots = item->PotionBeltSlots;
itbs.stacksize = (inst->IsStackable() ? item->StackSize : 0);
itbs.no_transfer = item->NoTransfer;
itbs.expendablearrow = item->ExpendableArrow;
itbs.potion_belt_slots = item->PotionBeltSlots;
itbs.stacksize =
item->ID == PARCEL_MONEY_ITEM_ID ? 0x7FFFFFFF : ((inst->IsStackable() ? item->StackSize : 0));
itbs.no_transfer = item->NoTransfer;
itbs.expendablearrow = item->ExpendableArrow;
// Done to hack older clients to label expendable fishing poles as such
// July 28th, 2018 patch
+1 -2
View File
@@ -116,7 +116,6 @@ E(OP_SendZonepoints)
E(OP_SetGuildRank)
E(OP_ShopPlayerBuy)
E(OP_ShopPlayerSell)
E(OP_ShopRequest)
E(OP_SkillUpdate)
E(OP_SomeItemPacketMaybe)
E(OP_SpawnAppearance)
@@ -200,7 +199,7 @@ D(OP_Save)
D(OP_SetServerFilter)
D(OP_ShopPlayerBuy)
D(OP_ShopPlayerSell)
D(OP_ShopRequest)
D(OP_ShopSendParcel)
D(OP_Trader)
D(OP_TraderBuy)
D(OP_TradeSkillCombine)
+41 -27
View File
@@ -2247,15 +2247,17 @@ struct TimeOfDay_Struct {
};
// Darvik: shopkeeper structs
struct Merchant_Click_Struct {
/*000*/ uint32 npcid; // Merchant NPC's entity id
/*004*/ uint32 playerid;
/*008*/ uint32 command; // 1=open, 0=cancel/close
/*012*/ float rate; // cost multiplier, dosent work anymore
/*016*/ int32 unknown01; // Seen 3 from Server or -1 from Client
/*020*/ int32 unknown02; // Seen 2592000 from Server or -1 from Client
/*024*/
struct MerchantClick_Struct
{
/*000*/ uint32 npc_id; // Merchant NPC's entity id
/*004*/ uint32 player_id;
/*008*/ uint32 command; // 1=open, 0=cancel/close
/*012*/ float rate; // cost multiplier, dosent work anymore
/*016*/ int32 tab_display; // bitmask b000 none, b001 Purchase/Sell, b010 Recover, b100 Parcels
/*020*/ int32 unknown02; // Seen 2592000 from Server or -1 from Client
/*024*/
};
/*
Unknowns:
0 is e7 from 01 to // MAYBE SLOT IN PURCHASE
@@ -4572,25 +4574,25 @@ struct RoF2SlotStruct
struct ItemSerializationHeader
{
/*000*/ char unknown000[17]; // New for HoT. Looks like a string.
/*017*/ uint32 stacksize;
/*021*/ uint32 unknown004;
/*025*/ uint8 slot_type; // 0 = normal, 1 = bank, 2 = shared bank, 9 = merchant, 20 = ?
/*026*/ uint16 main_slot;
/*028*/ uint16 sub_slot;
/*030*/ uint16 aug_slot; // 0xffff
/*032*/ uint32 price;
/*036*/ uint32 merchant_slot; //1 if not a merchant item
/*040*/ uint32 scaled_value; //0
/*044*/ uint32 instance_id; //unique instance id if not merchant item, else is merchant slot
/*048*/ uint32 unknown028; //0
/*052*/ uint32 last_cast_time; // Unix Time from PP of last cast for this recast type if recast delay > 0
/*056*/ uint32 charges; //Total Charges an item has (-1 for unlimited)
/*060*/ uint32 inst_nodrop; // 1 if the item is no drop (attuned items)
/*064*/ uint32 unknown044; // 0
/*068*/ uint32 unknown048; // 0
/*072*/ uint32 unknown052; // 0
uint8 isEvolving;
/*000*/ char unknown000[17]; // New for HoT. Looks like a string.
/*017*/ uint32 stacksize;
/*021*/ uint32 unknown004;
/*025*/ uint8 slot_type; // 0 = normal, 1 = bank, 2 = shared bank, 9 = merchant, 20 = ?
/*026*/ uint16 main_slot;
/*028*/ uint16 sub_slot;
/*030*/ uint16 aug_slot; // 0xffff
/*032*/ uint32 price;
/*036*/ uint32 merchant_slot; // 1 if not a merchant item
/*040*/ uint32 scaled_value; // 0
/*044*/ uint32 instance_id; // unique instance id if not merchant item, else is merchant slot
/*048*/ uint32 parcel_item_id;
/*052*/ uint32 last_cast_time; // Unix Time from PP of last cast for this recast type if recast delay > 0
/*056*/ uint32 charges; // Total Charges an item has (-1 for unlimited)
/*060*/ uint32 inst_nodrop; // 1 if the item is no drop (attuned items)
/*064*/ uint32 unknown044; // 0
/*068*/ uint32 unknown048; // 0
/*072*/ uint32 unknown052; // 0
uint8 isEvolving;
};
struct EvolvingItem {
@@ -5261,6 +5263,18 @@ struct Checksum_Struct {
uint8_t data[2048];
};
struct Parcel_Struct
{
/*000*/ uint32 npc_id;
/*004*/ TypelessInventorySlot_Struct inventory_slot;
/*012*/ uint32 quantity;
/*016*/ uint32 money_flag;
/*020*/ char send_to[64];
/*084*/ char note[128];
/*212*/ uint32 unknown_212;
/*216*/ uint32 unknown_216;
/*220*/ uint32 unknown_220;
};
}; /*structs*/
}; /*RoF2*/
-2
View File
@@ -101,7 +101,6 @@ E(OP_SendZonepoints)
E(OP_SetGuildRank)
E(OP_ShopPlayerBuy)
E(OP_ShopPlayerSell)
E(OP_ShopRequest)
E(OP_SkillUpdate)
E(OP_SomeItemPacketMaybe)
E(OP_SpawnAppearance)
@@ -183,7 +182,6 @@ D(OP_Save)
D(OP_SetServerFilter)
D(OP_ShopPlayerBuy)
D(OP_ShopPlayerSell)
D(OP_ShopRequest)
D(OP_Trader)
D(OP_TraderBuy)
D(OP_TradeSkillCombine)
+10 -8
View File
@@ -2200,15 +2200,17 @@ struct TimeOfDay_Struct {
};
// Darvik: shopkeeper structs
struct Merchant_Click_Struct {
/*000*/ uint32 npcid; // Merchant NPC's entity id
/*004*/ uint32 playerid;
/*008*/ uint32 command; // 1=open, 0=cancel/close
/*012*/ float rate; // cost multiplier, dosent work anymore
/*016*/ int32 unknown01; // Seen 3 from Server or -1 from Client
/*020*/ int32 unknown02; // Seen 2592000 from Server or -1 from Client
/*024*/
struct MerchantClick_Struct
{
/*000*/ uint32 npc_id; // Merchant NPC's entity id
/*004*/ uint32 player_id;
/*008*/ uint32 command; // 1=open, 0=cancel/close
/*012*/ float rate; // cost multiplier, dosent work anymore
/*016*/ int32 tab_display; // bitmask b000 none, b001 Purchase/Sell, b010 Recover, b100 Parcels
/*020*/ int32 unknown020; // Seen 2592000 from Server or -1 from Client
/*024*/
};
/*
Unknowns:
0 is e7 from 01 to // MAYBE SLOT IN PURCHASE
+28
View File
@@ -2026,6 +2026,19 @@ namespace SoD
FINISH_ENCODE();
}
ENCODE(OP_ShopRequest)
{
ENCODE_LENGTH_EXACT(MerchantClick_Struct);
SETUP_DIRECT_ENCODE(MerchantClick_Struct, structs::MerchantClick_Struct);
OUT(npc_id);
OUT(player_id);
OUT(command);
OUT(rate);
FINISH_ENCODE();
}
ENCODE(OP_SomeItemPacketMaybe)
{
// This Opcode is not named very well. It is used for the animation of arrows leaving the player's bow
@@ -3483,6 +3496,21 @@ namespace SoD
FINISH_DIRECT_DECODE();
}
DECODE(OP_ShopRequest)
{
DECODE_LENGTH_EXACT(structs::MerchantClick_Struct);
SETUP_DIRECT_DECODE(MerchantClick_Struct, structs::MerchantClick_Struct);
IN(npc_id);
IN(player_id);
IN(command);
IN(rate);
emu->tab_display = 0;
emu->unknown020 = 0;
FINISH_DIRECT_DECODE();
}
DECODE(OP_TraderBuy)
{
DECODE_LENGTH_EXACT(structs::TraderBuy_Struct);
+2
View File
@@ -78,6 +78,7 @@ E(OP_SendCharInfo)
E(OP_SendZonepoints)
E(OP_ShopPlayerBuy)
E(OP_ShopPlayerSell)
E(OP_ShopRequest)
E(OP_SomeItemPacketMaybe)
E(OP_SpawnDoor)
E(OP_SpecialMesg)
@@ -141,6 +142,7 @@ D(OP_Save)
D(OP_SetServerFilter)
D(OP_ShopPlayerBuy)
D(OP_ShopPlayerSell)
D(OP_ShopRequest)
D(OP_TraderBuy)
D(OP_TradeSkillCombine)
D(OP_TributeItem)
+9 -5
View File
@@ -1845,12 +1845,16 @@ struct TimeOfDay_Struct {
};
// Darvik: shopkeeper structs
struct Merchant_Click_Struct {
/*000*/ uint32 npcid; // Merchant NPC's entity id
/*004*/ uint32 playerid;
/*008*/ uint32 command; //1=open, 0=cancel/close
/*012*/ float rate; //cost multiplier, dosent work anymore
struct MerchantClick_Struct
{
/*000*/ uint32 npc_id; // Merchant NPC's entity id
/*004*/ uint32 player_id;
/*008*/ uint32 command; // 1=open, 0=cancel/close
/*012*/ float rate; // cost multiplier, dosent work anymore
/*016*/ int32 tab_display; // bitmask b000 none, b001 Purchase/Sell, b010 Recover, b100 Parcels
/*020*/ int32 unknown020; // Seen 2592000 from Server or -1 from Client
};
/*
Unknowns:
0 is e7 from 01 to // MAYBE SLOT IN PURCHASE
+28
View File
@@ -1683,6 +1683,19 @@ namespace SoF
FINISH_ENCODE();
}
ENCODE(OP_ShopRequest)
{
ENCODE_LENGTH_EXACT(MerchantClick_Struct);
SETUP_DIRECT_ENCODE(MerchantClick_Struct, structs::MerchantClick_Struct);
OUT(npc_id);
OUT(player_id);
OUT(command);
OUT(rate);
FINISH_ENCODE();
}
ENCODE(OP_SomeItemPacketMaybe)
{
// This Opcode is not named very well. It is used for the animation of arrows leaving the player's bow
@@ -2874,6 +2887,21 @@ namespace SoF
FINISH_DIRECT_DECODE();
}
DECODE(OP_ShopRequest)
{
DECODE_LENGTH_EXACT(structs::MerchantClick_Struct);
SETUP_DIRECT_DECODE(MerchantClick_Struct, structs::MerchantClick_Struct);
IN(npc_id);
IN(player_id);
IN(command);
IN(rate);
emu->tab_display = 0;
emu->unknown020 = 0;
FINISH_DIRECT_DECODE();
}
DECODE(OP_TraderBuy)
{
DECODE_LENGTH_EXACT(structs::TraderBuy_Struct);
+2
View File
@@ -72,6 +72,7 @@ E(OP_SendAATable)
E(OP_SendCharInfo)
E(OP_SendZonepoints)
E(OP_ShopPlayerSell)
E(OP_ShopRequest)
E(OP_SomeItemPacketMaybe)
E(OP_SpawnDoor)
E(OP_SpecialMesg)
@@ -128,6 +129,7 @@ D(OP_ReadBook)
D(OP_Save)
D(OP_SetServerFilter)
D(OP_ShopPlayerSell)
D(OP_ShopRequest)
D(OP_TraderBuy)
D(OP_TradeSkillCombine)
D(OP_TributeItem)
+9 -5
View File
@@ -1859,12 +1859,16 @@ struct TimeOfDay_Struct {
};
// Darvik: shopkeeper structs
struct Merchant_Click_Struct {
/*000*/ uint32 npcid; // Merchant NPC's entity id
/*004*/ uint32 playerid;
/*008*/ uint32 command; //1=open, 0=cancel/close
/*012*/ float rate; //cost multiplier, dosent work anymore
struct MerchantClick_Struct
{
/*000*/ uint32 npc_id; // Merchant NPC's entity id
/*004*/ uint32 player_id;
/*008*/ uint32 command; // 1=open, 0=cancel/close
/*012*/ float rate; // cost multiplier, dosent work anymore
/*016*/ int32 tab_display; // bitmask b000 none, b001 Purchase/Sell, b010 Recover, b100 Parcels
/*020*/ int32 unknown020; // Seen 2592000 from Server or -1 from Client
};
/*
Unknowns:
0 is e7 from 01 to // MAYBE SLOT IN PURCHASE
+28
View File
@@ -1858,6 +1858,19 @@ namespace Titanium
FINISH_ENCODE();
}
ENCODE(OP_ShopRequest)
{
ENCODE_LENGTH_EXACT(MerchantClick_Struct);
SETUP_DIRECT_ENCODE(MerchantClick_Struct, structs::MerchantClick_Struct);
OUT(npc_id);
OUT(player_id);
OUT(command);
OUT(rate);
FINISH_ENCODE();
}
ENCODE(OP_SpecialMesg)
{
EQApplicationPacket *in = *p;
@@ -2875,6 +2888,21 @@ namespace Titanium
FINISH_DIRECT_DECODE();
}
DECODE(OP_ShopRequest)
{
DECODE_LENGTH_EXACT(structs::MerchantClick_Struct);
SETUP_DIRECT_DECODE(MerchantClick_Struct, structs::MerchantClick_Struct);
IN(npc_id);
IN(player_id);
IN(command);
IN(rate);
emu->tab_display = 0;
emu->unknown020 = 0;
FINISH_DIRECT_DECODE();
}
DECODE(OP_TraderBuy)
{
DECODE_LENGTH_EXACT(structs::TraderBuy_Struct);
+2
View File
@@ -74,6 +74,7 @@ E(OP_SendCharInfo)
E(OP_SendAATable)
E(OP_SetFace)
E(OP_ShopPlayerSell)
E(OP_ShopRequest)
E(OP_SpawnAppearance)
E(OP_SpecialMesg)
E(OP_TaskDescription)
@@ -120,6 +121,7 @@ D(OP_RaidInvite)
D(OP_ReadBook)
D(OP_SetServerFilter)
D(OP_ShopPlayerSell)
D(OP_ShopRequest)
D(OP_TraderBuy)
D(OP_TradeSkillCombine)
D(OP_TributeItem)
+3 -3
View File
@@ -1669,9 +1669,9 @@ struct TimeOfDay_Struct {
};
// Darvik: shopkeeper structs
struct Merchant_Click_Struct {
/*000*/ uint32 npcid; // Merchant NPC's entity id
/*004*/ uint32 playerid;
struct MerchantClick_Struct {
/*000*/ uint32 npc_id; // Merchant NPC's entity id
/*004*/ uint32 player_id;
/*008*/ uint32 command; //1=open, 0=cancel/close
/*012*/ float rate; //cost multiplier, dosent work anymore
};
+28
View File
@@ -2454,6 +2454,19 @@ namespace UF
FINISH_ENCODE();
}
ENCODE(OP_ShopRequest)
{
ENCODE_LENGTH_EXACT(MerchantClick_Struct);
SETUP_DIRECT_ENCODE(MerchantClick_Struct, structs::MerchantClick_Struct);
OUT(npc_id);
OUT(player_id);
OUT(command);
OUT(rate);
FINISH_ENCODE();
}
ENCODE(OP_SomeItemPacketMaybe)
{
// This Opcode is not named very well. It is used for the animation of arrows leaving the player's bow
@@ -4047,6 +4060,21 @@ namespace UF
FINISH_DIRECT_DECODE();
}
DECODE(OP_ShopRequest)
{
DECODE_LENGTH_EXACT(structs::MerchantClick_Struct);
SETUP_DIRECT_DECODE(MerchantClick_Struct, structs::MerchantClick_Struct);
IN(npc_id);
IN(player_id);
IN(command);
IN(rate);
emu->tab_display = 0;
emu->unknown020 = 0;
FINISH_DIRECT_DECODE();
}
DECODE(OP_TraderBuy)
{
DECODE_LENGTH_EXACT(structs::TraderBuy_Struct);
+2
View File
@@ -89,6 +89,7 @@ E(OP_SendZonepoints)
E(OP_SetGuildRank)
E(OP_ShopPlayerBuy)
E(OP_ShopPlayerSell)
E(OP_ShopRequest)
E(OP_SomeItemPacketMaybe)
E(OP_SpawnAppearance)
E(OP_SpawnDoor)
@@ -158,6 +159,7 @@ D(OP_Save)
D(OP_SetServerFilter)
D(OP_ShopPlayerBuy)
D(OP_ShopPlayerSell)
D(OP_ShopRequest)
D(OP_TraderBuy)
D(OP_TradeSkillCombine)
D(OP_TributeItem)
+3 -3
View File
@@ -1916,9 +1916,9 @@ struct TimeOfDay_Struct {
};
// Darvik: shopkeeper structs
struct Merchant_Click_Struct {
/*000*/ uint32 npcid; // Merchant NPC's entity id
/*004*/ uint32 playerid;
struct MerchantClick_Struct {
/*000*/ uint32 npc_id; // Merchant NPC's entity id
/*004*/ uint32 player_id;
/*008*/ uint32 command; //1=open, 0=cancel/close
/*012*/ float rate; //cost multiplier, dosent work anymore
};
@@ -0,0 +1,463 @@
/**
* 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_REPOSITORY_H
#define EQEMU_BASE_CHARACTER_PARCELS_REPOSITORY_H
#include "../../database.h"
#include "../../strings.h"
#include <ctime>
class BaseCharacterParcelsRepository {
public:
struct CharacterParcels {
uint32_t id;
uint32_t char_id;
uint32_t item_id;
uint32_t slot_id;
uint32_t quantity;
std::string from_name;
std::string note;
time_t sent_date;
};
static std::string PrimaryKey()
{
return std::string("id");
}
static std::vector<std::string> Columns()
{
return {
"id",
"char_id",
"item_id",
"slot_id",
"quantity",
"from_name",
"note",
"sent_date",
};
}
static std::vector<std::string> SelectColumns()
{
return {
"id",
"char_id",
"item_id",
"slot_id",
"quantity",
"from_name",
"note",
"UNIX_TIMESTAMP(sent_date)",
};
}
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");
}
static std::string BaseSelect()
{
return fmt::format(
"SELECT {} FROM {}",
SelectColumnsRaw(),
TableName()
);
}
static std::string BaseInsert()
{
return fmt::format(
"INSERT INTO {} ({}) ",
TableName(),
ColumnsRaw()
);
}
static CharacterParcels NewEntity()
{
CharacterParcels e{};
e.id = 0;
e.char_id = 0;
e.item_id = 0;
e.slot_id = 0;
e.quantity = 0;
e.from_name = "";
e.note = "";
e.sent_date = 0;
return e;
}
static CharacterParcels GetCharacterParcels(
const std::vector<CharacterParcels> &character_parcelss,
int character_parcels_id
)
{
for (auto &character_parcels : character_parcelss) {
if (character_parcels.id == character_parcels_id) {
return character_parcels;
}
}
return NewEntity();
}
static CharacterParcels FindOne(
Database& db,
int character_parcels_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"{} WHERE {} = {} LIMIT 1",
BaseSelect(),
PrimaryKey(),
character_parcels_id
)
);
auto row = results.begin();
if (results.RowCount() == 1) {
CharacterParcels e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.slot_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.quantity = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.from_name = row[5] ? row[5] : "";
e.note = row[6] ? row[6] : "";
e.sent_date = strtoll(row[7] ? row[7] : "-1", nullptr, 10);
return e;
}
return NewEntity();
}
static int DeleteOne(
Database& db,
int character_parcels_id
)
{
auto results = db.QueryDatabase(
fmt::format(
"DELETE FROM {} WHERE {} = {}",
TableName(),
PrimaryKey(),
character_parcels_id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static int UpdateOne(
Database& db,
const CharacterParcels &e
)
{
std::vector<std::string> v;
auto columns = Columns();
v.push_back(columns[1] + " = " + std::to_string(e.char_id));
v.push_back(columns[2] + " = " + std::to_string(e.item_id));
v.push_back(columns[3] + " = " + std::to_string(e.slot_id));
v.push_back(columns[4] + " = " + std::to_string(e.quantity));
v.push_back(columns[5] + " = '" + Strings::Escape(e.from_name) + "'");
v.push_back(columns[6] + " = '" + Strings::Escape(e.note) + "'");
v.push_back(columns[7] + " = FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")");
auto results = db.QueryDatabase(
fmt::format(
"UPDATE {} SET {} WHERE {} = {}",
TableName(),
Strings::Implode(", ", v),
PrimaryKey(),
e.id
)
);
return (results.Success() ? results.RowsAffected() : 0);
}
static CharacterParcels InsertOne(
Database& db,
CharacterParcels e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.char_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.quantity));
v.push_back("'" + Strings::Escape(e.from_name) + "'");
v.push_back("'" + Strings::Escape(e.note) + "'");
v.push_back("FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")");
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<CharacterParcels> &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.char_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.quantity));
v.push_back("'" + Strings::Escape(e.from_name) + "'");
v.push_back("'" + Strings::Escape(e.note) + "'");
v.push_back("FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")");
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<CharacterParcels> All(Database& db)
{
std::vector<CharacterParcels> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"{}",
BaseSelect()
)
);
all_entries.reserve(results.RowCount());
for (auto row = results.begin(); row != results.end(); ++row) {
CharacterParcels e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.slot_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.quantity = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.from_name = row[5] ? row[5] : "";
e.note = row[6] ? row[6] : "";
e.sent_date = strtoll(row[7] ? row[7] : "-1", nullptr, 10);
all_entries.push_back(e);
}
return all_entries;
}
static std::vector<CharacterParcels> GetWhere(Database& db, const std::string &where_filter)
{
std::vector<CharacterParcels> 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) {
CharacterParcels e{};
e.id = row[0] ? static_cast<uint32_t>(strtoul(row[0], nullptr, 10)) : 0;
e.char_id = row[1] ? static_cast<uint32_t>(strtoul(row[1], nullptr, 10)) : 0;
e.item_id = row[2] ? static_cast<uint32_t>(strtoul(row[2], nullptr, 10)) : 0;
e.slot_id = row[3] ? static_cast<uint32_t>(strtoul(row[3], nullptr, 10)) : 0;
e.quantity = row[4] ? static_cast<uint32_t>(strtoul(row[4], nullptr, 10)) : 0;
e.from_name = row[5] ? row[5] : "";
e.note = row[6] ? row[6] : "";
e.sent_date = strtoll(row[7] ? row[7] : "-1", nullptr, 10);
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 CharacterParcels &e
)
{
std::vector<std::string> v;
v.push_back(std::to_string(e.id));
v.push_back(std::to_string(e.char_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.quantity));
v.push_back("'" + Strings::Escape(e.from_name) + "'");
v.push_back("'" + Strings::Escape(e.note) + "'");
v.push_back("FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")");
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<CharacterParcels> &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.char_id));
v.push_back(std::to_string(e.item_id));
v.push_back(std::to_string(e.slot_id));
v.push_back(std::to_string(e.quantity));
v.push_back("'" + Strings::Escape(e.from_name) + "'");
v.push_back("'" + Strings::Escape(e.note) + "'");
v.push_back("FROM_UNIXTIME(" + (e.sent_date > 0 ? std::to_string(e.sent_date) : "null") + ")");
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_REPOSITORY_H
@@ -146,6 +146,7 @@ public:
int32_t heroic_strikethrough;
int32_t faction_amount;
uint8_t keeps_sold_items;
uint8_t is_parcel_merchant;
};
static std::string PrimaryKey()
@@ -283,6 +284,7 @@ public:
"heroic_strikethrough",
"faction_amount",
"keeps_sold_items",
"is_parcel_merchant",
};
}
@@ -416,6 +418,7 @@ public:
"heroic_strikethrough",
"faction_amount",
"keeps_sold_items",
"is_parcel_merchant",
};
}
@@ -583,6 +586,7 @@ public:
e.heroic_strikethrough = 0;
e.faction_amount = 0;
e.keeps_sold_items = 1;
e.is_parcel_merchant = 0;
return e;
}
@@ -746,6 +750,7 @@ public:
e.heroic_strikethrough = row[124] ? static_cast<int32_t>(atoi(row[124])) : 0;
e.faction_amount = row[125] ? static_cast<int32_t>(atoi(row[125])) : 0;
e.keeps_sold_items = row[126] ? static_cast<uint8_t>(strtoul(row[126], nullptr, 10)) : 1;
e.is_parcel_merchant = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 0;
return e;
}
@@ -905,6 +910,7 @@ public:
v.push_back(columns[124] + " = " + std::to_string(e.heroic_strikethrough));
v.push_back(columns[125] + " = " + std::to_string(e.faction_amount));
v.push_back(columns[126] + " = " + std::to_string(e.keeps_sold_items));
v.push_back(columns[127] + " = " + std::to_string(e.is_parcel_merchant));
auto results = db.QueryDatabase(
fmt::format(
@@ -1053,6 +1059,7 @@ public:
v.push_back(std::to_string(e.heroic_strikethrough));
v.push_back(std::to_string(e.faction_amount));
v.push_back(std::to_string(e.keeps_sold_items));
v.push_back(std::to_string(e.is_parcel_merchant));
auto results = db.QueryDatabase(
fmt::format(
@@ -1209,6 +1216,7 @@ public:
v.push_back(std::to_string(e.heroic_strikethrough));
v.push_back(std::to_string(e.faction_amount));
v.push_back(std::to_string(e.keeps_sold_items));
v.push_back(std::to_string(e.is_parcel_merchant));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@@ -1369,6 +1377,7 @@ public:
e.heroic_strikethrough = row[124] ? static_cast<int32_t>(atoi(row[124])) : 0;
e.faction_amount = row[125] ? static_cast<int32_t>(atoi(row[125])) : 0;
e.keeps_sold_items = row[126] ? static_cast<uint8_t>(strtoul(row[126], nullptr, 10)) : 1;
e.is_parcel_merchant = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 0;
all_entries.push_back(e);
}
@@ -1520,6 +1529,7 @@ public:
e.heroic_strikethrough = row[124] ? static_cast<int32_t>(atoi(row[124])) : 0;
e.faction_amount = row[125] ? static_cast<int32_t>(atoi(row[125])) : 0;
e.keeps_sold_items = row[126] ? static_cast<uint8_t>(strtoul(row[126], nullptr, 10)) : 1;
e.is_parcel_merchant = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 0;
all_entries.push_back(e);
}
@@ -1721,6 +1731,7 @@ public:
v.push_back(std::to_string(e.heroic_strikethrough));
v.push_back(std::to_string(e.faction_amount));
v.push_back(std::to_string(e.keeps_sold_items));
v.push_back(std::to_string(e.is_parcel_merchant));
auto results = db.QueryDatabase(
fmt::format(
@@ -1870,6 +1881,7 @@ public:
v.push_back(std::to_string(e.heroic_strikethrough));
v.push_back(std::to_string(e.faction_amount));
v.push_back(std::to_string(e.keeps_sold_items));
v.push_back(std::to_string(e.is_parcel_merchant));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
}
@@ -0,0 +1,83 @@
#ifndef EQEMU_CHARACTER_PARCELS_REPOSITORY_H
#define EQEMU_CHARACTER_PARCELS_REPOSITORY_H
#include "../database.h"
#include "../strings.h"
#include "base/base_character_parcels_repository.h"
class CharacterParcelsRepository: public BaseCharacterParcelsRepository {
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
*
* ParcelsRepository::GetByZoneAndVersion(int zone_id, int zone_version)
* ParcelsRepository::GetWhereNeverExpires()
* ParcelsRepository::GetWhereXAndY()
* ParcelsRepository::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
struct ParcelCountAndCharacterName
{
std::string character_name;
uint32 char_id;
uint32 parcel_count;
};
static std::vector<ParcelCountAndCharacterName> GetParcelCountAndCharacterName(Database &db, const std::string &character_name)
{
std::vector<ParcelCountAndCharacterName> all_entries;
auto results = db.QueryDatabase(
fmt::format(
"SELECT c.name, COUNT(p.id), c.id FROM character_data c "
"JOIN character_parcels p ON p.char_id = c.id "
"WHERE c.name = '{}' "
"LIMIT 1",
character_name)
);
all_entries.reserve(results.RowCount());
for(auto row = results.begin(); row != results.end(); ++row) {
ParcelCountAndCharacterName e {};
e.character_name = row[0] ? row[0] : "";
e.parcel_count = row[1] ? Strings::ToUnsignedInt(row[1]) : 0;
e.char_id = row[2] ? Strings::ToUnsignedInt(row[2]) : 0;
all_entries.push_back(e);
}
return all_entries;
}
};
#endif //EQEMU_CHARACTER_PARCELS_REPOSITORY_H
+10
View File
@@ -959,6 +959,16 @@ RULE_BOOL(Items, DisablePotionBelt, false, "Enable this to disable Potion Belt I
RULE_BOOL(Items, DisableSpellFocusEffects, false, "Enable this to disable Spell Focus Effects on Items")
RULE_CATEGORY_END()
RULE_CATEGORY(Parcel)
RULE_BOOL(Parcel, EnableParcelMerchants, true, "Enable or Disable Parcel Merchants. Requires RoF+ Clients.")
RULE_BOOL(Parcel, EnableDirectToInventoryDelivery, false, "Enable or Disable RoF2 bazaar purchases to be delivered directly to the buyer's inventory.")
RULE_BOOL(Parcel, DeleteOnDuplicate, false, "Delete retrieved item if it creates a lore conflict.")
RULE_BOOL(Parcel, EnablePruning, false, "Enable the automatic pruning of sent parcels. Uses rule ParcelPruneDelay for prune delay.")
RULE_INT(Parcel, ParcelDeliveryDelay, 30000, "Sets the time that a player must wait between sending parcels.")
RULE_INT(Parcel, ParcelMaxItems, 50, "The maximum number of parcels a player is allowed to have in their mailbox.")
RULE_INT(Parcel, ParcelPruneDelay, 30, "The number of days after which a parcel is deleted. Items are lost!")
RULE_CATEGORY_END()
#undef RULE_CATEGORY
#undef RULE_INT
#undef RULE_REAL
+1 -1
View File
@@ -11,7 +11,7 @@ namespace ServerEvents {
static const std::string EVENT_TYPE_RELOAD_WORLD = "reload_world";
static const std::string EVENT_TYPE_RULE_CHANGE = "rule_change";
static const std::string EVENT_TYPE_CONTENT_FLAG_CHANGE = "content_flag_change";
}
} // namespace ServerEvents
class ServerEventScheduler {
public:
+3
View File
@@ -113,6 +113,9 @@
#define ServerOP_GuildSendGuildList 0x007E
#define ServerOP_GuildMembersList 0x007F
#define ServerOP_ParcelDelivery 0x0090
#define ServerOP_ParcelPrune 0x0091
#define ServerOP_RaidAdd 0x0100 //in use
#define ServerOP_RaidRemove 0x0101 //in use
#define ServerOP_RaidDisband 0x0102 //in use
+1 -1
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 9270
#define CURRENT_BINARY_DATABASE_VERSION 9271
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9043
#endif