[Feature] Add RoF2 Bazaar Support (#4315)

* Add RoF2 Bazaar Support

Enable RoF2 bazaar features

* Add augments to Trader Items

* Cleanup

Cleanup of formatting and unused functions

* Update PlayerProfile for correct char_id in trader transactions.  Further cleanup.

* Add parcel delivery price functionality

Add parcel delivery price functionality via rules and new delivery cost struct.

* Add RoF support for bazaar window outside of bazaar with parcel delivery

* Further Testing and ActiveTransaction added

Further testing and a few fixes and messages added.  Add active transaction check to ensure two clients cannot purchase from the bazaar window at the same time

* Cleanup and Formatting updates

Cleanup and Formatting updates

* Update database manifest for the trader table against default peq trader table

* Logs and formatting

* Update bazaarsearch to be content_db aware

* Fix crash

* Simplify search

* Search fixes

* Push up more search logging

* More search fixes

* Formatting

* Update trader_repository.h

* Add Rule for Bazaar Parcel Delivery

Add a rule Bazaar:EnableParcelDelivery to enable/disable bazaar parcel delivery.  Default is True.

* Fix crash

* Update Bazaar Search

Adds/Tested bazaar search with move to content_db
- race, class, money, number of returned items, stats, name, slot, level, traders, local traders, specific trader.
Outstanding
- type, more stats to add (heroic, etc)

* Formatting

* Push

* Update bazaarsearch to include all stats that are available in RoF2

* Update BazaarSearch

Updates the bazaar search for item types.  They should be working as per RoF2+ types.

* Formatting

* Final updates to BazaarSearch

Add search by augmentation slots available on the item.
This enables all but Prestige, which I believe are not implemented yet.

* Add Titanium functionality correct ItemType Search

Add Titanium /trader /bazaar functionality.
Added itemtype=armor bazaar search.  It was missed in the search work

* Close off for loops

---------

Co-authored-by: Akkadius <akkadius1@gmail.com>
This commit is contained in:
Mitch Freeman
2024-05-26 17:38:25 -03:00
committed by GitHub
parent d767217461
commit fc79614fac
51 changed files with 4793 additions and 2258 deletions
+397 -68
View File
@@ -32,6 +32,7 @@
#include "../strings.h"
#include "../item_instance.h"
#include "titanium_structs.h"
#include "../rulesys.h"
#include "../path_manager.h"
#include "../raid.h"
#include "../guilds.h"
@@ -197,64 +198,135 @@ namespace Titanium
ENCODE(OP_BazaarSearch)
{
if (((*p)->size == sizeof(BazaarReturnDone_Struct)) || ((*p)->size == sizeof(BazaarWelcome_Struct))) {
EQApplicationPacket *in = *p;
*p = nullptr;
dest->FastQueuePacket(&in, ack_req);
return;
}
//consume the packet
EQApplicationPacket *in = *p;
*p = nullptr;
//store away the emu struct
unsigned char *__emu_buffer = in->pBuffer;
BazaarSearchResults_Struct *emu = (BazaarSearchResults_Struct *)__emu_buffer;
uint32 action = *(uint32 *)in->pBuffer;
//determine and verify length
int entrycount = in->size / sizeof(BazaarSearchResults_Struct);
if (entrycount == 0 || (in->size % sizeof(BazaarSearchResults_Struct)) != 0) {
Log(Logs::General, Logs::Netcode, "[STRUCTS] Wrong size on outbound %s: Got %d, expected multiple of %d",
opcodes->EmuToName(in->GetOpcode()), in->size, sizeof(BazaarSearchResults_Struct));
delete in;
return;
switch (action) {
case BazaarSearch: {
LogTrading(
"Encode OP_BazaarSearch(Ti) BazaarSearch action <green>[{}]",
action
);
std::vector<BazaarSearchResultsFromDB_Struct> results {};
auto bsms = (BazaarSearchMessaging_Struct *)in->pBuffer;
EQ::Util::MemoryStreamReader ss(
reinterpret_cast<char *>(bsms->payload),
in->size - sizeof(BazaarSearchMessaging_Struct)
);
cereal::BinaryInputArchive ar(ss);
ar(results);
auto size = results.size() * sizeof(structs::BazaarSearchResults_Struct);
auto buffer = new uchar[size];
uchar *bufptr = buffer;
memset(buffer, 0, size);
for (auto row = results.begin(); row != results.end(); ++row) {
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, structs::TiBazaarTraderBuyerActions::BazaarSearch);
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, row->trader_entity_id);
bufptr += 4;
VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->item_id);
VARSTRUCT_ENCODE_TYPE(int32, bufptr, row->serial_number);
bufptr += 4;
if (row->stackable) {
strn0cpy(
reinterpret_cast<char *>(bufptr),
fmt::format("{}({})", row->item_name.c_str(), row->charges).c_str(),
64
);
}
else {
strn0cpy(
reinterpret_cast<char *>(bufptr),
fmt::format("{}({})", row->item_name.c_str(), row->count).c_str(),
64
);
}
bufptr += 64;
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, row->cost);
VARSTRUCT_ENCODE_TYPE(uint32, bufptr, row->item_stat);
}
auto outapp = new EQApplicationPacket(OP_BazaarSearch, size);
memcpy(outapp->pBuffer, buffer, size);
dest->FastQueuePacket(&outapp);
safe_delete(outapp);
safe_delete_array(buffer);
safe_delete(in);
break;
}
case BazaarInspect:
case WelcomeMessage: {
LogTrading(
"Encode OP_BazaarSearch(Ti) BazaarInspect/WelcomeMessage action <green>[{}]",
action
);
dest->FastQueuePacket(&in, ack_req);
break;
}
default: {
LogTrading(
"Encode OP_BazaarSearch(Ti) unhandled action <red>[{}]",
action
);
dest->FastQueuePacket(&in, ack_req);
}
}
//make the EQ struct.
in->size = sizeof(structs::BazaarSearchResults_Struct)*entrycount;
in->pBuffer = new unsigned char[in->size];
structs::BazaarSearchResults_Struct *eq = (structs::BazaarSearchResults_Struct *) in->pBuffer;
//zero out the packet. We could avoid this memset by setting all fields (including unknowns) in the loop.
memset(in->pBuffer, 0, in->size);
for (int i = 0; i < entrycount; i++, eq++, emu++) {
eq->Beginning.Action = emu->Beginning.Action;
eq->Beginning.Unknown001 = emu->Beginning.Unknown001;
eq->Beginning.Unknown002 = emu->Beginning.Unknown002;
eq->NumItems = emu->NumItems;
eq->SerialNumber = emu->SerialNumber;
eq->SellerID = emu->SellerID;
eq->Cost = emu->Cost;
eq->ItemStat = emu->ItemStat;
strcpy(eq->ItemName, emu->ItemName);
}
delete[] __emu_buffer;
dest->FastQueuePacket(&in, ack_req);
}
ENCODE(OP_BecomeTrader)
{
ENCODE_LENGTH_EXACT(BecomeTrader_Struct);
SETUP_DIRECT_ENCODE(BecomeTrader_Struct, structs::BecomeTrader_Struct);
uint32 action = *(uint32 *)(*p)->pBuffer;
OUT(ID);
OUT(Code);
FINISH_ENCODE();
switch (action)
{
case TraderOff:
{
ENCODE_LENGTH_EXACT(BecomeTrader_Struct);
SETUP_DIRECT_ENCODE(BecomeTrader_Struct, structs::BecomeTrader_Struct);
LogTrading(
"Encode OP_BecomeTrader(Ti) TraderOff action <green>[{}] entity_id <green>[{}] trader_name "
"<green>[{}]",
emu->action,
emu->entity_id,
emu->trader_name
);
eq->action = structs::TiBazaarTraderBuyerActions::Zero;
eq->entity_id = emu->entity_id;
FINISH_ENCODE();
break;
}
case TraderOn:
{
ENCODE_LENGTH_EXACT(BecomeTrader_Struct);
SETUP_DIRECT_ENCODE(BecomeTrader_Struct, structs::BecomeTrader_Struct);
LogTrading(
"Encode OP_BecomeTrader(Ti) TraderOn action <green>[{}] entity_id <green>[{}] trader_name "
"<green>[{}]",
emu->action,
emu->entity_id,
emu->trader_name
);
eq->action = structs::TiBazaarTraderBuyerActions::BeginTraderMode;
eq->entity_id = emu->entity_id;
strn0cpy(eq->trader_name, emu->trader_name, sizeof(eq->trader_name));
FINISH_ENCODE();
break;
}
default:
{
LogTrading(
"Encode OP_BecomeTrader(Ti) unhandled action <red>[{}] Sending packet as is.",
action
);
EQApplicationPacket *in = *p;
*p = nullptr;
dest->FastQueuePacket(&in, ack_req);
}
}
}
ENCODE(OP_Buff)
@@ -2010,32 +2082,170 @@ namespace Titanium
ENCODE(OP_Trader)
{
if ((*p)->size != sizeof(TraderBuy_Struct)) {
EQApplicationPacket *in = *p;
*p = nullptr;
dest->FastQueuePacket(&in, ack_req);
return;
}
auto action = *(uint32 *) (*p)->pBuffer;
ENCODE_FORWARD(OP_TraderBuy);
switch (action) {
case TraderOn: {
ENCODE_LENGTH_EXACT(Trader_ShowItems_Struct);
SETUP_DIRECT_ENCODE(Trader_ShowItems_Struct, structs::Trader_ShowItems_Struct);
LogTrading(
"Encode OP_Trader BeginTraderMode action <green>[{}]",
action
);
eq->action = structs::TiBazaarTraderBuyerActions::BeginTraderMode;
OUT(entity_id);
FINISH_ENCODE();
break;
}
case TraderOff: {
ENCODE_LENGTH_EXACT(Trader_ShowItems_Struct);
SETUP_DIRECT_ENCODE(Trader_ShowItems_Struct, structs::Trader_ShowItems_Struct);
LogTrading(
"Encode OP_Trader EndTraderMode action <green>[{}]",
action
);
eq->action = structs::TiBazaarTraderBuyerActions::EndTraderMode;
OUT(entity_id);
FINISH_ENCODE();
break;
}
case ListTraderItems: {
ENCODE_LENGTH_EXACT(Trader_Struct);
SETUP_DIRECT_ENCODE(Trader_Struct, structs::Trader_Struct);
LogTrading(
"Encode OP_Trader ListTraderItems action <green>[{}]",
action
);
eq->action = structs::TiBazaarTraderBuyerActions::ListTraderItems;
std::copy_n(emu->items, UF::invtype::BAZAAR_SIZE, eq->item_id);
std::copy_n(emu->item_cost, UF::invtype::BAZAAR_SIZE, eq->item_cost);
FINISH_ENCODE();
break;
}
case BuyTraderItem: {
ENCODE_LENGTH_EXACT(TraderBuy_Struct);
SETUP_DIRECT_ENCODE(TraderBuy_Struct, structs::TraderBuy_Struct);
LogTrading(
"Encode OP_Trader item_id <green>[{}] price <green>[{}] quantity <green>[{}] trader_id <green>[{}]",
eq->item_id,
eq->price,
eq->quantity,
eq->trader_id
);
eq->action = structs::TiBazaarTraderBuyerActions::BuyTraderItem;
OUT(price);
OUT(trader_id);
OUT(item_id);
OUT(already_sold);
OUT(quantity);
strn0cpy(eq->item_name, emu->item_name, sizeof(eq->item_name));
FINISH_ENCODE();
break;
}
case ItemMove: {
LogTrading(
"Encode OP_Trader ItemMove action <green>[{}]",
action
);
EQApplicationPacket *in = *p;
*p = nullptr;
dest->FastQueuePacket(&in, ack_req);
break;
}
default: {
EQApplicationPacket *in = *p;
*p = nullptr;
dest->FastQueuePacket(&in, ack_req);
LogError("Unknown Encode OP_Trader action <red>{} received. Unhandled.", action);
}
}
}
ENCODE(OP_TraderBuy)
{
ENCODE_LENGTH_EXACT(TraderBuy_Struct);
SETUP_DIRECT_ENCODE(TraderBuy_Struct, structs::TraderBuy_Struct);
LogTrading(
"Encode OP_TraderBuy item_id <green>[{}] price <green>[{}] quantity <green>[{}] trader_id <green>[{}]",
emu->item_id,
emu->price,
emu->quantity,
emu->trader_id
);
OUT(Action);
OUT(Price);
OUT(TraderID);
memcpy(eq->ItemName, emu->ItemName, sizeof(eq->ItemName));
OUT(ItemID);
OUT(Quantity);
OUT(AlreadySold);
OUT(action);
OUT(price);
OUT(trader_id);
OUT(item_id);
OUT(already_sold);
OUT(quantity);
OUT_str(item_name);
FINISH_ENCODE();
}
ENCODE(OP_TraderShop)
{
auto action = *(uint32 *)(*p)->pBuffer;
switch (action) {
case ClickTrader: {
ENCODE_LENGTH_EXACT(TraderClick_Struct);
SETUP_DIRECT_ENCODE(TraderClick_Struct, structs::TraderClick_Struct);
LogTrading(
"ClickTrader action <green>[{}] trader_id <green>[{}]",
action,
emu->TraderID
);
eq->action = 0;
eq->trader_id = emu->TraderID;
eq->approval = emu->Approval;
FINISH_ENCODE();
break;
}
case BuyTraderItem: {
ENCODE_LENGTH_EXACT(TraderBuy_Struct);
SETUP_DIRECT_ENCODE(TraderBuy_Struct, structs::TraderBuy_Struct);
LogTrading(
"Encode OP_TraderShop item_id <green>[{}] price <green>[{}] quantity <green>[{}] trader_id <green>[{}]",
eq->item_id,
eq->price,
eq->quantity,
eq->trader_id
);
eq->action = structs::TiBazaarTraderBuyerActions::BuyTraderItem;
OUT(price);
OUT(trader_id);
OUT(item_id);
OUT(already_sold);
OUT(quantity);
strn0cpy(eq->item_name, emu->item_name, sizeof(eq->item_name));
FINISH_ENCODE();
break;
}
default: {
EQApplicationPacket *in = *p;
*p = nullptr;
dest->FastQueuePacket(&in, ack_req);
LogError("Unknown Encode OP_TraderShop action <red>[{}] received. Unhandled.", action);
}
}
}
ENCODE(OP_TributeItem)
{
ENCODE_LENGTH_EXACT(TributeItem_Struct);
@@ -2286,6 +2496,53 @@ namespace Titanium
FINISH_DIRECT_DECODE();
}
DECODE(OP_BazaarSearch)
{
uint32 action = *(uint32 *) __packet->pBuffer;
switch (action) {
case structs::TiBazaarTraderBuyerActions::BazaarSearch: {
DECODE_LENGTH_EXACT(structs::BazaarSearch_Struct);
SETUP_DIRECT_DECODE(BazaarSearchCriteria_Struct, structs::BazaarSearch_Struct);
emu->action = eq->Beginning.Action;
emu->item_stat = eq->ItemStat;
emu->max_cost = eq->MaxPrice;
emu->min_cost = eq->MinPrice;
emu->max_level = eq->MaxLlevel;
emu->min_level = eq->Minlevel;
emu->race = eq->Race;
emu->slot = eq->Slot;
emu->type = eq->Type == UINT32_MAX ? UINT8_MAX : eq->Type;
emu->trader_entity_id = eq->TraderID;
emu->trader_id = 0;
emu->_class = eq->Class_;
emu->search_scope = eq->TraderID > 0 ? NonRoFBazaarSearchScope : Local_Scope;
emu->max_results = RuleI(Bazaar, MaxSearchResults);
strn0cpy(emu->item_name, eq->Name, sizeof(emu->item_name));
FINISH_DIRECT_DECODE();
break;
}
case structs::TiBazaarTraderBuyerActions::BazaarInspect: {
SETUP_DIRECT_DECODE(BazaarInspect_Struct, structs::BazaarInspect_Struct);
IN(action);
memcpy(emu->player_name, eq->player_name, sizeof(emu->player_name));
IN(serial_number);
FINISH_DIRECT_DECODE();
break;
}
case structs::TiBazaarTraderBuyerActions::WelcomeMessage: {
break;
}
default: {
LogTrading("(Ti) Unhandled action <red>[{}]", action);
}
}
}
DECODE(OP_Buff)
{
DECODE_LENGTH_EXACT(structs::SpellBuffPacket_Struct);
@@ -2925,18 +3182,90 @@ namespace Titanium
FINISH_DIRECT_DECODE();
}
DECODE(OP_Trader)
{
auto action = (uint32) __packet->pBuffer[0];
switch (action) {
case structs::TiBazaarTraderBuyerActions::BeginTraderMode: {
DECODE_LENGTH_EXACT(structs::BeginTrader_Struct);
SETUP_DIRECT_DECODE(ClickTrader_Struct, structs::BeginTrader_Struct);
LogTrading(
"Decode OP_Trader BeginTraderMode action <red>[{}]",
action
);
emu->action = TraderOn;
emu->unknown_004 = 0;
std::copy_n(eq->serial_number, Titanium::invtype::BAZAAR_SIZE, emu->serial_number);
std::copy_n(eq->cost, Titanium::invtype::BAZAAR_SIZE, emu->item_cost);
FINISH_DIRECT_DECODE();
break;
}
case structs::TiBazaarTraderBuyerActions::EndTraderMode: {
DECODE_LENGTH_EXACT(structs::Trader_ShowItems_Struct);
SETUP_DIRECT_DECODE(Trader_ShowItems_Struct, structs::Trader_ShowItems_Struct);
LogTrading(
"Decode OP_Trader(Ti) EndTraderMode action <red>[{}]",
action
);
emu->action = TraderOff;
IN(entity_id);
FINISH_DIRECT_DECODE();
break;
}
case structs::TiBazaarTraderBuyerActions::PriceUpdate:
case structs::TiBazaarTraderBuyerActions::ItemMove:
case structs::TiBazaarTraderBuyerActions::EndTransaction:
case structs::TiBazaarTraderBuyerActions::ListTraderItems: {
LogTrading(
"Decode OP_Trader(Ti) Price/ItemMove/EndTransaction/ListTraderItems action <red>[{}]",
action
);
break;
}
case structs::TiBazaarTraderBuyerActions::ReconcileItems: {
break;
}
default: {
LogError("Unhandled(Ti) action <red>[{}] received.", action);
}
}
}
DECODE(OP_TraderBuy)
{
DECODE_LENGTH_EXACT(structs::TraderBuy_Struct);
SETUP_DIRECT_DECODE(TraderBuy_Struct, structs::TraderBuy_Struct);
MEMSET_IN(TraderBuy_Struct);
IN(Action);
IN(Price);
IN(TraderID);
memcpy(emu->ItemName, eq->ItemName, sizeof(emu->ItemName));
IN(ItemID);
IN(Quantity);
IN(action);
IN(price);
IN(trader_id);
memcpy(emu->item_name, eq->item_name, sizeof(emu->item_name));
IN(item_id);
IN(quantity);
FINISH_DIRECT_DECODE();
}
DECODE(OP_TraderShop)
{
DECODE_LENGTH_EXACT(structs::TraderClick_Struct);
SETUP_DIRECT_DECODE(TraderClick_Struct, structs::TraderClick_Struct);
LogTrading(
"(Ti) action <green>[{}] trader_id <green>[{}] approval <green>[{}]",
eq->action,
eq->trader_id,
eq->approval
);
emu->Code = ClickTrader;
emu->TraderID = eq->trader_id;
emu->Approval = eq->approval;
FINISH_DIRECT_DECODE();
}