implement shared bank

This commit is contained in:
Martin Michelsen
2023-12-04 16:59:03 -08:00
parent 01b83044dc
commit c25569c688
8 changed files with 252 additions and 71 deletions
+30 -19
View File
@@ -980,11 +980,33 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
s->send_lobby_join_notifications(l, c);
}
// TODO: implement this (and make sure the bank name is filesystem-safe)
/* static void server_command_change_bank(shared_ptr<Client> c, const std::string&) {
static void server_command_change_bank(shared_ptr<Client> c, const std::string& args) {
check_version(c, Version::BB_V4);
...
} */
if (c->config.check_flag(Client::Flag::AT_BANK_COUNTER)) {
throw runtime_error("cannot change banks while at the bank counter");
}
ssize_t new_char_index = args.empty() ? (c->game_data.bb_character_index + 1) : stol(args, nullptr, 0);
if (new_char_index == 0) {
if (c->game_data.use_shared_bank()) {
send_text_message_printf(c, "$C6Using shared bank (0)");
} else {
send_text_message_printf(c, "$C6Created shared bank (0)");
}
} else if (new_char_index <= 4) {
c->game_data.use_character_bank(new_char_index - 1);
auto bp = c->game_data.current_bank_character();
auto name = bp->disp.name.decode(c->language());
send_text_message_printf(c, "$C6Using %s\'s bank (%zu)", name.c_str(), new_char_index);
} else {
throw runtime_error("invalid bank number");
}
const auto& bank = c->game_data.current_bank();
send_text_message_printf(c, "%" PRIu32 " items\n%" PRIu32 " Meseta", bank.num_items.load(), bank.meseta.load());
}
// TODO: This can be implemented on the proxy server too.
static void server_command_convert_char_to_bb(shared_ptr<Client> c, const std::string& args) {
@@ -1023,22 +1045,10 @@ static void server_command_convert_char_to_bb(shared_ptr<Client> c, const std::s
static void server_command_save(shared_ptr<Client> c, const std::string&) {
check_version(c, Version::BB_V4);
try {
c->game_data.save_character_file();
send_text_message(c, "Character data saved");
c->game_data.save_all();
send_text_message(c, "All data saved");
} catch (const exception& e) {
send_text_message_printf(c, "Can\'t save character:\n%s", e.what());
}
try {
c->game_data.save_system_file();
send_text_message(c, "System data saved");
} catch (const exception& e) {
send_text_message_printf(c, "Can\'t save system data:\n%s", e.what());
}
try {
c->game_data.save_guild_card_file();
send_text_message(c, "Guild Card data saved");
} catch (const exception& e) {
send_text_message_printf(c, "Can\'t save Guild Cards:\n%s", e.what());
send_text_message_printf(c, "Can\'t save data:\n%s", e.what());
}
c->reschedule_save_game_data_event();
}
@@ -1677,6 +1687,7 @@ static const unordered_map<string, ChatCommandDefinition> chat_commands({
{"$auction", {server_command_auction, proxy_command_auction}},
{"$ax", {server_command_ax, nullptr}},
{"$ban", {server_command_ban, nullptr}},
{"$bank", {server_command_change_bank, nullptr}},
{"$bbchar", {server_command_convert_char_to_bb, nullptr}},
{"$cheat", {server_command_cheat, nullptr}},
{"$debug", {server_command_debug, nullptr}},
+2 -2
View File
@@ -226,7 +226,7 @@ void Client::set_license(shared_ptr<License> l) {
this->license = l;
this->game_data.guild_card_number = this->license->serial_number;
if (this->version() == Version::BB_V4) {
this->game_data.bb_username = this->license->bb_username;
this->game_data.set_bb_username(this->license->bb_username);
}
}
@@ -296,7 +296,7 @@ void Client::save_game_data() {
throw logic_error("save_game_data called for non-BB client");
}
if (this->game_data.character(false)) {
this->game_data.save_character_file();
this->game_data.save_all();
}
}
+172 -27
View File
@@ -66,19 +66,35 @@ std::shared_ptr<PSOBBGuildCardFile> PlayerFilesManager::get_guild_card(const std
}
}
std::shared_ptr<PlayerBank> PlayerFilesManager::get_bank(const std::string& filename) {
try {
return this->loaded_bank_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file) {
if (!this->loaded_system_files.emplace(filename, file).second) {
throw runtime_error("Guild Card file already loaded");
throw runtime_error("Guild Card file already loaded: " + filename);
}
}
void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file) {
if (!this->loaded_character_files.emplace(filename, file).second) {
throw runtime_error("character file already loaded");
throw runtime_error("character file already loaded: " + filename);
}
}
void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file) {
if (!this->loaded_guild_card_files.emplace(filename, file).second) {
throw runtime_error("Guild Card file already loaded");
throw runtime_error("Guild Card file already loaded: " + filename);
}
}
void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<PlayerBank> file) {
if (!this->loaded_bank_files.emplace(filename, file).second) {
throw runtime_error("bank file already loaded: " + filename);
}
}
@@ -96,6 +112,10 @@ void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx)
if (num_deleted) {
player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted);
}
num_deleted = erase_unused(self->loaded_bank_files);
if (num_deleted) {
player_data_log.info("Cleared %zu expired bank file(s)", num_deleted);
}
}
ClientGameData::ClientGameData(std::shared_ptr<PlayerFilesManager> files_manager)
@@ -111,10 +131,24 @@ ClientGameData::ClientGameData(std::shared_ptr<PlayerFilesManager> files_manager
ClientGameData::~ClientGameData() {
if (!this->bb_username.empty() && this->character_data.get()) {
this->save_character_file();
this->save_all();
}
}
const string& ClientGameData::get_bb_username() const {
return this->bb_username;
}
void ClientGameData::set_bb_username(const string& bb_username) {
// Make sure bb_username is filename-safe
for (char ch : bb_username) {
if (!isalnum(ch) && (ch != '-') && (ch != '_')) {
throw runtime_error("invalid characters in username");
}
}
this->bb_username = bb_username;
}
void ClientGameData::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_ptr<const LevelTable> level_table) {
this->overlay_character_data = make_shared<PSOBBCharacterFile>(*this->character(true, false));
@@ -268,14 +302,17 @@ string ClientGameData::system_filename() const {
return string_printf("system/players/system_%s.psosys", this->bb_username.c_str());
}
string ClientGameData::character_filename() const {
string ClientGameData::character_filename(int8_t index) const {
if (this->bb_username.empty()) {
throw logic_error("non-BB players do not have character data");
}
if (this->bb_character_index < 0) {
if (index < 0) {
index = this->bb_character_index;
}
if (index < 0) {
throw logic_error("character index is not set");
}
return string_printf("system/players/player_%s_%hhd.psochar", this->bb_username.c_str(), this->bb_character_index);
return string_printf("system/players/player_%s_%hhd.psochar", this->bb_username.c_str(), index);
}
string ClientGameData::guild_card_filename() const {
@@ -285,6 +322,13 @@ string ClientGameData::guild_card_filename() const {
return string_printf("system/players/guild_cards_%s.psocard", this->bb_username.c_str());
}
string ClientGameData::shared_bank_filename() const {
if (this->bb_username.empty()) {
throw logic_error("non-BB players do not have shared bank files");
}
return string_printf("system/players/shared_bank_%s.psobank", this->bb_username.c_str());
}
string ClientGameData::legacy_account_filename() const {
if (this->bb_username.empty()) {
throw logic_error("non-BB players do not have legacy account data");
@@ -356,7 +400,7 @@ void ClientGameData::load_all_files() {
throw runtime_error("incorrect flag in character file header");
}
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
this->files_manager->set_character(this->character_filename(), this->character_data);
this->files_manager->set_character(char_filename, this->character_data);
player_data_log.info("Loaded character data from %s", char_filename.c_str());
// If there was no .psosys file, load the system file from the .psochar
@@ -473,6 +517,29 @@ void ClientGameData::load_all_files() {
}
}
void ClientGameData::save_all() {
if (this->system_data) {
this->save_system_file();
}
if (this->character_data) {
this->save_character_file();
}
if (this->guild_card_data) {
this->save_guild_card_file();
}
if (this->external_bank) {
string filename = this->shared_bank_filename();
save_object_file<PlayerBank>(filename, *this->external_bank);
player_data_log.info("Saved shared bank file %s", filename.c_str());
}
if (this->external_bank_character) {
this->save_character_file(
this->character_filename(this->external_bank_character_index),
this->system_data,
this->external_bank_character);
}
}
void ClientGameData::save_system_file() const {
if (!this->system_data) {
throw logic_error("no system file loaded");
@@ -482,6 +549,30 @@ void ClientGameData::save_system_file() const {
player_data_log.info("Saved system file %s", filename.c_str());
}
void ClientGameData::save_character_file(
const string& filename,
shared_ptr<const PSOBBBaseSystemFile> system,
shared_ptr<const PSOBBCharacterFile> character) {
auto f = fopen_unique(filename, "wb");
PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000};
fwritex(f.get(), header);
fwritex(f.get(), *character);
fwritex(f.get(), *system);
// TODO: Technically, we should write the actual team membership struct to the
// file here, but that would cause ClientGameData to depend on License, which
// it currently does not. This data doesn't matter at all for correctness
// within newserv, since it ignores this data entirely and instead generates
// the membership struct from the team ID in the License and the team's state.
// So, writing correct data here would mostly be for compatibility with other
// PSO servers. But if the other server is newserv, then this data would be
// used anyway, and if it's not, then it would presumably have a different set
// of teams with a different set of team IDs anyway, so the membership struct
// here would be useless either way.
static const PSOBBTeamMembership empty_membership;
fwritex(f.get(), empty_membership);
player_data_log.info("Saved character file %s", filename.c_str());
}
void ClientGameData::save_character_file() {
if (!this->system_data.get()) {
throw logic_error("no system file loaded");
@@ -500,25 +591,7 @@ void ClientGameData::save_character_file() {
this->last_play_time_update = t;
}
string filename = this->character_filename();
auto f = fopen_unique(filename, "wb");
PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000};
fwritex(f.get(), header);
fwritex(f.get(), *this->character_data);
fwritex(f.get(), *this->system_data);
// TODO: Technically, we should write the actual team membership struct to the
// file here, but that would cause ClientGameData to depend on License, which
// it currently does not. This data doesn't matter at all for correctness
// within newserv, since it ignores this data entirely and instead generates
// the membership struct from the team ID in the License and the team's state.
// So, writing correct data here would mostly be for compatibility with other
// PSO servers. But if the other server is newserv, then this data would be
// used anyway, and if it's not, then it would presumably have a different set
// of teams with a different set of team IDs anyway, so the membership struct
// here would be useless either way.
static const PSOBBTeamMembership empty_membership;
fwritex(f.get(), empty_membership);
player_data_log.info("Saved character file %s", filename.c_str());
this->save_character_file(this->character_filename(), this->system_data, this->character_data);
}
void ClientGameData::save_guild_card_file() const {
@@ -529,3 +602,75 @@ void ClientGameData::save_guild_card_file() const {
save_object_file(filename, *this->guild_card_data);
player_data_log.info("Saved Guild Card file %s", filename.c_str());
}
PlayerBank& ClientGameData::current_bank() {
if (this->external_bank) {
return *this->external_bank;
} else if (this->external_bank_character) {
return this->external_bank_character->bank;
}
return this->character()->bank;
}
std::shared_ptr<PSOBBCharacterFile> ClientGameData::current_bank_character() {
return this->external_bank_character ? this->external_bank_character : this->character();
}
void ClientGameData::use_default_bank() {
if (this->external_bank) {
string filename = this->shared_bank_filename();
save_object_file<PlayerBank>(filename, *this->external_bank);
this->external_bank.reset();
player_data_log.info("Detached shared bank %s", filename.c_str());
}
if (this->external_bank_character) {
string filename = this->character_filename(this->external_bank_character_index);
this->save_character_file(filename, this->system_data, this->external_bank_character);
this->external_bank_character.reset();
player_data_log.info("Detached character %s from bank", filename.c_str());
}
}
bool ClientGameData::use_shared_bank() {
this->use_default_bank();
string filename = this->shared_bank_filename();
if (isfile(filename)) {
this->external_bank = make_shared<PlayerBank>(load_object_file<PlayerBank>(filename));
player_data_log.info("Loaded shared bank %s", filename.c_str());
return true;
} else {
this->external_bank = make_shared<PlayerBank>();
player_data_log.info("Created shared bank for %s", filename.c_str());
return false;
}
}
void ClientGameData::use_character_bank(int8_t index) {
this->use_default_bank();
if (index != this->bb_character_index) {
string filename = this->character_filename(index);
this->external_bank_character = this->files_manager->get_character(filename);
if (this->external_bank_character) {
this->external_bank_character_index = index;
player_data_log.info("Using loaded character file %s for external bank", filename.c_str());
} else if (isfile(filename)) {
auto f = fopen_unique(filename, "rb");
auto header = freadx<PSOCommandHeaderBB>(f.get());
if (header.size != 0x399C) {
throw runtime_error("incorrect size in character file header");
}
if (header.command != 0x00E7) {
throw runtime_error("incorrect command in character file header");
}
if (header.flag != 0x00000000) {
throw runtime_error("incorrect flag in character file header");
}
this->external_bank_character = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
this->external_bank_character_index = index;
this->files_manager->set_character(filename, this->external_bank_character);
player_data_log.info("Loaded character data from %s for external bank", filename.c_str());
} else {
throw runtime_error("character does not exist");
}
}
}
+25 -2
View File
@@ -39,10 +39,12 @@ public:
std::shared_ptr<PSOBBBaseSystemFile> get_system(const std::string& filename);
std::shared_ptr<PSOBBCharacterFile> get_character(const std::string& filename);
std::shared_ptr<PSOBBGuildCardFile> get_guild_card(const std::string& filename);
std::shared_ptr<PlayerBank> get_bank(const std::string& filename);
void set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file);
void set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file);
void set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file);
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank> file);
private:
std::shared_ptr<struct event_base> base;
@@ -51,6 +53,7 @@ private:
std::unordered_map<std::string, std::shared_ptr<PSOBBBaseSystemFile>> loaded_system_files;
std::unordered_map<std::string, std::shared_ptr<PSOBBCharacterFile>> loaded_character_files;
std::unordered_map<std::string, std::shared_ptr<PSOBBGuildCardFile>> loaded_guild_card_files;
std::unordered_map<std::string, std::shared_ptr<PlayerBank>> loaded_bank_files;
static void clear_expired_files(evutil_socket_t fd, short events, void* ctx);
};
@@ -75,7 +78,6 @@ public:
std::shared_ptr<Episode3::PlayerConfig> ep3_config;
// These are only used if the client is BB
std::string bb_username;
int8_t bb_character_index;
ItemData identify_result;
std::array<std::vector<ItemData>, 3> shop_contents;
@@ -83,6 +85,9 @@ public:
explicit ClientGameData(std::shared_ptr<PlayerFilesManager> files_manager);
~ClientGameData();
const std::string& get_bb_username() const;
void set_bb_username(const std::string& bb_username);
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
inline void delete_overlay() {
@@ -107,12 +112,24 @@ public:
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table);
void save_all();
void save_system_file() const;
static void save_character_file(
const std::string& filename,
std::shared_ptr<const PSOBBBaseSystemFile> sys,
std::shared_ptr<const PSOBBCharacterFile> character);
// Note: This function is not const because it updates the player's play time.
void save_character_file();
void save_guild_card_file() const;
PlayerBank& current_bank();
std::shared_ptr<PSOBBCharacterFile> current_bank_character();
bool use_shared_bank(); // Returns true if the bank exists; false if it was created
void use_character_bank(int8_t bb_character_index);
void use_default_bank();
private:
std::string bb_username;
std::shared_ptr<PlayerFilesManager> files_manager;
// The overlay character data is used in battle and challenge modes, when
@@ -122,13 +139,19 @@ private:
std::shared_ptr<PSOBBCharacterFile> overlay_character_data;
std::shared_ptr<PSOBBCharacterFile> character_data;
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
std::shared_ptr<PlayerBank> external_bank;
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
int8_t external_bank_character_index;
uint64_t last_play_time_update;
void save_and_clear_external_bank();
void load_all_files();
std::string system_filename() const;
std::string character_filename() const;
std::string character_filename(int8_t index = -1) const;
std::string guild_card_filename() const;
std::string shared_bank_filename() const;
std::string legacy_player_filename() const;
std::string legacy_account_filename() const;
+5 -5
View File
@@ -2974,10 +2974,10 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
} else if (command == 0x61) {
if (!c->pending_bb_save_username.empty()) {
string prev_bb_username = c->game_data.bb_username;
string prev_bb_username = c->game_data.get_bb_username();
int8_t prev_bb_character_index = c->game_data.bb_character_index;
c->game_data.bb_username = c->pending_bb_save_username;
c->game_data.set_bb_username(c->pending_bb_save_username);
c->game_data.bb_character_index = c->pending_bb_save_character_index;
// Update a few fields for BB
@@ -3005,7 +3005,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
c->pending_bb_save_username.c_str());
}
c->game_data.bb_username = prev_bb_username;
c->game_data.set_bb_username(prev_bb_username);
c->game_data.bb_character_index = prev_bb_character_index;
c->pending_bb_save_username.clear();
@@ -3117,7 +3117,7 @@ static void on_E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
ClientGameData temp_gd(s->player_files_manager);
temp_gd.guild_card_number = c->license->serial_number;
temp_gd.bb_username = c->license->bb_username;
temp_gd.set_bb_username(c->license->bb_username);
temp_gd.bb_character_index = cmd.character_index;
try {
@@ -4789,7 +4789,7 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
}
}
if (!reward.reward_item.empty()) {
c->game_data.character()->bank.add_item(reward.reward_item);
c->game_data.current_bank().add_item(reward.reward_item);
}
}
break;
+11 -11
View File
@@ -1453,24 +1453,25 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint
}
auto p = c->game_data.character();
auto& bank = c->game_data.current_bank();
if (cmd.action == 0) { // Deposit
if (cmd.item_id == 0xFFFFFFFF) { // Deposit Meseta
if (cmd.meseta_amount > p->disp.stats.meseta) {
l->log.info("Player %hu attempted to deposit %" PRIu32 " Meseta in the bank, but has only %" PRIu32 " Meseta on hand",
c->lobby_client_id, cmd.meseta_amount.load(), p->disp.stats.meseta.load());
} else if ((p->bank.meseta + cmd.meseta_amount) > 999999) {
} else if ((bank.meseta + cmd.meseta_amount) > 999999) {
l->log.info("Player %hu attempted to deposit %" PRIu32 " Meseta in the bank, but already has %" PRIu32 " Meseta in the bank",
c->lobby_client_id, cmd.meseta_amount.load(), p->disp.stats.meseta.load());
} else {
p->bank.meseta += cmd.meseta_amount;
bank.meseta += cmd.meseta_amount;
p->disp.stats.meseta -= cmd.meseta_amount;
l->log.info("Player %hu deposited %" PRIu32 " Meseta in the bank (bank now has %" PRIu32 "; inventory now has %" PRIu32 ")",
c->lobby_client_id, cmd.meseta_amount.load(), p->bank.meseta.load(), p->disp.stats.meseta.load());
c->lobby_client_id, cmd.meseta_amount.load(), bank.meseta.load(), p->disp.stats.meseta.load());
}
} else { // Deposit item
auto item = p->remove_item(cmd.item_id, cmd.item_amount, c->version() != Version::BB_V4);
p->bank.add_item(item);
bank.add_item(item);
send_destroy_item(c, cmd.item_id, cmd.item_amount);
string name = s->item_name_index->describe_item(Version::BB_V4, item);
@@ -1481,21 +1482,21 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint
} else if (cmd.action == 1) { // Take
if (cmd.item_index == 0xFFFF) { // Take Meseta
if (cmd.meseta_amount > p->bank.meseta) {
if (cmd.meseta_amount > bank.meseta) {
l->log.info("Player %hu attempted to withdraw %" PRIu32 " Meseta from the bank, but has only %" PRIu32 " Meseta in the bank",
c->lobby_client_id, cmd.meseta_amount.load(), p->bank.meseta.load());
c->lobby_client_id, cmd.meseta_amount.load(), bank.meseta.load());
} else if ((p->disp.stats.meseta + cmd.meseta_amount) > 999999) {
l->log.info("Player %hu attempted to withdraw %" PRIu32 " Meseta from the bank, but already has %" PRIu32 " Meseta on hand",
c->lobby_client_id, cmd.meseta_amount.load(), p->disp.stats.meseta.load());
} else {
p->bank.meseta -= cmd.meseta_amount;
bank.meseta -= cmd.meseta_amount;
p->disp.stats.meseta += cmd.meseta_amount;
l->log.info("Player %hu withdrew %" PRIu32 " Meseta from the bank (bank now has %" PRIu32 "; inventory now has %" PRIu32 ")",
c->lobby_client_id, cmd.meseta_amount.load(), p->bank.meseta.load(), p->disp.stats.meseta.load());
c->lobby_client_id, cmd.meseta_amount.load(), bank.meseta.load(), p->disp.stats.meseta.load());
}
} else { // Take item
auto item = p->bank.remove_item_by_index(cmd.item_index, cmd.item_amount);
auto item = bank.remove_item_by_index(cmd.item_index, cmd.item_amount);
item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(item);
send_create_inventory_item(c, item);
@@ -2160,9 +2161,8 @@ void on_transfer_item_via_mail_message_bb(shared_ptr<Client> c, uint8_t command,
(target_c->version() == Version::BB_V4) &&
(target_c->game_data.character(false) != nullptr) &&
!target_c->config.check_flag(Client::Flag::AT_BANK_COUNTER)) {
auto target_p = target_c->game_data.character(false);
try {
target_p->bank.add_item(item);
target_c->game_data.current_bank().add_item(item);
item_sent = true;
} catch (const runtime_error&) {
}
+6 -5
View File
@@ -2357,14 +2357,15 @@ void send_bank(shared_ptr<Client> c) {
}
auto p = c->game_data.character();
const auto* items_it = p->bank.items.data();
vector<PlayerBankItem> items(items_it, items_it + p->bank.num_items);
const auto& bank = c->game_data.current_bank();
const auto* items_it = bank.items.data();
vector<PlayerBankItem> items(items_it, items_it + bank.num_items);
G_BankContentsHeader_BB_6xBC cmd = {
{{0xBC, 0, 0}, sizeof(G_BankContentsHeader_BB_6xBC) + items.size() * sizeof(PlayerBankItem)},
random_object<uint32_t>(),
p->bank.num_items,
p->bank.meseta};
bank.num_items,
bank.meseta};
send_command_t_vt(c, 0x6C, 0x00, cmd, items);
}
@@ -3459,7 +3460,7 @@ void send_team_reward_list(std::shared_ptr<Client> c, bool show_purchased) {
}
auto s = c->require_server_state();
bool show_item_rewards = show_purchased || (c->game_data.character()->bank.num_items < 200);
bool show_item_rewards = show_purchased || (c->game_data.current_bank().num_items < 200);
vector<S_TeamRewardList_BB_19EA_1AEA::Entry> entries;
for (const auto& reward : s->team_index->reward_definitions()) {