[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
+1
View File
@@ -101,6 +101,7 @@ SET(zone_sources
npc_scale_manager.cpp
object.cpp
oriented_bounding_box.cpp
parcels.cpp
pathfinder_interface.cpp
pathfinder_nav_mesh.cpp
pathfinder_null.cpp
+15 -1
View File
@@ -184,7 +184,8 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
mob_close_scan_timer(6000),
position_update_timer(10000),
consent_throttle_timer(2000),
tmSitting(0)
tmSitting(0),
parcel_timer(RuleI(Parcel, ParcelDeliveryDelay))
{
for (auto client_filter = FilterNone; client_filter < _FilterCount; client_filter = eqFilterType(client_filter + 1)) {
SetFilter(client_filter, FilterShow);
@@ -375,6 +376,15 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
bot_owner_options[booBuffCounter] = false;
bot_owner_options[booMonkWuMessage] = false;
m_parcel_platinum = 0;
m_parcel_gold = 0;
m_parcel_silver = 0;
m_parcel_copper = 0;
m_parcel_count = 0;
m_parcel_enabled = true;
m_parcel_merchant_engaged = false;
m_parcels.clear();
SetBotPulling(false);
SetBotPrecombat(false);
@@ -383,6 +393,10 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
}
Client::~Client() {
if (ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB (Parcel, EnableParcelMerchants)) {
DoParcelCancel();
}
mMovementManager->RemoveClient(this);
DataBucket::DeleteCachedBuckets(DataBucketLoadType::Client, CharacterID());
+39 -1
View File
@@ -68,6 +68,7 @@ namespace EQ
#include "cheat_manager.h"
#include "../common/events/player_events.h"
#include "../common/data_verification.h"
#include "../common/repositories/character_parcels_repository.h"
#ifdef _WINDOWS
// since windows defines these within windef.h (which windows.h include)
@@ -323,8 +324,36 @@ public:
void ReturnTraderReq(const EQApplicationPacket* app,int16 traderitemcharges, uint32 itemid = 0);
void TradeRequestFailed(const EQApplicationPacket* app);
void BuyTraderItem(TraderBuy_Struct* tbs,Client* trader,const EQApplicationPacket* app);
void FinishTrade(Mob* with, bool finalizer = false, void* event_entry = nullptr, std::list<void*>* event_details = nullptr);
void FinishTrade(
Mob *with,
bool finalizer = false,
void *event_entry = nullptr,
std::list<void *> *event_details = nullptr
);
void SendZonePoints();
void SendBulkParcels();
void DoParcelCancel();
void DoParcelSend(const Parcel_Struct *parcel_in);
void DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in);
void SendParcel(const Parcel_Struct &parcel);
void SendParcelStatus();
void SendParcelAck();
void SendParcelRetrieveAck();
void SendParcelDelete(const ParcelRetrieve_Struct &parcel_in);
void SendParcelDeliveryToWorld(const Parcel_Struct &parcel);
void SetParcelEnabled(bool status) { m_parcel_enabled = status; }
bool GetParcelEnabled() { return m_parcel_enabled; }
void SetParcelCount(uint32 count) { m_parcel_count = count; }
int32 GetParcelCount() { return m_parcel_count; }
bool GetEngagedWithParcelMerchant() { return m_parcel_merchant_engaged; }
void SetEngagedWithParcelMerchant(bool status) { m_parcel_merchant_engaged = status; }
Timer *GetParcelTimer() { return &parcel_timer; }
bool DeleteParcel(uint32 parcel_id);
void AddParcel(CharacterParcelsRepository::CharacterParcels &parcel);
void LoadParcels();
std::map<uint32, CharacterParcelsRepository::CharacterParcels> GetParcels() { return m_parcels; }
int32 FindNextFreeParcelSlot(uint32 char_id);
void SendParcelIconStatus();
void SendBuyerResults(char *SearchQuery, uint32 SearchID);
void ShowBuyLines(const EQApplicationPacket *app);
@@ -1857,6 +1886,14 @@ private:
bool Trader;
bool Buyer;
std::string BuyerWelcomeMessage;
int32 m_parcel_platinum;
int32 m_parcel_gold;
int32 m_parcel_silver;
int32 m_parcel_copper;
int32 m_parcel_count;
bool m_parcel_enabled;
bool m_parcel_merchant_engaged;
std::map<uint32, CharacterParcelsRepository::CharacterParcels> m_parcels{};
int Haste; //precalced value
uint32 tmSitting; // time stamp started sitting, used for HP regen bonus added on MAY 5, 2004
@@ -1955,6 +1992,7 @@ private:
Timer dynamiczone_removal_timer;
Timer task_request_timer;
Timer pick_lock_timer;
Timer parcel_timer; //Used to limit the number of parcels to one every 30 seconds (default). Changable via rule.
glm::vec3 m_Proximity;
glm::vec4 last_position_before_bulk_update;
+115 -43
View File
@@ -69,6 +69,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#include "../common/events/player_event_logs.h"
#include "../common/repositories/character_stats_record_repository.h"
#include "dialogue_window.h"
#include "../common/rulesys.h"
extern QueryServ* QServ;
extern Zone* zone;
@@ -321,6 +322,8 @@ void MapOpcodes()
ConnectedOpcodes[OP_OpenGuildTributeMaster] = &Client::Handle_OP_OpenGuildTributeMaster;
ConnectedOpcodes[OP_OpenInventory] = &Client::Handle_OP_OpenInventory;
ConnectedOpcodes[OP_OpenTributeMaster] = &Client::Handle_OP_OpenTributeMaster;
ConnectedOpcodes[OP_ShopSendParcel] = &Client::Handle_OP_ShopSendParcel;
ConnectedOpcodes[OP_ShopRetrieveParcel] = &Client::Handle_OP_ShopRetrieveParcel;
ConnectedOpcodes[OP_PDeletePetition] = &Client::Handle_OP_PDeletePetition;
ConnectedOpcodes[OP_PetCommands] = &Client::Handle_OP_PetCommands;
ConnectedOpcodes[OP_Petition] = &Client::Handle_OP_Petition;
@@ -818,6 +821,10 @@ void Client::CompleteConnect()
);
}
if(ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
SendParcelStatus();
}
if (zone && zone->GetInstanceTimer()) {
bool is_permanent = false;
uint32 remaining_time = database.GetTimeRemainingInstance(zone->GetInstanceID(), is_permanent);
@@ -4271,6 +4278,12 @@ void Client::Handle_OP_Bug(const EQApplicationPacket *app)
void Client::Handle_OP_Camp(const EQApplicationPacket *app)
{
if (ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants) && GetEngagedWithParcelMerchant()) {
Stand();
MessageString(Chat::Yellow, TRADER_BUSY_TWO);
return;
}
if (IsLFP())
worldserver.StopLFP(CharacterID());
@@ -4324,6 +4337,12 @@ void Client::Handle_OP_CancelTrade(const EQApplicationPacket *app)
FinishTrade(this);
trade->Reset();
}
if (ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
DoParcelCancel();
SetEngagedWithParcelMerchant(false);
}
EQApplicationPacket end_trade1(OP_FinishWindow, 0);
QueuePacket(&end_trade1);
@@ -13877,6 +13896,11 @@ void Client::Handle_OP_Shielding(const EQApplicationPacket *app)
void Client::Handle_OP_ShopEnd(const EQApplicationPacket *app)
{
if (ClientVersion() == EQ::versions::ClientVersion::RoF2 && RuleB(Parcel, EnableParcelMerchants)) {
DoParcelCancel();
SetEngagedWithParcelMerchant(false);
}
EQApplicationPacket empty(OP_ShopEndConfirm);
QueuePacket(&empty);
return;
@@ -14423,82 +14447,107 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
void Client::Handle_OP_ShopRequest(const EQApplicationPacket *app)
{
if (app->size != sizeof(Merchant_Click_Struct)) {
LogError("Wrong size: OP_ShopRequest, size=[{}], expected [{}]", app->size, sizeof(Merchant_Click_Struct));
if (app->size != sizeof(MerchantClick_Struct)) {
LogError("Wrong size: OP_ShopRequest, size=[{}], expected [{}]", app->size, sizeof(MerchantClick_Struct));
return;
}
Merchant_Click_Struct* mc = (Merchant_Click_Struct*)app->pBuffer;
MerchantClick_Struct *mc = (MerchantClick_Struct *) app->pBuffer;
// Send back opcode OP_ShopRequest - tells client to open merchant window.
//EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct));
//Merchant_Click_Struct* mco=(Merchant_Click_Struct*)outapp->pBuffer;
int merchantid = 0;
Mob* tmp = entity_list.GetMob(mc->npcid);
// EQApplicationPacket* outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct));
// Merchant_Click_Struct* mco=(Merchant_Click_Struct*)outapp->pBuffer;
int merchant_id = 0;
int tabs_to_display = None;
Mob *tmp = entity_list.GetMob(mc->npc_id);
if (tmp == 0 || !tmp->IsNPC() || tmp->GetClass() != Class::Merchant)
if (tmp == 0 || !tmp->IsNPC() || tmp->GetClass() != Class::Merchant) {
return;
}
//you have to be somewhat close to them to be properly using them
if (DistanceSquared(m_Position, tmp->GetPosition()) > USE_NPC_RANGE2)
// you have to be somewhat close to them to be properly using them
if (DistanceSquared(m_Position, tmp->GetPosition()) > USE_NPC_RANGE2) {
return;
}
merchantid = tmp->CastToNPC()->MerchantType;
merchant_id = tmp->CastToNPC()->MerchantType;
if (ClientVersion() == EQ::versions::ClientVersion::RoF2 && tmp->CastToNPC()->GetParcelMerchant()) {
tabs_to_display = SellBuyParcel;
}
else {
tabs_to_display = SellBuy;
}
int action = MerchantActions::Open;
if (merchant_id == 0) {
auto outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(MerchantClick_Struct));
auto mco = (MerchantClick_Struct *) outapp->pBuffer;
mco->npc_id = mc->npc_id;
mco->player_id = 0;
mco->command = MerchantActions::Open;
mco->rate = 1.0;
mco->tab_display = tabs_to_display;
int action = 1;
if (merchantid == 0) {
auto outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct));
Merchant_Click_Struct* mco = (Merchant_Click_Struct*)outapp->pBuffer;
mco->npcid = mc->npcid;
mco->playerid = 0;
mco->command = 1; //open...
mco->rate = 1.0;
QueuePacket(outapp);
safe_delete(outapp);
return;
}
if (tmp->IsEngaged()) {
MessageString(Chat::White, MERCHANT_BUSY);
action = 0;
}
if (GetFeigned() || IsInvisible())
{
Message(Chat::White, "You cannot use a merchant right now.");
action = 0;
}
int primaryfaction = tmp->CastToNPC()->GetPrimaryFaction();
int factionlvl = GetFactionLevel(CharacterID(), tmp->CastToNPC()->GetNPCTypeID(), GetRace(), GetClass(), GetDeity(), primaryfaction, tmp);
if (factionlvl >= 7) {
MerchantRejectMessage(tmp, primaryfaction);
action = 0;
action = MerchantActions::Close;
}
if (tmp->Charmed())
action = 0;
if (GetFeigned() || IsInvisible()) {
Message(Chat::White, "You cannot use a merchant right now.");
action = MerchantActions::Close;
}
int primaryfaction = tmp->CastToNPC()->GetPrimaryFaction();
int factionlvl = GetFactionLevel(
CharacterID(), tmp->CastToNPC()->GetNPCTypeID(), GetRace(), GetClass(), GetDeity(),
primaryfaction, tmp
);
if (factionlvl >= 7) {
MerchantRejectMessage(tmp, primaryfaction);
action = MerchantActions::Close;
}
if (tmp->Charmed()) {
action = MerchantActions::Close;
}
if (!tmp->CastToNPC()->IsMerchantOpen()) {
tmp->SayString(zone->random.Int(MERCHANT_CLOSED_ONE, MERCHANT_CLOSED_THREE));
action = 0;
action = MerchantActions::Close;
}
auto outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(Merchant_Click_Struct));
Merchant_Click_Struct* mco = (Merchant_Click_Struct*)outapp->pBuffer;
auto outapp = new EQApplicationPacket(OP_ShopRequest, sizeof(MerchantClick_Struct));
auto mco = (MerchantClick_Struct *) outapp->pBuffer;
mco->npc_id = mc->npc_id;
mco->player_id = 0;
mco->command = action; // Merchant command 0x01 = open
mco->tab_display = tabs_to_display;
mco->npcid = mc->npcid;
mco->playerid = 0;
mco->command = action; // Merchant command 0x01 = open
if (RuleB(Merchant, UsePriceMod)) {
mco->rate = 1 / ((RuleR(Merchant, BuyCostMod))*Client::CalcPriceMod(tmp, true)); // works
mco->rate = 1 / ((RuleR(Merchant, BuyCostMod)) * Client::CalcPriceMod(tmp, true)); // works
}
else
else {
mco->rate = 1 / (RuleR(Merchant, BuyCostMod));
}
outapp->priority = 6;
QueuePacket(outapp);
safe_delete(outapp);
if (action == 1)
BulkSendMerchantInventory(merchantid, tmp->GetNPCTypeID());
if (action == MerchantActions::Open) {
BulkSendMerchantInventory(merchant_id, tmp->GetNPCTypeID());
if ((tabs_to_display & Parcel) == Parcel) {
SendBulkParcels();
}
}
return;
}
@@ -17162,3 +17211,26 @@ void Client::Handle_OP_GuildTributeDonatePlat(const EQApplicationPacket *app)
}
}
void Client::Handle_OP_ShopSendParcel(const EQApplicationPacket *app)
{
if (app->size != sizeof(Parcel_Struct)) {
LogError("Received Handle_OP_ShopSendParcel packet. Expected size {}, received size {}.", sizeof(Parcel_Struct),
app->size);
return;
}
auto parcel_in = (Parcel_Struct *)app->pBuffer;
DoParcelSend(parcel_in);
}
void Client::Handle_OP_ShopRetrieveParcel(const EQApplicationPacket *app)
{
if (app->size != sizeof(ParcelRetrieve_Struct)) {
LogError("Received Handle_OP_ShopRetrieveParcel packet. Expected size {}, received size {}.",
sizeof(ParcelRetrieve_Struct), app->size);
return;
}
auto parcel_in = (ParcelRetrieve_Struct *)app->pBuffer;
DoParcelRetrieve(*parcel_in);
}
+3
View File
@@ -335,3 +335,6 @@
void Handle_OP_SharedTaskAccept(const EQApplicationPacket *app);
void Handle_OP_SharedTaskQuit(const EQApplicationPacket *app);
void Handle_OP_SharedTaskPlayerList(const EQApplicationPacket *app);
void Handle_OP_ShopSendParcel(const EQApplicationPacket *app);
void Handle_OP_ShopRetrieveParcel(const EQApplicationPacket *app);
+17
View File
@@ -555,6 +555,7 @@ bool Client::Process() {
guild_mgr.UpdateDbMemberOnline(CharacterID(), false);
guild_mgr.SendToWorldSendGuildMembersList(GuildID());
}
return false;
}
else if (!linkdead_timer.Enabled()) {
@@ -1429,6 +1430,22 @@ void Client::OPMoveCoin(const EQApplicationPacket* app)
to_bucket = (int32 *) &trade->cp; break;
}
}
else {
switch (mc->cointype2) {
case COINTYPE_PP:
m_parcel_platinum += mc->amount;
break;
case COINTYPE_GP:
m_parcel_gold += mc->amount;
break;
case COINTYPE_SP:
m_parcel_silver += mc->amount;
break;
case COINTYPE_CP:
m_parcel_copper += mc->amount;
break;
}
}
break;
}
case 4: // shared bank
+2
View File
@@ -182,6 +182,7 @@ int command_init(void)
command_add("nukeitem", "[Item ID] - Removes the specified Item ID from you or your player target's inventory", AccountStatus::GMLeadAdmin, command_nukeitem) ||
command_add("object", "List|Add|Edit|Move|Rotate|Copy|Save|Undo|Delete - Manipulate static and tradeskill objects within the zone", AccountStatus::GMAdmin, command_object) ||
command_add("opcode", "Reloads all opcodes from server patch files", AccountStatus::GMMgmt, command_reload) ||
command_add("parcels", "View and edit the parcel system. Requires parcels to be enabled in rules.", AccountStatus::GMMgmt, command_parcels) ||
command_add("path", "view and edit pathing", AccountStatus::GMMgmt, command_path) ||
command_add("peqzone", "[Zone ID|Zone Short Name] - Teleports you to the specified zone if you meet the requirements.", AccountStatus::Player, command_peqzone) ||
command_add("petitems", "View your pet's items if you have one", AccountStatus::ApprenticeGuide, command_petitems) ||
@@ -873,6 +874,7 @@ void command_bot(Client *c, const Seperator *sep)
#include "gm_commands/nukeitem.cpp"
#include "gm_commands/object.cpp"
#include "gm_commands/object_manipulation.cpp"
#include "gm_commands/parcels.cpp"
#include "gm_commands/path.cpp"
#include "gm_commands/peqzone.cpp"
#include "gm_commands/petitems.cpp"
+2
View File
@@ -37,6 +37,7 @@ void SendGuildSubCommands(Client *c);
void SendShowInventorySubCommands(Client *c);
void SendFixMobSubCommands(Client *c);
void SendDataBucketsSubCommands(Client *c);
void SendParcelsSubCommands(Client *c);
// Commands
void command_acceptrules(Client *c, const Seperator *sep);
@@ -135,6 +136,7 @@ void command_nukebuffs(Client *c, const Seperator *sep);
void command_nukeitem(Client *c, const Seperator *sep);
void command_object(Client *c, const Seperator *sep);
void command_oocmute(Client *c, const Seperator *sep);
void command_parcels(Client *c, const Seperator *sep);
void command_path(Client *c, const Seperator *sep);
void command_peqzone(Client *c, const Seperator *sep);
void command_petitems(Client *c, const Seperator *sep);
+307
View File
@@ -0,0 +1,307 @@
#include "../client.h"
#include "../worldserver.h"
#include "../../common/events/player_events.h"
extern WorldServer worldserver;
void command_parcels(Client *c, const Seperator *sep)
{
const auto arguments = sep->argnum;
if (!arguments) {
SendParcelsSubCommands(c);
return;
}
bool is_listdb = !strcasecmp(sep->arg[1], "listdb");
bool is_listmemory = !strcasecmp(sep->arg[1], "listmemory");
bool is_details = !strcasecmp(sep->arg[1], "details");
bool is_add = !strcasecmp(sep->arg[1], "add");
if (!is_listdb && !is_listmemory && !is_details && !is_add) {
SendParcelsSubCommands(c);
return;
}
if (is_listdb) {
auto player_name = std::string(sep->arg[2]);
if (arguments < 2) {
c->Message(Chat::White, "Usage: #parcels listdb [Character Name]");
}
if (player_name.empty()) {
c->Message(
Chat::White,
fmt::format("You must provide a player name.").c_str());
return;
}
auto player_id = CharacterParcelsRepository::GetParcelCountAndCharacterName(database, player_name);
if (!player_id.at(0).char_id) {
c->MessageString(Chat::Yellow, CANT_FIND_PLAYER, player_name.c_str());
return;
}
auto results = CharacterParcelsRepository::GetWhere(
database,
fmt::format("char_id = '{}' ORDER BY slot_id ASC", player_id.at(0).char_id)
);
if (results.empty()) {
c->Message(Chat::White, fmt::format("No parcels could be found for {}", player_name).c_str());
return;
}
c->Message(Chat::Yellow, fmt::format("Found {} parcels for {}.", results.size(), player_name).c_str());
for (auto const &p: results) {
c->Message(
Chat::Yellow,
fmt::format(
"Slot [{:02}] has item id [{:10}] with quantity [{}].",
p.slot_id,
p.item_id,
p.quantity
).c_str()
);
}
}
if (is_listmemory) {
auto player_name = std::string(sep->arg[2]);
auto player = entity_list.GetClientByName(player_name.c_str());
if (arguments < 2) {
c->Message(Chat::White, "Usage: #parcels listmemory [Character Name] (Must be in the same zone)");
}
if (!player) {
c->Message(
Chat::White,
fmt::format(
"Player {} could not be found in this zone. Ensure you are in the same zone as the player.",
player_name
).c_str()
);
return;
}
auto parcels = player->GetParcels();
if (parcels.empty()) {
c->Message(Chat::White, fmt::format("No parcels could be found for {}", player_name).c_str());
return;
}
c->Message(Chat::Yellow, fmt::format("Found {} parcels for {}.", parcels.size(), player_name).c_str());
for (auto const &p: parcels) {
c->Message(
Chat::Yellow,
fmt::format(
"Slot [{:02}] has item id [{:10}] with quantity [{}].",
p.second.slot_id,
p.second.item_id,
p.second.quantity
).c_str()
);
}
}
if (is_add) {
if (arguments < 4) {
SendParcelsSubCommands(c);
return;
}
if (!Strings::IsNumber(sep->arg[3]) || !Strings::IsNumber(sep->arg[4])) {
SendParcelsSubCommands(c);
return;
}
auto to_name = std::string(sep->arg[2]);
auto item_id = Strings::ToUnsignedInt(sep->arg[3]);
auto quantity = Strings::ToUnsignedInt(sep->arg[4]);
auto note = std::string(sep->argplus[5]);
auto send_to_client = CharacterParcelsRepository::GetParcelCountAndCharacterName(
database,
to_name
);
if (send_to_client.at(0).character_name.empty()) {
c->MessageString(Chat::Yellow, CANT_FIND_PLAYER, to_name.c_str());
return;
}
auto next_slot = c->FindNextFreeParcelSlot(send_to_client.at(0).char_id);
if (next_slot == INVALID_INDEX) {
c->Message(
Chat::Yellow,
fmt::format(
"Unfortunately, {} cannot accept any more parcels at this time. Please try again later.",
send_to_client.at(0).character_name
).c_str()
);
return;
}
if (item_id == PARCEL_MONEY_ITEM_ID) {
if (quantity > INT32_MAX) {
c->Message(
Chat::Yellow,
"Your quantity of {} copper pieces was too large. Set to max quantity of {}.",
quantity,
INT32_MAX
);
quantity = INT32_MAX;
}
auto item = database.GetItem(PARCEL_MONEY_ITEM_ID);
if (!item) {
c->Message(Chat::Yellow, "Could not find item with id {}", item_id);
return;
}
std::unique_ptr<EQ::ItemInstance> inst(database.CreateItem(item, 1));
if (!inst) {
c->Message(Chat::Yellow, "Could not find item with id {}", item_id);
return;
}
CharacterParcelsRepository::CharacterParcels parcel_out;
parcel_out.from_name = c->GetName();
parcel_out.note = note;
parcel_out.sent_date = time(nullptr);
parcel_out.quantity = quantity == 0 ? 1 : quantity;
parcel_out.item_id = PARCEL_MONEY_ITEM_ID;
parcel_out.char_id = send_to_client.at(0).char_id;
parcel_out.slot_id = next_slot;
parcel_out.id = 0;
auto result = CharacterParcelsRepository::InsertOne(database, parcel_out);
if (!result.id) {
LogError(
"Failed to add parcel to database. From {} to {} item {} quantity {}",
parcel_out.from_name,
send_to_client.at(0).character_name,
parcel_out.item_id,
parcel_out.quantity
);
c->Message(
Chat::Yellow,
"Unable to save parcel to the database. Please contact an administrator."
);
return;
}
c->MessageString(
Chat::Yellow,
PARCEL_DELIVERY,
c->GetCleanName(),
"Money",
send_to_client.at(0).character_name.c_str()
);
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
PlayerEvent::ParcelSend e{};
e.from_player_name = parcel_out.from_name;
e.to_player_name = send_to_client.at(0).character_name;
e.item_id = parcel_out.item_id;
e.quantity = parcel_out.quantity;
e.sent_date = parcel_out.sent_date;
RecordPlayerEventLogWithClient(c, PlayerEvent::PARCEL_SEND, e);
}
Parcel_Struct ps{};
ps.item_slot = parcel_out.slot_id;
strn0cpy(ps.send_to, send_to_client.at(0).character_name.c_str(), sizeof(ps.send_to));
c->SendParcelDeliveryToWorld(ps);
}
else {
auto item = database.GetItem(item_id);
if (!item) {
c->Message(Chat::Yellow, "Could not find an item with id {}", item_id);
return;
}
std::unique_ptr<EQ::ItemInstance> inst(
database.CreateItem(
item,
quantity > INT16_MAX ? INT16_MAX : (int16) quantity
)
);
if (!inst) {
c->Message(Chat::Yellow, "Could not find an item with id {}", item_id);
return;
}
if (inst->IsStackable()) {
quantity = quantity > inst->GetItem()->StackSize
? inst->GetItem()->StackSize : (int16) quantity;
}
else if (inst->GetItem()->MaxCharges > 0) {
quantity = quantity >= inst->GetItem()->MaxCharges
? inst->GetItem()->MaxCharges : (int16) quantity;
}
CharacterParcelsRepository::CharacterParcels parcel_out;
parcel_out.from_name = c->GetName();
parcel_out.note = note.empty() ? "" : note;
parcel_out.sent_date = time(nullptr);
parcel_out.quantity = quantity;
parcel_out.item_id = item_id;
parcel_out.char_id = send_to_client.at(0).char_id;
parcel_out.slot_id = next_slot;
parcel_out.id = 0;
auto result = CharacterParcelsRepository::InsertOne(database, parcel_out);
if (!result.id) {
LogError(
"Failed to add parcel to database. From {} to {} item {} quantity {}",
parcel_out.from_name,
send_to_client.at(0).character_name,
parcel_out.item_id,
parcel_out.quantity
);
c->Message(
Chat::Yellow,
"Unable to save parcel to the database. Please contact an administrator."
);
return;
}
c->MessageString(
Chat::Yellow,
PARCEL_DELIVERY,
c->GetCleanName(),
inst->GetItem()->Name,
send_to_client.at(0).character_name.c_str()
);
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
PlayerEvent::ParcelSend e{};
e.from_player_name = parcel_out.from_name;
e.to_player_name = send_to_client.at(0).character_name;
e.item_id = parcel_out.item_id;
e.quantity = parcel_out.quantity;
e.sent_date = parcel_out.sent_date;
RecordPlayerEventLogWithClient(c, PlayerEvent::PARCEL_SEND, e);
}
Parcel_Struct ps{};
ps.item_slot = parcel_out.slot_id;
strn0cpy(ps.send_to, send_to_client.at(0).character_name.c_str(), sizeof(ps.send_to));
c->SendParcelDeliveryToWorld(ps);
}
}
}
void SendParcelsSubCommands(Client *c)
{
c->Message(Chat::White, "#parcels listdb [Character Name]");
c->Message(Chat::White, "#parcels listmemory [Character Name] (Must be in the same zone)");
c->Message(
Chat::White,
"#parcels add [Character Name] [item id] [quantity] [note]. To send money use item id of 99990. Quantity is valid for stackable items, charges on an item, or amount of copper."
);
c->Message(Chat::White, "#parcels details [Character Name]");
}
+1
View File
@@ -268,6 +268,7 @@ public:
inline void MerchantOpenShop() { merchant_open = true; }
inline void MerchantCloseShop() { merchant_open = false; }
inline bool IsMerchantOpen() { return merchant_open; }
inline bool GetParcelMerchant() { return NPCTypedata->is_parcel_merchant; }
void Depop(bool start_spawn_timer = false);
void Stun(int duration);
void UnStun();
+752
View File
@@ -0,0 +1,752 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2002 EQEMu Development Team (http://eqemu.org)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../common/global_define.h"
#include "../common/events/player_event_logs.h"
#include "../common/repositories/trader_repository.h"
#include "../common/repositories/character_parcels_repository.h"
#include "worldserver.h"
#include "string_ids.h"
#include "client.h"
#include "../common/ruletypes.h"
extern WorldServer worldserver;
void Client::SendBulkParcels()
{
SetEngagedWithParcelMerchant(true);
LoadParcels();
if (m_parcels.empty()) {
return;
}
ParcelMessaging_Struct pms{};
pms.packet_type = ItemPacketParcel;
std::stringstream ss;
cereal::BinaryOutputArchive ar(ss);
for (auto &p: m_parcels) {
auto item = database.GetItem(p.second.item_id);
if (item) {
std::unique_ptr<EQ::ItemInstance> inst(database.CreateItem(item, p.second.quantity));
if (inst) {
inst->SetCharges(p.second.quantity > 0 ? p.second.quantity : 1);
inst->SetMerchantCount(1);
inst->SetMerchantSlot(p.second.slot_id);
if (inst->IsStackable()) {
inst->SetCharges(p.second.quantity);
}
if (item->ID == PARCEL_MONEY_ITEM_ID) {
inst->SetPrice(p.second.quantity);
inst->SetCharges(1);
}
pms.player_name = p.second.from_name;
pms.sent_time = p.second.sent_date;
pms.note = p.second.note;
pms.serialized_item = inst->Serialize(p.second.slot_id);
pms.slot_id = p.second.slot_id;
ar(pms);
uint32 packet_size = ss.str().length();
std::unique_ptr<EQApplicationPacket> out(new EQApplicationPacket(OP_ItemPacket, packet_size));
if (out->size != packet_size) {
LogError(
"Attempted to send a parcel packet of mismatched size {} with a buffer size of {}.",
out->Size(),
packet_size
);
return;
}
memcpy(out->pBuffer, ss.str().data(), out->size);
QueuePacket(out.get());
ss.str("");
ss.clear();
}
}
}
if (m_parcels.size() >= RuleI(Parcel, ParcelMaxItems) + PARCEL_LIMIT) {
LogError(
"Found {} parcels for Character {}. List truncated to the ParcelMaxItems rule [{}] + PARCEL_LIMIT.",
m_parcels.size(),
GetCleanName(),
RuleI(Parcel, ParcelMaxItems)
);
SendParcelStatus();
return;
}
}
void Client::SendParcel(const Parcel_Struct &parcel_in)
{
auto results = CharacterParcelsRepository::GetWhere(
database,
fmt::format(
"`char_id` = '{}' AND `slot_id` = '{}' LIMIT 1",
CharacterID(),
parcel_in.item_slot
)
);
if (results.empty()) {
return;
}
ParcelMessaging_Struct pms{};
pms.packet_type = ItemPacketParcel;
std::stringstream ss;
cereal::BinaryOutputArchive ar(ss);
CharacterParcelsRepository::CharacterParcels parcel{};
parcel.from_name = results[0].from_name;
parcel.id = results[0].id;
parcel.note = results[0].note;
parcel.quantity = results[0].quantity;
parcel.sent_date = results[0].sent_date;
parcel.item_id = results[0].item_id;
parcel.slot_id = results[0].slot_id;
parcel.char_id = results[0].char_id;
auto item = database.GetItem(parcel.item_id);
if (item) {
std::unique_ptr<EQ::ItemInstance> inst(database.CreateItem(item, parcel.quantity));
if (inst) {
inst->SetCharges(parcel.quantity > 0 ? parcel.quantity : 1);
inst->SetMerchantCount(1);
inst->SetMerchantSlot(parcel.slot_id);
if (inst->IsStackable()) {
inst->SetCharges(parcel.quantity);
}
if (item->ID == PARCEL_MONEY_ITEM_ID) {
inst->SetPrice(parcel.quantity);
inst->SetCharges(1);
}
pms.player_name = parcel.from_name;
pms.sent_time = parcel.sent_date;
pms.note = parcel.note;
pms.serialized_item = inst->Serialize(parcel.slot_id);
pms.slot_id = parcel.slot_id;
ar(pms);
uint32 packet_size = ss.str().length();
std::unique_ptr<EQApplicationPacket> out(new EQApplicationPacket(OP_ItemPacket, packet_size));
if (out->size != packet_size) {
LogError(
"Attempted to send a parcel packet of mismatched size {} with a buffer size of {}.",
out->Size(),
packet_size
);
return;
}
memcpy(out->pBuffer, ss.str().data(), out->size);
QueuePacket(out.get());
ss.str("");
ss.clear();
m_parcels.emplace(parcel.slot_id, parcel);
}
}
}
void Client::DoParcelCancel()
{
if (
m_parcel_platinum ||
m_parcel_gold ||
m_parcel_silver ||
m_parcel_copper
) {
m_pp.platinum += m_parcel_platinum;
m_pp.gold += m_parcel_gold;
m_pp.silver += m_parcel_silver;
m_pp.copper += m_parcel_copper;
m_parcel_platinum = 0;
m_parcel_gold = 0;
m_parcel_silver = 0;
m_parcel_copper = 0;
SaveCurrency();
SendMoneyUpdate();
}
}
void Client::SendParcelStatus()
{
LoadParcels();
int32 num_of_parcels = GetParcelCount();
if (num_of_parcels > 0) {
int32 num_over_limit = (num_of_parcels - RuleI(Parcel, ParcelMaxItems)) < 0 ? 0 : (num_of_parcels - RuleI(Parcel, ParcelMaxItems));
if (num_of_parcels == RuleI(Parcel, ParcelMaxItems)) {
Message(
Chat::Red,
fmt::format(
"You have reached the limit of {} parcels in your mailbox. You will not be able to send parcels until you retrieve at least 1 parcel. ",
RuleI(Parcel, ParcelMaxItems)
).c_str()
);
}
else if (num_over_limit == 1) {
MessageString(
Chat::Red,
PARCEL_STATUS_1,
std::to_string(num_of_parcels).c_str(),
std::to_string(RuleI(Parcel, ParcelMaxItems)).c_str()
);
}
else if (num_over_limit > 1) {
MessageString(
Chat::Red,
PARCEL_STATUS_2,
std::to_string(num_of_parcels).c_str(),
std::to_string(num_over_limit).c_str(),
std::to_string(RuleI(Parcel, ParcelMaxItems)).c_str()
);
}
else {
Message(
Chat::Yellow,
fmt::format(
"You have {} parcels in your mailbox. Please visit a parcel merchant soon.",
num_of_parcels
).c_str()
);
}
}
SendParcelIconStatus();
}
void Client::DoParcelSend(const Parcel_Struct *parcel_in)
{
auto send_to_client = CharacterParcelsRepository::GetParcelCountAndCharacterName(database, parcel_in->send_to);
auto merchant = entity_list.GetMob(parcel_in->npc_id);
if (!merchant) {
SendParcelAck();
return;
}
auto num_of_parcels = GetParcelCount();
if (num_of_parcels >= RuleI(Parcel, ParcelMaxItems)) {
SendParcelIconStatus();
Message(
Chat::Yellow,
fmt::format(
"{} tells you, 'Unfortunately, I cannot send your parcel as you are at your parcel limit of {}. Please retrieve a parcel and try again.",
merchant->GetCleanName(),
RuleI(Parcel, ParcelMaxItems)
).c_str()
);
DoParcelCancel();
SendParcelAck();
return;
}
if (send_to_client.at(0).parcel_count >= RuleI(Parcel, ParcelMaxItems)) {
Message(
Chat::Yellow,
fmt::format(
"{} tells you, 'Unfortunately, {} cannot accept any more parcels at this time. Please try again later.",
merchant->GetCleanName(),
send_to_client.at(0).character_name == GetCleanName() ? "you" : send_to_client.at(0).character_name
).c_str()
);
SendParcelAck();
DoParcelCancel();
return;
}
if (GetParcelTimer()->Check()) {
SetParcelEnabled(true);
}
if (!GetParcelEnabled()) {
MessageString(Chat::Yellow, PARCEL_DELAY, merchant->GetCleanName());
DoParcelCancel();
SendParcelAck();
return;
}
auto next_slot = INVALID_INDEX;
if (!send_to_client.at(0).character_name.empty()) {
next_slot = FindNextFreeParcelSlot(send_to_client.at(0).char_id);
if (next_slot == INVALID_INDEX) {
Message(
Chat::Yellow,
fmt::format(
"{} tells you, 'Unfortunately, {} cannot accept any more parcels at this time. Please try again later.",
merchant->GetCleanName(),
send_to_client.at(0).character_name
).c_str()
);
SendParcelAck();
DoParcelCancel();
return;
}
}
switch (parcel_in->money_flag) {
case PARCEL_SEND_ITEMS: {
auto inst = GetInv().GetItem(parcel_in->item_slot);
if (!inst) {
LogError(
"Handle_OP_ShopSendParcel Could not find item in inventory slot {} for character {}.",
parcel_in->item_slot,
GetCleanName()
);
SendParcelAck();
DoParcelCancel();
return;
}
if (send_to_client.at(0).character_name.empty()) {
MessageString(
Chat::Yellow,
PARCEL_UNKNOWN_NAME,
merchant->GetCleanName(),
parcel_in->send_to,
inst->GetItem()->Name
);
SendParcelAck();
DoParcelCancel();
return;
}
uint32 quantity{};
if (inst->IsStackable()) {
quantity = parcel_in->quantity;
}
else {
quantity = inst->GetCharges() > 0 ? inst->GetCharges() : parcel_in->quantity;
}
CharacterParcelsRepository::CharacterParcels parcel_out;
parcel_out.from_name = GetName();
parcel_out.note = parcel_in->note;
parcel_out.sent_date = time(nullptr);
parcel_out.quantity = quantity;
parcel_out.item_id = inst->GetID();
parcel_out.char_id = send_to_client.at(0).char_id;
parcel_out.slot_id = next_slot;
parcel_out.id = 0;
auto result = CharacterParcelsRepository::InsertOne(database, parcel_out);
if (!result.id) {
LogError(
"Failed to add parcel to database. From {} to {} item {} quantity {}",
parcel_out.from_name,
parcel_out.char_id,
parcel_out.item_id,
parcel_out.quantity
);
Message(Chat::Yellow, "Unable to save parcel to the database. Please see an administrator.");
return;
}
RemoveItem(parcel_out.item_id, parcel_out.quantity);
std::unique_ptr<EQApplicationPacket> outapp(new EQApplicationPacket(OP_ShopSendParcel));
QueuePacket(outapp.get());
if (inst->IsStackable() && (quantity - parcel_in->quantity > 0)) {
inst->SetCharges(quantity - parcel_in->quantity);
PutItemInInventory(parcel_in->item_slot, *inst, true);
}
MessageString(
Chat::Yellow,
PARCEL_DELIVERY,
merchant->GetCleanName(),
inst->GetItem()->Name,
send_to_client.at(0).character_name.c_str()
);
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
PlayerEvent::ParcelSend e{};
e.from_player_name = parcel_out.from_name;
e.to_player_name = send_to_client.at(0).character_name;
e.item_id = parcel_out.item_id;
e.quantity = parcel_out.quantity;
e.sent_date = parcel_out.sent_date;
RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e);
}
Parcel_Struct ps{};
ps.item_slot = parcel_out.slot_id;
strn0cpy(ps.send_to, send_to_client.at(0).character_name.c_str(), sizeof(ps.send_to));
SendParcelDeliveryToWorld(ps);
break;
}
case PARCEL_SEND_MONEY: {
auto item = database.GetItem(PARCEL_MONEY_ITEM_ID);
if (!item) {
DoParcelCancel();
SendParcelAck();
return;
}
std::unique_ptr<EQ::ItemInstance> inst(database.CreateItem(item, 1));
if (!inst) {
DoParcelCancel();
SendParcelAck();
return;
}
if (send_to_client.at(0).character_name.empty()) {
MessageString(
Chat::Yellow,
PARCEL_UNKNOWN_NAME,
merchant->GetCleanName(),
parcel_in->send_to,
"Money"
);
DoParcelCancel();
SendParcelAck();
return;
}
CharacterParcelsRepository::CharacterParcels parcel_out;
parcel_out.from_name = GetName();
parcel_out.note = parcel_in->note;
parcel_out.sent_date = time(nullptr);
parcel_out.quantity = parcel_in->quantity;
parcel_out.item_id = PARCEL_MONEY_ITEM_ID;
parcel_out.char_id = send_to_client.at(0).char_id;
parcel_out.slot_id = next_slot;
parcel_out.id = 0;
auto result = CharacterParcelsRepository::InsertOne(database, parcel_out);
if (!result.id) {
LogError(
"Failed to add parcel to database. From {} to {} item {} quantity {}",
parcel_out.from_name,
send_to_client.at(0).character_name,
parcel_out.item_id,
parcel_out.quantity
);
Message(
Chat::Yellow,
"Unable to save parcel to the database. Please see an administrator."
);
return;
}
MessageString(
Chat::Yellow,
PARCEL_DELIVERY,
merchant->GetCleanName(),
"Money",
send_to_client.at(0).character_name.c_str()
);
if (player_event_logs.IsEventEnabled(PlayerEvent::PARCEL_SEND)) {
PlayerEvent::ParcelSend e{};
e.from_player_name = parcel_out.from_name;
e.to_player_name = send_to_client.at(0).character_name;
e.item_id = parcel_out.item_id;
e.quantity = parcel_out.quantity;
e.sent_date = parcel_out.sent_date;
RecordPlayerEventLog(PlayerEvent::PARCEL_SEND, e);
}
m_parcel_platinum = 0;
m_parcel_gold = 0;
m_parcel_silver = 0;
m_parcel_copper = 0;
std::unique_ptr<EQApplicationPacket> outapp(new EQApplicationPacket(OP_FinishTrade));
QueuePacket(outapp.get());
Parcel_Struct ps{};
ps.item_slot = parcel_out.slot_id;
strn0cpy(ps.send_to, send_to_client.at(0).character_name.c_str(), sizeof(ps.send_to));
SendParcelDeliveryToWorld(ps);
break;
}
}
SendParcelAck();
SendParcelIconStatus();
SetParcelEnabled(false);
GetParcelTimer()->Enable();
}
void Client::SendParcelAck()
{
std::unique_ptr<EQApplicationPacket> outapp(new EQApplicationPacket(OP_FinishTrade));
QueuePacket(outapp.get());
std::unique_ptr<EQApplicationPacket> outapp2(new EQApplicationPacket(OP_ShopSendParcel, sizeof(Parcel_Struct)));
auto data = (Parcel_Struct *) outapp2->pBuffer;
data->item_slot = 0xffffffff;
data->quantity = 0xffffffff;
QueuePacket(outapp2.get());
}
void Client::SendParcelRetrieveAck()
{
std::unique_ptr<EQApplicationPacket> outapp(new EQApplicationPacket(OP_ShopRetrieveParcel));
QueuePacket(outapp.get());
}
void Client::SendParcelDeliveryToWorld(const Parcel_Struct &parcel)
{
std::unique_ptr<ServerPacket> out(new ServerPacket(ServerOP_ParcelDelivery, sizeof(Parcel_Struct)));
auto data = (Parcel_Struct *) out->pBuffer;
data->item_slot = parcel.item_slot;
strn0cpy(data->send_to, parcel.send_to, sizeof(data->send_to));
worldserver.SendPacket(out.get());
}
void Client::DoParcelRetrieve(const ParcelRetrieve_Struct &parcel_in)
{
auto merchant = entity_list.GetNPCByID(parcel_in.merchant_entity_id);
if (!merchant) {
SendParcelRetrieveAck();
return;
}
auto p = m_parcels.find(parcel_in.parcel_slot_id);
if (p != m_parcels.end()) {
uint32 item_id = parcel_in.parcel_item_id;
uint32 item_quantity = p->second.quantity;
if (!item_id || !item_quantity) {
LogError(
"Attempt to retrieve parcel with erroneous item id or quantity for client character id {}.",
CharacterID()
);
SendParcelRetrieveAck();
return;
}
std::unique_ptr<EQ::ItemInstance> inst(database.CreateItem(item_id, item_quantity));
if (!inst) {
SendParcelRetrieveAck();
return;
}
switch (parcel_in.parcel_item_id) {
case PARCEL_MONEY_ITEM_ID: {
AddMoneyToPP(p->second.quantity, true);
MessageString(
Chat::Yellow,
PARCEL_DELIVERED,
merchant->GetCleanName(),
"Money", //inst->DetermineMoneyStringForParcels(p->second.quantity).c_str(),
p->second.from_name.c_str()
);
break;
}
default: {
auto free_id = GetInv().FindFreeSlot(false, false);
if (CheckLoreConflict(inst->GetItem())) {
if (RuleB(Parcel, DeleteOnDuplicate)) {
MessageString(Chat::Yellow, PARCEL_DUPLICATE_DELETE, inst->GetItem()->Name);
}
else {
MessageString(Chat::Yellow, DUP_LORE);
SendParcelRetrieveAck();
return;
}
}
else if (inst->IsStackable()) {
inst->SetCharges(item_quantity);
if (TryStacking(inst.get(), ItemPacketTrade, true, false)) {
MessageString(
Chat::Yellow,
PARCEL_DELIVERED_2,
merchant->GetCleanName(),
std::to_string(item_quantity).c_str(),
inst->GetItem()->Name,
p->second.from_name.c_str()
);
}
else if (free_id != INVALID_INDEX) {
inst->SetCharges(item_quantity);
if (PutItemInInventory(free_id, *inst, true)) {
MessageString(
Chat::Yellow,
PARCEL_DELIVERED_2,
merchant->GetCleanName(),
std::to_string(item_quantity).c_str(),
inst->GetItem()->Name,
p->second.from_name.c_str()
);
}
}
else {
MessageString(Chat::Yellow, PARCEL_INV_FULL, merchant->GetCleanName());
SendParcelRetrieveAck();
return;
}
}
else if (free_id != INVALID_INDEX) {
inst->SetCharges(item_quantity > 0 ? item_quantity : 1);
if (PutItemInInventory(free_id, *inst.get(), true)) {
MessageString(
Chat::Yellow,
PARCEL_DELIVERED,
merchant->GetCleanName(),
inst->GetItem()->Name,
p->second.from_name.c_str()
);
}
else {
MessageString(Chat::Yellow, PARCEL_INV_FULL, merchant->GetCleanName());
SendParcelRetrieveAck();
return;
}
}
else {
MessageString(Chat::Yellow, PARCEL_INV_FULL, merchant->GetCleanName());
SendParcelRetrieveAck();
return;
}
}
}
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.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);
}
SendParcelRetrieveAck();
SendParcelIconStatus();
}
bool Client::DeleteParcel(uint32 parcel_id)
{
auto result = CharacterParcelsRepository::DeleteOne(database, parcel_id);
if (!result) {
LogError("Error deleting parcel id {} from the database.", parcel_id);
return false;
}
auto it = std::find_if(m_parcels.cbegin(), m_parcels.cend(), [&](const auto &x) { return x.second.id == parcel_id; });
SetParcelCount(GetParcelCount() - 1);
return true;
}
void Client::LoadParcels()
{
m_parcels.clear();
auto results = CharacterParcelsRepository::GetWhere(database, fmt::format("char_id = '{}'", CharacterID()));
for (auto const &p: results) {
m_parcels.emplace(p.slot_id, p);
}
SetParcelCount(m_parcels.size());
}
void Client::SendParcelDelete(const ParcelRetrieve_Struct &parcel_in)
{
std::unique_ptr<EQApplicationPacket> outapp(new EQApplicationPacket(OP_ShopDeleteParcel, sizeof(ParcelRetrieve_Struct)));
auto data = (ParcelRetrieve_Struct *) outapp->pBuffer;
data->merchant_entity_id = parcel_in.merchant_entity_id;
data->player_entity_id = parcel_in.player_entity_id;
data->parcel_slot_id = parcel_in.parcel_slot_id;
data->parcel_item_id = parcel_in.parcel_item_id;
QueuePacket(outapp.get());
}
int32 Client::FindNextFreeParcelSlot(uint32 char_id)
{
auto results = CharacterParcelsRepository::GetWhere(
database,
fmt::format("char_id = '{}' ORDER BY slot_id ASC", char_id)
);
if (results.empty()) {
return PARCEL_BEGIN_SLOT;
}
for (uint32 i = PARCEL_BEGIN_SLOT; i <= RuleI(Parcel, ParcelMaxItems); i++) {
auto it = std::find_if(results.cbegin(), results.cend(), [&](const auto &x) { return x.slot_id == i; });
if (it == results.end()) {
return i;
}
}
return INVALID_INDEX;
}
void Client::SendParcelIconStatus()
{
std::unique_ptr<EQApplicationPacket> outapp(new EQApplicationPacket(OP_ShopParcelIcon, sizeof(ParcelIcon_Struct)));
auto data = (ParcelIcon_Struct *) outapp->pBuffer;
auto const num_of_parcels = GetParcelCount();
data->status = IconOn;
if (num_of_parcels == 0) {
data->status = IconOff;
}
else if (num_of_parcels > RuleI(Parcel, ParcelMaxItems)) {
data->status = Overlimit;
}
QueuePacket(outapp.get());
}
void Client::AddParcel(CharacterParcelsRepository::CharacterParcels &parcel)
{
auto result = CharacterParcelsRepository::InsertOne(database, parcel);
if (!result.id) {
LogError(
"Failed to add parcel to database. From {} to id {} item {} quantity {}",
parcel.from_name,
parcel.char_id,
parcel.item_id,
parcel.quantity
);
Message(
Chat::Yellow,
"Unable to send parcel at this time. Please try again later."
);
SendParcelAck();
return;
}
}
+13
View File
@@ -192,6 +192,10 @@
#define PET_SPELLHOLD_SET_ON 702 //The pet spellhold mode has been set to on.
#define PET_SPELLHOLD_SET_OFF 703 //The pet spellhold mode has been set to off.
#define GUILD_NAME_IN_USE 711 //You cannot create a guild with that name, that guild already exists on this server.
#define PARCEL_DELAY 734 //%1 tells you, 'You must give me a chance to send the last parcel before I can send another!'
#define PARCEL_DUPLICATE_DELETE 737 //Duplicate lore items are not allowed! Your duplicate %1 has been deleted!
#define PARCEL_DELIVER_3 741 //%1 told you, 'I will deliver the stack of %2 %3 to %4 as soon as possible!'
#define PARCEL_INV_FULL 790 //%1 tells you, 'Your inventory appears full! Unable to retrieve parceled item.'
#define AA_CAP 1000 //You have reached the AA point cap, and cannot gain any further experience until some of your stored AA point pool is used.
#define GM_GAINXP 1002 //[GM] You have gained %1 AXP and %2 EXP (%3).
#define MALE_SLAYUNDEAD 1007 //%1's holy blade cleanses his target!(%2)
@@ -277,6 +281,7 @@
#define SPARKLES 1236 //Your %1 sparkles.
#define GROWS_DIM 1237 //Your %1 grows dim.
#define BEGINS_TO_SHINE 1238 //Your %1 begins to shine.
#define CANT_FIND_PLAYER 1276 //I can't find a player named %1!
#define SURNAME_REJECTED 1374 //Your new surname was rejected. Please try a different name.
#define GUILD_DISBANDED 1377 //Your guild has been disbanded! You are no longer a member of any guild.
#define DUEL_DECLINE 1383 //%1 has declined your challenge to duel to the death.
@@ -302,6 +307,7 @@
#define SENSE_CORPSE_DIRECTION 1563 //You sense a corpse in this direction.
#define QUEUED_TELL 2458 //[queued]
#define QUEUE_TELL_FULL 2459 //[zoing and queue is full]
#define TRADER_BUSY_TWO 3192 //Sorry, that action cannot be performed while trading.
#define SUSPEND_MINION_UNSUSPEND 3267 //%1 tells you, 'I live again...'
#define SUSPEND_MINION_SUSPEND 3268 //%1 tells you, 'By your command, master.'
#define ONLY_SUMMONED_PETS 3269 //3269 This effect only works with summoned pets.
@@ -384,6 +390,8 @@
#define ALREADY_IN_GRP_RAID 5088 //% 1 rejects your invite because they are in a raid and you are not in theirs, or they are a raid group leader
#define DUNGEON_SEALED 5141 //The gateway to the dungeon is sealed off to you. Perhaps you would be able to enter if you needed to adventure there.
#define ADVENTURE_COMPLETE 5147 //You received %1 points for successfully completing the adventure.
#define PARCEL_STATUS_2 5433 //You currently have % 1 parcels in your mail and are % 2 parcels over the limit of % 3!If you do not retrieve at least % 2 parcels before you logout, they will be lost!
#define PARCEL_STATUS_1 5434 //You currently have % 1 parcels in your mail and are 1 parcel over the limit of % 2!If you do not retrieve at least 1 parcel before you logout, it will be lost!
#define SUCCOR_FAIL 5169 //The portal collapes before you can escape!
#define NO_PROPER_ACCESS 5410 //You don't have the proper access rights.
#define AUGMENT_RESTRICTED 5480 //The item does not satisfy the augment's restrictions.
@@ -411,6 +419,11 @@
#define TRANSFORM_COMPLETE 6327 //You have successfully transformed your %1.
#define DETRANSFORM_FAILED 6341 //%1 has no transformation that can be removed.
#define GUILD_PERMISSION_FAILED 6418 //You do not have permission to change access options.
#define PARCEL_DELIVERY_ARRIVED 6465 //You have received a new parcel delivery!
#define PARCEL_DELIVERY 6466 //%1 tells you, 'I will deliver the %2 to %3 as soon as possible!'
#define PARCEL_UNKNOWN_NAME 6467 //%1 tells you, 'Unfortunately, I don't know anyone by the name of %2. Here is your %3 back.''
#define PARCEL_DELIVERED 6471 //%1 hands you the %2 that was sent from %3.
#define PARCEL_DELIVERED_2 6472 //%1 hands you the stack of %2 %3 that was sent from %4.
#define GENERIC_STRING 6688 //%1 (used to any basic message)
#define SENTINEL_TRIG_YOU 6724 //You have triggered your sentinel.
#define SENTINEL_TRIG_OTHER 6725 //%1 has triggered your sentinel.
+45
View File
@@ -3859,6 +3859,51 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p)
}
break;
}
case ServerOP_ParcelDelivery: {
auto in = (Parcel_Struct *) pack->pBuffer;
if (strlen(in->send_to) == 0) {
LogError(
"ServerOP_ParcelDelivery pack received with incorrect character_name of {}.",
in->send_to
);
return;
}
for (auto const &c: entity_list.GetClientList()) {
if (strcasecmp(c.second->GetCleanName(), in->send_to) == 0) {
c.second->MessageString(
Chat::Yellow,
PARCEL_DELIVERY_ARRIVED
);
c.second->SendParcelStatus();
if (c.second->GetEngagedWithParcelMerchant()) {
c.second->SendParcel(*in);
}
return;
}
}
break;
}
case ServerOP_ParcelPrune: {
for (auto const &c: entity_list.GetClientList()) {
if (c.second->GetEngagedWithParcelMerchant()) {
c.second->Message(
Chat::Red,
"Parcel data has been updated. Please re-open the Merchant Window."
);
c.second->SetEngagedWithParcelMerchant(false);
c.second->DoParcelCancel();
auto out = new EQApplicationPacket(OP_ShopEndConfirm);
c.second->QueuePacket(out);
safe_delete(out);
return;
}
}
break;
}
default: {
LogInfo("Unknown ZS Opcode [{}] size [{}]", (int)pack->opcode, pack->size);
break;
+1
View File
@@ -1792,6 +1792,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
t->min_dmg = n.mindmg;
t->max_dmg = n.maxdmg;
t->attack_count = n.attack_count;
t->is_parcel_merchant = n.is_parcel_merchant ? true : false;
if (!n.special_abilities.empty()) {
strn0cpy(t->special_abilities, n.special_abilities.c_str(), 512);
+1
View File
@@ -154,6 +154,7 @@ struct NPCType
int exp_mod;
int heroic_strikethrough;
bool keeps_sold_items;
bool is_parcel_merchant;
};
#pragma pack()