mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-17 03:08:26 +00:00
[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:
@@ -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]");
|
||||
}
|
||||
Reference in New Issue
Block a user