add BB BankSize patch

This commit is contained in:
Martin Michelsen
2025-08-22 22:39:32 -07:00
parent 45824b46fe
commit 0b4d5b2f89
23 changed files with 814 additions and 805 deletions
+1 -1
View File
@@ -102,7 +102,7 @@ set(SOURCES
src/Menu.cc
src/NetworkAddresses.cc
src/PatchFileIndex.cc
src/PlayerFilesManager.cc
src/PlayerInventory.cc
src/PlayerSubordinates.cc
src/PPKArchive.cc
src/ProxyCommands.cc
+28 -38
View File
@@ -225,7 +225,7 @@ static asio::awaitable<void> server_command_announce_inner(const Args& a, bool m
send_text_or_scrolling_message(s, a.text, a.text);
}
} else {
auto from_name = a.c->character()->disp.name.decode(a.c->language());
auto from_name = a.c->character_file()->disp.name.decode(a.c->language());
if (mail) {
send_simple_mail(s, 0, from_name, a.text);
} else {
@@ -332,7 +332,7 @@ ChatCommandDefinition cc_auction(
});
static string name_for_client(shared_ptr<Client> c) {
auto player = c->character(false);
auto player = c->character_file(false);
if (player.get()) {
return escape_player_name(player->disp.name.decode(player->inventory.language));
}
@@ -417,30 +417,24 @@ ChatCommandDefinition cc_bank(
ssize_t new_char_index = a.text.empty() ? (a.c->bb_character_index + 1) : stol(a.text, nullptr, 0);
if (new_char_index == 0) {
if (a.c->use_shared_bank()) {
send_text_message(a.c, "$C6Using shared bank (0)");
} else {
send_text_message(a.c, "$C6Created shared bank (0)");
}
if (new_char_index <= 0) {
a.c->change_bank(-1);
send_text_message(a.c, "$C6Using shared bank");
} else if (new_char_index <= 127) {
a.c->use_character_bank(new_char_index - 1);
auto bp = a.c->current_bank_character();
auto name = escape_player_name(bp->disp.name.decode(a.c->language()));
send_text_message_fmt(a.c, "$C6Using {}\'s bank ({})", name, new_char_index);
a.c->change_bank(new_char_index - 1);
send_text_message_fmt(a.c, "$C6Using character {}'s bank", new_char_index);
} else {
throw precondition_failed("$C6Invalid bank number");
}
auto& bank = a.c->current_bank();
bank.assign_ids(0x99000000 + (a.c->lobby_client_id << 20));
auto bank = a.c->bank_file();
bank->assign_ids(0x99000000 + (a.c->lobby_client_id << 20));
a.c->log.info_f("Assigned bank item IDs");
a.c->print_bank();
send_text_message_fmt(a.c, "{} items\n{} Meseta", bank.num_items, bank.meseta);
send_text_message_fmt(a.c, "{} items\n{} Meseta", bank->items.size(), bank->meseta);
co_return;
});
@@ -491,7 +485,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
// server already has it)
GetPlayerInfoResult ch;
if (a.c->version() == Version::BB_V4) {
ch.character = a.c->character();
ch.character = a.c->character_file();
ch.is_full_info = true;
} else {
ch = co_await send_get_player_info(a.c, true);
@@ -501,11 +495,6 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
? Client::character_filename(dest_bb_license->username, dest_character_index)
: Client::backup_character_filename(dest_account->account_id, dest_character_index, is_ep3(a.c->version()));
if (s->player_files_manager->get_character(filename)) {
send_text_message(a.c, "$C6The target player\nis currently loaded.\nSign off in Blue\nBurst and try again.");
co_return;
}
if (ch.is_full_info) {
// Client sent 30; ch contains the verbatim save file from the client
if (ch.ep3_character) {
@@ -911,7 +900,7 @@ ChatCommandDefinition cc_edit(
using MatType = PSOBBCharacterFile::MaterialType;
try {
auto p = a.c->character();
auto p = a.c->character_file();
if (tokens.at(0) == "atp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
p->disp.stats.char_stats.atp = stoul(tokens.at(1));
} else if (tokens.at(0) == "mst" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
@@ -1331,7 +1320,7 @@ ChatCommandDefinition cc_killcount(
+[](const Args& a) -> asio::awaitable<void> {
a.check_is_proxy(false);
auto p = a.c->character();
auto p = a.c->character_file();
vector<size_t> item_indexes;
for (size_t z = 0; z < p->inventory.num_items; z++) {
const auto& item = p->inventory.items[z];
@@ -1597,13 +1586,13 @@ ChatCommandDefinition cc_loadchar(
};
if (a.c->version() == Version::DC_V2) {
PSODCV2CharacterFile::Character dc_char = *a.c->character();
PSODCV2CharacterFile::Character dc_char = *a.c->character_file();
co_await send_set_extended_player_info(dc_char);
} else if (a.c->version() == Version::GC_NTE) {
PSOGCNTECharacterFileCharacter gc_char = *a.c->character();
PSOGCNTECharacterFileCharacter gc_char = *a.c->character_file();
co_await send_set_extended_player_info(gc_char);
} else if (a.c->version() == Version::GC_V3) {
PSOGCCharacterFile::Character gc_char = *a.c->character();
PSOGCCharacterFile::Character gc_char = *a.c->character_file();
co_await send_set_extended_player_info(gc_char);
} else if (a.c->version() == Version::GC_EP3_NTE) {
PSOGCEp3NTECharacter nte_char = *ep3_char;
@@ -1614,7 +1603,7 @@ ChatCommandDefinition cc_loadchar(
if (!a.c->login || !a.c->login->xb_license) {
throw runtime_error("XB client is not logged in");
}
PSOXBCharacterFile::Character xb_char = *a.c->character();
PSOXBCharacterFile::Character xb_char = *a.c->character_file();
xb_char.guild_card.xb_user_id_high = (a.c->login->xb_license->user_id >> 32) & 0xFFFFFFFF;
xb_char.guild_card.xb_user_id_low = a.c->login->xb_license->user_id & 0xFFFFFFFF;
co_await send_set_extended_player_info(xb_char);
@@ -1637,7 +1626,7 @@ ChatCommandDefinition cc_matcount(
+[](const Args& a) -> asio::awaitable<void> {
a.check_is_proxy(false);
auto p = a.c->character();
auto p = a.c->character_file();
if (is_v1_or_v2(a.c->version())) {
send_text_message_fmt(a.c, "{} HP, {} TP",
p->get_material_usage(PSOBBCharacterFile::MaterialType::HP),
@@ -1879,7 +1868,7 @@ ChatCommandDefinition cc_qcheck(
if (!l->quest_flags_known || l->quest_flags_known->get(l->difficulty, flag_num)) {
send_text_message_fmt(a.c, "$C7Game: flag 0x{:X} ({})\nis {} on {}",
flag_num, flag_num,
a.c->character()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set",
a.c->character_file()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set",
name_for_difficulty(l->difficulty));
} else {
send_text_message_fmt(a.c, "$C7Game: flag 0x{:X} ({})\nis unknown on {}",
@@ -1888,7 +1877,7 @@ ChatCommandDefinition cc_qcheck(
} else if (a.c->version() == Version::BB_V4) {
send_text_message_fmt(a.c, "$C7Player: flag 0x{:X} ({})\nis {} on {}",
flag_num, flag_num,
a.c->character()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set",
a.c->character_file()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set",
name_for_difficulty(l->difficulty));
}
co_return;
@@ -1913,7 +1902,7 @@ static void command_qset_qclear(const Args& a, bool should_set) {
}
}
auto p = a.c->character(false);
auto p = a.c->character_file(false);
if (p) {
if (should_set) {
p->quest_flags.set(l->difficulty, flag_num);
@@ -1966,7 +1955,7 @@ ChatCommandDefinition cc_qfread(
throw runtime_error("invalid quest counter definition");
}
uint32_t counter_value = a.c->character()->quest_counters.at(counter_index) & mask;
uint32_t counter_value = a.c->character_file()->quest_counters.at(counter_index) & mask;
while (!(mask & 1)) {
mask >>= 1;
@@ -1986,7 +1975,7 @@ ChatCommandDefinition cc_qgread(
+[](const Args& a) -> asio::awaitable<void> {
a.check_is_proxy(false);
uint8_t counter_num = stoul(a.text, nullptr, 0);
const auto& counters = a.c->character()->quest_counters;
const auto& counters = a.c->character_file()->quest_counters;
if (counter_num >= counters.size()) {
throw precondition_failed("$C7Counter ID must be\nless than {}", counters.size());
} else {
@@ -2015,11 +2004,11 @@ ChatCommandDefinition cc_qgwrite(
uint8_t counter_num = stoul(tokens[0], nullptr, 0);
uint32_t value = stoul(tokens[1], nullptr, 0);
auto& counters = a.c->character()->quest_counters;
auto& counters = a.c->character_file()->quest_counters;
if (counter_num >= counters.size()) {
throw precondition_failed("$C7Counter ID must be\nless than {}", counters.size());
} else {
a.c->character()->quest_counters[counter_num] = value;
a.c->character_file()->quest_counters[counter_num] = value;
G_SetQuestCounter_BB_6xD2 cmd = {{0xD2, sizeof(G_SetQuestCounter_BB_6xD2) / 4, a.c->lobby_client_id}, counter_num, value};
send_command_t(a.c, 0x60, 0x00, cmd);
send_text_message_fmt(a.c, "$C7Quest counter {}\nset to {}", counter_num, value);
@@ -2534,7 +2523,7 @@ ChatCommandDefinition cc_surrender(
if (!ps || !ps->is_alive()) {
throw precondition_failed("$C6Defeated players\ncannot surrender");
}
string name = remove_color(a.c->character()->disp.name.decode(a.c->language()));
string name = remove_color(a.c->character_file()->disp.name.decode(a.c->language()));
send_text_message_fmt(l, "$C6{} has\nsurrendered", name);
for (const auto& watcher_l : l->watcher_lobbies) {
send_text_message_fmt(watcher_l, "$C6{} has\nsurrendered", name);
@@ -2662,6 +2651,7 @@ ChatCommandDefinition cc_switchchar(
a.c->save_and_unload_character();
a.c->bb_character_index = index;
a.c->bb_bank_character_index = index;
// TODO: This can trigger a client bug where the previous character's
// name label object isn't deleted if the leave and join notifications
@@ -2952,7 +2942,7 @@ ChatCommandDefinition cc_where(
if (!a.c->proxy_session && l && l->is_game()) {
for (auto lc : l->clients) {
if (lc && (lc != a.c)) {
string name = lc->character()->disp.name.decode(lc->language());
string name = lc->character_file()->disp.name.decode(lc->language());
send_text_message_fmt(a.c, "$C6{}$C7 {:X}:{}",
name, lc->floor, FloorDefinition::get(l->episode, lc->floor).short_name);
}
+7 -7
View File
@@ -28,10 +28,10 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
if (choice_id == 0x0000) {
return true;
}
uint32_t target_level = target_c->character()->disp.stats.level + 1;
uint32_t target_level = target_c->character_file()->disp.stats.level + 1;
switch (choice_id) {
case 0x0001:
return (labs(static_cast<int32_t>(target_level - searcher_c->character()->disp.stats.level)) <= 5);
return (labs(static_cast<int32_t>(target_level - searcher_c->character_file()->disp.stats.level)) <= 5);
case 0x0002:
return (target_level <= 10);
case 0x0003:
@@ -80,13 +80,13 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
case 0x0000:
return true;
case 0x0010:
return target_c->character()->disp.visual.class_flags & 0x20;
return target_c->character_file()->disp.visual.class_flags & 0x20;
case 0x0011:
return target_c->character()->disp.visual.class_flags & 0x40;
return target_c->character_file()->disp.visual.class_flags & 0x40;
case 0x0012:
return target_c->character()->disp.visual.class_flags & 0x80;
return target_c->character_file()->disp.visual.class_flags & 0x80;
default:
return ((choice_id - 1) == target_c->character()->disp.visual.char_class);
return ((choice_id - 1) == target_c->character_file()->disp.visual.char_class);
}
},
},
@@ -143,7 +143,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
{0x0006, "Challenge"},
},
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
uint16_t target_choice_id = target_c->character()->choice_search_config.get_setting(0x0204);
uint16_t target_choice_id = target_c->character_file()->choice_search_config.get_setting(0x0204);
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
},
},
+307 -295
View File
@@ -244,12 +244,11 @@ Client::~Client() {
void Client::update_channel_name() {
string default_name = this->channel->default_name();
auto player = this->character(false, false);
auto player = this->character_file(false, false);
if (player) {
string name_str = player->disp.name.decode(this->language());
size_t level = player->disp.stats.level + 1;
this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}",
this->id, name_str, level, default_name);
this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}", this->id, name_str, level, default_name);
} else {
this->channel->name = std::format("C-{:X} @ {}", this->id, default_name);
}
@@ -263,7 +262,7 @@ void Client::reschedule_save_game_data_timer() {
this->save_game_data_timer.expires_after(std::chrono::seconds(60));
this->save_game_data_timer.async_wait([this](std::error_code ec) {
if (!ec) {
if (this->character(false)) {
if (this->character_file(false)) {
this->save_all();
}
this->reschedule_save_game_data_timer();
@@ -336,7 +335,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
return nullptr;
}
auto p = this->character(false);
auto p = this->character_file(false);
auto s = this->require_server_state();
auto team = s->team_index->get_by_id(this->login->account->bb_team_id);
if (!team) {
@@ -384,7 +383,7 @@ bool Client::evaluate_quest_availability_expression(
if (game && !game->quest_flag_values) {
throw logic_error("quest flags are missing from game");
}
auto p = this->character();
auto p = this->character_file();
IntegralExpression::Env env = {
.flags = &p->quest_flags.data.at(difficulty),
.challenge_records = &p->challenge_records,
@@ -448,8 +447,184 @@ void Client::set_login(shared_ptr<Login> login) {
}
}
// System file
string Client::system_filename(const string& bb_username) {
return std::format("system/players/system_{}.psosys", bb_username);
}
string Client::system_filename() const {
if (this->version() != Version::BB_V4) {
throw logic_error("non-BB players do not have system data");
}
if (!this->login || !this->login->bb_license) {
throw logic_error("client is not logged in");
}
return this->system_filename(this->login->bb_license->username);
}
shared_ptr<PSOBBBaseSystemFile> Client::system_file(bool allow_load) {
if (!this->system_data && allow_load) {
this->load_all_files();
}
return this->system_data;
}
shared_ptr<const PSOBBBaseSystemFile> Client::system_file(bool throw_if_missing) const {
if (!this->system_data.get() && throw_if_missing) {
throw runtime_error("system file is not loaded");
}
return this->system_data;
}
void Client::save_system_file() const {
if (!this->system_data) {
throw logic_error("no system file loaded");
}
string filename = this->system_filename();
phosg::save_object_file(filename, *this->system_data);
this->log.info_f("Saved system file {}", filename);
}
// Guild Card file
string Client::guild_card_filename(const string& bb_username) {
return std::format("system/players/guild_cards_{}.psocard", bb_username);
}
string Client::guild_card_filename() const {
if (this->version() != Version::BB_V4) {
throw logic_error("non-BB players do not have saved character data");
}
if (!this->login || !this->login->bb_license) {
throw logic_error("client is not logged in");
}
return this->guild_card_filename(this->login->bb_license->username);
}
shared_ptr<PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) {
if (!this->guild_card_data && allow_load) {
this->load_all_files();
}
return this->guild_card_data;
}
shared_ptr<const PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) const {
if (!this->guild_card_data && allow_load) {
throw runtime_error("account data is not loaded");
}
return this->guild_card_data;
}
void Client::save_guild_card_file() const {
if (!this->guild_card_data.get()) {
throw logic_error("no Guild Card file loaded");
}
string filename = this->guild_card_filename();
phosg::save_object_file(filename, *this->guild_card_data);
this->log.info_f("Saved Guild Card file {}", filename);
}
// Character file
string Client::character_filename(const std::string& bb_username, ssize_t index) {
if (bb_username.empty()) {
throw logic_error("non-BB players do not have saved character data");
}
if (index < 0) {
throw logic_error("character index is not set");
}
return std::format("system/players/player_{}_{}.psochar", bb_username, index);
}
string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) {
return std::format("system/players/backup_player_{}_{}.{}",
account_id, index, is_ep3 ? "pso3char" : "psochar");
}
string Client::character_filename() const {
if (this->version() != Version::BB_V4) {
throw logic_error("non-BB players do not have saved character data");
}
if (!this->login || !this->login->bb_license) {
throw logic_error("client is not logged in");
}
return this->character_filename(this->login->bb_license->username, this->bb_character_index);
}
shared_ptr<PSOBBCharacterFile> Client::character_file(bool allow_load, bool allow_overlay) {
if (this->overlay_character_data && allow_overlay) {
return this->overlay_character_data;
}
if (!this->character_data && allow_load) {
if ((this->version() == Version::BB_V4) && (this->bb_character_index < 0)) {
throw runtime_error("character index not specified");
}
this->load_all_files();
}
return this->character_data;
}
shared_ptr<const PSOBBCharacterFile> Client::character_file(bool throw_if_missing, bool allow_overlay) const {
if (allow_overlay && this->overlay_character_data) {
return this->overlay_character_data;
}
if (!this->character_data && throw_if_missing) {
throw runtime_error("character data is not loaded");
}
return this->character_data;
}
void Client::save_character_file(
const string& filename,
shared_ptr<const PSOBBBaseSystemFile> system,
shared_ptr<const PSOBBCharacterFile> character) {
PSOCHARFile::save(filename, system, character);
}
void Client::save_ep3_character_file(
const string& filename,
const PSOGCEp3CharacterFile::Character& character) {
phosg::save_file(filename, &character, sizeof(character));
}
void Client::save_character_file() {
if (!this->system_data.get()) {
throw logic_error("no system file loaded");
}
if (!this->character_data.get()) {
throw logic_error("no character file loaded");
}
if (this->should_update_play_time) {
// This is slightly inaccurate, since fractions of a second are truncated
// off each time we save. I'm lazy, so insert shrug emoji here.
uint64_t t = phosg::now();
uint64_t seconds = (t - this->last_play_time_update) / 1000000;
this->character_data->play_time_seconds += seconds;
this->log.info_f("Added {} seconds to play time", seconds);
this->last_play_time_update = t;
if (this->bank_data && (this->bb_bank_character_index == this->bb_character_index)) {
this->character_data->bank = *this->bank_data;
this->log.info_f("Committed bank data back to character file");
}
}
auto filename = this->character_filename();
this->save_character_file(filename, this->system_data, this->character_data);
this->log.info_f("Saved character file {}", filename);
}
void Client::create_character_file(
uint32_t guild_card_number,
uint8_t language,
const PlayerDispDataBBPreview& preview,
shared_ptr<const LevelTable> level_table) {
this->character_data = PSOBBCharacterFile::create_from_preview(guild_card_number, language, preview, level_table);
this->save_character_file();
}
void Client::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));
this->overlay_character_data = make_shared<PSOBBCharacterFile>(*this->character_file(true, false));
if (rules->weapon_and_armor_mode != BattleRules::WeaponAndArmorMode::ALLOW) {
this->overlay_character_data->inventory.remove_all_items_of_type(0);
@@ -499,7 +674,7 @@ void Client::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_p
}
void Client::create_challenge_overlay(Version version, size_t template_index, shared_ptr<const LevelTable> level_table) {
auto p = this->character(true, false);
auto p = this->character_file(true, false);
const auto& tpl = get_challenge_template_definition(version, p->disp.visual.class_flags, template_index);
this->overlay_character_data = make_shared<PSOBBCharacterFile>(*p);
@@ -543,124 +718,109 @@ void Client::create_challenge_overlay(Version version, size_t template_index, sh
}
}
void Client::import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders) {
this->blocked_senders.clear();
for (size_t z = 0; z < blocked_senders.size(); z++) {
if (blocked_senders[z]) {
this->blocked_senders.emplace(blocked_senders[z]);
}
}
}
// Bank file
shared_ptr<PSOBBBaseSystemFile> Client::system_file(bool allow_load) {
if (!this->system_data && allow_load) {
this->load_all_files();
}
return this->system_data;
}
shared_ptr<const PSOBBBaseSystemFile> Client::system_file(bool allow_load) const {
if (!this->system_data.get() && allow_load) {
throw runtime_error("system data is not loaded");
}
return this->system_data;
}
shared_ptr<PSOBBCharacterFile> Client::character(bool allow_load, bool allow_overlay) {
if (this->overlay_character_data && allow_overlay) {
return this->overlay_character_data;
}
if (!this->character_data && allow_load) {
if ((this->version() == Version::BB_V4) && (this->bb_character_index < 0)) {
throw runtime_error("character index not specified");
}
this->load_all_files();
}
return this->character_data;
}
shared_ptr<const PSOBBCharacterFile> Client::character(bool allow_load, bool allow_overlay) const {
if (allow_overlay && this->overlay_character_data) {
return this->overlay_character_data;
}
if (!this->character_data && allow_load) {
throw runtime_error("character data is not loaded");
}
return this->character_data;
}
shared_ptr<PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) {
if (!this->guild_card_data && allow_load) {
this->load_all_files();
}
return this->guild_card_data;
}
shared_ptr<const PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) const {
if (!this->guild_card_data && allow_load) {
throw runtime_error("account data is not loaded");
}
return this->guild_card_data;
}
string Client::system_filename() const {
if (this->version() != Version::BB_V4) {
throw logic_error("non-BB players do not have system data");
}
if (!this->login || !this->login->bb_license) {
throw logic_error("client is not logged in");
}
return std::format("system/players/system_{}.psosys", this->login->bb_license->username);
}
string Client::character_filename(const std::string& bb_username, ssize_t index) {
string Client::bank_filename(const std::string& bb_username, ssize_t index) {
if (bb_username.empty()) {
throw logic_error("non-BB players do not have character data");
throw logic_error("non-BB players do not have saved character data");
}
if (index < 0) {
throw logic_error("character index is not set");
return std::format("system/players/shared_bank_{}.psobank", bb_username);
} else {
return std::format("system/players/player_{}_{}.psobank", bb_username, index);
}
return std::format("system/players/player_{}_{}.psochar", bb_username, index);
}
string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) {
return std::format("system/players/backup_player_{}_{}.{}",
account_id, index, is_ep3 ? "pso3char" : "psochar");
}
string Client::character_filename(ssize_t index) const {
string Client::bank_filename() const {
if (this->version() != Version::BB_V4) {
throw logic_error("non-BB players do not have character data");
throw logic_error("non-BB players do not have saved character data");
}
if (!this->login || !this->login->bb_license) {
throw logic_error("client is not logged in");
}
return this->character_filename(this->login->bb_license->username, (index < 0) ? this->bb_character_index : index);
return this->bank_filename(this->login->bb_license->username, this->bb_bank_character_index);
}
string Client::guild_card_filename() const {
std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
if (this->version() != Version::BB_V4) {
throw logic_error("non-BB players do not have character data");
throw logic_error("non-BB players do not have saved character data");
}
if (!this->login || !this->login->bb_license) {
throw logic_error("client is not logged in");
if (this->has_overlay()) {
throw std::runtime_error("bank is inaccessible when overlay is present");
}
return std::format("system/players/guild_cards_{}.psocard", this->login->bb_license->username);
if (!this->bank_data && allow_load) {
try {
// If there's a psobank file, load it and ignore the character file bank
auto filename = this->bank_filename();
auto f = phosg::fopen_unique(filename, "rb");
this->bank_data = make_shared<PlayerBank>();
this->bank_data->load(f.get());
this->log.info_f("Loaded bank data from {}", filename);
} catch (const phosg::cannot_open_file&) {
// If there isn't a psobank file, use the loaded character data if the
// bank character index matches the current character index (that is, we
// should use the current character's bank); otherwise, load the
// corresponding character and parse the bank from that character file
if (this->bb_bank_character_index == this->bb_character_index) {
this->bank_data = std::make_shared<PlayerBank>(this->character_file(false, false)->bank);
this->log.info_f("Using bank data from loaded character");
} else {
if (!this->login || !this->login->bb_license) {
throw logic_error("client is not logged in");
}
string filename = this->character_filename(this->login->bb_license->username, this->bb_bank_character_index);
auto character = PSOCHARFile::load_shared(filename, false).character_file;
this->bank_data = std::make_shared<PlayerBank>(character->bank);
this->log.info_f("Using bank data from {}", filename);
}
}
auto s = this->require_server_state();
this->bank_data->max_items = s->bb_max_bank_items;
this->bank_data->max_meseta = s->bb_max_bank_meseta;
}
return this->bank_data;
}
string Client::shared_bank_filename() const {
if (this->version() != Version::BB_V4) {
throw logic_error("non-BB players do not have character data");
std::shared_ptr<const PlayerBank> Client::bank_file(bool throw_if_missing) const {
if (!this->bank_data && throw_if_missing) {
throw std::runtime_error("bank is not loaded");
}
if (!this->login || !this->login->bb_license) {
throw logic_error("client is not logged in");
}
return std::format("system/players/shared_bank_{}.psobank", this->login->bb_license->username);
return this->bank_data;
}
void Client::save_bank_file(const string& filename, const PlayerBank& bank) {
auto f = phosg::fopen_unique(filename, "wb");
bank.save(f.get());
}
void Client::save_bank_file() const {
if (!this->bank_data) {
throw logic_error("no bank file loaded");
}
auto filename = this->bank_filename();
this->save_bank_file(filename, *this->bank_data);
this->log.info_f("Saved bank file {}", filename);
}
void Client::change_bank(ssize_t index) {
if (this->bank_data) {
this->save_bank_file();
this->bank_data.reset();
if (this->bb_bank_character_index < 0) {
this->log.info_f("Unloaded shared bank");
} else {
this->log.info_f("Unloaded bank from character {}", this->bb_bank_character_index);
}
}
this->bb_bank_character_index = index;
}
// Legacy files
string Client::legacy_account_filename() const {
if (this->version() != Version::BB_V4) {
throw logic_error("non-BB players do not have character data");
throw logic_error("non-BB players do not have saved character data");
}
if (!this->login || !this->login->bb_license) {
throw logic_error("client is not logged in");
@@ -670,7 +830,7 @@ string Client::legacy_account_filename() const {
string Client::legacy_player_filename() const {
if (this->version() != Version::BB_V4) {
throw logic_error("non-BB players do not have character data");
throw logic_error("non-BB players do not have saved character data");
}
if (!this->login || !this->login->bb_license) {
throw logic_error("client is not logged in");
@@ -684,13 +844,13 @@ string Client::legacy_player_filename() const {
static_cast<ssize_t>(this->bb_character_index + 1));
}
void Client::create_character_file(
uint32_t guild_card_number,
uint8_t language,
const PlayerDispDataBBPreview& preview,
shared_ptr<const LevelTable> level_table) {
this->character_data = PSOBBCharacterFile::create_from_preview(guild_card_number, language, preview, level_table);
this->save_character_file();
void Client::import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders) {
this->blocked_senders.clear();
for (size_t z = 0; z < blocked_senders.size(); z++) {
if (blocked_senders[z]) {
this->blocked_senders.emplace(blocked_senders[z]);
}
}
}
void Client::load_all_files() {
@@ -698,6 +858,7 @@ void Client::load_all_files() {
this->system_data = make_shared<PSOBBBaseSystemFile>();
this->character_data = make_shared<PSOBBCharacterFile>();
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
this->bank_data = make_shared<PlayerBank>();
return;
}
if (!this->login || !this->login->bb_license) {
@@ -707,31 +868,22 @@ void Client::load_all_files() {
this->system_data.reset();
this->character_data.reset();
this->guild_card_data.reset();
auto files_manager = this->require_server_state()->player_files_manager;
this->bank_data.reset();
string sys_filename = this->system_filename();
this->system_data = files_manager->get_system(sys_filename);
if (this->system_data) {
player_data_log.info_f("Using loaded system file {}", sys_filename);
} else if (std::filesystem::is_regular_file(sys_filename)) {
if (std::filesystem::is_regular_file(sys_filename)) {
this->system_data = make_shared<PSOBBBaseSystemFile>(phosg::load_object_file<PSOBBBaseSystemFile>(sys_filename, true));
files_manager->set_system(sys_filename, this->system_data);
player_data_log.info_f("Loaded system data from {}", sys_filename);
this->log.info_f("Loaded system data from {}", sys_filename);
} else {
player_data_log.info_f("System file is missing: {}", sys_filename);
this->log.info_f("System file is missing: {}", sys_filename);
}
if (this->bb_character_index >= 0) {
string char_filename = this->character_filename();
this->character_data = files_manager->get_character(char_filename);
if (this->character_data) {
player_data_log.info_f("Using loaded character file {}", char_filename);
} else if (std::filesystem::is_regular_file(char_filename)) {
if (std::filesystem::is_regular_file(char_filename)) {
auto psochar = PSOCHARFile::load_shared(char_filename, !this->system_data);
this->character_data = psochar.character_file;
files_manager->set_character(char_filename, this->character_data);
player_data_log.info_f("Loaded character data from {}", char_filename);
this->log.info_f("Loaded character data from {}", char_filename);
// If there was no .psosys file, use the system file from the .psochar
// file instead
@@ -740,28 +892,23 @@ void Client::load_all_files() {
throw logic_error("account system data not present, and also not loaded from psochar file");
}
this->system_data = psochar.system_file;
files_manager->set_system(sys_filename, this->system_data);
player_data_log.info_f("Loaded system data from {}", char_filename);
this->log.info_f("Loaded system data from {}", char_filename);
}
this->update_character_data_after_load(this->character_data);
this->system_data->language = this->language();
} else {
player_data_log.info_f("Character file is missing: {}", char_filename);
this->log.info_f("Character file is missing: {}", char_filename);
}
}
string card_filename = this->guild_card_filename();
this->guild_card_data = files_manager->get_guild_card(card_filename);
if (this->guild_card_data) {
player_data_log.info_f("Using loaded Guild Card file {}", card_filename);
} else if (std::filesystem::is_regular_file(card_filename)) {
if (std::filesystem::is_regular_file(card_filename)) {
this->guild_card_data = make_shared<PSOBBGuildCardFile>(phosg::load_object_file<PSOBBGuildCardFile>(card_filename));
files_manager->set_guild_card(card_filename, this->guild_card_data);
player_data_log.info_f("Loaded Guild Card data from {}", card_filename);
this->log.info_f("Loaded Guild Card data from {}", card_filename);
} else {
player_data_log.info_f("Guild Card file is missing: {}", card_filename);
this->log.info_f("Guild Card file is missing: {}", card_filename);
}
// If any of the above files were missing, try to load from .nsa/.nsc files instead
@@ -775,13 +922,11 @@ void Client::load_all_files() {
}
if (!this->system_data) {
this->system_data = make_shared<PSOBBBaseSystemFile>(nsa_data->system_file);
files_manager->set_system(sys_filename, this->system_data);
player_data_log.info_f("Loaded legacy system data from {}", nsa_filename);
this->log.info_f("Loaded legacy system data from {}", nsa_filename);
}
if (!this->guild_card_data) {
this->guild_card_data = make_shared<PSOBBGuildCardFile>(nsa_data->guild_card_file);
files_manager->set_guild_card(card_filename, this->guild_card_data);
player_data_log.info_f("Loaded legacy Guild Card data from {}", nsa_filename);
this->log.info_f("Loaded legacy Guild Card data from {}", nsa_filename);
}
}
@@ -794,13 +939,11 @@ void Client::load_all_files() {
if (s->bb_default_joystick_config) {
this->system_data->joystick_config = *s->bb_default_joystick_config;
}
files_manager->set_system(sys_filename, this->system_data);
player_data_log.info_f("Created new system data");
this->log.info_f("Created new system data");
}
if (!this->guild_card_data) {
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
files_manager->set_guild_card(card_filename, this->guild_card_data);
player_data_log.info_f("Created new Guild Card data");
this->log.info_f("Created new Guild Card data");
}
if (!this->character_data && (this->bb_character_index >= 0)) {
@@ -817,7 +960,6 @@ void Client::load_all_files() {
}
this->character_data = make_shared<PSOBBCharacterFile>();
files_manager->set_character(this->character_filename(), this->character_data);
this->character_data->inventory = nsc_data.inventory;
this->character_data->disp = nsc_data.disp;
this->character_data->play_time_seconds = 0;
@@ -841,14 +983,20 @@ void Client::load_all_files() {
this->character_data->option_flags = nsa_data->option_flags;
this->character_data->symbol_chats = nsa_data->symbol_chats;
this->character_data->shortcuts = nsa_data->shortcuts;
player_data_log.info_f("Loaded legacy player data from {} and {}", nsa_filename, nsc_filename);
this->log.info_f("Loaded legacy player data from {} and {}", nsa_filename, nsc_filename);
} else {
player_data_log.info_f("Loaded legacy player data from {}", nsc_filename);
this->log.info_f("Loaded legacy player data from {}", nsc_filename);
}
this->update_character_data_after_load(this->character_data);
}
}
auto s = this->require_server_state();
auto stack_limits = s->item_stack_limits(this->version());
// bank_file() loads the bank data
this->bank_file()->enforce_stack_limits(stack_limits);
this->blocked_senders.clear();
for (size_t z = 0; z < this->guild_card_data->blocked.size(); z++) {
if (this->guild_card_data->blocked[z].present) {
@@ -860,11 +1008,7 @@ void Client::load_all_files() {
// Clear legacy play_time field
this->character_data->disp.name.clear_after_bytes(0x18);
// Enforce item stack limits, in case they've changed
auto s = this->require_server_state();
auto stack_limits = s->item_stack_limits(this->version());
this->character_data->inventory.enforce_stack_limits(stack_limits);
this->character_data->bank.enforce_stack_limits(stack_limits);
this->login->account->auto_reply_message = this->character_data->auto_reply.decode();
this->login->account->save();
@@ -876,7 +1020,7 @@ void Client::update_character_data_after_load(shared_ptr<PSOBBCharacterFile> cha
charfile->import_tethealla_material_usage(this->require_server_state()->level_table(this->version()));
uint8_t lang = this->language();
player_data_log.info_f("Overriding language fields in save files with {:02X} ({})", lang, char_for_language_code(lang));
this->log.info_f("Overriding language fields in save files with {:02X} ({})", lang, char_for_language_code(lang));
charfile->inventory.language = lang;
charfile->guild_card.language = lang;
}
@@ -891,70 +1035,9 @@ void Client::save_all() {
if (this->guild_card_data) {
this->save_guild_card_file();
}
if (this->external_bank) {
string filename = this->shared_bank_filename();
phosg::save_object_file<PlayerBank200>(filename, *this->external_bank);
player_data_log.info_f("Saved shared bank file {}", filename);
if (this->bank_data) {
this->save_bank_file();
}
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 Client::save_system_file() const {
if (!this->system_data) {
throw logic_error("no system file loaded");
}
string filename = this->system_filename();
phosg::save_object_file(filename, *this->system_data);
player_data_log.info_f("Saved system file {}", filename);
}
void Client::save_character_file(
const string& filename,
shared_ptr<const PSOBBBaseSystemFile> system,
shared_ptr<const PSOBBCharacterFile> character) {
PSOCHARFile::save(filename, system, character);
player_data_log.info_f("Saved character file {}", filename);
}
void Client::save_ep3_character_file(
const string& filename,
const PSOGCEp3CharacterFile::Character& character) {
phosg::save_file(filename, &character, sizeof(character));
player_data_log.info_f("Saved Episode 3 character file {}", filename);
}
void Client::save_character_file() {
if (!this->system_data.get()) {
throw logic_error("no system file loaded");
}
if (!this->character_data.get()) {
throw logic_error("no character file loaded");
}
if (this->should_update_play_time) {
// This is slightly inaccurate, since fractions of a second are truncated
// off each time we save. I'm lazy, so insert shrug emoji here.
uint64_t t = phosg::now();
uint64_t seconds = (t - this->last_play_time_update) / 1000000;
this->character_data->play_time_seconds += seconds;
player_data_log.info_f("Added {} seconds to play time", seconds);
this->last_play_time_update = t;
}
this->save_character_file(this->character_filename(), this->system_data, this->character_data);
}
void Client::save_guild_card_file() const {
if (!this->guild_card_data.get()) {
throw logic_error("no Guild Card file loaded");
}
string filename = this->guild_card_filename();
phosg::save_object_file(filename, *this->guild_card_data);
player_data_log.info_f("Saved Guild Card file {}", filename);
}
void Client::load_backup_character(uint32_t account_id, size_t index) {
@@ -979,88 +1062,17 @@ void Client::save_and_unload_character() {
this->save_character_file();
this->character_data.reset();
this->log.info_f("Unloaded character");
}
}
PlayerBank200& Client::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;
}
const PlayerBank200& Client::current_bank() const {
return const_cast<Client*>(this)->current_bank();
}
std::shared_ptr<PSOBBCharacterFile> Client::current_bank_character() {
return this->external_bank_character ? this->external_bank_character : this->character();
}
void Client::use_default_bank() {
if (this->external_bank) {
string filename = this->shared_bank_filename();
phosg::save_object_file<PlayerBank200>(filename, *this->external_bank);
this->external_bank.reset();
player_data_log.info_f("Detached shared bank {}", filename);
}
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_f("Detached character {} from bank", filename);
}
}
bool Client::use_shared_bank() {
this->use_default_bank();
string filename = this->shared_bank_filename();
auto files_manager = this->require_server_state()->player_files_manager;
this->external_bank = files_manager->get_bank(filename);
if (this->external_bank) {
player_data_log.info_f("Using loaded shared bank {}", filename);
return true;
} else if (std::filesystem::is_regular_file(filename)) {
this->external_bank = make_shared<PlayerBank200>(phosg::load_object_file<PlayerBank200>(filename));
files_manager->set_bank(filename, this->external_bank);
player_data_log.info_f("Loaded shared bank {}", filename);
return true;
} else {
this->external_bank = make_shared<PlayerBank200>();
files_manager->set_bank(filename, this->external_bank);
player_data_log.info_f("Created shared bank for {}", filename);
return false;
}
}
void Client::use_character_bank(ssize_t index) {
this->use_default_bank();
if (index != this->bb_character_index) {
auto files_manager = this->require_server_state()->player_files_manager;
string filename = this->character_filename(index);
this->external_bank_character = files_manager->get_character(filename);
if (this->external_bank_character) {
this->external_bank_character_index = index;
player_data_log.info_f("Using loaded character file {} for external bank", filename);
} else if (std::filesystem::is_regular_file(filename)) {
this->external_bank_character = PSOCHARFile::load_shared(filename, false).character_file;
this->update_character_data_after_load(this->external_bank_character);
this->external_bank_character_index = index;
files_manager->set_character(filename, this->external_bank_character);
player_data_log.info_f("Loaded character data from {} for external bank", filename);
} else {
throw runtime_error("character does not exist");
if (this->bank_data) {
this->save_bank_file();
this->bank_data.reset();
this->log.info_f("Unloaded bank");
}
}
}
void Client::print_inventory() const {
auto s = this->require_server_state();
auto p = this->character();
auto p = this->character_file();
this->log.info_f("[PlayerInventory] Meseta: {}", p->disp.stats.meseta);
this->log.info_f("[PlayerInventory] {} items", p->inventory.num_items);
for (size_t x = 0; x < p->inventory.num_items; x++) {
@@ -1073,11 +1085,11 @@ void Client::print_inventory() const {
void Client::print_bank() const {
auto s = this->require_server_state();
auto bank = this->current_bank();
this->log.info_f("[PlayerBank] Meseta: {}", bank.meseta);
this->log.info_f("[PlayerBank] {} items", bank.num_items);
for (size_t x = 0; x < bank.num_items; x++) {
const auto& item = bank.items[x];
auto bank = this->bank_file();
this->log.info_f("[PlayerBank] Meseta: {}", bank->meseta);
this->log.info_f("[PlayerBank] {} items", bank->items.size());
for (size_t x = 0; x < bank->items.size(); x++) {
const auto& item = bank->items[x];
const char* present_token = item.present ? "" : " (missing present flag)";
auto hex = item.data.hex();
auto name = s->describe_item(this->version(), item.data);
+40 -43
View File
@@ -114,6 +114,7 @@ public:
uint8_t bb_client_code = 0;
uint8_t bb_connection_phase = 0xFF;
ssize_t bb_character_index = -1; // -1 = not set
ssize_t bb_bank_character_index = -1; // -1 = shared bank
uint32_t bb_security_token = 0;
parray<uint8_t, 0x28> bb_client_config;
std::string login_character_name;
@@ -288,6 +289,36 @@ public:
void set_login(std::shared_ptr<Login> login);
void import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders);
static std::string system_filename(const std::string& bb_username);
std::string system_filename() const;
std::shared_ptr<PSOBBBaseSystemFile> system_file(bool allow_load = true);
std::shared_ptr<const PSOBBBaseSystemFile> system_file(bool throw_if_missing = true) const;
void save_system_file() const;
static std::string guild_card_filename(const std::string& bb_username);
std::string guild_card_filename() const;
std::shared_ptr<PSOBBGuildCardFile> guild_card_file(bool allow_load = true);
std::shared_ptr<const PSOBBGuildCardFile> guild_card_file(bool allow_load = true) const;
void save_guild_card_file() const;
static std::string character_filename(const std::string& bb_username, ssize_t index);
static std::string backup_character_filename(uint32_t account_id, size_t index, bool is_ep3);
std::string character_filename() const;
std::shared_ptr<PSOBBCharacterFile> character_file(bool allow_load = true, bool allow_overlay = true);
std::shared_ptr<const PSOBBCharacterFile> character_file(bool throw_if_missing = true, bool allow_overlay = true) const;
static void save_character_file(
const std::string& filename,
std::shared_ptr<const PSOBBBaseSystemFile> sys,
std::shared_ptr<const PSOBBCharacterFile> character);
static void save_ep3_character_file(const std::string& filename, const PSOGCEp3CharacterFile::Character& character);
void save_character_file();
void create_character_file(
uint32_t guild_card_number,
uint8_t language,
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table);
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() {
@@ -297,55 +328,23 @@ public:
return this->overlay_character_data.get() != nullptr;
}
void import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders);
static std::string bank_filename(const std::string& bb_username, ssize_t index);
std::string bank_filename() const;
std::shared_ptr<PlayerBank> bank_file(bool allow_load = true);
std::shared_ptr<const PlayerBank> bank_file(bool throw_if_missing = true) const;
static void save_bank_file(const std::string& filename, const PlayerBank& bank);
void save_bank_file() const;
void change_bank(ssize_t bb_character_index); // -1 = use shared bank
std::shared_ptr<PSOBBBaseSystemFile> system_file(bool allow_load = true);
std::shared_ptr<PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true);
std::shared_ptr<PSOBBGuildCardFile> guild_card_file(bool allow_load = true);
std::shared_ptr<const PSOBBBaseSystemFile> system_file(bool allow_load = true) const;
std::shared_ptr<const PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true) const;
std::shared_ptr<const PSOBBGuildCardFile> guild_card_file(bool allow_load = true) const;
void create_character_file(
uint32_t guild_card_number,
uint8_t language,
const PlayerDispDataBBPreview& preview,
std::shared_ptr<const LevelTable> level_table);
std::string system_filename() const;
static std::string character_filename(const std::string& bb_username, ssize_t index);
static std::string backup_character_filename(uint32_t account_id, size_t index, bool is_ep3);
std::string character_filename(ssize_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;
std::string legacy_player_filename() const;
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);
static void save_ep3_character_file(
const std::string& filename,
const PSOGCEp3CharacterFile::Character& 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;
void load_backup_character(uint32_t account_id, size_t index);
std::shared_ptr<PSOGCEp3CharacterFile::Character> load_ep3_backup_character(uint32_t account_id, size_t index);
void save_and_unload_character();
PlayerBank200& current_bank();
const PlayerBank200& current_bank() const;
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(ssize_t bb_character_index);
void use_default_bank();
void print_inventory() const;
void print_bank() const;
@@ -359,9 +358,7 @@ private:
std::shared_ptr<PSOBBCharacterFile> overlay_character_data;
std::shared_ptr<PSOBBCharacterFile> character_data;
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
std::shared_ptr<PlayerBank200> external_bank;
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
ssize_t external_bank_character_index = -1;
std::shared_ptr<PlayerBank> bank_data;
uint64_t last_play_time_update = 0;
void load_all_files();
+1 -1
View File
@@ -18,7 +18,7 @@ Tournament::PlayerEntry::PlayerEntry(uint32_t account_id, const string& player_n
Tournament::PlayerEntry::PlayerEntry(shared_ptr<Client> c)
: account_id(c->login->account->account_id),
client(c),
player_name(c->character()->disp.name.decode(c->language())) {}
player_name(c->character_file()->disp.name.decode(c->language())) {}
Tournament::PlayerEntry::PlayerEntry(
shared_ptr<const COMDeckDefinition> com_deck)
+1 -1
View File
@@ -96,7 +96,7 @@ vector<shared_ptr<Client>> GameServer::get_clients_by_identifier(const string& i
continue;
}
auto p = c->character(false, false);
auto p = c->character_file(false, false);
if (p && p->disp.name.eq(ident, p->inventory.language)) {
results.emplace_back(c);
continue;
+2 -2
View File
@@ -169,7 +169,7 @@ std::shared_ptr<phosg::JSON> HTTPServer::generate_client_json(
if (c->version() == Version::BB_V4) {
ret->emplace("BBCharacterIndex", c->bb_character_index);
}
auto p = c->character(false, false);
auto p = c->character_file(false, false);
if (p) {
if (!is_ep3(c->version())) {
if (c->version() != Version::DC_NTE) {
@@ -596,7 +596,7 @@ std::shared_ptr<phosg::JSON> HTTPServer::generate_lobbies_json() const {
std::shared_ptr<phosg::JSON> HTTPServer::generate_summary_json() const {
auto clients_json = phosg::JSON::list();
for (const auto& c : this->state->game_server->all_clients()) {
auto p = c->character(false, false);
auto p = c->character_file(false, false);
auto l = c->lobby.lock();
clients_json.emplace_back(phosg::JSON::dict({
{"ID", c->id},
+3 -3
View File
@@ -16,7 +16,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
bool is_v3_or_later = is_v3(c->version()) || is_v4;
bool should_delete_item = is_v3_or_later;
auto player = c->character();
auto player = c->character_file();
auto& item = player->inventory.items[item_index];
uint32_t primary_identifier = item.data.primary_identifier();
@@ -47,7 +47,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
weapon.data.data1[3] = min<uint8_t>(weapon.data.data1[3] + item.data.data1[2] + 1, weapon_def.max_grind);
} else if ((primary_identifier & 0xFFFF0000) == 0x030B0000) { // Material
auto p = c->character();
auto p = c->character_file();
using Type = PSOBBCharacterFile::MaterialType;
Type type;
@@ -499,7 +499,7 @@ void apply_mag_feed_result(
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index) {
auto s = c->require_server_state();
auto player = c->character();
auto player = c->character_file();
apply_mag_feed_result(
player->inventory.items[mag_item_index].data,
player->inventory.items[fed_item_index].data,
+8 -8
View File
@@ -246,7 +246,7 @@ uint8_t Lobby::effective_section_id() const {
}
auto leader = this->clients.at(this->leader_id);
if (leader) {
return leader->character()->disp.visual.section_id;
return leader->character_file()->disp.visual.section_id;
}
return 0;
}
@@ -465,7 +465,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
// If the lobby is recording a battle record, add the player join event
if (this->battle_record) {
auto p = c->character();
auto p = c->character_file();
PlayerLobbyDataDCGC lobby_data;
lobby_data.player_tag = 0x00010000;
lobby_data.guild_card_number = c->login->account->account_id;
@@ -607,7 +607,7 @@ shared_ptr<Client> Lobby::find_client(const string* identifier, uint64_t account
if (account_id && lc->login && (lc->login->account->account_id == account_id)) {
return lc;
}
if (identifier && (lc->character()->disp.name.eq(*identifier, lc->language()))) {
if (identifier && (lc->character_file()->disp.name.eq(*identifier, lc->language()))) {
return lc;
}
}
@@ -644,7 +644,7 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr<Client> c, const s
if (password && !this->password.empty() && (*password != this->password)) {
return JoinError::INCORRECT_PASSWORD;
}
auto p = c->character();
auto p = c->character_file();
if (p->disp.stats.level < this->min_level) {
return JoinError::LEVEL_TOO_LOW;
}
@@ -738,7 +738,7 @@ void Lobby::on_item_id_generated_externally(uint32_t item_id) {
}
void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c, bool consume_ids) {
auto p = c->character();
auto p = c->character_file();
uint32_t orig_next_item_id = this->next_item_id_for_client.at(c->lobby_client_id);
for (size_t z = 0; z < p->inventory.num_items; z++) {
p->inventory.items[z].data.id = this->generate_item_id(c->lobby_client_id);
@@ -749,9 +749,9 @@ void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c, bool consum
if (c->log.info_f("Assigned inventory item IDs{}", consume_ids ? "" : " but did not mark IDs as used")) {
c->print_inventory();
auto& bank = c->current_bank();
if (p->bank.num_items) {
bank.assign_ids(0x99000000 + (c->lobby_client_id << 20));
auto bank = c->bank_file();
if (!bank->items.empty()) {
bank->assign_ids(0x99000000 + (c->lobby_client_id << 20));
c->log.info_f("Assigned bank item IDs");
c->print_bank();
} else {
-122
View File
@@ -1,122 +0,0 @@
#include "PlayerFilesManager.hh"
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <phosg/Filesystem.hh>
#include <phosg/Hash.hh>
#include <stdexcept>
#include "FileContentsCache.hh"
#include "ItemData.hh"
#include "Loggers.hh"
#include "PSOEncryption.hh"
#include "PSOProtocol.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
using namespace std;
PlayerFilesManager::PlayerFilesManager(std::shared_ptr<asio::io_context> io_context)
: io_context(io_context),
clear_expired_files_timer(*this->io_context) {
this->schedule_callback();
}
std::shared_ptr<PSOBBBaseSystemFile> PlayerFilesManager::get_system(const std::string& filename) {
try {
return this->loaded_system_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
std::shared_ptr<PSOBBCharacterFile> PlayerFilesManager::get_character(const std::string& filename) {
try {
return this->loaded_character_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
std::shared_ptr<PSOBBGuildCardFile> PlayerFilesManager::get_guild_card(const std::string& filename) {
try {
return this->loaded_guild_card_files.at(filename);
} catch (const out_of_range&) {
return nullptr;
}
}
std::shared_ptr<PlayerBank200> 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: " + 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: " + 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: " + filename);
}
}
void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file) {
if (!this->loaded_bank_files.emplace(filename, file).second) {
throw runtime_error("bank file already loaded: " + filename);
}
}
void PlayerFilesManager::schedule_callback() {
this->clear_expired_files_timer.expires_after(std::chrono::seconds(30));
this->clear_expired_files_timer.async_wait(bind(&PlayerFilesManager::clear_expired_files, this));
}
template <typename KeyT, typename ValueT>
size_t erase_unused(std::unordered_map<KeyT, std::shared_ptr<ValueT>>& m) {
size_t ret = 0;
for (auto it = m.begin(); it != m.end();) {
if (it->second.use_count() <= 1) {
it = m.erase(it);
ret++;
} else {
it++;
}
}
return ret;
}
void PlayerFilesManager::clear_expired_files() {
size_t num_deleted = erase_unused(this->loaded_system_files);
if (num_deleted) {
player_data_log.info_f("Cleared {} expired system file(s)", num_deleted);
}
num_deleted = erase_unused(this->loaded_character_files);
if (num_deleted) {
player_data_log.info_f("Cleared {} expired character file(s)", num_deleted);
}
num_deleted = erase_unused(this->loaded_guild_card_files);
if (num_deleted) {
player_data_log.info_f("Cleared {} expired Guild Card file(s)", num_deleted);
}
num_deleted = erase_unused(this->loaded_bank_files);
if (num_deleted) {
player_data_log.info_f("Cleared {} expired bank file(s)", num_deleted);
}
this->schedule_callback();
}
-48
View File
@@ -1,48 +0,0 @@
#pragma once
#include <inttypes.h>
#include <stddef.h>
#include <array>
#include <asio.hpp>
#include <phosg/Encoding.hh>
#include <string>
#include <utility>
#include <vector>
#include "Episode3/DataIndexes.hh"
#include "ItemCreator.hh"
#include "ItemNameIndex.hh"
#include "LevelTable.hh"
#include "PlayerSubordinates.hh"
#include "SaveFileFormats.hh"
#include "Text.hh"
#include "Version.hh"
class PlayerFilesManager {
public:
explicit PlayerFilesManager(std::shared_ptr<asio::io_context> io_context);
~PlayerFilesManager() = default;
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<PlayerBank200> 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<PlayerBank200> file);
private:
std::shared_ptr<asio::io_context> io_context;
asio::steady_timer clear_expired_files_timer;
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<PlayerBank200>> loaded_bank_files;
void schedule_callback();
void clear_expired_files();
};
+116
View File
@@ -0,0 +1,116 @@
#include "PlayerInventory.hh"
void PlayerBank::load(FILE* f) {
le_uint32_t num_items;
le_uint32_t meseta;
phosg::freadx(f, &num_items, sizeof(num_items));
phosg::freadx(f, &meseta, sizeof(meseta));
this->meseta = meseta;
this->items.reserve(num_items);
while (this->items.size() < num_items) {
auto& item = this->items.emplace_back();
phosg::freadx(f, &item, sizeof(item));
}
}
void PlayerBank::save(FILE* f) const {
le_uint32_t num_items = this->items.size();
le_uint32_t meseta = this->meseta;
phosg::fwritex(f, &num_items, sizeof(num_items));
phosg::fwritex(f, &meseta, sizeof(meseta));
for (const auto& item : this->items) {
phosg::fwritex(f, &item, sizeof(item));
}
}
uint32_t PlayerBank::bb_checksum() const {
le_uint32_t num_items = this->items.size();
le_uint32_t meseta = this->meseta;
uint32_t ret = phosg::crc32(&num_items, sizeof(num_items));
ret = phosg::crc32(&meseta, sizeof(meseta), ret);
for (const auto& item : this->items) {
ret = phosg::crc32(&item, sizeof(item), ret);
}
return ret;
}
void PlayerBank::add_item(const ItemData& item, const ItemData::StackLimits& limits) {
uint32_t primary_identifier = item.primary_identifier();
if (primary_identifier == 0x04000000) {
this->meseta += item.data2d;
if (this->meseta > this->max_meseta) {
this->meseta = this->max_meseta;
}
return;
}
size_t combine_max = item.max_stack_size(limits);
if (combine_max > 1) {
size_t y;
for (y = 0; y < this->items.size(); y++) {
if (this->items[y].data.primary_identifier() == primary_identifier) {
break;
}
}
if (y < this->items.size()) {
uint8_t new_count = this->items[y].data.data1[5] + item.data1[5];
if (new_count > combine_max) {
throw std::runtime_error("stack size would exceed limit");
}
this->items[y].data.data1[5] = new_count;
this->items[y].amount = new_count;
return;
}
}
if (this->items.size() >= this->max_items) {
throw std::runtime_error("no free space in bank");
}
auto& new_item = this->items.emplace_back();
new_item.data = item;
new_item.amount = (item.max_stack_size(limits) > 1) ? item.data1[5] : 1;
new_item.present = 1;
}
ItemData PlayerBank::remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits) {
size_t index = this->find_item(item_id);
auto& bank_item = this->items[index];
ItemData ret = bank_item.data;
if (amount && (bank_item.data.stack_size(limits) > 1) && (amount < bank_item.data.data1[5])) {
ret.data1[5] = amount;
bank_item.data.data1[5] -= amount;
bank_item.amount -= amount;
} else {
this->items.erase(this->items.begin() + index);
}
return ret;
}
size_t PlayerBank::find_item(uint32_t item_id) {
for (size_t x = 0; x < this->items.size(); x++) {
if (this->items[x].data.id == item_id) {
return x;
}
}
throw std::out_of_range("item not present");
}
void PlayerBank::sort() {
std::sort(this->items.begin(), this->items.end());
}
void PlayerBank::assign_ids(uint32_t base_id) {
for (size_t z = 0; z < this->items.size(); z++) {
this->items[z].data.id = base_id + z;
}
}
void PlayerBank::enforce_stack_limits(std::shared_ptr<const ItemData::StackLimits> stack_limits) {
for (auto& item : this->items) {
item.data.enforce_stack_size_limits(*stack_limits);
}
}
+43 -95
View File
@@ -315,95 +315,6 @@ struct PlayerBankT {
/* 0008 */ parray<PlayerBankItemT<BE>, SlotCount> items;
/* 05A8 for 60 items (v1/v2), 12C8 for 200 items (v3/v4) */
uint32_t checksum() const {
return phosg::crc32(this, 2 * sizeof(U32T<BE>) + sizeof(PlayerBankItemT<BE>) * std::min<size_t>(SlotCount, this->num_items));
}
void add_item(const ItemData& item, const ItemData::StackLimits& limits) {
uint32_t primary_identifier = item.primary_identifier();
if (primary_identifier == 0x04000000) {
this->meseta += item.data2d;
if (this->meseta > 999999) {
this->meseta = 999999;
}
return;
}
size_t combine_max = item.max_stack_size(limits);
if (combine_max > 1) {
size_t y;
for (y = 0; y < this->num_items; y++) {
if (this->items[y].data.primary_identifier() == primary_identifier) {
break;
}
}
if (y < this->num_items) {
uint8_t new_count = this->items[y].data.data1[5] + item.data1[5];
if (new_count > combine_max) {
throw std::runtime_error("stack size would exceed limit");
}
this->items[y].data.data1[5] = new_count;
this->items[y].amount = new_count;
return;
}
}
if (this->num_items >= SlotCount) {
throw std::runtime_error("no free space in bank");
}
auto& last_item = this->items[this->num_items];
last_item.data = item;
last_item.amount = (item.max_stack_size(limits) > 1) ? item.data1[5] : 1;
last_item.present = 1;
this->num_items++;
}
ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits) {
size_t index = this->find_item(item_id);
auto& bank_item = this->items[index];
ItemData ret;
if (amount && (bank_item.data.stack_size(limits) > 1) && (amount < bank_item.data.data1[5])) {
ret = bank_item.data;
ret.data1[5] = amount;
bank_item.data.data1[5] -= amount;
bank_item.amount -= amount;
return ret;
}
ret = bank_item.data;
this->num_items--;
for (size_t x = index; x < this->num_items; x++) {
this->items[x] = this->items[x + 1];
}
auto& last_item = this->items[this->num_items];
last_item.amount = 0;
last_item.present = 0;
last_item.data.clear();
return ret;
}
size_t find_item(uint32_t item_id) {
for (size_t x = 0; x < this->num_items; x++) {
if (this->items[x].data.id == item_id) {
return x;
}
}
throw std::out_of_range("item not present");
}
void sort() {
std::sort(this->items.data(), this->items.data() + this->num_items);
}
void assign_ids(uint32_t base_id) {
for (size_t z = 0; z < this->num_items; z++) {
this->items[z].data.id = base_id + z;
}
}
void decode_from_client(Version v) {
for (size_t z = 0; z < this->items.size(); z++) {
this->items[z].data.decode_for_version(v);
@@ -416,12 +327,6 @@ struct PlayerBankT {
}
}
void enforce_stack_limits(std::shared_ptr<const ItemData::StackLimits> stack_limits) {
for (size_t z = 0; z < std::min<uint8_t>(this->num_items, this->items.size()); z++) {
this->items[z].data.enforce_stack_size_limits(*stack_limits);
}
}
template <size_t DestSlotCount, bool DestBE>
operator PlayerBankT<DestSlotCount, DestBE>() const {
PlayerBankT<DestSlotCount, DestBE> ret;
@@ -439,3 +344,46 @@ using PlayerBank200BE = PlayerBankT<200, true>;
check_struct_size(PlayerBank60, 0x05A8);
check_struct_size(PlayerBank200, 0x12C8);
check_struct_size(PlayerBank200BE, 0x12C8);
struct PlayerBank {
uint32_t max_meseta = 999999;
uint32_t max_items = 200;
uint32_t meseta = 0;
std::vector<PlayerBankItem> items;
PlayerBank() = default;
template <size_t SrcSlotCount, bool SrcBE>
PlayerBank(const PlayerBankT<SrcSlotCount, SrcBE>& src)
: max_meseta(999999), max_items(SrcSlotCount), meseta(src.meseta) {
this->items.reserve(src.num_items);
for (size_t z = 0; z < src.num_items; z++) {
this->items.emplace_back(src.items[z]);
}
}
template <size_t DestSlotCount, bool DestBE>
operator PlayerBankT<DestSlotCount, DestBE>() const {
PlayerBankT<DestSlotCount, DestBE> ret;
ret.num_items = std::min<size_t>(ret.items.size(), this->items.size());
ret.meseta = this->meseta;
for (size_t z = 0; z < ret.num_items; z++) {
ret.items[z] = this->items[z];
}
return ret;
}
void load(FILE* f);
void save(FILE* f) const;
uint32_t bb_checksum() const;
void add_item(const ItemData& item, const ItemData::StackLimits& limits);
ItemData remove_item(uint32_t item_id, uint32_t amount, const ItemData::StackLimits& limits);
size_t find_item(uint32_t item_id);
void sort();
void assign_ids(uint32_t base_id);
void enforce_stack_limits(std::shared_ptr<const ItemData::StackLimits> stack_limits);
};
+1 -1
View File
@@ -1600,7 +1600,7 @@ static asio::awaitable<HandlerResult> S_64(shared_ptr<Client> c, Channel::Messag
} else {
c->proxy_session->lobby_event = 0;
c->proxy_session->lobby_difficulty = 0;
c->proxy_session->lobby_section_id = c->character()->disp.visual.section_id;
c->proxy_session->lobby_section_id = c->character_file()->disp.visual.section_id;
c->proxy_session->lobby_mode = GameMode::NORMAL;
c->proxy_session->lobby_random_seed = phosg::random_object<uint32_t>();
}
+39 -34
View File
@@ -1397,6 +1397,7 @@ static asio::awaitable<void> on_93_BB(shared_ptr<Client> c, Channel::Message& ms
c->sub_version = base_cmd.sub_version;
// c->channel->language set after version check
c->bb_character_index = base_cmd.character_slot;
c->bb_bank_character_index = base_cmd.character_slot;
c->bb_connection_phase = base_cmd.connection_phase;
c->bb_client_code = base_cmd.client_code;
c->bb_security_token = base_cmd.security_token;
@@ -1968,7 +1969,7 @@ static asio::awaitable<void> on_CA_Ep3(shared_ptr<Client> c, Channel::Message& m
l->battle_record = make_shared<Episode3::BattleRecord>(s->ep3_behavior_flags);
for (auto existing_c : l->clients) {
if (existing_c) {
auto existing_p = existing_c->character();
auto existing_p = existing_c->character_file();
PlayerLobbyDataDCGC lobby_data;
lobby_data.name.encode(existing_p->disp.name.decode(existing_c->language()), c->language());
lobby_data.player_tag = 0x00010000;
@@ -2209,7 +2210,7 @@ static asio::awaitable<void> on_09(shared_ptr<Client> c, Channel::Message& msg)
const char* version_token = (game_c->version() != c->version())
? version_tokens.at(static_cast<size_t>(game_c->version()))
: "";
auto player = game_c->character();
auto player = game_c->character_file();
string name = escape_player_name(player->disp.name.decode(game_c->language()));
info += std::format("{}{}\n {} Lv{} {}\n",
name,
@@ -2388,14 +2389,14 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
lc->delete_overlay();
if (l->quest->battle_rules) {
lc->use_default_bank();
lc->change_bank(lc->bb_character_index);
lc->create_battle_overlay(l->quest->battle_rules, s->level_table(lc->version()));
lc->log.info_f("Created battle overlay");
} else if (l->quest->challenge_template_index >= 0 && !is_v4(lc->version())) {
// On BB, the client will send a sequence of DF commands that creates the
// overlay; on non-BB, we do it at quest start time instead (hence the
// version check above).
lc->use_default_bank();
lc->change_bank(lc->bb_character_index);
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(lc->version()));
lc->log.info_f("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true);
@@ -2924,7 +2925,7 @@ static asio::awaitable<void> on_10_tournament_entries(
co_return;
}
if (team_name.empty()) {
team_name = c->character()->disp.name.decode(c->language());
team_name = c->character_file()->disp.name.decode(c->language());
team_name += std::format("/{:X}", c->login->account->account_id);
}
uint16_t tourn_num = item_id >> 16;
@@ -3337,7 +3338,7 @@ static asio::awaitable<void> on_61_98(shared_ptr<Client> c, Channel::Message& ms
c->clear_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_PLAYER_STATES);
}
auto player = c->character();
auto player = c->character_file();
switch (c->version()) {
case Version::DC_NTE:
@@ -3642,7 +3643,7 @@ static asio::awaitable<void> on_06(shared_ptr<Client> c, Channel::Message& msg)
co_return;
}
auto p = c->character();
auto p = c->character_file();
string from_name = p->disp.name.decode(c->language());
static const string whisper_text = "(whisper)";
for (size_t x = 0; x < l->max_clients; x++) {
@@ -3696,6 +3697,7 @@ static asio::awaitable<void> on_E3_BB(shared_ptr<Client> c, Channel::Message& ms
if (c->bb_connection_phase != 0x00) {
c->save_and_unload_character();
c->bb_character_index = cmd.character_index;
c->bb_bank_character_index = cmd.character_index;
send_approve_player_choice_bb(c);
} else {
@@ -3707,8 +3709,9 @@ static asio::awaitable<void> on_E3_BB(shared_ptr<Client> c, Channel::Message& ms
auto send_preview = [&c](size_t index) -> void {
c->save_and_unload_character();
c->bb_character_index = index;
c->bb_bank_character_index = index;
try {
auto preview = c->character()->to_preview();
auto preview = c->character_file()->to_preview();
send_player_preview_bb(c, c->bb_character_index, &preview);
} catch (const exception& e) {
@@ -3793,7 +3796,7 @@ static asio::awaitable<void> on_E8_BB(shared_ptr<Client> c, Channel::Message& ms
}
}
if (c->login && new_gc.guild_card_number == c->login->account->account_id) {
c->character(true, false)->guild_card.description = new_gc.description;
c->character_file(true, false)->guild_card.description = new_gc.description;
c->log.info_f("Updated character's guild card");
}
break;
@@ -3919,18 +3922,20 @@ static asio::awaitable<void> on_E5_BB(shared_ptr<Client> c, Channel::Message& ms
co_return;
}
if (c->character(false).get()) {
if (c->character_file(false).get()) {
throw runtime_error("player already exists");
}
c->bb_character_index = -1;
c->bb_bank_character_index = -1;
c->system_file(); // Ensure system file is loaded
c->bb_character_index = cmd.character_index;
c->bb_bank_character_index = cmd.character_index;
bool should_send_approve = true;
if (c->bb_connection_phase == 0x03) { // Dressing room
try {
c->character()->disp.apply_dressing_room(cmd.preview);
c->character_file()->disp.apply_dressing_room(cmd.preview);
} catch (const exception& e) {
send_message_box(c, std::format("$C6Character could not be modified:\n{}", e.what()));
should_send_approve = false;
@@ -3955,17 +3960,17 @@ static asio::awaitable<void> on_ED_BB(shared_ptr<Client> c, Channel::Message& ms
switch (msg.command) {
case 0x01ED: {
const auto& cmd = check_size_t<C_UpdateOptionFlags_BB_01ED>(msg.data);
c->character(true, false)->option_flags = cmd.option_flags;
c->character_file(true, false)->option_flags = cmd.option_flags;
break;
}
case 0x02ED: {
const auto& cmd = check_size_t<C_UpdateSymbolChats_BB_02ED>(msg.data);
c->character(true, false)->symbol_chats = cmd.symbol_chats;
c->character_file(true, false)->symbol_chats = cmd.symbol_chats;
break;
}
case 0x03ED: {
const auto& cmd = check_size_t<C_UpdateChatShortcuts_BB_03ED>(msg.data);
c->character(true, false)->shortcuts = cmd.chat_shortcuts;
c->character_file(true, false)->shortcuts = cmd.chat_shortcuts;
break;
}
case 0x04ED: {
@@ -3982,17 +3987,17 @@ static asio::awaitable<void> on_ED_BB(shared_ptr<Client> c, Channel::Message& ms
}
case 0x06ED: {
const auto& cmd = check_size_t<C_UpdateTechMenu_BB_06ED>(msg.data);
c->character(true, false)->tech_menu_shortcut_entries = cmd.tech_menu;
c->character_file(true, false)->tech_menu_shortcut_entries = cmd.tech_menu;
break;
}
case 0x07ED: {
const auto& cmd = check_size_t<C_UpdateCustomizeMenu_BB_07ED>(msg.data);
c->character()->disp.config = cmd.customize;
c->character_file()->disp.config = cmd.customize;
break;
}
case 0x08ED: {
const auto& cmd = check_size_t<C_UpdateChallengeRecords_BB_08ED>(msg.data);
c->character(true, false)->challenge_records = cmd.records;
c->character_file(true, false)->challenge_records = cmd.records;
break;
}
default:
@@ -4007,7 +4012,7 @@ static asio::awaitable<void> on_E7_BB(shared_ptr<Client> c, Channel::Message& ms
// TODO: In the future, we shouldn't need to trust any of the client's data
// here. We should instead verify our copy of the player against what the
// client sent, and alert on anything that's out of sync.
auto p = c->character();
auto p = c->character_file();
p->challenge_records = cmd.char_file.challenge_records;
p->battle_records = cmd.char_file.battle_records;
p->death_count = cmd.char_file.death_count;
@@ -4062,7 +4067,7 @@ static asio::awaitable<void> on_DF_BB(shared_ptr<Client> c, Channel::Message& ms
// On non-BB, there is no DF command, and overlays are created at quest
// start time instead, hence the version check here.
if (lc && is_v4(lc->version())) {
lc->use_default_bank();
lc->change_bank(lc->bb_character_index);
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(lc->version()));
lc->log.info_f("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true);
@@ -4111,7 +4116,7 @@ static asio::awaitable<void> on_DF_BB(shared_ptr<Client> c, Channel::Message& ms
case 0x07DF: {
const auto& cmd = check_size_t<C_CreateChallengeModeAwardItem_BB_07DF>(msg.data);
auto p = c->character(true, false);
auto p = c->character_file(true, false);
auto& award_state = (l->episode == Episode::EP2)
? p->challenge_records.ep2_online_award_state
: p->challenge_records.ep1_online_award_state;
@@ -4159,7 +4164,7 @@ static asio::awaitable<void> on_C0(shared_ptr<Client> c, Channel::Message&) {
}
static asio::awaitable<void> on_C2(shared_ptr<Client> c, Channel::Message& msg) {
c->character()->choice_search_config = check_size_t<ChoiceSearchConfig>(msg.data);
c->character_file()->choice_search_config = check_size_t<ChoiceSearchConfig>(msg.data);
co_return;
}
@@ -4170,7 +4175,7 @@ static void on_choice_search_t(shared_ptr<Client> c, const ChoiceSearchConfig& c
vector<ResultT> results;
for (const auto& l : s->all_lobbies()) {
for (const auto& lc : l->clients) {
if (!lc || !lc->login || lc->character()->choice_search_config.disabled) {
if (!lc || !lc->login || lc->character_file()->choice_search_config.disabled) {
continue;
}
@@ -4191,7 +4196,7 @@ static void on_choice_search_t(shared_ptr<Client> c, const ChoiceSearchConfig& c
}
if (is_match) {
auto lp = lc->character();
auto lp = lc->character_file();
auto& result = results.emplace_back();
result.guild_card_number = lc->login->account->account_id;
result.name.encode(lp->disp.name.decode(lc->language()), c->language());
@@ -4336,7 +4341,7 @@ static asio::awaitable<void> on_81(shared_ptr<Client> c, Channel::Message& msg)
// If the target has auto-reply enabled, send the autoreply. Note that we also
// forward the message in this case.
if (!c->blocked_senders.count(target->login->account->account_id)) {
auto target_p = target->character();
auto target_p = target->character_file();
if (!target_p->auto_reply.empty()) {
send_simple_mail(
c,
@@ -4350,7 +4355,7 @@ static asio::awaitable<void> on_81(shared_ptr<Client> c, Channel::Message& msg)
send_simple_mail(
target,
c->login->account->account_id,
c->character()->disp.name.decode(c->language()),
c->character_file()->disp.name.decode(c->language()),
message);
}
}
@@ -4368,7 +4373,7 @@ static asio::awaitable<void> on_D9(shared_ptr<Client> c, Channel::Message& msg)
msg.data.push_back(0);
}
try {
c->character(true, false)->info_board.encode(tt_decode_marked(msg.data, c->language(), is_w), c->language());
c->character_file(true, false)->info_board.encode(tt_decode_marked(msg.data, c->language(), is_w), c->language());
} catch (const runtime_error& e) {
c->log.warning_f("Failed to decode info board message: {}", e.what());
}
@@ -4383,7 +4388,7 @@ static asio::awaitable<void> on_C7(shared_ptr<Client> c, Channel::Message& msg)
}
string message = tt_decode_marked(msg.data, c->language(), is_w);
c->character(true, false)->auto_reply.encode(message, c->language());
c->character_file(true, false)->auto_reply.encode(message, c->language());
c->login->account->auto_reply_message = message;
c->login->account->save();
co_return;
@@ -4391,7 +4396,7 @@ static asio::awaitable<void> on_C7(shared_ptr<Client> c, Channel::Message& msg)
static asio::awaitable<void> on_C8(shared_ptr<Client> c, Channel::Message& msg) {
check_size_v(msg.data.size(), 0);
c->character(true, false)->auto_reply.clear();
c->character_file(true, false)->auto_reply.clear();
c->login->account->auto_reply_message.clear();
c->login->account->save();
co_return;
@@ -4442,7 +4447,7 @@ shared_ptr<Lobby> create_game_generic(
size_t min_level = s->default_min_level_for_game(creator_c->version(), episode, difficulty);
auto p = creator_c->character();
auto p = creator_c->character_file();
if (!creator_c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) {
// Note: We don't throw here because this is a situation players might
// actually encounter while playing the game normally
@@ -5121,8 +5126,8 @@ static asio::awaitable<void> on_D2_V3_BB(shared_ptr<Client> c, Channel::Message&
if (to_c->version() == Version::BB_V4) {
// On BB, the server is expected to generate the delete item and create
// item commands
auto to_p = to_c->character();
auto from_p = from_c->character();
auto to_p = to_c->character_file();
auto from_p = from_c->character_file();
for (const auto& trade_item : from_c->pending_item_trade->items) {
size_t amount = trade_item.stack_size(*s->item_stack_limits(from_c->version()));
@@ -5316,7 +5321,7 @@ static asio::awaitable<void> on_EA_BB(shared_ptr<Client> c, Channel::Message& ms
// TODO: What's the right error code to use here?
send_command(c, 0x02EA, 0x00000001);
} else {
string player_name = c->character()->disp.name.decode(c->language());
string player_name = c->character_file()->disp.name.decode(c->language());
auto team = s->team_index->create(team_name, c->login->account->account_id, player_name);
c->login->account->bb_team_id = team->team_id;
c->login->account->save();
@@ -5355,7 +5360,7 @@ static asio::awaitable<void> on_EA_BB(shared_ptr<Client> c, Channel::Message& ms
s->team_index->add_member(
team->team_id,
added_c->login->account->account_id,
added_c->character()->disp.name.decode(added_c->language()));
added_c->character_file()->disp.name.decode(added_c->language()));
send_command(c, 0x04EA, 0x00000000);
send_command(added_c, 0x04EA, 0x00000000);
send_team_metadata_change_notifications(
@@ -5516,7 +5521,7 @@ static asio::awaitable<void> on_EA_BB(shared_ptr<Client> c, Channel::Message& ms
send_team_metadata_change_notifications(s, team, 0, TeamMetadataChange::REWARD_FLAGS);
}
if (!reward.reward_item.empty()) {
c->current_bank().add_item(reward.reward_item, *s->item_stack_limits(c->version()));
c->bank_file()->add_item(reward.reward_item, *s->item_stack_limits(c->version()));
}
}
break;
+67 -67
View File
@@ -624,7 +624,7 @@ static asio::awaitable<void> on_sync_joining_player_compressed_state(shared_ptr<
}
auto l = c->require_lobby();
size_t target_num_items = target->character()->inventory.num_items;
size_t target_num_items = target->character_file()->inventory.num_items;
for (size_t z = 0; z < 12; z++) {
uint32_t client_next_id = decompressed_cmd->next_item_id_per_player[z];
uint32_t server_next_id = l->next_item_id_for_client[z];
@@ -1422,20 +1422,20 @@ static asio::awaitable<void> on_send_guild_card(shared_ptr<Client> c, Subcommand
switch (c->version()) {
case Version::DC_NTE: {
const auto& cmd = msg.check_size_t<G_SendGuildCard_DCNTE_6x06>();
c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
c->character_file(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
case Version::DC_11_2000:
case Version::DC_V1:
case Version::DC_V2: {
const auto& cmd = msg.check_size_t<G_SendGuildCard_DC_6x06>();
c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
c->character_file(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
case Version::PC_NTE:
case Version::PC_V2: {
const auto& cmd = msg.check_size_t<G_SendGuildCard_PC_6x06>();
c->character(true, false)->guild_card.description = cmd.guild_card.description;
c->character_file(true, false)->guild_card.description = cmd.guild_card.description;
break;
}
case Version::GC_NTE:
@@ -1443,12 +1443,12 @@ static asio::awaitable<void> on_send_guild_card(shared_ptr<Client> c, Subcommand
case Version::GC_EP3_NTE:
case Version::GC_EP3: {
const auto& cmd = msg.check_size_t<G_SendGuildCard_GC_6x06>();
c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
c->character_file(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
case Version::XB_V3: {
const auto& cmd = msg.check_size_t<G_SendGuildCard_XB_6x06>();
c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
c->character_file(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
break;
}
case Version::BB_V4:
@@ -1535,7 +1535,7 @@ static asio::awaitable<void> on_word_select_t(shared_ptr<Client> c, SubcommandMe
}
} catch (const exception& e) {
string name = escape_player_name(c->character()->disp.name.decode(c->language()));
string name = escape_player_name(c->character_file()->disp.name.decode(c->language()));
lc->log.warning_f("Untranslatable Word Select message: {}", e.what());
send_text_message_fmt(lc, "$C4Untranslatable Word\nSelect message from\n{}", name);
}
@@ -1641,7 +1641,7 @@ static asio::awaitable<void> on_player_died(shared_ptr<Client> c, SubcommandMess
// Decrease MAG's synchro
try {
auto& inventory = c->character()->inventory;
auto& inventory = c->character_file()->inventory;
size_t mag_index = inventory.find_equipped_item(EquipSlot::MAG);
auto& data = inventory.items[mag_index].data;
data.data2[0] = max<int8_t>(static_cast<int8_t>(data.data2[0] - 5), 0);
@@ -1946,7 +1946,7 @@ static asio::awaitable<void> on_player_drop_item(shared_ptr<Client> c, Subcomman
auto s = c->require_server_state();
auto l = c->require_lobby();
auto p = c->character();
auto p = c->character_file();
auto item = p->remove_item(cmd.item_id, 0, *s->item_stack_limits(c->version()));
l->add_item(cmd.floor, item, cmd.pos, nullptr, nullptr, 0x00F);
@@ -1995,7 +1995,7 @@ static asio::awaitable<void> on_create_inventory_item_t(shared_ptr<Client> c, Su
}
} else {
c->character()->add_item(item, *s->item_stack_limits(c->version()));
c->character_file()->add_item(item, *s->item_stack_limits(c->version()));
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
@@ -2075,7 +2075,7 @@ static asio::awaitable<void> on_drop_partial_stack_bb(shared_ptr<Client> c, Subc
}
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
const auto& limits = *s->item_stack_limits(c->version());
auto item = p->remove_item(cmd.item_id, cmd.amount, limits);
@@ -2118,7 +2118,7 @@ static asio::awaitable<void> on_buy_shop_item(shared_ptr<Client> c, SubcommandMe
}
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
ItemData item = cmd.item_data;
item.data2d = 0; // Clear the price field
item.decode_for_version(c->version());
@@ -2261,7 +2261,7 @@ static asio::awaitable<void> on_pick_up_item_generic(
// it receives a 6x5A and the floor item exists, so we just implement that
// logic here instead of forwarding the 6x5A to the leader.
auto p = c->character();
auto p = c->character_file();
auto s = c->require_server_state();
auto fi = l->remove_item(floor, item_id, c->lobby_client_id);
if (!fi->visible_to_client(c->lobby_client_id)) {
@@ -2372,7 +2372,7 @@ static asio::awaitable<void> on_equip_item(shared_ptr<Client> c, SubcommandMessa
auto l = c->require_lobby();
EquipSlot slot = static_cast<EquipSlot>(cmd.equip_slot.load());
auto p = c->character();
auto p = c->character_file();
p->inventory.equip_item_id(cmd.item_id, slot, is_pre_v1(c->version()));
c->log.info_f("Equipped item {:08X}", cmd.item_id);
@@ -2387,7 +2387,7 @@ static asio::awaitable<void> on_unequip_item(shared_ptr<Client> c, SubcommandMes
}
auto l = c->require_lobby();
auto p = c->character();
auto p = c->character_file();
p->inventory.unequip_item_id(cmd.item_id);
c->log.info_f("Unequipped item {:08X}", cmd.item_id);
@@ -2403,7 +2403,7 @@ static asio::awaitable<void> on_use_item(shared_ptr<Client> c, SubcommandMessage
auto l = c->require_lobby();
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
size_t index = p->inventory.find_item(cmd.item_id);
string name;
{
@@ -2432,7 +2432,7 @@ static asio::awaitable<void> on_feed_mag(shared_ptr<Client> c, SubcommandMessage
auto s = c->require_server_state();
auto l = c->require_lobby();
auto p = c->character();
auto p = c->character_file();
size_t mag_index = p->inventory.find_item(cmd.mag_item_id);
size_t fed_index = p->inventory.find_item(cmd.fed_item_id);
@@ -2534,7 +2534,7 @@ static asio::awaitable<void> on_open_shop_bb_or_ep3_battle_subs(shared_ptr<Clien
} else {
const auto& cmd = msg.check_size_t<G_ShopContentsRequest_BB_6xB5>();
auto s = c->require_server_state();
size_t level = c->character()->disp.stats.level + 1;
size_t level = c->character_file()->disp.stats.level + 1;
switch (cmd.shop_type) {
case 0:
c->bb_shop_contents[0] = l->item_creator->generate_tool_shop_contents(level);
@@ -2619,7 +2619,7 @@ static asio::awaitable<void> on_ep3_private_word_select_bb_bank_action(
const auto& cmd = msg.check_size_t<G_PrivateWordSelect_Ep3_6xBD>();
s->word_select_table->validate(cmd.message, c->version());
string from_name = c->character()->disp.name.decode(c->language());
string from_name = c->character_file()->disp.name.decode(c->language());
static const string whisper_text = "(whisper)";
auto send_to_client = [&](shared_ptr<Client> lc) -> void {
if (cmd.private_flags & (1 << lc->lobby_client_id)) {
@@ -2671,21 +2671,21 @@ static asio::awaitable<void> on_ep3_private_word_select_bb_bank_action(
co_return;
}
auto p = c->character();
auto& bank = c->current_bank();
auto p = c->character_file();
auto bank = c->bank_file();
if (cmd.action == 0) { // Deposit
if (cmd.item_id == 0xFFFFFFFF) { // Deposit Meseta
if (cmd.meseta_amount > p->disp.stats.meseta) {
l->log.info_f("Player {} attempted to deposit {} Meseta in the bank, but has only {} Meseta on hand",
c->lobby_client_id, cmd.meseta_amount, p->disp.stats.meseta);
} else if ((bank.meseta + cmd.meseta_amount) > 999999) {
} else if ((bank->meseta + cmd.meseta_amount) > bank->max_meseta) {
l->log.info_f("Player {} attempted to deposit {} Meseta in the bank, but already has {} Meseta in the bank",
c->lobby_client_id, cmd.meseta_amount, p->disp.stats.meseta);
} else {
bank.meseta += cmd.meseta_amount;
bank->meseta += cmd.meseta_amount;
p->disp.stats.meseta -= cmd.meseta_amount;
l->log.info_f("Player {} deposited {} Meseta in the bank (bank now has {}; inventory now has {})",
c->lobby_client_id, cmd.meseta_amount, bank.meseta, p->disp.stats.meseta);
c->lobby_client_id, cmd.meseta_amount, bank->meseta, p->disp.stats.meseta);
}
} else { // Deposit item
@@ -2698,7 +2698,7 @@ static asio::awaitable<void> on_ep3_private_word_select_bb_bank_action(
if (item.id == 0xFFFFFFFF) {
item.id = cmd.item_id;
}
bank.add_item(item, limits);
bank->add_item(item, limits);
send_destroy_item_to_lobby(c, cmd.item_id, cmd.item_amount, true);
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
@@ -2711,22 +2711,22 @@ static asio::awaitable<void> on_ep3_private_word_select_bb_bank_action(
} else if (cmd.action == 1) { // Take
if (cmd.item_index == 0xFFFF) { // Take Meseta
if (cmd.meseta_amount > bank.meseta) {
if (cmd.meseta_amount > bank->meseta) {
l->log.info_f("Player {} attempted to withdraw {} Meseta from the bank, but has only {} Meseta in the bank",
c->lobby_client_id, cmd.meseta_amount, bank.meseta);
c->lobby_client_id, cmd.meseta_amount, bank->meseta);
} else if ((p->disp.stats.meseta + cmd.meseta_amount) > 999999) {
l->log.info_f("Player {} attempted to withdraw {} Meseta from the bank, but already has {} Meseta on hand",
c->lobby_client_id, cmd.meseta_amount, p->disp.stats.meseta);
} else {
bank.meseta -= cmd.meseta_amount;
bank->meseta -= cmd.meseta_amount;
p->disp.stats.meseta += cmd.meseta_amount;
l->log.info_f("Player {} withdrew {} Meseta from the bank (bank now has {}; inventory now has {})",
c->lobby_client_id, cmd.meseta_amount, bank.meseta, p->disp.stats.meseta);
c->lobby_client_id, cmd.meseta_amount, bank->meseta, p->disp.stats.meseta);
}
} else { // Take item
const auto& limits = *s->item_stack_limits(c->version());
auto item = bank.remove_item(cmd.item_id, cmd.item_amount, limits);
auto item = bank->remove_item(cmd.item_id, cmd.item_amount, limits);
item.id = l->generate_item_id(c->lobby_client_id);
p->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
@@ -2751,7 +2751,7 @@ static void on_sort_inventory_bb_inner(shared_ptr<Client> c, const SubcommandMes
}
const auto& cmd = msg.check_size_t<G_SortInventory_BB_6xC4>();
auto p = c->character();
auto p = c->character_file();
// Make sure the set of item IDs passed in by the client exactly matches the
// set of item IDs present in the inventory
@@ -3123,7 +3123,7 @@ static asio::awaitable<void> on_set_quest_flag(shared_ptr<Client> c, SubcommandM
if (c->version() == Version::BB_V4) {
auto s = c->require_server_state();
// TODO: Should we allow overlays here?
auto p = c->character(true, false);
auto p = c->character_file(true, false);
if (should_set) {
c->log.info_f("Setting quest flag {}:{:04X}", name_for_difficulty(difficulty), flag_num);
p->quest_flags.set(difficulty, flag_num);
@@ -3721,7 +3721,7 @@ static asio::awaitable<void> on_charge_attack_bb(shared_ptr<Client> c, Subcomman
}
const auto& cmd = msg.check_size_t<G_ChargeAttack_BB_6xC7>();
auto& disp = c->character()->disp;
auto& disp = c->character_file()->disp;
if (cmd.meseta_amount > disp.stats.meseta) {
disp.stats.meseta = 0;
} else {
@@ -3745,7 +3745,7 @@ static void send_max_level_notification_if_needed(shared_ptr<Client> c) {
max_level = 998;
}
auto p = c->character();
auto p = c->character_file();
if (p->disp.stats.level == max_level) {
string name = p->disp.name.decode(c->language());
size_t level_for_str = max_level + 1;
@@ -3769,7 +3769,7 @@ static asio::awaitable<void> on_level_up(shared_ptr<Client> c, SubcommandMessage
// On the DC prototypes, this command doesn't include any stats - it just
// increments the player's level by 1.
auto p = c->character();
auto p = c->character_file();
if (is_pre_v1(c->version())) {
msg.check_size_t<G_ChangePlayerLevel_DCNTE_6x30>();
auto s = c->require_server_state();
@@ -3800,7 +3800,7 @@ static asio::awaitable<void> on_level_up(shared_ptr<Client> c, SubcommandMessage
static void add_player_exp(shared_ptr<Client> c, uint32_t exp) {
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
p->disp.stats.experience += exp;
if (c->version() == Version::BB_V4) {
@@ -3886,8 +3886,8 @@ static asio::awaitable<void> on_steal_exp_bb(shared_ptr<Client> c, SubcommandMes
auto s = c->require_server_state();
const auto& cmd = msg.check_size_t<G_StealEXP_BB_6xC6>();
auto p = c->character();
if (c->character()->disp.stats.level >= 199) {
auto p = c->character_file();
if (c->character_file()->disp.stats.level >= 199) {
co_return;
}
@@ -4005,7 +4005,7 @@ static asio::awaitable<void> on_enemy_exp_request_bb(shared_ptr<Client> c, Subco
ene_st->e_id,
phosg::name_for_enum(type));
}
if (lc->character()->disp.stats.level < 199) {
if (lc->character_file()->disp.stats.level < 199) {
add_player_exp(lc, player_exp);
}
}
@@ -4014,7 +4014,7 @@ static asio::awaitable<void> on_enemy_exp_request_bb(shared_ptr<Client> c, Subco
// Update kill counts on unsealable items, but only for the player who
// actually killed the enemy
if (ene_st->last_hit_by_client_id(client_id)) {
auto& inventory = lc->character()->inventory;
auto& inventory = lc->character_file()->inventory;
for (size_t z = 0; z < inventory.num_items; z++) {
auto& item = inventory.items[z];
if ((item.flags & 0x08) && s->item_parameter_table(lc->version())->is_unsealable_item(item.data)) {
@@ -4028,7 +4028,7 @@ static asio::awaitable<void> on_enemy_exp_request_bb(shared_ptr<Client> c, Subco
static asio::awaitable<void> on_adjust_player_meseta_bb(shared_ptr<Client> c, SubcommandMessage& msg) {
const auto& cmd = msg.check_size_t<G_AdjustPlayerMeseta_BB_6xC9>();
auto p = c->character();
auto p = c->character_file();
if (cmd.amount < 0) {
if (-cmd.amount > static_cast<int32_t>(p->disp.stats.meseta)) {
p->disp.stats.meseta = 0;
@@ -4072,7 +4072,7 @@ static asio::awaitable<void> on_item_reward_request_bb(shared_ptr<Client> c, Sub
// from the server. To handle this, we simply ignore any 6xCA command if the
// item can't be created.
try {
c->character()->add_item(item, limits);
c->character_file()->add_item(item, limits);
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
auto name = s->describe_item(c->version(), item);
@@ -4109,7 +4109,7 @@ asio::awaitable<void> on_transfer_item_via_mail_message_bb(shared_ptr<Client> c,
}
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
const auto& limits = *s->item_stack_limits(c->version());
auto item = p->remove_item(cmd.item_id, cmd.amount, limits);
@@ -4127,10 +4127,10 @@ asio::awaitable<void> on_transfer_item_via_mail_message_bb(shared_ptr<Client> c,
auto target_c = s->find_client(nullptr, cmd.target_guild_card_number);
if (target_c &&
(target_c->version() == Version::BB_V4) &&
(target_c->character(false) != nullptr) &&
(target_c->character_file(false) != nullptr) &&
!target_c->check_flag(Client::Flag::AT_BANK_COUNTER)) {
try {
target_c->current_bank().add_item(item, limits);
target_c->bank_file()->add_item(item, limits);
item_sent = true;
} catch (const runtime_error&) {
}
@@ -4174,7 +4174,7 @@ static asio::awaitable<void> on_exchange_item_for_team_points_bb(shared_ptr<Clie
}
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version()));
size_t points = s->item_parameter_table(Version::BB_V4)->get_item_team_points(item);
@@ -4210,7 +4210,7 @@ static asio::awaitable<void> on_destroy_inventory_item(shared_ptr<Client> c, Sub
}
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version()));
if (l->log.should_log(phosg::LogLevel::L_INFO)) {
@@ -4306,7 +4306,7 @@ static asio::awaitable<void> on_identify_item_bb(shared_ptr<Client> c, Subcomman
co_return;
}
auto p = c->character();
auto p = c->character_file();
size_t x = p->inventory.find_item(cmd.item_id);
if (p->inventory.items[x].data.data1[0] != 0) {
throw runtime_error("non-weapon items cannot be unidentified");
@@ -4347,7 +4347,7 @@ static asio::awaitable<void> on_accept_identify_item_bb(shared_ptr<Client> c, Su
throw runtime_error("accepted item ID does not match previous identify request");
}
auto s = c->require_server_state();
c->character()->add_item(c->bb_identify_result, *s->item_stack_limits(c->version()));
c->character_file()->add_item(c->bb_identify_result, *s->item_stack_limits(c->version()));
send_create_inventory_item_to_lobby(c, c->lobby_client_id, c->bb_identify_result);
c->bb_identify_result.clear();
}
@@ -4366,7 +4366,7 @@ static asio::awaitable<void> on_sell_item_at_shop_bb(shared_ptr<Client> c, Subco
const auto& cmd = msg.check_size_t<G_SellItemAtShop_BB_6xC0>();
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version()));
size_t price = (s->item_parameter_table(c->version())->price_for_item(item) >> 3) * cmd.amount;
p->add_meseta(price);
@@ -4405,7 +4405,7 @@ static asio::awaitable<void> on_buy_shop_item_bb(shared_ptr<Client> c, Subcomman
size_t price = item.data2d * cmd.amount;
item.data2d = 0;
auto p = c->character();
auto p = c->character_file();
p->remove_meseta(price, false);
item.id = cmd.shop_item_id;
@@ -4431,7 +4431,7 @@ static asio::awaitable<void> on_medical_center_bb(shared_ptr<Client> c, Subcomma
throw runtime_error("6xC5 command sent in non-game lobby");
}
c->character()->remove_meseta(10, false);
c->character_file()->remove_meseta(10, false);
co_return;
}
@@ -4468,7 +4468,7 @@ static asio::awaitable<void> on_battle_restart_bb(shared_ptr<Client> c, Subcomma
for (auto& lc : l->clients) {
if (lc) {
lc->delete_overlay();
lc->use_default_bank();
lc->change_bank(lc->bb_character_index);
lc->create_battle_overlay(new_rules, s->level_table(c->version()));
}
}
@@ -4495,7 +4495,7 @@ static asio::awaitable<void> on_battle_level_up_bb(shared_ptr<Client> c, Subcomm
auto lc = l->clients.at(cmd.header.client_id);
if (lc) {
auto s = c->require_server_state();
auto lp = lc->character();
auto lp = lc->character_file();
uint32_t target_level = min<uint32_t>(lp->disp.stats.level + cmd.num_levels, 199);
uint32_t before_exp = lp->disp.stats.experience;
int32_t exp_delta = lp->disp.stats.experience - before_exp;
@@ -4554,7 +4554,7 @@ static asio::awaitable<void> on_challenge_mode_retry_or_quit(shared_ptr<Client>
for (auto lc : l->clients) {
if (lc) {
lc->use_default_bank();
lc->change_bank(lc->bb_character_index);
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(c->version()));
lc->log.info_f("Created challenge overlay");
l->assign_inventory_and_bank_item_ids(lc, true);
@@ -4580,7 +4580,7 @@ static asio::awaitable<void> on_challenge_update_records(shared_ptr<Client> c, S
co_return;
}
auto p = c->character(true, false);
auto p = c->character_file(true, false);
Version c_version = c->version();
switch (c_version) {
case Version::DC_V2:
@@ -4708,7 +4708,7 @@ static asio::awaitable<void> on_quest_exchange_item_bb(shared_ptr<Client> c, Sub
auto s = c->require_server_state();
try {
auto p = c->character();
auto p = c->character_file();
const auto& limits = *s->item_stack_limits(c->version());
size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier());
@@ -4744,7 +4744,7 @@ static asio::awaitable<void> on_wrap_item_bb(shared_ptr<Client> c, SubcommandMes
const auto& cmd = msg.check_size_t<G_WrapItem_BB_6xD6>();
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
auto item = p->remove_item(cmd.item.id, 1, *s->item_stack_limits(c->version()));
send_destroy_item_to_lobby(c, item.id, 1);
item.wrap(*s->item_stack_limits(c->version()), cmd.present_color);
@@ -4766,7 +4766,7 @@ static asio::awaitable<void> on_photon_drop_exchange_for_item_bb(shared_ptr<Clie
auto s = c->require_server_state();
try {
auto p = c->character();
auto p = c->character_file();
const auto& limits = *s->item_stack_limits(c->version());
size_t found_index = p->inventory.find_item_by_primary_identifier(0x03100000);
@@ -4804,7 +4804,7 @@ static asio::awaitable<void> on_photon_drop_exchange_for_s_rank_special_bb(share
const auto& limits = *s->item_stack_limits(c->version());
try {
auto p = c->character();
auto p = c->character_file();
static const array<uint8_t, 0x10> costs({60, 60, 20, 20, 30, 30, 30, 50, 40, 50, 40, 40, 50, 40, 40, 40});
uint8_t cost = costs.at(cmd.special_type);
@@ -4851,7 +4851,7 @@ static asio::awaitable<void> on_secret_lottery_ticket_exchange_bb(shared_ptr<Cli
throw runtime_error("no secret lottery results are defined");
}
auto p = c->character();
auto p = c->character_file();
ssize_t slt_index = -1;
try {
slt_index = p->inventory.find_item_by_primary_identifier(0x03100300); // Secret Lottery Ticket
@@ -4912,7 +4912,7 @@ static asio::awaitable<void> on_photon_crystal_exchange_bb(shared_ptr<Client> c,
msg.check_size_t<G_ExchangePhotonCrystals_BB_6xDF>();
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
size_t index = p->inventory.find_item_by_primary_identifier(0x03100200);
auto item = p->remove_item(p->inventory.items[index].data.id, 1, *s->item_stack_limits(c->version()));
send_destroy_item_to_lobby(c, item.id, 1);
@@ -4976,7 +4976,7 @@ static asio::awaitable<void> on_quest_F95F_result_bb(shared_ptr<Client> c, Subco
const auto& cmd = msg.check_size_t<G_ExchangePhotonTickets_BB_6xE1>();
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
const auto& result = s->quest_F95F_results.at(cmd.result_index);
if (result.second.empty()) {
@@ -5023,7 +5023,7 @@ static asio::awaitable<void> on_quest_F960_result_bb(shared_ptr<Client> c, Subco
const auto& cmd = msg.check_size_t<G_GetMesetaSlotPrize_BB_6xE2>();
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
time_t t_secs = phosg::now() / 1000000;
struct tm t_parsed;
@@ -5103,7 +5103,7 @@ static asio::awaitable<void> on_momoka_item_exchange_bb(shared_ptr<Client> c, Su
const auto& cmd = msg.check_size_t<G_MomokaItemExchange_BB_6xD9>();
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
try {
const auto& limits = *s->item_stack_limits(c->version());
size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier());
@@ -5144,7 +5144,7 @@ static asio::awaitable<void> on_upgrade_weapon_attribute_bb(shared_ptr<Client> c
const auto& cmd = msg.check_size_t<G_UpgradeWeaponAttribute_BB_6xDA>();
auto s = c->require_server_state();
auto p = c->character();
auto p = c->character_file();
try {
size_t item_index = p->inventory.find_item(cmd.item_id);
auto& item = p->inventory.items[item_index].data;
@@ -5195,7 +5195,7 @@ static asio::awaitable<void> on_upgrade_weapon_attribute_bb(shared_ptr<Client> c
static asio::awaitable<void> on_write_quest_counter_bb(shared_ptr<Client> c, SubcommandMessage& msg) {
const auto& cmd = msg.check_size_t<G_SetQuestCounter_BB_6xD2>();
c->character()->quest_counters[cmd.index] = cmd.value;
c->character_file()->quest_counters[cmd.index] = cmd.value;
co_return;
}
+32 -33
View File
@@ -790,7 +790,7 @@ void send_approve_player_choice_bb(shared_ptr<Client> c) {
}
void send_complete_player_bb(shared_ptr<Client> c) {
auto p = c->character(true, false);
auto p = c->character_file(true, false);
auto sys = c->system_file(true);
auto team = c->team();
if (c->check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB)) {
@@ -1151,7 +1151,7 @@ void send_info_board_t(shared_ptr<Client> c) {
if (!other_c.get()) {
continue;
}
auto other_p = other_c->character(true, false);
auto other_p = other_c->character_file(true, false);
auto& e = entries.emplace_back();
e.name.encode(other_p->disp.name.decode(other_p->inventory.language), c->language());
e.message.encode(add_color(other_p->info_board.decode(other_p->inventory.language)), c->language());
@@ -1235,7 +1235,7 @@ void send_card_search_result_t(
cmd.location_string.encode(location_string, c->language());
cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY;
cmd.extension.lobby_refs[0].item_id = result_lobby->lobby_id;
auto rp = result->character(true, false);
auto rp = result->character_file(true, false);
cmd.extension.player_name.encode(rp->disp.name.decode(rp->inventory.language), c->language());
send_command_t(c, 0x41, 0x00, cmd);
@@ -1393,7 +1393,7 @@ void send_guild_card(shared_ptr<Client> c, shared_ptr<Client> source) {
throw runtime_error("source player does not have an account");
}
auto source_p = source->character(true, false);
auto source_p = source->character_file(true, false);
auto source_team = source->team();
uint64_t xb_user_id = (source->login->xb_license && source->login->xb_license->user_id)
@@ -1783,7 +1783,7 @@ template <typename EntryT>
void send_player_records_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Client> joining_client) {
vector<EntryT> entries;
auto add_client = [&](shared_ptr<Client> lc) -> void {
auto lp = lc->character(true, false);
auto lp = lc->character_file(true, false);
auto& e = entries.emplace_back();
e.client_id = lc->lobby_client_id;
e.challenge = lp->challenge_records;
@@ -1808,7 +1808,7 @@ void populate_lobby_data_for_client(LobbyDataT& ret, shared_ptr<const Client> c,
ret.player_tag = 0x00010000;
ret.guild_card_number = c->login->account->account_id;
ret.client_id = c->lobby_client_id;
string name = c->character()->disp.name.decode(c->language());
string name = c->character_file()->disp.name.decode(c->language());
ret.name.encode(name, viewer_c->language());
}
@@ -1822,7 +1822,7 @@ void populate_lobby_data_for_client(PlayerLobbyDataXB& ret, shared_ptr<const Cli
ret.netloc.account_id = 0xAE00000000000000 | c->login->account->account_id;
}
ret.client_id = c->lobby_client_id;
string name = c->character()->disp.name.decode(c->language());
string name = c->character_file()->disp.name.decode(c->language());
ret.name.encode(name, viewer_c->language());
}
@@ -1839,7 +1839,7 @@ void populate_lobby_data_for_client<PlayerLobbyDataBB>(PlayerLobbyDataBB& ret, s
ret.team_master_guild_card_number = 0;
ret.team_id = 0;
}
string name = c->character()->disp.name.decode(c->language());
string name = c->character_file()->disp.name.decode(c->language());
ret.name.encode(name, viewer_c->language());
}
@@ -1875,7 +1875,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
if (!wc) {
continue;
}
auto wc_p = wc->character();
auto wc_p = wc->character_file();
auto& p = cmd.players[z];
populate_lobby_data_for_client(p.lobby_data, wc, c);
p.inventory = wc_p->inventory;
@@ -1945,7 +1945,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
for (size_t z = 4; z < 12; z++) {
if (l->clients[z]) {
auto other_c = l->clients[z];
auto other_p = other_c->character();
auto other_p = other_c->character_file();
auto& cmd_p = cmd.spectator_players[z - 4];
auto& cmd_e = cmd.entries[z];
populate_lobby_data_for_client(cmd_p.lobby_data, other_c, c);
@@ -2069,7 +2069,7 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
for (size_t x = 0; x < 4; x++) {
auto lc = l->clients[x];
if (lc) {
auto other_p = lc->character();
auto other_p = lc->character_file();
auto& cmd_p = cmd.players_ep3[x];
cmd_p.inventory = other_p->inventory;
cmd_p.inventory.encode_for_client(c->version(), s->item_parameter_table_for_encode(c->version()));
@@ -2181,7 +2181,7 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cli
size_t used_entries = 0;
for (const auto& lc : lobby_clients) {
auto lp = lc->character();
auto lp = lc->character_file();
auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory;
@@ -2254,7 +2254,7 @@ void send_join_lobby_xb(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cl
size_t used_entries = 0;
for (const auto& lc : lobby_clients) {
auto lp = lc->character();
auto lp = lc->character_file();
auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory;
@@ -2303,7 +2303,7 @@ void send_join_lobby_dc_nte(shared_ptr<Client> c, shared_ptr<Lobby> l,
size_t used_entries = 0;
for (const auto& lc : lobby_clients) {
auto lp = lc->character();
auto lp = lc->character_file();
auto& e = cmd.entries[used_entries++];
populate_lobby_data_for_client(e.lobby_data, lc, c);
e.inventory = lp->inventory;
@@ -2958,7 +2958,7 @@ void send_game_flag_state_t(shared_ptr<Client> c) {
cmd.header.subcommand = 0x6F;
cmd.header.size = sizeof(CmdT) >> 2;
cmd.header.unused = 0x0000;
cmd.quest_flags = (l && !l->quest_flags_known) ? *l->quest_flag_values : c->character()->quest_flags;
cmd.quest_flags = (l && !l->quest_flags_known) ? *l->quest_flag_values : c->character_file()->quest_flags;
if (c->game_join_command_queue) {
c->log.info_f("Client not ready to receive join commands; adding to queue");
@@ -3007,7 +3007,7 @@ void send_game_player_state(shared_ptr<Client> to_c, shared_ptr<Client> from_c,
}
if (apply_overrides) {
auto from_p = from_c->character();
auto from_p = from_c->character_file();
to_send.base.pos.x = from_c->pos.x;
to_send.base.pos.y = 0.0;
to_send.base.pos.z = from_c->pos.z;
@@ -3172,17 +3172,15 @@ void send_bank(shared_ptr<Client> c) {
throw logic_error("6xBC can only be sent to BB clients");
}
auto p = c->character();
auto& bank = c->current_bank();
bank.sort();
const auto* items_it = bank.items.data();
vector<PlayerBankItem> items(items_it, items_it + bank.num_items);
auto p = c->character_file();
auto bank = c->bank_file();
bank->sort();
G_BankContentsHeader_BB_6xBC cmd = {
{{0xBC, 0, 0}, sizeof(G_BankContentsHeader_BB_6xBC) + items.size() * sizeof(PlayerBankItem)},
bank.checksum(), bank.num_items, bank.meseta};
{{0xBC, 0, 0}, sizeof(G_BankContentsHeader_BB_6xBC) + bank->items.size() * sizeof(PlayerBankItem)},
bank->bb_checksum(), bank->items.size(), bank->meseta};
send_command_t_vt(c, 0x6C, 0x00, cmd, items);
send_command_t_vt(c, 0x6C, 0x00, cmd, bank->items);
}
void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
@@ -3208,7 +3206,7 @@ void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
void send_level_up(shared_ptr<Client> c) {
auto l = c->require_lobby();
auto p = c->character();
auto p = c->character_file();
CharacterStats stats = p->disp.stats.char_stats;
const ItemData* mag = nullptr;
@@ -3499,7 +3497,7 @@ string ep3_description_for_client(shared_ptr<Client> c) {
if (!is_ep3(c->version())) {
throw runtime_error("client is not Episode 3");
}
auto p = c->character();
auto p = c->character_file();
return std::format(
"{} CLv{} {}",
name_for_char_class(p->disp.visual.char_class),
@@ -3549,7 +3547,7 @@ void send_ep3_game_details_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
if (player.is_human()) {
try {
auto other_c = account_id_to_client.at(player.account_id);
entry.name.encode(other_c->character()->disp.name.decode(other_c->language()), c->language());
entry.name.encode(other_c->character_file()->disp.name.decode(other_c->language()), c->language());
entry.description.encode(ep3_description_for_client(other_c), c->language());
} catch (const out_of_range&) {
entry.name.encode(player.player_name, c->language());
@@ -3570,7 +3568,7 @@ void send_ep3_game_details_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
for (auto spec_c : l->clients) {
if (spec_c) {
auto& entry = cmd.spectator_entries[cmd.num_spectators++];
entry.name.encode(spec_c->character()->disp.name.decode(spec_c->language()), c->language());
entry.name.encode(spec_c->character_file()->disp.name.decode(spec_c->language()), c->language());
entry.description.encode(ep3_description_for_client(spec_c), c->language());
}
}
@@ -3587,7 +3585,7 @@ void send_ep3_game_details_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
size_t num_players = 0;
for (const auto& opp_c : primary_lobby->clients) {
if (opp_c) {
cmd.player_entries[num_players].name.encode(opp_c->character()->disp.name.decode(opp_c->language()), c->language());
cmd.player_entries[num_players].name.encode(opp_c->character_file()->disp.name.decode(opp_c->language()), c->language());
cmd.player_entries[num_players].description.encode(ep3_description_for_client(opp_c), c->language());
num_players++;
}
@@ -3600,7 +3598,7 @@ void send_ep3_game_details_t(shared_ptr<Client> c, shared_ptr<Lobby> l) {
for (auto spec_c : l->clients) {
if (spec_c) {
auto& entry = cmd.spectator_entries[num_spectators++];
entry.name.encode(spec_c->character()->disp.name.decode(spec_c->language()), c->language());
entry.name.encode(spec_c->character_file()->disp.name.decode(spec_c->language()), c->language());
entry.description.encode(ep3_description_for_client(spec_c), c->language());
}
}
@@ -3721,7 +3719,7 @@ void send_ep3_tournament_match_result(shared_ptr<Lobby> l, uint32_t meseta_rewar
if (player.is_human()) {
try {
auto pc = account_id_to_client.at(player.account_id);
entry.player_names[z].encode(pc->character()->disp.name.decode(pc->language()), lc->language());
entry.player_names[z].encode(pc->character_file()->disp.name.decode(pc->language()), lc->language());
} catch (const out_of_range&) {
entry.player_names[z].encode(player.player_name, lc->language());
}
@@ -4211,7 +4209,7 @@ static S_TeamInfoForPlayer_BB_13EA_15EA_Entry team_metadata_for_client(shared_pt
S_TeamInfoForPlayer_BB_13EA_15EA_Entry cmd;
cmd.lobby_client_id = c->lobby_client_id;
cmd.guild_card_number = c->login->account->account_id;
cmd.player_name = c->character()->disp.name;
cmd.player_name = c->character_file()->disp.name;
if (team) {
cmd.membership = team->base_membership_for_member(c->login->account->account_id);
if (team->flag_data) {
@@ -4353,7 +4351,8 @@ void send_team_reward_list(shared_ptr<Client> c, bool show_purchased) {
auto s = c->require_server_state();
// Hide item rewards if the player's bank is full
bool show_item_rewards = show_purchased || (c->current_bank().num_items < 200);
auto bank = c->bank_file();
bool show_item_rewards = show_purchased || (bank->items.size() < bank->max_items);
vector<S_TeamRewardList_BB_19EA_1AEA::Entry> entries;
for (const auto& reward : s->team_index->reward_definitions()) {
+4 -2
View File
@@ -72,8 +72,7 @@ ServerState::ServerState(const string& config_filename)
thread_pool(make_unique<asio::thread_pool>()),
bb_stream_files_cache(new FileContentsCache(3600000000ULL)),
bb_system_cache(new FileContentsCache(3600000000ULL)),
gba_files_cache(new FileContentsCache(3600000000ULL)),
player_files_manager(make_shared<PlayerFilesManager>(this->io_context)) {}
gba_files_cache(new FileContentsCache(3600000000ULL)) {}
void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
shared_ptr<Lobby> added_to_lobby;
@@ -1065,6 +1064,9 @@ void ServerState::load_config_early() {
} catch (const out_of_range&) {
}
this->bb_max_bank_items = this->config_json->get_int("BBMaxBankItems", 200);
this->bb_max_bank_meseta = this->config_json->get_int("BBMaxBankMeseta", 999999);
for (size_t v_s = NUM_PATCH_VERSIONS; v_s < NUM_VERSIONS; v_s++) {
if (!this->item_stack_limits_tables[v_s]) {
Version v = static_cast<Version>(v_s);
+2 -2
View File
@@ -24,7 +24,6 @@
#include "LevelTable.hh"
#include "Lobby.hh"
#include "Menu.hh"
#include "PlayerFilesManager.hh"
#include "Quest.hh"
#include "TeamIndex.hh"
#include "WordSelectTable.hh"
@@ -205,6 +204,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::array<std::shared_ptr<const ItemParameterTable>, NUM_VERSIONS> item_parameter_tables;
std::shared_ptr<const ItemTranslationTable> item_translation_table;
std::array<std::shared_ptr<const ItemData::StackLimits>, NUM_VERSIONS> item_stack_limits_tables;
size_t bb_max_bank_items = 200;
size_t bb_max_bank_meseta = 999999;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table_v1_v2;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table_v3;
std::shared_ptr<const MagEvolutionTable> mag_evolution_table_v4;
@@ -287,7 +288,6 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::string pc_patch_server_message;
std::string bb_patch_server_message;
std::shared_ptr<PlayerFilesManager> player_files_manager;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
std::array<std::vector<uint32_t>, NUM_VERSIONS> public_lobby_search_orders;
std::vector<uint32_t> client_customization_public_lobby_search_order;
+3 -2
View File
@@ -975,7 +975,7 @@ asio::awaitable<deque<string>> fn_chat(ShellCommand::Args& args) {
auto l = c->require_lobby();
for (auto& lc : l->clients) {
if (lc) {
send_chat_message(lc, c->login->account->account_id, c->character()->disp.name.decode(c->language()), text, 0);
send_chat_message(lc, c->login->account->account_id, c->character_file()->disp.name.decode(c->language()), text, 0);
}
}
}
@@ -1008,7 +1008,8 @@ asio::awaitable<deque<string>> fn_wchat(ShellCommand::Args& args) {
auto l = c->require_lobby();
for (auto& lc : l->clients) {
if (lc) {
send_chat_message(lc, c->login->account->account_id, c->character()->disp.name.decode(c->language()), args.args, 0x40);
send_chat_message(
lc, c->login->account->account_id, c->character_file()->disp.name.decode(c->language()), args.args, 0x40);
}
}
}
@@ -0,0 +1,102 @@
# This patch changes the amount of items and Meseta that can be stored in the
# bank. If the bank item limit is increased beyond 200, this patch requires
# server support for extended bank data stored outside of the player's data.
# newserv has support for this, but you must set the BBBankItemLimit and
# BBBankMesetaLimit values in config.json to match the values used here.
# As written, this changes the meseta limit to 2000000000 and the item limit to
# 1000. The meseta limit can be any value up to 2147483647, and the item limit
# can be any value up to 1321. To use different values than the defaults, first
# compute the data size as ((slot count * 0x18) + 8), then replace each value
# below appropriately.
.meta name="More bank slots"
.meta description=""
.meta hide_from_patches_menu
entry_ptr:
reloc0:
.offsetof start
start:
.include WriteCodeBlocksBB
.data 0x006C8C0F
.data 4
.data 1000 # slot count
.data 0x006C8C4D
.data 4
.data 1000 # slot count
.data 0x006C8B54
.data 4
.data 999 # slot count - 1
.data 0x006C8B94
.data 4
.data 0x5DC0 # data size - 8
.data 0x006C8D16
.data 4
.data 999 # slot count - 1
.data 0x006C8E5E
.data 4
.data 999 # slot count - 1
.data 0x006C8F2C
.data 4
.data 999 # slot count - 1
.data 0x006C9016
.data 4
.data 0x5DB0 # data size - 0x18
.data 0x006C9034
.data 4
.data 0x5DC0 # data size - 8
.data 0x006C910D
.data 4
.data 0x5DB0 # data size - 0x18
.data 0x006C9129
.data 4
.data 0x5DC8 # data size
.data 0x006C9236
.data 4
.data 1000 # slot count
.data 0x006C924C
.data 4
.data 999 # slot count - 1
.data 0x006C9286
.data 4
.data 999 # slot count - 1
.data 0x006C92FA
.data 4
.data 1000 # slot count
.data 0x006C9883
.data 4
.data 1000 # slot count
.data 0x006C9A22
.data 4
.data 2000000000 # max meseta
.data 0x006CA2DB
.data 4
.data 0x5DC8 # data size
.data 0x006CA303
.data 4
.data 1000 # slot count
.data 0x006CA37F
.data 4
.data 0x5DC8 # data size
.data 0x006D7DAC
.data 4
.data 1000 # slot count
.data 0x006D7DBD
.data 4
.data 1000 # slot count
.data 0x006D7E14
.data 4
.data 1000 # slot count
.data 0x006D7BF5
.data 4
.data 1000 # slot count
.data 0x006C8DBF
.data 2
jmp +0x27
.data 0
.data 0
+7
View File
@@ -672,6 +672,13 @@
[0x040, "download-ep3", "Download", "$E$C6Quests to download\nto your Memory Card"],
],
// BB bank size. If you change either of these values, you must also add
// "BankSize" to BBRequiredPatches, and change the patch contents in
// system/client-functions/BlueBurstExclusive/BankSize.59NL.patch.s to
// reflect the counts you set here.
"BBMaxBankItems": 200,
"BBMaxBankMeseta": 999999,
// Item stack limits. Note that changing these does not affect the client's
// behavior automatically - this only exists to allow the server to
// understand the behavior of clients that are already patched with different