mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 16:51:29 +00:00
* 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>
308 lines
8.6 KiB
C++
308 lines
8.6 KiB
C++
#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]");
|
|
}
|