factor ClientGameData into Client to reduce data duplication
This commit is contained in:
+1
-1
@@ -86,7 +86,7 @@ set(SOURCES
|
|||||||
src/Menu.cc
|
src/Menu.cc
|
||||||
src/NetworkAddresses.cc
|
src/NetworkAddresses.cc
|
||||||
src/PatchFileIndex.cc
|
src/PatchFileIndex.cc
|
||||||
src/Player.cc
|
src/PlayerFilesManager.cc
|
||||||
src/PlayerSubordinates.cc
|
src/PlayerSubordinates.cc
|
||||||
src/ProxyCommands.cc
|
src/ProxyCommands.cc
|
||||||
src/ProxyServer.cc
|
src/ProxyServer.cc
|
||||||
|
|||||||
+14
-14
@@ -269,7 +269,7 @@ static void server_command_qcheck(shared_ptr<Client> c, const std::string& args)
|
|||||||
|
|
||||||
send_text_message_printf(c, "$C7Quest flag 0x%hX (%hu)\nis %s on %s",
|
send_text_message_printf(c, "$C7Quest flag 0x%hX (%hu)\nis %s on %s",
|
||||||
flag_num, flag_num,
|
flag_num, flag_num,
|
||||||
c->game_data.character()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set",
|
c->character()->quest_flags.get(l->difficulty, flag_num) ? "set" : "not set",
|
||||||
name_for_difficulty(l->difficulty));
|
name_for_difficulty(l->difficulty));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,9 +287,9 @@ static void server_command_qset_qclear(shared_ptr<Client> c, const std::string&
|
|||||||
uint16_t flag_num = stoul(args, nullptr, 0);
|
uint16_t flag_num = stoul(args, nullptr, 0);
|
||||||
|
|
||||||
if (should_set) {
|
if (should_set) {
|
||||||
c->game_data.character()->quest_flags.set(l->difficulty, flag_num);
|
c->character()->quest_flags.set(l->difficulty, flag_num);
|
||||||
} else {
|
} else {
|
||||||
c->game_data.character()->quest_flags.clear(l->difficulty, flag_num);
|
c->character()->quest_flags.clear(l->difficulty, flag_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_v1_or_v2(c->version())) {
|
if (is_v1_or_v2(c->version())) {
|
||||||
@@ -364,7 +364,7 @@ static void proxy_command_qcall(shared_ptr<ProxyServer::LinkedSession> ses, cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void server_command_show_material_counts(shared_ptr<Client> c, const std::string&) {
|
static void server_command_show_material_counts(shared_ptr<Client> c, const std::string&) {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
if (is_v1_or_v2(c->version())) {
|
if (is_v1_or_v2(c->version())) {
|
||||||
send_text_message_printf(c, "%hhu HP, %hhu TP",
|
send_text_message_printf(c, "%hhu HP, %hhu TP",
|
||||||
p->get_material_usage(PSOBBCharacterFile::MaterialType::HP),
|
p->get_material_usage(PSOBBCharacterFile::MaterialType::HP),
|
||||||
@@ -903,7 +903,7 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
|
|||||||
vector<string> tokens = split(encoded_args, ' ');
|
vector<string> tokens = split(encoded_args, ' ');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
if (tokens.at(0) == "atp") {
|
if (tokens.at(0) == "atp") {
|
||||||
p->disp.stats.char_stats.atp = stoul(tokens.at(1));
|
p->disp.stats.char_stats.atp = stoul(tokens.at(1));
|
||||||
} else if (tokens.at(0) == "mst") {
|
} else if (tokens.at(0) == "mst") {
|
||||||
@@ -997,24 +997,24 @@ static void server_command_change_bank(shared_ptr<Client> c, const std::string&
|
|||||||
throw runtime_error("cannot change banks while at the bank counter");
|
throw runtime_error("cannot change banks while at the bank counter");
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t new_char_index = args.empty() ? (c->game_data.bb_character_index + 1) : stol(args, nullptr, 0);
|
ssize_t new_char_index = args.empty() ? (c->bb_character_index + 1) : stol(args, nullptr, 0);
|
||||||
|
|
||||||
if (new_char_index == 0) {
|
if (new_char_index == 0) {
|
||||||
if (c->game_data.use_shared_bank()) {
|
if (c->use_shared_bank()) {
|
||||||
send_text_message_printf(c, "$C6Using shared bank (0)");
|
send_text_message_printf(c, "$C6Using shared bank (0)");
|
||||||
} else {
|
} else {
|
||||||
send_text_message_printf(c, "$C6Created shared bank (0)");
|
send_text_message_printf(c, "$C6Created shared bank (0)");
|
||||||
}
|
}
|
||||||
} else if (new_char_index <= 4) {
|
} else if (new_char_index <= 4) {
|
||||||
c->game_data.use_character_bank(new_char_index - 1);
|
c->use_character_bank(new_char_index - 1);
|
||||||
auto bp = c->game_data.current_bank_character();
|
auto bp = c->current_bank_character();
|
||||||
auto name = bp->disp.name.decode(c->language());
|
auto name = bp->disp.name.decode(c->language());
|
||||||
send_text_message_printf(c, "$C6Using %s\'s bank (%zu)", name.c_str(), new_char_index);
|
send_text_message_printf(c, "$C6Using %s\'s bank (%zu)", name.c_str(), new_char_index);
|
||||||
} else {
|
} else {
|
||||||
throw runtime_error("invalid bank number");
|
throw runtime_error("invalid bank number");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& bank = c->game_data.current_bank();
|
const auto& bank = c->current_bank();
|
||||||
send_text_message_printf(c, "%" PRIu32 " items\n%" PRIu32 " Meseta", bank.num_items.load(), bank.meseta.load());
|
send_text_message_printf(c, "%" PRIu32 " items\n%" PRIu32 " Meseta", bank.num_items.load(), bank.meseta.load());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1079,7 +1079,7 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
|
|||||||
check_is_game(l, false);
|
check_is_game(l, false);
|
||||||
|
|
||||||
size_t index = stoull(args, nullptr, 0);
|
size_t index = stoull(args, nullptr, 0);
|
||||||
c->game_data.load_backup_character(c->license->serial_number, index);
|
c->load_backup_character(c->license->serial_number, index);
|
||||||
|
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
send_player_leave_notification(l, c->lobby_client_id);
|
send_player_leave_notification(l, c->lobby_client_id);
|
||||||
@@ -1089,7 +1089,7 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
|
|||||||
static void server_command_save(shared_ptr<Client> c, const std::string&) {
|
static void server_command_save(shared_ptr<Client> c, const std::string&) {
|
||||||
check_version(c, Version::BB_V4);
|
check_version(c, Version::BB_V4);
|
||||||
try {
|
try {
|
||||||
c->game_data.save_all();
|
c->save_all();
|
||||||
send_text_message(c, "All data saved");
|
send_text_message(c, "All data saved");
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
send_text_message_printf(c, "Can\'t save data:\n%s", e.what());
|
send_text_message_printf(c, "Can\'t save data:\n%s", e.what());
|
||||||
@@ -1101,7 +1101,7 @@ static void server_command_save(shared_ptr<Client> c, const std::string&) {
|
|||||||
// Administration commands
|
// Administration commands
|
||||||
|
|
||||||
static string name_for_client(shared_ptr<Client> c) {
|
static string name_for_client(shared_ptr<Client> c) {
|
||||||
auto player = c->game_data.character(false);
|
auto player = c->character(false);
|
||||||
if (player.get()) {
|
if (player.get()) {
|
||||||
return player->disp.name.decode(player->inventory.language);
|
return player->disp.name.decode(player->inventory.language);
|
||||||
}
|
}
|
||||||
@@ -1624,7 +1624,7 @@ static void server_command_surrender(shared_ptr<Client> c, const std::string&) {
|
|||||||
send_text_message(c, "$C6Battle has not\nyet started");
|
send_text_message(c, "$C6Battle has not\nyet started");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const string& name = c->game_data.character()->disp.name.decode(c->language());
|
const string& name = c->character()->disp.name.decode(c->language());
|
||||||
send_text_message_printf(l, "$C6%s has\nsurrendered", name.c_str());
|
send_text_message_printf(l, "$C6%s has\nsurrendered", name.c_str());
|
||||||
for (const auto& watcher_l : l->watcher_lobbies) {
|
for (const auto& watcher_l : l->watcher_lobbies) {
|
||||||
send_text_message_printf(watcher_l, "$C6%s has\nsurrendered", name.c_str());
|
send_text_message_printf(watcher_l, "$C6%s has\nsurrendered", name.c_str());
|
||||||
|
|||||||
+7
-7
@@ -28,10 +28,10 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
|||||||
if (choice_id == 0x0000) {
|
if (choice_id == 0x0000) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
uint32_t target_level = target_c->game_data.character()->disp.stats.level + 1;
|
uint32_t target_level = target_c->character()->disp.stats.level + 1;
|
||||||
switch (choice_id) {
|
switch (choice_id) {
|
||||||
case 0x0001:
|
case 0x0001:
|
||||||
return (labs(static_cast<int32_t>(target_level - searcher_c->game_data.character()->disp.stats.level)) <= 5);
|
return (labs(static_cast<int32_t>(target_level - searcher_c->character()->disp.stats.level)) <= 5);
|
||||||
case 0x0002:
|
case 0x0002:
|
||||||
return (target_level <= 10);
|
return (target_level <= 10);
|
||||||
case 0x0003:
|
case 0x0003:
|
||||||
@@ -80,13 +80,13 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
|||||||
case 0x0000:
|
case 0x0000:
|
||||||
return true;
|
return true;
|
||||||
case 0x0010:
|
case 0x0010:
|
||||||
return target_c->game_data.character()->disp.visual.class_flags & 0x20;
|
return target_c->character()->disp.visual.class_flags & 0x20;
|
||||||
case 0x0011:
|
case 0x0011:
|
||||||
return target_c->game_data.character()->disp.visual.class_flags & 0x40;
|
return target_c->character()->disp.visual.class_flags & 0x40;
|
||||||
case 0x0012:
|
case 0x0012:
|
||||||
return target_c->game_data.character()->disp.visual.class_flags & 0x80;
|
return target_c->character()->disp.visual.class_flags & 0x80;
|
||||||
default:
|
default:
|
||||||
return ((choice_id - 1) == target_c->game_data.character()->disp.visual.char_class);
|
return ((choice_id - 1) == target_c->character()->disp.visual.char_class);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -142,7 +142,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
|||||||
{0x0006, "Challenge"},
|
{0x0006, "Challenge"},
|
||||||
},
|
},
|
||||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||||
uint16_t target_choice_id = target_c->game_data.character()->choice_search_config.get_setting(0x0204);
|
uint16_t target_choice_id = target_c->character()->choice_search_config.get_setting(0x0204);
|
||||||
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
|
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
struct Client;
|
class Client;
|
||||||
|
|
||||||
struct ChoiceSearchConfig {
|
struct ChoiceSearchConfig {
|
||||||
le_uint32_t disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
|
le_uint32_t disabled = 1; // 0 = enabled, 1 = disabled. Unused in command C3
|
||||||
|
|||||||
+610
-10
@@ -153,7 +153,6 @@ Client::Client(
|
|||||||
lobby_client_id(0),
|
lobby_client_id(0),
|
||||||
lobby_arrow_color(0),
|
lobby_arrow_color(0),
|
||||||
preferred_lobby_id(-1),
|
preferred_lobby_id(-1),
|
||||||
game_data(server->get_state()->player_files_manager),
|
|
||||||
save_game_data_event(
|
save_game_data_event(
|
||||||
event_new(
|
event_new(
|
||||||
bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST,
|
bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST,
|
||||||
@@ -172,10 +171,13 @@ Client::Client(
|
|||||||
card_battle_table_number(-1),
|
card_battle_table_number(-1),
|
||||||
card_battle_table_seat_number(0),
|
card_battle_table_seat_number(0),
|
||||||
card_battle_table_seat_state(0),
|
card_battle_table_seat_state(0),
|
||||||
|
should_update_play_time(false),
|
||||||
|
bb_character_index(-1),
|
||||||
next_exp_value(0),
|
next_exp_value(0),
|
||||||
can_chat(true),
|
can_chat(true),
|
||||||
dol_base_addr(0) {
|
dol_base_addr(0),
|
||||||
|
external_bank_character_index(-1),
|
||||||
|
last_play_time_update(0) {
|
||||||
this->config.set_flags_for_version(version, -1);
|
this->config.set_flags_for_version(version, -1);
|
||||||
this->config.specific_version = default_specific_version_for_version(version, -1);
|
this->config.specific_version = default_specific_version_for_version(version, -1);
|
||||||
|
|
||||||
@@ -204,6 +206,9 @@ Client::~Client() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((this->version() == Version::BB_V4) && (this->character_data.get())) {
|
||||||
|
this->save_all();
|
||||||
|
}
|
||||||
this->log.info("Deleted");
|
this->log.info("Deleted");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,11 +227,15 @@ void Client::reschedule_ping_and_timeout_events() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Client::set_license(shared_ptr<License> l) {
|
void Client::set_license(shared_ptr<License> l) {
|
||||||
this->license = l;
|
|
||||||
this->game_data.guild_card_number = this->license->serial_number;
|
|
||||||
if (this->version() == Version::BB_V4) {
|
if (this->version() == Version::BB_V4) {
|
||||||
this->game_data.set_bb_username(this->license->bb_username);
|
// Make sure bb_username is filename-safe
|
||||||
|
for (char ch : l->bb_username) {
|
||||||
|
if (!isalnum(ch) && (ch != '-') && (ch != '_')) {
|
||||||
|
throw runtime_error("invalid characters in username");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this->license = l;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<ServerState> Client::require_server_state() const {
|
shared_ptr<ServerState> Client::require_server_state() const {
|
||||||
@@ -254,7 +263,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = this->game_data.character(false);
|
auto p = this->character(false);
|
||||||
auto s = this->require_server_state();
|
auto s = this->require_server_state();
|
||||||
auto team = s->team_index->get_by_id(this->license->bb_team_id);
|
auto team = s->team_index->get_by_id(this->license->bb_team_id);
|
||||||
if (!team) {
|
if (!team) {
|
||||||
@@ -291,7 +300,7 @@ bool Client::can_access_quest(shared_ptr<const Quest> q, uint8_t difficulty) con
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if ((q->require_flag >= 0) &&
|
if ((q->require_flag >= 0) &&
|
||||||
!this->game_data.character()->quest_flags.get(difficulty, q->require_flag)) {
|
!this->character()->quest_flags.get(difficulty, q->require_flag)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!q->require_team_reward_key.empty()) {
|
if (!q->require_team_reward_key.empty()) {
|
||||||
@@ -311,8 +320,8 @@ void Client::save_game_data() {
|
|||||||
if (this->version() != Version::BB_V4) {
|
if (this->version() != Version::BB_V4) {
|
||||||
throw logic_error("save_game_data called for non-BB client");
|
throw logic_error("save_game_data called for non-BB client");
|
||||||
}
|
}
|
||||||
if (this->game_data.character(false)) {
|
if (this->character(false)) {
|
||||||
this->game_data.save_all();
|
this->save_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,3 +362,594 @@ void Client::suspend_timeouts() {
|
|||||||
event_del(this->idle_timeout_event.get());
|
event_del(this->idle_timeout_event.get());
|
||||||
this->log.info("Timeouts suspended");
|
this->log.info("Timeouts suspended");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
if (rules->weapon_and_armor_mode != BattleRules::WeaponAndArmorMode::ALLOW) {
|
||||||
|
this->overlay_character_data->inventory.remove_all_items_of_type(0);
|
||||||
|
this->overlay_character_data->inventory.remove_all_items_of_type(1);
|
||||||
|
}
|
||||||
|
if (rules->mag_mode == BattleRules::MagMode::FORBID_ALL) {
|
||||||
|
this->overlay_character_data->inventory.remove_all_items_of_type(2);
|
||||||
|
}
|
||||||
|
if (rules->tool_mode != BattleRules::ToolMode::ALLOW) {
|
||||||
|
this->overlay_character_data->inventory.remove_all_items_of_type(3);
|
||||||
|
}
|
||||||
|
if (rules->replace_char) {
|
||||||
|
// TODO: Shouldn't we clear other material usage here? It looks like the
|
||||||
|
// original code doesn't, but that seems wrong.
|
||||||
|
this->overlay_character_data->inventory.hp_from_materials = 0;
|
||||||
|
this->overlay_character_data->inventory.tp_from_materials = 0;
|
||||||
|
|
||||||
|
uint32_t target_level = clamp<uint32_t>(rules->char_level, 0, 199);
|
||||||
|
uint8_t char_class = this->overlay_character_data->disp.visual.char_class;
|
||||||
|
auto& stats = this->overlay_character_data->disp.stats;
|
||||||
|
|
||||||
|
stats.reset_to_base(char_class, level_table);
|
||||||
|
stats.advance_to_level(char_class, target_level, level_table);
|
||||||
|
|
||||||
|
stats.unknown_a1 = 40;
|
||||||
|
stats.meseta = 300;
|
||||||
|
}
|
||||||
|
if (rules->tech_disk_mode == BattleRules::TechDiskMode::LIMIT_LEVEL) {
|
||||||
|
// TODO: Verify this is what the game actually does.
|
||||||
|
for (uint8_t tech_num = 0; tech_num < 0x13; tech_num++) {
|
||||||
|
uint8_t existing_level = this->overlay_character_data->get_technique_level(tech_num);
|
||||||
|
if ((existing_level != 0xFF) && (existing_level > rules->max_tech_level)) {
|
||||||
|
this->overlay_character_data->set_technique_level(tech_num, rules->max_tech_level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (rules->tech_disk_mode == BattleRules::TechDiskMode::FORBID_ALL) {
|
||||||
|
for (uint8_t tech_num = 0; tech_num < 0x13; tech_num++) {
|
||||||
|
this->overlay_character_data->set_technique_level(tech_num, 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rules->meseta_mode != BattleRules::MesetaMode::ALLOW) {
|
||||||
|
this->overlay_character_data->disp.stats.meseta = 0;
|
||||||
|
}
|
||||||
|
if (rules->forbid_scape_dolls) {
|
||||||
|
this->overlay_character_data->inventory.remove_all_items_of_type(3, 9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::create_challenge_overlay(Version version, size_t template_index, shared_ptr<const LevelTable> level_table) {
|
||||||
|
auto p = this->character(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);
|
||||||
|
auto overlay = this->overlay_character_data;
|
||||||
|
|
||||||
|
for (size_t z = 0; z < overlay->inventory.items.size(); z++) {
|
||||||
|
auto& i = overlay->inventory.items[z];
|
||||||
|
i.present = 0;
|
||||||
|
i.unknown_a1 = 0;
|
||||||
|
i.extension_data1 = 0;
|
||||||
|
i.extension_data2 = 0;
|
||||||
|
i.flags = 0;
|
||||||
|
i.data = ItemData();
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay->inventory.items[13].extension_data2 = 1;
|
||||||
|
|
||||||
|
overlay->disp.stats.reset_to_base(overlay->disp.visual.char_class, level_table);
|
||||||
|
overlay->disp.stats.advance_to_level(overlay->disp.visual.char_class, tpl.level, level_table);
|
||||||
|
|
||||||
|
overlay->disp.stats.unknown_a1 = 40;
|
||||||
|
overlay->disp.stats.unknown_a3 = 10.0;
|
||||||
|
overlay->disp.stats.experience = level_table->stats_delta_for_level(overlay->disp.visual.char_class, overlay->disp.stats.level).experience;
|
||||||
|
overlay->disp.stats.meseta = 0;
|
||||||
|
overlay->clear_all_material_usage();
|
||||||
|
for (size_t z = 0; z < 0x13; z++) {
|
||||||
|
overlay->set_technique_level(z, 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t z = 0; z < tpl.items.size(); z++) {
|
||||||
|
auto& inv_item = overlay->inventory.items[z];
|
||||||
|
inv_item.present = tpl.items[z].present;
|
||||||
|
inv_item.unknown_a1 = tpl.items[z].unknown_a1;
|
||||||
|
inv_item.flags = tpl.items[z].flags;
|
||||||
|
inv_item.data = tpl.items[z].data;
|
||||||
|
}
|
||||||
|
overlay->inventory.num_items = tpl.items.size();
|
||||||
|
|
||||||
|
for (const auto& tech_level : tpl.tech_levels) {
|
||||||
|
overlay->set_technique_level(tech_level.tech_num, tech_level.level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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->license) {
|
||||||
|
throw logic_error("client is not logged in");
|
||||||
|
}
|
||||||
|
return string_printf("system/players/system_%s.psosys", this->license->bb_username.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
string Client::character_filename(const std::string& bb_username, int8_t index) {
|
||||||
|
if (bb_username.empty()) {
|
||||||
|
throw logic_error("non-BB players do not have character data");
|
||||||
|
}
|
||||||
|
if (index < 0) {
|
||||||
|
throw logic_error("character index is not set");
|
||||||
|
}
|
||||||
|
return string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index);
|
||||||
|
}
|
||||||
|
|
||||||
|
string Client::backup_character_filename(uint32_t serial_number, size_t index) {
|
||||||
|
return string_printf("system/players/backup_player_%" PRIu32 "_%zu.psochar", serial_number, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
string Client::character_filename(int8_t index) const {
|
||||||
|
if (this->version() != Version::BB_V4) {
|
||||||
|
throw logic_error("non-BB players do not have character data");
|
||||||
|
}
|
||||||
|
if (!this->license) {
|
||||||
|
throw logic_error("client is not logged in");
|
||||||
|
}
|
||||||
|
return this->character_filename(this->license->bb_username, (index < 0) ? this->bb_character_index : index);
|
||||||
|
}
|
||||||
|
|
||||||
|
string Client::guild_card_filename() const {
|
||||||
|
if (this->version() != Version::BB_V4) {
|
||||||
|
throw logic_error("non-BB players do not have character data");
|
||||||
|
}
|
||||||
|
if (!this->license) {
|
||||||
|
throw logic_error("client is not logged in");
|
||||||
|
}
|
||||||
|
return string_printf("system/players/guild_cards_%s.psocard", this->license->bb_username.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
string Client::shared_bank_filename() const {
|
||||||
|
if (this->version() != Version::BB_V4) {
|
||||||
|
throw logic_error("non-BB players do not have character data");
|
||||||
|
}
|
||||||
|
if (!this->license) {
|
||||||
|
throw logic_error("client is not logged in");
|
||||||
|
}
|
||||||
|
return string_printf("system/players/shared_bank_%s.psobank", this->license->bb_username.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
string Client::legacy_account_filename() const {
|
||||||
|
if (this->version() != Version::BB_V4) {
|
||||||
|
throw logic_error("non-BB players do not have character data");
|
||||||
|
}
|
||||||
|
if (!this->license) {
|
||||||
|
throw logic_error("client is not logged in");
|
||||||
|
}
|
||||||
|
return string_printf("system/players/account_%s.nsa", this->license->bb_username.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
string Client::legacy_player_filename() const {
|
||||||
|
if (this->version() != Version::BB_V4) {
|
||||||
|
throw logic_error("non-BB players do not have character data");
|
||||||
|
}
|
||||||
|
if (!this->license) {
|
||||||
|
throw logic_error("client is not logged in");
|
||||||
|
}
|
||||||
|
if (this->bb_character_index < 0) {
|
||||||
|
throw logic_error("character index is not set");
|
||||||
|
}
|
||||||
|
return string_printf(
|
||||||
|
"system/players/player_%s_%hhd.nsc",
|
||||||
|
this->license->bb_username.c_str(),
|
||||||
|
static_cast<int8_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::load_all_files() {
|
||||||
|
if (this->version() != Version::BB_V4) {
|
||||||
|
this->system_data = make_shared<PSOBBBaseSystemFile>();
|
||||||
|
this->character_data = make_shared<PSOBBCharacterFile>();
|
||||||
|
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this->license) {
|
||||||
|
throw logic_error("cannot load BB player data until client is logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->system_data.reset();
|
||||||
|
this->character_data.reset();
|
||||||
|
this->guild_card_data.reset();
|
||||||
|
|
||||||
|
auto files_manager = this->require_server_state()->player_files_manager;
|
||||||
|
|
||||||
|
string sys_filename = this->system_filename();
|
||||||
|
this->system_data = files_manager->get_system(sys_filename);
|
||||||
|
if (this->system_data) {
|
||||||
|
player_data_log.info("Using loaded system file %s", sys_filename.c_str());
|
||||||
|
} else if (isfile(sys_filename)) {
|
||||||
|
this->system_data = make_shared<PSOBBBaseSystemFile>(load_object_file<PSOBBBaseSystemFile>(sys_filename, true));
|
||||||
|
files_manager->set_system(sys_filename, this->system_data);
|
||||||
|
player_data_log.info("Loaded system data from %s", sys_filename.c_str());
|
||||||
|
} else {
|
||||||
|
player_data_log.info("System file is missing: %s", sys_filename.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
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("Using loaded character file %s", char_filename.c_str());
|
||||||
|
} else if (isfile(char_filename)) {
|
||||||
|
auto f = fopen_unique(char_filename, "rb");
|
||||||
|
auto header = freadx<PSOCommandHeaderBB>(f.get());
|
||||||
|
if (header.size != 0x399C) {
|
||||||
|
throw runtime_error("incorrect size in character file header");
|
||||||
|
}
|
||||||
|
if (header.command != 0x00E7) {
|
||||||
|
throw runtime_error("incorrect command in character file header");
|
||||||
|
}
|
||||||
|
if (header.flag != 0x00000000) {
|
||||||
|
throw runtime_error("incorrect flag in character file header");
|
||||||
|
}
|
||||||
|
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
|
||||||
|
files_manager->set_character(char_filename, this->character_data);
|
||||||
|
player_data_log.info("Loaded character data from %s", char_filename.c_str());
|
||||||
|
|
||||||
|
// If there was no .psosys file, load the system file from the .psochar
|
||||||
|
// file instead
|
||||||
|
if (!this->system_data) {
|
||||||
|
this->system_data = make_shared<PSOBBBaseSystemFile>(freadx<PSOBBBaseSystemFile>(f.get()));
|
||||||
|
files_manager->set_system(sys_filename, this->system_data);
|
||||||
|
player_data_log.info("Loaded system data from %s", char_filename.c_str());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
player_data_log.info("Character file is missing: %s", char_filename.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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("Using loaded Guild Card file %s", card_filename.c_str());
|
||||||
|
} else if (isfile(card_filename)) {
|
||||||
|
this->guild_card_data = make_shared<PSOBBGuildCardFile>(load_object_file<PSOBBGuildCardFile>(card_filename));
|
||||||
|
files_manager->set_guild_card(card_filename, this->guild_card_data);
|
||||||
|
player_data_log.info("Loaded Guild Card data from %s", card_filename.c_str());
|
||||||
|
} else {
|
||||||
|
player_data_log.info("Guild Card file is missing: %s", card_filename.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the above files were missing, try to load from .nsa/.nsc files instead
|
||||||
|
if (!this->system_data || (!this->character_data && (this->bb_character_index >= 0)) || !this->guild_card_data) {
|
||||||
|
string nsa_filename = this->legacy_account_filename();
|
||||||
|
shared_ptr<LegacySavedAccountDataBB> nsa_data;
|
||||||
|
if (isfile(nsa_filename)) {
|
||||||
|
nsa_data = make_shared<LegacySavedAccountDataBB>(load_object_file<LegacySavedAccountDataBB>(nsa_filename));
|
||||||
|
if (!nsa_data->signature.eq(LegacySavedAccountDataBB::SIGNATURE)) {
|
||||||
|
throw runtime_error("account data header is incorrect");
|
||||||
|
}
|
||||||
|
if (!this->system_data) {
|
||||||
|
this->system_data = make_shared<PSOBBBaseSystemFile>(nsa_data->system_file.base);
|
||||||
|
files_manager->set_system(sys_filename, this->system_data);
|
||||||
|
player_data_log.info("Loaded legacy system data from %s", nsa_filename.c_str());
|
||||||
|
}
|
||||||
|
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("Loaded legacy Guild Card data from %s", nsa_filename.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->system_data) {
|
||||||
|
this->system_data = make_shared<PSOBBBaseSystemFile>();
|
||||||
|
files_manager->set_system(sys_filename, this->system_data);
|
||||||
|
player_data_log.info("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("Created new Guild Card data");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->character_data && (this->bb_character_index >= 0)) {
|
||||||
|
string nsc_filename = this->legacy_player_filename();
|
||||||
|
auto nsc_data = load_object_file<LegacySavedPlayerDataBB>(nsc_filename);
|
||||||
|
if (nsc_data.signature == LegacySavedPlayerDataBB::SIGNATURE_V0) {
|
||||||
|
nsc_data.signature = LegacySavedPlayerDataBB::SIGNATURE_V0;
|
||||||
|
nsc_data.unused.clear();
|
||||||
|
nsc_data.battle_records.place_counts.clear(0);
|
||||||
|
nsc_data.battle_records.disconnect_count = 0;
|
||||||
|
nsc_data.battle_records.unknown_a1.clear(0);
|
||||||
|
} else if (nsc_data.signature != LegacySavedPlayerDataBB::SIGNATURE_V1) {
|
||||||
|
throw runtime_error("legacy player data has incorrect signature");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = nsc_data.disp.play_time;
|
||||||
|
this->character_data->unknown_a2 = nsc_data.unknown_a2;
|
||||||
|
this->character_data->quest_flags = nsc_data.quest_flags;
|
||||||
|
this->character_data->death_count = nsc_data.death_count;
|
||||||
|
this->character_data->bank = nsc_data.bank;
|
||||||
|
this->character_data->guild_card.guild_card_number = this->license->serial_number;
|
||||||
|
this->character_data->guild_card.name = nsc_data.disp.name;
|
||||||
|
this->character_data->guild_card.description = nsc_data.guild_card_description;
|
||||||
|
this->character_data->guild_card.present = 1;
|
||||||
|
this->character_data->guild_card.language = nsc_data.inventory.language;
|
||||||
|
this->character_data->guild_card.section_id = nsc_data.disp.visual.section_id;
|
||||||
|
this->character_data->guild_card.char_class = nsc_data.disp.visual.char_class;
|
||||||
|
this->character_data->auto_reply = nsc_data.auto_reply;
|
||||||
|
this->character_data->info_board = nsc_data.info_board;
|
||||||
|
this->character_data->battle_records = nsc_data.battle_records;
|
||||||
|
this->character_data->challenge_records = nsc_data.challenge_records;
|
||||||
|
this->character_data->tech_menu_config = nsc_data.tech_menu_config;
|
||||||
|
this->character_data->quest_global_flags = nsc_data.quest_global_flags;
|
||||||
|
if (nsa_data) {
|
||||||
|
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("Loaded legacy player data from %s and %s", nsa_filename.c_str(), nsc_filename.c_str());
|
||||||
|
} else {
|
||||||
|
player_data_log.info("Loaded legacy player data from %s", nsc_filename.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
this->blocked_senders.emplace(this->guild_card_data->blocked[z].guild_card_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->character_data) {
|
||||||
|
this->last_play_time_update = now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::save_all() {
|
||||||
|
if (this->system_data) {
|
||||||
|
this->save_system_file();
|
||||||
|
}
|
||||||
|
if (this->character_data) {
|
||||||
|
this->save_character_file();
|
||||||
|
}
|
||||||
|
if (this->guild_card_data) {
|
||||||
|
this->save_guild_card_file();
|
||||||
|
}
|
||||||
|
if (this->external_bank) {
|
||||||
|
string filename = this->shared_bank_filename();
|
||||||
|
save_object_file<PlayerBank>(filename, *this->external_bank);
|
||||||
|
player_data_log.info("Saved shared bank file %s", filename.c_str());
|
||||||
|
}
|
||||||
|
if (this->external_bank_character) {
|
||||||
|
this->save_character_file(
|
||||||
|
this->character_filename(this->external_bank_character_index),
|
||||||
|
this->system_data,
|
||||||
|
this->external_bank_character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::save_system_file() const {
|
||||||
|
if (!this->system_data) {
|
||||||
|
throw logic_error("no system file loaded");
|
||||||
|
}
|
||||||
|
string filename = this->system_filename();
|
||||||
|
save_object_file(filename, *this->system_data);
|
||||||
|
player_data_log.info("Saved system file %s", filename.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::save_character_file(
|
||||||
|
const string& filename,
|
||||||
|
shared_ptr<const PSOBBBaseSystemFile> system,
|
||||||
|
shared_ptr<const PSOBBCharacterFile> character) {
|
||||||
|
auto f = fopen_unique(filename, "wb");
|
||||||
|
PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000};
|
||||||
|
fwritex(f.get(), header);
|
||||||
|
fwritex(f.get(), *character);
|
||||||
|
fwritex(f.get(), *system);
|
||||||
|
// TODO: Technically, we should write the actual team membership struct to the
|
||||||
|
// file here, but that would cause Client to depend on License, which
|
||||||
|
// it currently does not. This data doesn't matter at all for correctness
|
||||||
|
// within newserv, since it ignores this data entirely and instead generates
|
||||||
|
// the membership struct from the team ID in the License and the team's state.
|
||||||
|
// So, writing correct data here would mostly be for compatibility with other
|
||||||
|
// PSO servers. But if the other server is newserv, then this data would be
|
||||||
|
// used anyway, and if it's not, then it would presumably have a different set
|
||||||
|
// of teams with a different set of team IDs anyway, so the membership struct
|
||||||
|
// here would be useless either way.
|
||||||
|
static const PSOBBTeamMembership empty_membership;
|
||||||
|
fwritex(f.get(), empty_membership);
|
||||||
|
player_data_log.info("Saved character file %s", filename.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void 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 = now();
|
||||||
|
uint64_t seconds = (t - this->last_play_time_update) / 1000000;
|
||||||
|
this->character_data->disp.play_time += seconds;
|
||||||
|
this->character_data->play_time_seconds = this->character_data->disp.play_time;
|
||||||
|
player_data_log.info("Added %" PRIu64 " 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();
|
||||||
|
save_object_file(filename, *this->guild_card_data);
|
||||||
|
player_data_log.info("Saved Guild Card file %s", filename.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::load_backup_character(uint32_t serial_number, size_t index) {
|
||||||
|
string filename = this->backup_character_filename(serial_number, index);
|
||||||
|
auto f = fopen_unique(filename, "rb");
|
||||||
|
auto header = freadx<PSOCommandHeaderBB>(f.get());
|
||||||
|
if (header.size != 0x399C) {
|
||||||
|
throw runtime_error("incorrect size in character file header");
|
||||||
|
}
|
||||||
|
if (header.command != 0x00E7) {
|
||||||
|
throw runtime_error("incorrect command in character file header");
|
||||||
|
}
|
||||||
|
if (header.flag != 0x00000000) {
|
||||||
|
throw runtime_error("incorrect flag in character file header");
|
||||||
|
}
|
||||||
|
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
|
||||||
|
this->v1_v2_last_reported_disp.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::unload_character() {
|
||||||
|
this->save_character_file();
|
||||||
|
this->character_data.reset();
|
||||||
|
this->log.info("Unloaded character");
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerBank& 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
save_object_file<PlayerBank>(filename, *this->external_bank);
|
||||||
|
this->external_bank.reset();
|
||||||
|
player_data_log.info("Detached shared bank %s", filename.c_str());
|
||||||
|
}
|
||||||
|
if (this->external_bank_character) {
|
||||||
|
string filename = this->character_filename(this->external_bank_character_index);
|
||||||
|
this->save_character_file(filename, this->system_data, this->external_bank_character);
|
||||||
|
this->external_bank_character.reset();
|
||||||
|
player_data_log.info("Detached character %s from bank", filename.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::use_shared_bank() {
|
||||||
|
this->use_default_bank();
|
||||||
|
string filename = this->shared_bank_filename();
|
||||||
|
if (isfile(filename)) {
|
||||||
|
this->external_bank = make_shared<PlayerBank>(load_object_file<PlayerBank>(filename));
|
||||||
|
player_data_log.info("Loaded shared bank %s", filename.c_str());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this->external_bank = make_shared<PlayerBank>();
|
||||||
|
player_data_log.info("Created shared bank for %s", filename.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::use_character_bank(int8_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("Using loaded character file %s for external bank", filename.c_str());
|
||||||
|
} else if (isfile(filename)) {
|
||||||
|
auto f = fopen_unique(filename, "rb");
|
||||||
|
auto header = freadx<PSOCommandHeaderBB>(f.get());
|
||||||
|
if (header.size != 0x399C) {
|
||||||
|
throw runtime_error("incorrect size in character file header");
|
||||||
|
}
|
||||||
|
if (header.command != 0x00E7) {
|
||||||
|
throw runtime_error("incorrect command in character file header");
|
||||||
|
}
|
||||||
|
if (header.flag != 0x00000000) {
|
||||||
|
throw runtime_error("incorrect flag in character file header");
|
||||||
|
}
|
||||||
|
this->external_bank_character = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
|
||||||
|
this->external_bank_character_index = index;
|
||||||
|
files_manager->set_character(filename, this->external_bank_character);
|
||||||
|
player_data_log.info("Loaded character data from %s for external bank", filename.c_str());
|
||||||
|
} else {
|
||||||
|
throw runtime_error("character does not exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+98
-4
@@ -15,7 +15,6 @@
|
|||||||
#include "PSOEncryption.hh"
|
#include "PSOEncryption.hh"
|
||||||
#include "PSOProtocol.hh"
|
#include "PSOProtocol.hh"
|
||||||
#include "PatchFileIndex.hh"
|
#include "PatchFileIndex.hh"
|
||||||
#include "Player.hh"
|
|
||||||
#include "Quest.hh"
|
#include "Quest.hh"
|
||||||
#include "QuestScript.hh"
|
#include "QuestScript.hh"
|
||||||
#include "TeamIndex.hh"
|
#include "TeamIndex.hh"
|
||||||
@@ -26,7 +25,8 @@ extern const uint64_t CLIENT_CONFIG_MAGIC;
|
|||||||
class Server;
|
class Server;
|
||||||
struct Lobby;
|
struct Lobby;
|
||||||
|
|
||||||
struct Client : public std::enable_shared_from_this<Client> {
|
class Client : public std::enable_shared_from_this<Client> {
|
||||||
|
public:
|
||||||
enum class Flag : uint64_t {
|
enum class Flag : uint64_t {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
||||||
@@ -148,7 +148,6 @@ struct Client : public std::enable_shared_from_this<Client> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::weak_ptr<Server> server;
|
std::weak_ptr<Server> server;
|
||||||
std::weak_ptr<ServerState> server_state;
|
|
||||||
uint64_t id;
|
uint64_t id;
|
||||||
PrefixedLogger log;
|
PrefixedLogger log;
|
||||||
|
|
||||||
@@ -181,7 +180,7 @@ struct Client : public std::enable_shared_from_this<Client> {
|
|||||||
uint8_t lobby_client_id;
|
uint8_t lobby_client_id;
|
||||||
uint8_t lobby_arrow_color;
|
uint8_t lobby_arrow_color;
|
||||||
int64_t preferred_lobby_id; // <0 = no preference
|
int64_t preferred_lobby_id; // <0 = no preference
|
||||||
ClientGameData game_data;
|
|
||||||
std::unique_ptr<struct event, void (*)(struct event*)> save_game_data_event;
|
std::unique_ptr<struct event, void (*)(struct event*)> save_game_data_event;
|
||||||
std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event;
|
std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event;
|
||||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||||
@@ -198,6 +197,28 @@ struct Client : public std::enable_shared_from_this<Client> {
|
|||||||
};
|
};
|
||||||
std::unique_ptr<std::deque<JoinCommand>> game_join_command_queue;
|
std::unique_ptr<std::deque<JoinCommand>> game_join_command_queue;
|
||||||
|
|
||||||
|
// Character / game data
|
||||||
|
struct PendingItemTrade {
|
||||||
|
uint8_t other_client_id;
|
||||||
|
bool confirmed; // true if client has sent a D2 command
|
||||||
|
std::vector<ItemData> items;
|
||||||
|
};
|
||||||
|
struct PendingCardTrade {
|
||||||
|
uint8_t other_client_id;
|
||||||
|
bool confirmed; // true if client has sent an EE D2 command
|
||||||
|
std::vector<std::pair<uint32_t, uint32_t>> card_to_count;
|
||||||
|
};
|
||||||
|
bool should_update_play_time;
|
||||||
|
std::unordered_set<uint32_t> blocked_senders;
|
||||||
|
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
|
||||||
|
// These are null unless the client is within the trade sequence (D0-D4 or EE commands)
|
||||||
|
std::unique_ptr<PendingItemTrade> pending_item_trade;
|
||||||
|
std::unique_ptr<PendingCardTrade> pending_card_trade;
|
||||||
|
std::shared_ptr<Episode3::PlayerConfig> ep3_config; // Null for non-Ep3
|
||||||
|
int8_t bb_character_index;
|
||||||
|
ItemData bb_identify_result;
|
||||||
|
std::array<std::vector<ItemData>, 3> bb_shop_contents;
|
||||||
|
|
||||||
// Miscellaneous (used by chat commands)
|
// Miscellaneous (used by chat commands)
|
||||||
uint32_t next_exp_value; // next EXP value to give
|
uint32_t next_exp_value; // next EXP value to give
|
||||||
G_SwitchStateChanged_6x05 last_switch_enabled_command;
|
G_SwitchStateChanged_6x05 last_switch_enabled_command;
|
||||||
@@ -249,4 +270,77 @@ struct Client : public std::enable_shared_from_this<Client> {
|
|||||||
void idle_timeout();
|
void idle_timeout();
|
||||||
|
|
||||||
void suspend_timeouts();
|
void suspend_timeouts();
|
||||||
|
|
||||||
|
const std::string& get_bb_username() const;
|
||||||
|
void set_bb_username(const std::string& bb_username);
|
||||||
|
|
||||||
|
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
|
||||||
|
void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
|
||||||
|
inline void delete_overlay() {
|
||||||
|
this->overlay_character_data.reset();
|
||||||
|
}
|
||||||
|
inline bool has_overlay() const {
|
||||||
|
return this->overlay_character_data.get() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders);
|
||||||
|
|
||||||
|
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, int8_t index);
|
||||||
|
static std::string backup_character_filename(uint32_t serial_number, size_t index);
|
||||||
|
std::string character_filename(int8_t index = -1) const;
|
||||||
|
std::string guild_card_filename() const;
|
||||||
|
std::string shared_bank_filename() const;
|
||||||
|
|
||||||
|
std::string legacy_player_filename() const;
|
||||||
|
std::string legacy_account_filename() const;
|
||||||
|
|
||||||
|
void save_all();
|
||||||
|
void save_system_file() const;
|
||||||
|
static void save_character_file(
|
||||||
|
const std::string& filename,
|
||||||
|
std::shared_ptr<const PSOBBBaseSystemFile> sys,
|
||||||
|
std::shared_ptr<const PSOBBCharacterFile> character);
|
||||||
|
// Note: This function is not const because it updates the player's play time.
|
||||||
|
void save_character_file();
|
||||||
|
void save_guild_card_file() const;
|
||||||
|
|
||||||
|
void load_backup_character(uint32_t serial_number, size_t index);
|
||||||
|
void unload_character();
|
||||||
|
|
||||||
|
PlayerBank& current_bank();
|
||||||
|
std::shared_ptr<PSOBBCharacterFile> current_bank_character();
|
||||||
|
bool use_shared_bank(); // Returns true if the bank exists; false if it was created
|
||||||
|
void use_character_bank(int8_t bb_character_index);
|
||||||
|
void use_default_bank();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// The overlay character data is used in battle and challenge modes, when
|
||||||
|
// character data is temporarily replaced in-game. In other play modes and in
|
||||||
|
// lobbies, overlay_character_data is null.
|
||||||
|
std::shared_ptr<PSOBBBaseSystemFile> system_data;
|
||||||
|
std::shared_ptr<PSOBBCharacterFile> overlay_character_data;
|
||||||
|
std::shared_ptr<PSOBBCharacterFile> character_data;
|
||||||
|
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
|
||||||
|
std::shared_ptr<PlayerBank> external_bank;
|
||||||
|
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
|
||||||
|
int8_t external_bank_character_index;
|
||||||
|
uint64_t last_play_time_update;
|
||||||
|
|
||||||
|
void save_and_clear_external_bank();
|
||||||
|
|
||||||
|
void load_all_files();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
#include "Episode3/MapState.hh"
|
#include "Episode3/MapState.hh"
|
||||||
#include "Episode3/PlayerStateSubordinates.hh"
|
#include "Episode3/PlayerStateSubordinates.hh"
|
||||||
#include "PSOProtocol.hh"
|
#include "PSOProtocol.hh"
|
||||||
#include "Player.hh"
|
#include "PlayerSubordinates.hh"
|
||||||
#include "SaveFileFormats.hh"
|
#include "SaveFileFormats.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|
||||||
@@ -1053,7 +1053,6 @@ struct C_OpenFileConfirmation_44_A6 {
|
|||||||
|
|
||||||
// 61 (C->S): Player data
|
// 61 (C->S): Player data
|
||||||
// Internal name: SndCharaDataV2 (SndCharaData in DCv1)
|
// Internal name: SndCharaDataV2 (SndCharaData in DCv1)
|
||||||
// See the PSOPlayerData structs in Player.hh for this command's format.
|
|
||||||
// header.flag specifies the format version, which is related to (but not
|
// header.flag specifies the format version, which is related to (but not
|
||||||
// identical to) the game's major version. For example, the format version is 01
|
// identical to) the game's major version. For example, the format version is 01
|
||||||
// on DC v1, 02 on PSO PC, 03 on PSO GC, XB, and BB, and 04 on Episode 3.
|
// on DC v1, 02 on PSO PC, 03 on PSO GC, XB, and BB, and 04 on Episode 3.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include "../Player.hh"
|
#include "../PlayerSubordinates.hh"
|
||||||
|
|
||||||
struct Lobby;
|
struct Lobby;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Tournament::PlayerEntry::PlayerEntry(uint32_t serial_number, const string& playe
|
|||||||
Tournament::PlayerEntry::PlayerEntry(shared_ptr<Client> c)
|
Tournament::PlayerEntry::PlayerEntry(shared_ptr<Client> c)
|
||||||
: serial_number(c->license->serial_number),
|
: serial_number(c->license->serial_number),
|
||||||
client(c),
|
client(c),
|
||||||
player_name(c->game_data.character()->disp.name.decode(c->language())) {}
|
player_name(c->character()->disp.name.decode(c->language())) {}
|
||||||
|
|
||||||
Tournament::PlayerEntry::PlayerEntry(
|
Tournament::PlayerEntry::PlayerEntry(
|
||||||
shared_ptr<const COMDeckDefinition> com_deck)
|
shared_ptr<const COMDeckDefinition> com_deck)
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "../Player.hh"
|
#include "DataIndexes.hh"
|
||||||
|
|
||||||
struct Lobby;
|
struct Lobby;
|
||||||
struct Client;
|
class Client;
|
||||||
struct ServerState;
|
struct ServerState;
|
||||||
|
|
||||||
namespace Episode3 {
|
namespace Episode3 {
|
||||||
|
|||||||
+3
-3
@@ -17,7 +17,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
|||||||
bool is_v3_or_later = is_v3(c->version()) || is_v4(c->version());
|
bool is_v3_or_later = is_v3(c->version()) || is_v4(c->version());
|
||||||
bool should_delete_item = is_v3_or_later;
|
bool should_delete_item = is_v3_or_later;
|
||||||
|
|
||||||
auto player = c->game_data.character();
|
auto player = c->character();
|
||||||
auto& item = player->inventory.items[item_index];
|
auto& item = player->inventory.items[item_index];
|
||||||
uint32_t item_identifier = item.data.primary_identifier();
|
uint32_t item_identifier = item.data.primary_identifier();
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index) {
|
|||||||
weapon.data.data1[3] += (item.data.data1[2] + 1);
|
weapon.data.data1[3] += (item.data.data1[2] + 1);
|
||||||
|
|
||||||
} else if ((item_identifier & 0xFFFF00) == 0x030B00) { // Material
|
} else if ((item_identifier & 0xFFFF00) == 0x030B00) { // Material
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
|
|
||||||
using Type = PSOBBCharacterFile::MaterialType;
|
using Type = PSOBBCharacterFile::MaterialType;
|
||||||
Type type;
|
Type type;
|
||||||
@@ -272,7 +272,7 @@ void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fe
|
|||||||
});
|
});
|
||||||
|
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto player = c->game_data.character();
|
auto player = c->character();
|
||||||
auto& fed_item = player->inventory.items[fed_item_index];
|
auto& fed_item = player->inventory.items[fed_item_index];
|
||||||
auto& mag_item = player->inventory.items[mag_item_index];
|
auto& mag_item = player->inventory.items[mag_item_index];
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ License::License(const JSON& json)
|
|||||||
this->bb_password = json.get_string("BBPassword", "");
|
this->bb_password = json.get_string("BBPassword", "");
|
||||||
this->flags = json.get_int("Flags", 0);
|
this->flags = json.get_int("Flags", 0);
|
||||||
this->ban_end_time = json.get_int("BanEndTime", 0);
|
this->ban_end_time = json.get_int("BanEndTime", 0);
|
||||||
|
this->auto_reply_message = json.get_string("AutoReplyMessage", "");
|
||||||
this->ep3_current_meseta = json.get_int("Ep3CurrentMeseta", 0);
|
this->ep3_current_meseta = json.get_int("Ep3CurrentMeseta", 0);
|
||||||
this->ep3_total_meseta_earned = json.get_int("Ep3TotalMesetaEarned", 0);
|
this->ep3_total_meseta_earned = json.get_int("Ep3TotalMesetaEarned", 0);
|
||||||
this->bb_team_id = json.get_int("BBTeamID", 0);
|
this->bb_team_id = json.get_int("BBTeamID", 0);
|
||||||
@@ -43,6 +44,7 @@ JSON License::json() const {
|
|||||||
{"BBPassword", this->bb_password},
|
{"BBPassword", this->bb_password},
|
||||||
{"Flags", this->flags},
|
{"Flags", this->flags},
|
||||||
{"BanEndTime", this->ban_end_time},
|
{"BanEndTime", this->ban_end_time},
|
||||||
|
{"AutoReplyMessage", this->auto_reply_message},
|
||||||
{"Ep3CurrentMeseta", this->ep3_current_meseta},
|
{"Ep3CurrentMeseta", this->ep3_current_meseta},
|
||||||
{"Ep3TotalMesetaEarned", this->ep3_total_meseta_earned},
|
{"Ep3TotalMesetaEarned", this->ep3_total_meseta_earned},
|
||||||
{"BBTeamID", this->bb_team_id},
|
{"BBTeamID", this->bb_team_id},
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ struct License {
|
|||||||
|
|
||||||
uint32_t flags = 0;
|
uint32_t flags = 0;
|
||||||
uint64_t ban_end_time = 0; // 0 = not banned
|
uint64_t ban_end_time = 0; // 0 = not banned
|
||||||
|
std::string auto_reply_message;
|
||||||
|
|
||||||
uint32_t ep3_current_meseta = 0;
|
uint32_t ep3_current_meseta = 0;
|
||||||
uint32_t ep3_total_meseta_earned = 0;
|
uint32_t ep3_total_meseta_earned = 0;
|
||||||
|
|||||||
+4
-4
@@ -219,7 +219,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 the lobby is recording a battle record, add the player join event
|
||||||
if (this->battle_record) {
|
if (this->battle_record) {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
PlayerLobbyDataDCGC lobby_data;
|
PlayerLobbyDataDCGC lobby_data;
|
||||||
lobby_data.player_tag = 0x00010000;
|
lobby_data.player_tag = 0x00010000;
|
||||||
lobby_data.guild_card_number = c->license->serial_number;
|
lobby_data.guild_card_number = c->license->serial_number;
|
||||||
@@ -228,7 +228,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
|||||||
lobby_data,
|
lobby_data,
|
||||||
p->inventory,
|
p->inventory,
|
||||||
p->disp.to_dcpcv3(c->language(), c->language()),
|
p->disp.to_dcpcv3(c->language(), c->language()),
|
||||||
c->game_data.ep3_config ? (c->game_data.ep3_config->online_clv_exp / 100) : 0);
|
c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send spectator count notifications if needed
|
// Send spectator count notifications if needed
|
||||||
@@ -318,7 +318,7 @@ shared_ptr<Client> Lobby::find_client(const string* identifier, uint64_t serial_
|
|||||||
(lc->license->serial_number == serial_number)) {
|
(lc->license->serial_number == serial_number)) {
|
||||||
return lc;
|
return lc;
|
||||||
}
|
}
|
||||||
if (identifier && (lc->game_data.character()->disp.name.eq(*identifier, lc->language()))) {
|
if (identifier && (lc->character()->disp.name.eq(*identifier, lc->language()))) {
|
||||||
return lc;
|
return lc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -384,7 +384,7 @@ void Lobby::on_item_id_generated_externally(uint32_t item_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c) {
|
void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c) {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
for (size_t z = 0; z < p->inventory.num_items; z++) {
|
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);
|
p->inventory.items[z].data.id = this->generate_item_id(c->lobby_client_id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
#include "Episode3/Server.hh"
|
#include "Episode3/Server.hh"
|
||||||
#include "ItemCreator.hh"
|
#include "ItemCreator.hh"
|
||||||
#include "Map.hh"
|
#include "Map.hh"
|
||||||
#include "Player.hh"
|
|
||||||
#include "Quest.hh"
|
#include "Quest.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
|
|||||||
-707
@@ -1,707 +0,0 @@
|
|||||||
#include "Player.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<struct event_base> base)
|
|
||||||
: base(base),
|
|
||||||
clear_expired_files_event(
|
|
||||||
event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this),
|
|
||||||
event_free) {
|
|
||||||
auto tv = usecs_to_timeval(30 * 1000 * 1000);
|
|
||||||
event_add(this->clear_expired_files_event.get(), &tv);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<PlayerBank> PlayerFilesManager::get_bank(const std::string& filename) {
|
|
||||||
try {
|
|
||||||
return this->loaded_bank_files.at(filename);
|
|
||||||
} catch (const out_of_range&) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file) {
|
|
||||||
if (!this->loaded_system_files.emplace(filename, file).second) {
|
|
||||||
throw runtime_error("Guild Card file already loaded: " + 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<PlayerBank> file) {
|
|
||||||
if (!this->loaded_bank_files.emplace(filename, file).second) {
|
|
||||||
throw runtime_error("bank file already loaded: " + filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) {
|
|
||||||
auto* self = reinterpret_cast<PlayerFilesManager*>(ctx);
|
|
||||||
size_t num_deleted = erase_unused(self->loaded_system_files);
|
|
||||||
if (num_deleted) {
|
|
||||||
player_data_log.info("Cleared %zu expired system file(s)", num_deleted);
|
|
||||||
}
|
|
||||||
num_deleted = erase_unused(self->loaded_character_files);
|
|
||||||
if (num_deleted) {
|
|
||||||
player_data_log.info("Cleared %zu expired character file(s)", num_deleted);
|
|
||||||
}
|
|
||||||
num_deleted = erase_unused(self->loaded_guild_card_files);
|
|
||||||
if (num_deleted) {
|
|
||||||
player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted);
|
|
||||||
}
|
|
||||||
num_deleted = erase_unused(self->loaded_bank_files);
|
|
||||||
if (num_deleted) {
|
|
||||||
player_data_log.info("Cleared %zu expired bank file(s)", num_deleted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientGameData::ClientGameData(std::shared_ptr<PlayerFilesManager> files_manager)
|
|
||||||
: guild_card_number(0),
|
|
||||||
should_update_play_time(false),
|
|
||||||
bb_character_index(-1),
|
|
||||||
files_manager(files_manager),
|
|
||||||
last_play_time_update(0) {
|
|
||||||
for (size_t z = 0; z < this->blocked_senders.size(); z++) {
|
|
||||||
this->blocked_senders[z] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientGameData::~ClientGameData() {
|
|
||||||
if (!this->bb_username.empty() && this->character_data.get()) {
|
|
||||||
this->save_all();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const string& ClientGameData::get_bb_username() const {
|
|
||||||
return this->bb_username;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::set_bb_username(const string& bb_username) {
|
|
||||||
// Make sure bb_username is filename-safe
|
|
||||||
for (char ch : bb_username) {
|
|
||||||
if (!isalnum(ch) && (ch != '-') && (ch != '_')) {
|
|
||||||
throw runtime_error("invalid characters in username");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this->bb_username = bb_username;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_ptr<const LevelTable> level_table) {
|
|
||||||
this->overlay_character_data = make_shared<PSOBBCharacterFile>(*this->character(true, false));
|
|
||||||
|
|
||||||
if (rules->weapon_and_armor_mode != BattleRules::WeaponAndArmorMode::ALLOW) {
|
|
||||||
this->overlay_character_data->inventory.remove_all_items_of_type(0);
|
|
||||||
this->overlay_character_data->inventory.remove_all_items_of_type(1);
|
|
||||||
}
|
|
||||||
if (rules->mag_mode == BattleRules::MagMode::FORBID_ALL) {
|
|
||||||
this->overlay_character_data->inventory.remove_all_items_of_type(2);
|
|
||||||
}
|
|
||||||
if (rules->tool_mode != BattleRules::ToolMode::ALLOW) {
|
|
||||||
this->overlay_character_data->inventory.remove_all_items_of_type(3);
|
|
||||||
}
|
|
||||||
if (rules->replace_char) {
|
|
||||||
// TODO: Shouldn't we clear other material usage here? It looks like the
|
|
||||||
// original code doesn't, but that seems wrong.
|
|
||||||
this->overlay_character_data->inventory.hp_from_materials = 0;
|
|
||||||
this->overlay_character_data->inventory.tp_from_materials = 0;
|
|
||||||
|
|
||||||
uint32_t target_level = clamp<uint32_t>(rules->char_level, 0, 199);
|
|
||||||
uint8_t char_class = this->overlay_character_data->disp.visual.char_class;
|
|
||||||
auto& stats = this->overlay_character_data->disp.stats;
|
|
||||||
|
|
||||||
stats.reset_to_base(char_class, level_table);
|
|
||||||
stats.advance_to_level(char_class, target_level, level_table);
|
|
||||||
|
|
||||||
stats.unknown_a1 = 40;
|
|
||||||
stats.meseta = 300;
|
|
||||||
}
|
|
||||||
if (rules->tech_disk_mode == BattleRules::TechDiskMode::LIMIT_LEVEL) {
|
|
||||||
// TODO: Verify this is what the game actually does.
|
|
||||||
for (uint8_t tech_num = 0; tech_num < 0x13; tech_num++) {
|
|
||||||
uint8_t existing_level = this->overlay_character_data->get_technique_level(tech_num);
|
|
||||||
if ((existing_level != 0xFF) && (existing_level > rules->max_tech_level)) {
|
|
||||||
this->overlay_character_data->set_technique_level(tech_num, rules->max_tech_level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (rules->tech_disk_mode == BattleRules::TechDiskMode::FORBID_ALL) {
|
|
||||||
for (uint8_t tech_num = 0; tech_num < 0x13; tech_num++) {
|
|
||||||
this->overlay_character_data->set_technique_level(tech_num, 0xFF);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rules->meseta_mode != BattleRules::MesetaMode::ALLOW) {
|
|
||||||
this->overlay_character_data->disp.stats.meseta = 0;
|
|
||||||
}
|
|
||||||
if (rules->forbid_scape_dolls) {
|
|
||||||
this->overlay_character_data->inventory.remove_all_items_of_type(3, 9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::create_challenge_overlay(Version version, size_t template_index, shared_ptr<const LevelTable> level_table) {
|
|
||||||
auto p = this->character(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);
|
|
||||||
auto overlay = this->overlay_character_data;
|
|
||||||
|
|
||||||
for (size_t z = 0; z < overlay->inventory.items.size(); z++) {
|
|
||||||
auto& i = overlay->inventory.items[z];
|
|
||||||
i.present = 0;
|
|
||||||
i.unknown_a1 = 0;
|
|
||||||
i.extension_data1 = 0;
|
|
||||||
i.extension_data2 = 0;
|
|
||||||
i.flags = 0;
|
|
||||||
i.data = ItemData();
|
|
||||||
}
|
|
||||||
|
|
||||||
overlay->inventory.items[13].extension_data2 = 1;
|
|
||||||
|
|
||||||
overlay->disp.stats.reset_to_base(overlay->disp.visual.char_class, level_table);
|
|
||||||
overlay->disp.stats.advance_to_level(overlay->disp.visual.char_class, tpl.level, level_table);
|
|
||||||
|
|
||||||
overlay->disp.stats.unknown_a1 = 40;
|
|
||||||
overlay->disp.stats.unknown_a3 = 10.0;
|
|
||||||
overlay->disp.stats.experience = level_table->stats_delta_for_level(overlay->disp.visual.char_class, overlay->disp.stats.level).experience;
|
|
||||||
overlay->disp.stats.meseta = 0;
|
|
||||||
overlay->clear_all_material_usage();
|
|
||||||
for (size_t z = 0; z < 0x13; z++) {
|
|
||||||
overlay->set_technique_level(z, 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t z = 0; z < tpl.items.size(); z++) {
|
|
||||||
auto& inv_item = overlay->inventory.items[z];
|
|
||||||
inv_item.present = tpl.items[z].present;
|
|
||||||
inv_item.unknown_a1 = tpl.items[z].unknown_a1;
|
|
||||||
inv_item.flags = tpl.items[z].flags;
|
|
||||||
inv_item.data = tpl.items[z].data;
|
|
||||||
}
|
|
||||||
overlay->inventory.num_items = tpl.items.size();
|
|
||||||
|
|
||||||
for (const auto& tech_level : tpl.tech_levels) {
|
|
||||||
overlay->set_technique_level(tech_level.tech_num, tech_level.level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<PSOBBBaseSystemFile> ClientGameData::system(bool allow_load) {
|
|
||||||
if (!this->system_data && allow_load) {
|
|
||||||
this->load_all_files();
|
|
||||||
}
|
|
||||||
return this->system_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<const PSOBBBaseSystemFile> ClientGameData::system(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> ClientGameData::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->bb_username.empty() && (this->bb_character_index < 0)) {
|
|
||||||
throw runtime_error("character index not specified");
|
|
||||||
}
|
|
||||||
this->load_all_files();
|
|
||||||
}
|
|
||||||
return this->character_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<const PSOBBCharacterFile> ClientGameData::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> ClientGameData::guild_cards(bool allow_load) {
|
|
||||||
if (!this->guild_card_data && allow_load) {
|
|
||||||
this->load_all_files();
|
|
||||||
}
|
|
||||||
return this->guild_card_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_ptr<const PSOBBGuildCardFile> ClientGameData::guild_cards(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 ClientGameData::system_filename() const {
|
|
||||||
if (this->bb_username.empty()) {
|
|
||||||
throw logic_error("non-BB players do not have system data");
|
|
||||||
}
|
|
||||||
return string_printf("system/players/system_%s.psosys", this->bb_username.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
string ClientGameData::character_filename(const std::string& bb_username, int8_t index) {
|
|
||||||
if (bb_username.empty()) {
|
|
||||||
throw logic_error("non-BB players do not have character data");
|
|
||||||
}
|
|
||||||
if (index < 0) {
|
|
||||||
throw logic_error("character index is not set");
|
|
||||||
}
|
|
||||||
return string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index);
|
|
||||||
}
|
|
||||||
|
|
||||||
string ClientGameData::backup_character_filename(uint32_t serial_number, size_t index) {
|
|
||||||
return string_printf("system/players/backup_player_%" PRIu32 "_%zu.psochar", serial_number, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
string ClientGameData::character_filename(int8_t index) const {
|
|
||||||
if (this->bb_username.empty()) {
|
|
||||||
throw logic_error("non-BB players do not have character data");
|
|
||||||
}
|
|
||||||
if (index < 0) {
|
|
||||||
index = this->bb_character_index;
|
|
||||||
}
|
|
||||||
if (index < 0) {
|
|
||||||
throw logic_error("character index is not set");
|
|
||||||
}
|
|
||||||
return this->character_filename(this->bb_username, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
string ClientGameData::guild_card_filename() const {
|
|
||||||
if (this->bb_username.empty()) {
|
|
||||||
throw logic_error("non-BB players do not have Guild Card files");
|
|
||||||
}
|
|
||||||
return string_printf("system/players/guild_cards_%s.psocard", this->bb_username.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
string ClientGameData::shared_bank_filename() const {
|
|
||||||
if (this->bb_username.empty()) {
|
|
||||||
throw logic_error("non-BB players do not have shared bank files");
|
|
||||||
}
|
|
||||||
return string_printf("system/players/shared_bank_%s.psobank", this->bb_username.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
string ClientGameData::legacy_account_filename() const {
|
|
||||||
if (this->bb_username.empty()) {
|
|
||||||
throw logic_error("non-BB players do not have legacy account data");
|
|
||||||
}
|
|
||||||
return string_printf("system/players/account_%s.nsa", this->bb_username.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
string ClientGameData::legacy_player_filename() const {
|
|
||||||
if (this->bb_username.empty()) {
|
|
||||||
throw logic_error("non-BB players do not have legacy player data");
|
|
||||||
}
|
|
||||||
if (this->bb_character_index < 0) {
|
|
||||||
throw logic_error("character index is not set");
|
|
||||||
}
|
|
||||||
return string_printf(
|
|
||||||
"system/players/player_%s_%hhd.nsc",
|
|
||||||
this->bb_username.c_str(),
|
|
||||||
static_cast<int8_t>(this->bb_character_index + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::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 ClientGameData::load_all_files() {
|
|
||||||
if (this->bb_username.empty()) {
|
|
||||||
this->system_data = make_shared<PSOBBBaseSystemFile>();
|
|
||||||
this->character_data = make_shared<PSOBBCharacterFile>();
|
|
||||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->system_data.reset();
|
|
||||||
this->character_data.reset();
|
|
||||||
this->guild_card_data.reset();
|
|
||||||
|
|
||||||
string sys_filename = this->system_filename();
|
|
||||||
this->system_data = this->files_manager->get_system(sys_filename);
|
|
||||||
if (this->system_data) {
|
|
||||||
player_data_log.info("Using loaded system file %s", sys_filename.c_str());
|
|
||||||
} else if (isfile(sys_filename)) {
|
|
||||||
this->system_data = make_shared<PSOBBBaseSystemFile>(load_object_file<PSOBBBaseSystemFile>(sys_filename, true));
|
|
||||||
this->files_manager->set_system(sys_filename, this->system_data);
|
|
||||||
player_data_log.info("Loaded system data from %s", sys_filename.c_str());
|
|
||||||
} else {
|
|
||||||
player_data_log.info("System file is missing: %s", sys_filename.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->bb_character_index >= 0) {
|
|
||||||
string char_filename = this->character_filename();
|
|
||||||
this->character_data = this->files_manager->get_character(char_filename);
|
|
||||||
if (this->character_data) {
|
|
||||||
player_data_log.info("Using loaded character file %s", char_filename.c_str());
|
|
||||||
} else if (isfile(char_filename)) {
|
|
||||||
auto f = fopen_unique(char_filename, "rb");
|
|
||||||
auto header = freadx<PSOCommandHeaderBB>(f.get());
|
|
||||||
if (header.size != 0x399C) {
|
|
||||||
throw runtime_error("incorrect size in character file header");
|
|
||||||
}
|
|
||||||
if (header.command != 0x00E7) {
|
|
||||||
throw runtime_error("incorrect command in character file header");
|
|
||||||
}
|
|
||||||
if (header.flag != 0x00000000) {
|
|
||||||
throw runtime_error("incorrect flag in character file header");
|
|
||||||
}
|
|
||||||
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
|
|
||||||
this->files_manager->set_character(char_filename, this->character_data);
|
|
||||||
player_data_log.info("Loaded character data from %s", char_filename.c_str());
|
|
||||||
|
|
||||||
// If there was no .psosys file, load the system file from the .psochar
|
|
||||||
// file instead
|
|
||||||
if (!this->system_data) {
|
|
||||||
this->system_data = make_shared<PSOBBBaseSystemFile>(freadx<PSOBBBaseSystemFile>(f.get()));
|
|
||||||
this->files_manager->set_system(sys_filename, this->system_data);
|
|
||||||
player_data_log.info("Loaded system data from %s", char_filename.c_str());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
player_data_log.info("Character file is missing: %s", char_filename.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string card_filename = this->guild_card_filename();
|
|
||||||
this->guild_card_data = this->files_manager->get_guild_card(card_filename);
|
|
||||||
if (this->guild_card_data) {
|
|
||||||
player_data_log.info("Using loaded Guild Card file %s", card_filename.c_str());
|
|
||||||
} else if (isfile(card_filename)) {
|
|
||||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>(load_object_file<PSOBBGuildCardFile>(card_filename));
|
|
||||||
this->files_manager->set_guild_card(card_filename, this->guild_card_data);
|
|
||||||
player_data_log.info("Loaded Guild Card data from %s", card_filename.c_str());
|
|
||||||
} else {
|
|
||||||
player_data_log.info("Guild Card file is missing: %s", card_filename.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any of the above files were missing, try to load from .nsa/.nsc files instead
|
|
||||||
if (!this->system_data || (!this->character_data && (this->bb_character_index >= 0)) || !this->guild_card_data) {
|
|
||||||
string nsa_filename = this->legacy_account_filename();
|
|
||||||
shared_ptr<LegacySavedAccountDataBB> nsa_data;
|
|
||||||
if (isfile(nsa_filename)) {
|
|
||||||
nsa_data = make_shared<LegacySavedAccountDataBB>(load_object_file<LegacySavedAccountDataBB>(nsa_filename));
|
|
||||||
if (!nsa_data->signature.eq(LegacySavedAccountDataBB::SIGNATURE)) {
|
|
||||||
throw runtime_error("account data header is incorrect");
|
|
||||||
}
|
|
||||||
if (!this->system_data) {
|
|
||||||
this->system_data = make_shared<PSOBBBaseSystemFile>(nsa_data->system_file.base);
|
|
||||||
this->files_manager->set_system(sys_filename, this->system_data);
|
|
||||||
player_data_log.info("Loaded legacy system data from %s", nsa_filename.c_str());
|
|
||||||
}
|
|
||||||
if (!this->guild_card_data) {
|
|
||||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>(nsa_data->guild_card_file);
|
|
||||||
this->files_manager->set_guild_card(card_filename, this->guild_card_data);
|
|
||||||
player_data_log.info("Loaded legacy Guild Card data from %s", nsa_filename.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->system_data) {
|
|
||||||
this->system_data = make_shared<PSOBBBaseSystemFile>();
|
|
||||||
this->files_manager->set_system(sys_filename, this->system_data);
|
|
||||||
player_data_log.info("Created new system data");
|
|
||||||
}
|
|
||||||
if (!this->guild_card_data) {
|
|
||||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
|
|
||||||
this->files_manager->set_guild_card(card_filename, this->guild_card_data);
|
|
||||||
player_data_log.info("Created new Guild Card data");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->character_data && (this->bb_character_index >= 0)) {
|
|
||||||
string nsc_filename = this->legacy_player_filename();
|
|
||||||
auto nsc_data = load_object_file<LegacySavedPlayerDataBB>(nsc_filename);
|
|
||||||
if (nsc_data.signature == LegacySavedPlayerDataBB::SIGNATURE_V0) {
|
|
||||||
nsc_data.signature = LegacySavedPlayerDataBB::SIGNATURE_V0;
|
|
||||||
nsc_data.unused.clear();
|
|
||||||
nsc_data.battle_records.place_counts.clear(0);
|
|
||||||
nsc_data.battle_records.disconnect_count = 0;
|
|
||||||
nsc_data.battle_records.unknown_a1.clear(0);
|
|
||||||
} else if (nsc_data.signature != LegacySavedPlayerDataBB::SIGNATURE_V1) {
|
|
||||||
throw runtime_error("legacy player data has incorrect signature");
|
|
||||||
}
|
|
||||||
|
|
||||||
this->character_data = make_shared<PSOBBCharacterFile>();
|
|
||||||
this->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 = nsc_data.disp.play_time;
|
|
||||||
this->character_data->unknown_a2 = nsc_data.unknown_a2;
|
|
||||||
this->character_data->quest_flags = nsc_data.quest_flags;
|
|
||||||
this->character_data->death_count = nsc_data.death_count;
|
|
||||||
this->character_data->bank = nsc_data.bank;
|
|
||||||
this->character_data->guild_card.guild_card_number = this->guild_card_number;
|
|
||||||
this->character_data->guild_card.name = nsc_data.disp.name;
|
|
||||||
this->character_data->guild_card.description = nsc_data.guild_card_description;
|
|
||||||
this->character_data->guild_card.present = 1;
|
|
||||||
this->character_data->guild_card.language = nsc_data.inventory.language;
|
|
||||||
this->character_data->guild_card.section_id = nsc_data.disp.visual.section_id;
|
|
||||||
this->character_data->guild_card.char_class = nsc_data.disp.visual.char_class;
|
|
||||||
this->character_data->auto_reply = nsc_data.auto_reply;
|
|
||||||
this->character_data->info_board = nsc_data.info_board;
|
|
||||||
this->character_data->battle_records = nsc_data.battle_records;
|
|
||||||
this->character_data->challenge_records = nsc_data.challenge_records;
|
|
||||||
this->character_data->tech_menu_config = nsc_data.tech_menu_config;
|
|
||||||
this->character_data->quest_global_flags = nsc_data.quest_global_flags;
|
|
||||||
if (nsa_data) {
|
|
||||||
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("Loaded legacy player data from %s and %s", nsa_filename.c_str(), nsc_filename.c_str());
|
|
||||||
} else {
|
|
||||||
player_data_log.info("Loaded legacy player data from %s", nsc_filename.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->blocked_senders.fill(0);
|
|
||||||
for (size_t z = 0; z < this->guild_card_data->blocked.size(); z++) {
|
|
||||||
if (this->guild_card_data->blocked[z].present) {
|
|
||||||
this->blocked_senders[z] = this->guild_card_data->blocked[z].guild_card_number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->character_data) {
|
|
||||||
this->last_play_time_update = now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::save_all() {
|
|
||||||
if (this->system_data) {
|
|
||||||
this->save_system_file();
|
|
||||||
}
|
|
||||||
if (this->character_data) {
|
|
||||||
this->save_character_file();
|
|
||||||
}
|
|
||||||
if (this->guild_card_data) {
|
|
||||||
this->save_guild_card_file();
|
|
||||||
}
|
|
||||||
if (this->external_bank) {
|
|
||||||
string filename = this->shared_bank_filename();
|
|
||||||
save_object_file<PlayerBank>(filename, *this->external_bank);
|
|
||||||
player_data_log.info("Saved shared bank file %s", filename.c_str());
|
|
||||||
}
|
|
||||||
if (this->external_bank_character) {
|
|
||||||
this->save_character_file(
|
|
||||||
this->character_filename(this->external_bank_character_index),
|
|
||||||
this->system_data,
|
|
||||||
this->external_bank_character);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::save_system_file() const {
|
|
||||||
if (!this->system_data) {
|
|
||||||
throw logic_error("no system file loaded");
|
|
||||||
}
|
|
||||||
string filename = this->system_filename();
|
|
||||||
save_object_file(filename, *this->system_data);
|
|
||||||
player_data_log.info("Saved system file %s", filename.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::save_character_file(
|
|
||||||
const string& filename,
|
|
||||||
shared_ptr<const PSOBBBaseSystemFile> system,
|
|
||||||
shared_ptr<const PSOBBCharacterFile> character) {
|
|
||||||
auto f = fopen_unique(filename, "wb");
|
|
||||||
PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000};
|
|
||||||
fwritex(f.get(), header);
|
|
||||||
fwritex(f.get(), *character);
|
|
||||||
fwritex(f.get(), *system);
|
|
||||||
// TODO: Technically, we should write the actual team membership struct to the
|
|
||||||
// file here, but that would cause ClientGameData to depend on License, which
|
|
||||||
// it currently does not. This data doesn't matter at all for correctness
|
|
||||||
// within newserv, since it ignores this data entirely and instead generates
|
|
||||||
// the membership struct from the team ID in the License and the team's state.
|
|
||||||
// So, writing correct data here would mostly be for compatibility with other
|
|
||||||
// PSO servers. But if the other server is newserv, then this data would be
|
|
||||||
// used anyway, and if it's not, then it would presumably have a different set
|
|
||||||
// of teams with a different set of team IDs anyway, so the membership struct
|
|
||||||
// here would be useless either way.
|
|
||||||
static const PSOBBTeamMembership empty_membership;
|
|
||||||
fwritex(f.get(), empty_membership);
|
|
||||||
player_data_log.info("Saved character file %s", filename.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::save_character_file() {
|
|
||||||
if (!this->system_data.get()) {
|
|
||||||
throw logic_error("no system file loaded");
|
|
||||||
}
|
|
||||||
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 = now();
|
|
||||||
uint64_t seconds = (t - this->last_play_time_update) / 1000000;
|
|
||||||
this->character_data->disp.play_time += seconds;
|
|
||||||
this->character_data->play_time_seconds = this->character_data->disp.play_time;
|
|
||||||
player_data_log.info("Added %" PRIu64 " 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 ClientGameData::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();
|
|
||||||
save_object_file(filename, *this->guild_card_data);
|
|
||||||
player_data_log.info("Saved Guild Card file %s", filename.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::load_backup_character(uint32_t serial_number, size_t index) {
|
|
||||||
string filename = this->backup_character_filename(serial_number, index);
|
|
||||||
auto f = fopen_unique(filename, "rb");
|
|
||||||
auto header = freadx<PSOCommandHeaderBB>(f.get());
|
|
||||||
if (header.size != 0x399C) {
|
|
||||||
throw runtime_error("incorrect size in character file header");
|
|
||||||
}
|
|
||||||
if (header.command != 0x00E7) {
|
|
||||||
throw runtime_error("incorrect command in character file header");
|
|
||||||
}
|
|
||||||
if (header.flag != 0x00000000) {
|
|
||||||
throw runtime_error("incorrect flag in character file header");
|
|
||||||
}
|
|
||||||
this->character_data = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
|
|
||||||
this->last_reported_disp_v1_v2.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerBank& ClientGameData::current_bank() {
|
|
||||||
if (this->external_bank) {
|
|
||||||
return *this->external_bank;
|
|
||||||
} else if (this->external_bank_character) {
|
|
||||||
return this->external_bank_character->bank;
|
|
||||||
}
|
|
||||||
return this->character()->bank;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<PSOBBCharacterFile> ClientGameData::current_bank_character() {
|
|
||||||
return this->external_bank_character ? this->external_bank_character : this->character();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::use_default_bank() {
|
|
||||||
if (this->external_bank) {
|
|
||||||
string filename = this->shared_bank_filename();
|
|
||||||
save_object_file<PlayerBank>(filename, *this->external_bank);
|
|
||||||
this->external_bank.reset();
|
|
||||||
player_data_log.info("Detached shared bank %s", filename.c_str());
|
|
||||||
}
|
|
||||||
if (this->external_bank_character) {
|
|
||||||
string filename = this->character_filename(this->external_bank_character_index);
|
|
||||||
this->save_character_file(filename, this->system_data, this->external_bank_character);
|
|
||||||
this->external_bank_character.reset();
|
|
||||||
player_data_log.info("Detached character %s from bank", filename.c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ClientGameData::use_shared_bank() {
|
|
||||||
this->use_default_bank();
|
|
||||||
string filename = this->shared_bank_filename();
|
|
||||||
if (isfile(filename)) {
|
|
||||||
this->external_bank = make_shared<PlayerBank>(load_object_file<PlayerBank>(filename));
|
|
||||||
player_data_log.info("Loaded shared bank %s", filename.c_str());
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
this->external_bank = make_shared<PlayerBank>();
|
|
||||||
player_data_log.info("Created shared bank for %s", filename.c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientGameData::use_character_bank(int8_t index) {
|
|
||||||
this->use_default_bank();
|
|
||||||
if (index != this->bb_character_index) {
|
|
||||||
string filename = this->character_filename(index);
|
|
||||||
this->external_bank_character = this->files_manager->get_character(filename);
|
|
||||||
if (this->external_bank_character) {
|
|
||||||
this->external_bank_character_index = index;
|
|
||||||
player_data_log.info("Using loaded character file %s for external bank", filename.c_str());
|
|
||||||
} else if (isfile(filename)) {
|
|
||||||
auto f = fopen_unique(filename, "rb");
|
|
||||||
auto header = freadx<PSOCommandHeaderBB>(f.get());
|
|
||||||
if (header.size != 0x399C) {
|
|
||||||
throw runtime_error("incorrect size in character file header");
|
|
||||||
}
|
|
||||||
if (header.command != 0x00E7) {
|
|
||||||
throw runtime_error("incorrect command in character file header");
|
|
||||||
}
|
|
||||||
if (header.flag != 0x00000000) {
|
|
||||||
throw runtime_error("incorrect flag in character file header");
|
|
||||||
}
|
|
||||||
this->external_bank_character = make_shared<PSOBBCharacterFile>(freadx<PSOBBCharacterFile>(f.get()));
|
|
||||||
this->external_bank_character_index = index;
|
|
||||||
this->files_manager->set_character(filename, this->external_bank_character);
|
|
||||||
player_data_log.info("Loaded character data from %s for external bank", filename.c_str());
|
|
||||||
} else {
|
|
||||||
throw runtime_error("character does not exist");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-162
@@ -1,162 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <event2/event.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#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"
|
|
||||||
|
|
||||||
struct PendingItemTrade {
|
|
||||||
uint8_t other_client_id;
|
|
||||||
bool confirmed; // true if client has sent a D2 command
|
|
||||||
std::vector<ItemData> items;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PendingCardTrade {
|
|
||||||
uint8_t other_client_id;
|
|
||||||
bool confirmed; // true if client has sent an EE D2 command
|
|
||||||
std::vector<std::pair<uint32_t, uint32_t>> card_to_count;
|
|
||||||
};
|
|
||||||
|
|
||||||
class PlayerFilesManager {
|
|
||||||
public:
|
|
||||||
explicit PlayerFilesManager(std::shared_ptr<struct event_base> base);
|
|
||||||
~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<PlayerBank> get_bank(const std::string& filename);
|
|
||||||
|
|
||||||
void set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file);
|
|
||||||
void set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file);
|
|
||||||
void set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file);
|
|
||||||
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank> file);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::shared_ptr<struct event_base> base;
|
|
||||||
std::unique_ptr<struct event, void (*)(struct event*)> clear_expired_files_event;
|
|
||||||
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<PSOBBBaseSystemFile>> loaded_system_files;
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<PSOBBCharacterFile>> loaded_character_files;
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<PSOBBGuildCardFile>> loaded_guild_card_files;
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<PlayerBank>> loaded_bank_files;
|
|
||||||
|
|
||||||
static void clear_expired_files(evutil_socket_t fd, short events, void* ctx);
|
|
||||||
};
|
|
||||||
|
|
||||||
class ClientGameData {
|
|
||||||
public:
|
|
||||||
uint32_t guild_card_number;
|
|
||||||
bool should_update_play_time;
|
|
||||||
|
|
||||||
// The following fields are not saved, and are only used in certain situations
|
|
||||||
|
|
||||||
std::array<uint32_t, 30> blocked_senders;
|
|
||||||
|
|
||||||
// This is only used if the client is v1 or v2
|
|
||||||
std::unique_ptr<PlayerDispDataDCPCV3> last_reported_disp_v1_v2;
|
|
||||||
|
|
||||||
// Null unless the client is within the trade sequence (D0-D4 or EE commands)
|
|
||||||
std::unique_ptr<PendingItemTrade> pending_item_trade;
|
|
||||||
std::unique_ptr<PendingCardTrade> pending_card_trade;
|
|
||||||
|
|
||||||
// Null unless the client is Episode 3 and has sent its config already
|
|
||||||
std::shared_ptr<Episode3::PlayerConfig> ep3_config;
|
|
||||||
|
|
||||||
// These are only used if the client is BB
|
|
||||||
int8_t bb_character_index;
|
|
||||||
ItemData identify_result;
|
|
||||||
std::array<std::vector<ItemData>, 3> shop_contents;
|
|
||||||
|
|
||||||
explicit ClientGameData(std::shared_ptr<PlayerFilesManager> files_manager);
|
|
||||||
~ClientGameData();
|
|
||||||
|
|
||||||
const std::string& get_bb_username() const;
|
|
||||||
void set_bb_username(const std::string& bb_username);
|
|
||||||
|
|
||||||
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
|
|
||||||
void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table);
|
|
||||||
inline void delete_overlay() {
|
|
||||||
this->overlay_character_data.reset();
|
|
||||||
}
|
|
||||||
inline bool has_overlay() const {
|
|
||||||
return this->overlay_character_data.get() != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<PSOBBBaseSystemFile> system(bool allow_load = true);
|
|
||||||
std::shared_ptr<const PSOBBBaseSystemFile> system(bool allow_load = true) const;
|
|
||||||
|
|
||||||
std::shared_ptr<PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true);
|
|
||||||
std::shared_ptr<const PSOBBCharacterFile> character(bool allow_load = true, bool allow_overlay = true) const;
|
|
||||||
|
|
||||||
std::shared_ptr<PSOBBGuildCardFile> guild_cards(bool allow_load = true);
|
|
||||||
std::shared_ptr<const PSOBBGuildCardFile> guild_cards(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, int8_t index);
|
|
||||||
static std::string backup_character_filename(uint32_t serial_number, size_t index);
|
|
||||||
std::string character_filename(int8_t index = -1) const;
|
|
||||||
std::string guild_card_filename() const;
|
|
||||||
std::string shared_bank_filename() const;
|
|
||||||
|
|
||||||
std::string legacy_player_filename() const;
|
|
||||||
std::string legacy_account_filename() const;
|
|
||||||
|
|
||||||
void save_all();
|
|
||||||
void save_system_file() const;
|
|
||||||
static void save_character_file(
|
|
||||||
const std::string& filename,
|
|
||||||
std::shared_ptr<const PSOBBBaseSystemFile> sys,
|
|
||||||
std::shared_ptr<const PSOBBCharacterFile> character);
|
|
||||||
// Note: This function is not const because it updates the player's play time.
|
|
||||||
void save_character_file();
|
|
||||||
void save_guild_card_file() const;
|
|
||||||
|
|
||||||
void load_backup_character(uint32_t serial_number, size_t index);
|
|
||||||
|
|
||||||
PlayerBank& current_bank();
|
|
||||||
std::shared_ptr<PSOBBCharacterFile> current_bank_character();
|
|
||||||
bool use_shared_bank(); // Returns true if the bank exists; false if it was created
|
|
||||||
void use_character_bank(int8_t bb_character_index);
|
|
||||||
void use_default_bank();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string bb_username;
|
|
||||||
std::shared_ptr<PlayerFilesManager> files_manager;
|
|
||||||
|
|
||||||
// The overlay character data is used in battle and challenge modes, when
|
|
||||||
// character data is temporarily replaced in-game. In other play modes and in
|
|
||||||
// lobbies, overlay_character_data is null.
|
|
||||||
std::shared_ptr<PSOBBBaseSystemFile> system_data;
|
|
||||||
std::shared_ptr<PSOBBCharacterFile> overlay_character_data;
|
|
||||||
std::shared_ptr<PSOBBCharacterFile> character_data;
|
|
||||||
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
|
|
||||||
std::shared_ptr<PlayerBank> external_bank;
|
|
||||||
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
|
|
||||||
int8_t external_bank_character_index;
|
|
||||||
uint64_t last_play_time_update;
|
|
||||||
|
|
||||||
void save_and_clear_external_bank();
|
|
||||||
|
|
||||||
void load_all_files();
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#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<struct event_base> base)
|
||||||
|
: base(base),
|
||||||
|
clear_expired_files_event(
|
||||||
|
event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this),
|
||||||
|
event_free) {
|
||||||
|
auto tv = usecs_to_timeval(30 * 1000 * 1000);
|
||||||
|
event_add(this->clear_expired_files_event.get(), &tv);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<PlayerBank> PlayerFilesManager::get_bank(const std::string& filename) {
|
||||||
|
try {
|
||||||
|
return this->loaded_bank_files.at(filename);
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file) {
|
||||||
|
if (!this->loaded_system_files.emplace(filename, file).second) {
|
||||||
|
throw runtime_error("Guild Card file already loaded: " + 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<PlayerBank> file) {
|
||||||
|
if (!this->loaded_bank_files.emplace(filename, file).second) {
|
||||||
|
throw runtime_error("bank file already loaded: " + filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) {
|
||||||
|
auto* self = reinterpret_cast<PlayerFilesManager*>(ctx);
|
||||||
|
size_t num_deleted = erase_unused(self->loaded_system_files);
|
||||||
|
if (num_deleted) {
|
||||||
|
player_data_log.info("Cleared %zu expired system file(s)", num_deleted);
|
||||||
|
}
|
||||||
|
num_deleted = erase_unused(self->loaded_character_files);
|
||||||
|
if (num_deleted) {
|
||||||
|
player_data_log.info("Cleared %zu expired character file(s)", num_deleted);
|
||||||
|
}
|
||||||
|
num_deleted = erase_unused(self->loaded_guild_card_files);
|
||||||
|
if (num_deleted) {
|
||||||
|
player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted);
|
||||||
|
}
|
||||||
|
num_deleted = erase_unused(self->loaded_bank_files);
|
||||||
|
if (num_deleted) {
|
||||||
|
player_data_log.info("Cleared %zu expired bank file(s)", num_deleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <event2/event.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#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<struct event_base> base);
|
||||||
|
~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<PlayerBank> get_bank(const std::string& filename);
|
||||||
|
|
||||||
|
void set_system(const std::string& filename, std::shared_ptr<PSOBBBaseSystemFile> file);
|
||||||
|
void set_character(const std::string& filename, std::shared_ptr<PSOBBCharacterFile> file);
|
||||||
|
void set_guild_card(const std::string& filename, std::shared_ptr<PSOBBGuildCardFile> file);
|
||||||
|
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank> file);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<struct event_base> base;
|
||||||
|
std::unique_ptr<struct event, void (*)(struct event*)> clear_expired_files_event;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<PSOBBBaseSystemFile>> loaded_system_files;
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<PSOBBCharacterFile>> loaded_character_files;
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<PSOBBGuildCardFile>> loaded_guild_card_files;
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<PlayerBank>> loaded_bank_files;
|
||||||
|
|
||||||
|
static void clear_expired_files(evutil_socket_t fd, short events, void* ctx);
|
||||||
|
};
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
#include "Version.hh"
|
#include "Version.hh"
|
||||||
|
|
||||||
struct Client;
|
class Client;
|
||||||
class ItemParameterTable;
|
class ItemParameterTable;
|
||||||
|
|
||||||
// PSO V2 stored some extra data in the character structs in a format that I'm
|
// PSO V2 stored some extra data in the character structs in a format that I'm
|
||||||
|
|||||||
+104
-123
@@ -295,7 +295,7 @@ void on_login_complete(shared_ptr<Client> c) {
|
|||||||
if (c->version() == Version::BB_V4) {
|
if (c->version() == Version::BB_V4) {
|
||||||
// This implicitly loads the client's account and player data
|
// This implicitly loads the client's account and player data
|
||||||
send_complete_player_bb(c);
|
send_complete_player_bb(c);
|
||||||
c->game_data.should_update_play_time = true;
|
c->should_update_play_time = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_ep3(c->version())) {
|
if (is_ep3(c->version())) {
|
||||||
@@ -1040,7 +1040,7 @@ static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
}
|
}
|
||||||
c->channel.language = c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB) ? 1 : cmd.language;
|
c->channel.language = c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB) ? 1 : cmd.language;
|
||||||
c->bb_connection_phase = cmd.connection_phase;
|
c->bb_connection_phase = cmd.connection_phase;
|
||||||
c->game_data.bb_character_index = cmd.character_slot;
|
c->bb_character_index = cmd.character_slot;
|
||||||
|
|
||||||
if (cmd.menu_id == MenuID::LOBBY) {
|
if (cmd.menu_id == MenuID::LOBBY) {
|
||||||
c->preferred_lobby_id = cmd.preferred_lobby_id;
|
c->preferred_lobby_id = cmd.preferred_lobby_id;
|
||||||
@@ -1522,7 +1522,7 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
l->battle_record = make_shared<Episode3::BattleRecord>(s->ep3_behavior_flags);
|
l->battle_record = make_shared<Episode3::BattleRecord>(s->ep3_behavior_flags);
|
||||||
for (auto existing_c : l->clients) {
|
for (auto existing_c : l->clients) {
|
||||||
if (existing_c) {
|
if (existing_c) {
|
||||||
auto existing_p = existing_c->game_data.character();
|
auto existing_p = existing_c->character();
|
||||||
PlayerLobbyDataDCGC lobby_data;
|
PlayerLobbyDataDCGC lobby_data;
|
||||||
lobby_data.name.encode(existing_p->disp.name.decode(existing_c->language()), c->language());
|
lobby_data.name.encode(existing_p->disp.name.decode(existing_c->language()), c->language());
|
||||||
lobby_data.player_tag = 0x00010000;
|
lobby_data.player_tag = 0x00010000;
|
||||||
@@ -1531,7 +1531,7 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
lobby_data,
|
lobby_data,
|
||||||
existing_p->inventory,
|
existing_p->inventory,
|
||||||
existing_p->disp.to_dcpcv3(c->language(), c->language()),
|
existing_p->disp.to_dcpcv3(c->language(), c->language()),
|
||||||
c->game_data.ep3_config ? (c->game_data.ep3_config->online_clv_exp / 100) : 0);
|
c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
|
if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) {
|
||||||
@@ -1707,7 +1707,7 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
for (size_t x = 0; x < game->max_clients; x++) {
|
for (size_t x = 0; x < game->max_clients; x++) {
|
||||||
const auto& game_c = game->clients[x];
|
const auto& game_c = game->clients[x];
|
||||||
if (game_c.get()) {
|
if (game_c.get()) {
|
||||||
auto player = game_c->game_data.character();
|
auto player = game_c->character();
|
||||||
string name = player->disp.name.decode(game_c->language());
|
string name = player->disp.name.decode(game_c->language());
|
||||||
if (game->is_ep3()) {
|
if (game->is_ep3()) {
|
||||||
info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n",
|
info += string_printf("%zu: $C6%s$C7 L%" PRIu32 "\n",
|
||||||
@@ -1892,12 +1892,12 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
|
|||||||
// data. On BB, this is instead done in the 6xCF handler (for battle) or
|
// data. On BB, this is instead done in the 6xCF handler (for battle) or
|
||||||
// the 02DF handler (for challenge).
|
// the 02DF handler (for challenge).
|
||||||
if (l->base_version != Version::BB_V4) {
|
if (l->base_version != Version::BB_V4) {
|
||||||
lc->game_data.delete_overlay();
|
lc->delete_overlay();
|
||||||
if (l->quest->battle_rules) {
|
if (l->quest->battle_rules) {
|
||||||
lc->game_data.create_battle_overlay(l->quest->battle_rules, s->level_table);
|
lc->create_battle_overlay(l->quest->battle_rules, s->level_table);
|
||||||
lc->log.info("Created battle overlay");
|
lc->log.info("Created battle overlay");
|
||||||
} else if (l->quest->challenge_template_index >= 0) {
|
} else if (l->quest->challenge_template_index >= 0) {
|
||||||
lc->game_data.create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table);
|
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table);
|
||||||
lc->log.info("Created challenge overlay");
|
lc->log.info("Created challenge overlay");
|
||||||
l->assign_inventory_and_bank_item_ids(lc);
|
l->assign_inventory_and_bank_item_ids(lc);
|
||||||
}
|
}
|
||||||
@@ -2044,7 +2044,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case MainMenuItemID::PROXY_DESTINATIONS:
|
case MainMenuItemID::PROXY_DESTINATIONS:
|
||||||
if (!c->game_data.character(false, false)) {
|
if (!c->character(false, false)) {
|
||||||
send_get_player_info(c);
|
send_get_player_info(c);
|
||||||
}
|
}
|
||||||
send_proxy_destinations_menu(c);
|
send_proxy_destinations_menu(c);
|
||||||
@@ -2278,7 +2278,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
send_lobby_message_box(c, "$C6Incorrect password.");
|
send_lobby_message_box(c, "$C6Incorrect password.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
if (p->disp.stats.level < game->min_level) {
|
if (p->disp.stats.level < game->min_level) {
|
||||||
send_lobby_message_box(c, "$C6Your level is too\nlow to join this\ngame.");
|
send_lobby_message_box(c, "$C6Your level is too\nlow to join this\ngame.");
|
||||||
break;
|
break;
|
||||||
@@ -2464,7 +2464,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (team_name.empty()) {
|
if (team_name.empty()) {
|
||||||
team_name = c->game_data.character()->disp.name.decode(c->language());
|
team_name = c->character()->disp.name.decode(c->language());
|
||||||
team_name += string_printf("/%" PRIX32, c->license->serial_number);
|
team_name += string_printf("/%" PRIX32, c->license->serial_number);
|
||||||
}
|
}
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
@@ -2794,21 +2794,21 @@ static void on_13_A7_V3_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag
|
|||||||
|
|
||||||
static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, string& data) {
|
static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, string& data) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto player = c->game_data.character();
|
auto player = c->character();
|
||||||
|
|
||||||
switch (c->version()) {
|
switch (c->version()) {
|
||||||
case Version::DC_NTE:
|
case Version::DC_NTE:
|
||||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||||
case Version::DC_V1: {
|
case Version::DC_V1: {
|
||||||
const auto& cmd = check_size_t<C_CharacterData_DCv1_61_98>(data);
|
const auto& cmd = check_size_t<C_CharacterData_DCv1_61_98>(data);
|
||||||
c->game_data.last_reported_disp_v1_v2 = make_unique<PlayerDispDataDCPCV3>(cmd.disp);
|
c->v1_v2_last_reported_disp = make_unique<PlayerDispDataDCPCV3>(cmd.disp);
|
||||||
player->inventory = cmd.inventory;
|
player->inventory = cmd.inventory;
|
||||||
player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language);
|
player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Version::DC_V2: {
|
case Version::DC_V2: {
|
||||||
const auto& cmd = check_size_t<C_CharacterData_DCv2_61_98>(data, 0xFFFF);
|
const auto& cmd = check_size_t<C_CharacterData_DCv2_61_98>(data, 0xFFFF);
|
||||||
c->game_data.last_reported_disp_v1_v2 = make_unique<PlayerDispDataDCPCV3>(cmd.disp);
|
c->v1_v2_last_reported_disp = make_unique<PlayerDispDataDCPCV3>(cmd.disp);
|
||||||
player->inventory = cmd.inventory;
|
player->inventory = cmd.inventory;
|
||||||
player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language);
|
player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language);
|
||||||
player->battle_records = cmd.records.battle;
|
player->battle_records = cmd.records.battle;
|
||||||
@@ -2818,15 +2818,13 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
}
|
}
|
||||||
case Version::PC_V2: {
|
case Version::PC_V2: {
|
||||||
const auto& cmd = check_size_t<C_CharacterData_PC_61_98>(data, 0xFFFF);
|
const auto& cmd = check_size_t<C_CharacterData_PC_61_98>(data, 0xFFFF);
|
||||||
c->game_data.last_reported_disp_v1_v2 = make_unique<PlayerDispDataDCPCV3>(cmd.disp);
|
c->v1_v2_last_reported_disp = make_unique<PlayerDispDataDCPCV3>(cmd.disp);
|
||||||
player->inventory = cmd.inventory;
|
player->inventory = cmd.inventory;
|
||||||
player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language);
|
player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language);
|
||||||
player->battle_records = cmd.records.battle;
|
player->battle_records = cmd.records.battle;
|
||||||
player->challenge_records = cmd.records.challenge;
|
player->challenge_records = cmd.records.challenge;
|
||||||
player->choice_search_config = cmd.choice_search_config;
|
player->choice_search_config = cmd.choice_search_config;
|
||||||
for (size_t z = 0; z < cmd.blocked_senders.size(); z++) {
|
c->import_blocked_senders(cmd.blocked_senders);
|
||||||
c->game_data.blocked_senders.at(z) = cmd.blocked_senders[z];
|
|
||||||
}
|
|
||||||
if (cmd.auto_reply_enabled) {
|
if (cmd.auto_reply_enabled) {
|
||||||
string auto_reply = data.substr(sizeof(cmd));
|
string auto_reply = data.substr(sizeof(cmd));
|
||||||
strip_trailing_zeroes(auto_reply);
|
strip_trailing_zeroes(auto_reply);
|
||||||
@@ -2841,7 +2839,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
}
|
}
|
||||||
case Version::GC_NTE: {
|
case Version::GC_NTE: {
|
||||||
const auto& cmd = check_size_t<C_CharacterData_GCNTE_61_98>(data, 0xFFFF);
|
const auto& cmd = check_size_t<C_CharacterData_GCNTE_61_98>(data, 0xFFFF);
|
||||||
c->game_data.last_reported_disp_v1_v2 = make_unique<PlayerDispDataDCPCV3>(cmd.disp);
|
c->v1_v2_last_reported_disp = make_unique<PlayerDispDataDCPCV3>(cmd.disp);
|
||||||
|
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
player->inventory = cmd.inventory;
|
player->inventory = cmd.inventory;
|
||||||
@@ -2849,9 +2847,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
player->battle_records = cmd.records.battle;
|
player->battle_records = cmd.records.battle;
|
||||||
player->challenge_records = cmd.records.challenge;
|
player->challenge_records = cmd.records.challenge;
|
||||||
player->choice_search_config = cmd.choice_search_config;
|
player->choice_search_config = cmd.choice_search_config;
|
||||||
for (size_t z = 0; z < cmd.blocked_senders.size(); z++) {
|
c->import_blocked_senders(cmd.blocked_senders);
|
||||||
c->game_data.blocked_senders.at(z) = cmd.blocked_senders[z];
|
|
||||||
}
|
|
||||||
if (cmd.auto_reply_enabled) {
|
if (cmd.auto_reply_enabled) {
|
||||||
string auto_reply = data.substr(sizeof(cmd), 0xAC);
|
string auto_reply = data.substr(sizeof(cmd), 0xAC);
|
||||||
strip_trailing_zeroes(auto_reply);
|
strip_trailing_zeroes(auto_reply);
|
||||||
@@ -2873,7 +2869,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
throw runtime_error("non-Episode 3 client sent Episode 3 player data");
|
throw runtime_error("non-Episode 3 client sent Episode 3 player data");
|
||||||
}
|
}
|
||||||
const auto* cmd3 = &check_size_t<C_CharacterData_GC_Ep3_61_98>(data);
|
const auto* cmd3 = &check_size_t<C_CharacterData_GC_Ep3_61_98>(data);
|
||||||
c->game_data.ep3_config = make_shared<Episode3::PlayerConfig>(cmd3->ep3_config);
|
c->ep3_config = make_shared<Episode3::PlayerConfig>(cmd3->ep3_config);
|
||||||
cmd = reinterpret_cast<const C_CharacterData_V3_61_98*>(cmd3);
|
cmd = reinterpret_cast<const C_CharacterData_V3_61_98*>(cmd3);
|
||||||
if (c->config.specific_version == 0x33000000) {
|
if (c->config.specific_version == 0x33000000) {
|
||||||
c->config.specific_version = 0x33534A30; // 3SJ0
|
c->config.specific_version = 0x33534A30; // 3SJ0
|
||||||
@@ -2924,9 +2920,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
player->challenge_records = cmd->records.challenge;
|
player->challenge_records = cmd->records.challenge;
|
||||||
player->choice_search_config = cmd->choice_search_config;
|
player->choice_search_config = cmd->choice_search_config;
|
||||||
player->info_board.encode(cmd->info_board.decode(player->inventory.language), player->inventory.language);
|
player->info_board.encode(cmd->info_board.decode(player->inventory.language), player->inventory.language);
|
||||||
for (size_t z = 0; z < cmd->blocked_senders.size(); z++) {
|
c->import_blocked_senders(cmd->blocked_senders);
|
||||||
c->game_data.blocked_senders.at(z) = cmd->blocked_senders[z];
|
|
||||||
}
|
|
||||||
if (cmd->auto_reply_enabled) {
|
if (cmd->auto_reply_enabled) {
|
||||||
string auto_reply = data.substr(sizeof(cmd), 0xAC);
|
string auto_reply = data.substr(sizeof(cmd), 0xAC);
|
||||||
strip_trailing_zeroes(auto_reply);
|
strip_trailing_zeroes(auto_reply);
|
||||||
@@ -2945,9 +2939,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
player->challenge_records = cmd.records.challenge;
|
player->challenge_records = cmd.records.challenge;
|
||||||
player->choice_search_config = cmd.choice_search_config;
|
player->choice_search_config = cmd.choice_search_config;
|
||||||
player->info_board = cmd.info_board;
|
player->info_board = cmd.info_board;
|
||||||
for (size_t z = 0; z < cmd.blocked_senders.size(); z++) {
|
c->import_blocked_senders(cmd.blocked_senders);
|
||||||
c->game_data.blocked_senders.at(z) = cmd.blocked_senders[z];
|
|
||||||
}
|
|
||||||
if (cmd.auto_reply_enabled) {
|
if (cmd.auto_reply_enabled) {
|
||||||
string auto_reply = data.substr(sizeof(cmd), 0xAC);
|
string auto_reply = data.substr(sizeof(cmd), 0xAC);
|
||||||
strip_trailing_zeroes(auto_reply);
|
strip_trailing_zeroes(auto_reply);
|
||||||
@@ -2976,7 +2968,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
// in no lobby (they will send an 84 soon afterward to choose a lobby).
|
// in no lobby (they will send an 84 soon afterward to choose a lobby).
|
||||||
if (command == 0x98) {
|
if (command == 0x98) {
|
||||||
// If the client had an overlay (for battle/challenge modes), delete it
|
// If the client had an overlay (for battle/challenge modes), delete it
|
||||||
c->game_data.delete_overlay();
|
c->delete_overlay();
|
||||||
|
|
||||||
s->remove_client_from_lobby(c);
|
s->remove_client_from_lobby(c);
|
||||||
|
|
||||||
@@ -2987,11 +2979,11 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
|
|
||||||
string filename;
|
string filename;
|
||||||
if (pending_export->is_bb_conversion) {
|
if (pending_export->is_bb_conversion) {
|
||||||
filename = ClientGameData::character_filename(
|
filename = Client::character_filename(
|
||||||
pending_export->license->bb_username,
|
pending_export->license->bb_username,
|
||||||
pending_export->character_index);
|
pending_export->character_index);
|
||||||
} else {
|
} else {
|
||||||
filename = ClientGameData::backup_character_filename(
|
filename = Client::backup_character_filename(
|
||||||
pending_export->license->serial_number,
|
pending_export->license->serial_number,
|
||||||
pending_export->character_index);
|
pending_export->character_index);
|
||||||
}
|
}
|
||||||
@@ -3025,7 +3017,7 @@ static void on_61_98(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
bb_player->challenge_records = player->challenge_records;
|
bb_player->challenge_records = player->challenge_records;
|
||||||
bb_player->choice_search_config = player->choice_search_config;
|
bb_player->choice_search_config = player->choice_search_config;
|
||||||
try {
|
try {
|
||||||
ClientGameData::save_character_file(filename, c->game_data.system(), bb_player);
|
Client::save_character_file(filename, c->system_file(), bb_player);
|
||||||
send_text_message(c, "$C6Character data saved");
|
send_text_message(c, "$C6Character data saved");
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what());
|
send_text_message_printf(c, "$C6Character data could\nnot be saved:\n%s", e.what());
|
||||||
@@ -3086,7 +3078,7 @@ static void on_06(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
string from_name = p->disp.name.decode(c->language());
|
string from_name = p->disp.name.decode(c->language());
|
||||||
if (from_name.size() >= 2 && from_name[0] == '\t' && (from_name[1] == 'E' || from_name[1] == 'J')) {
|
if (from_name.size() >= 2 && from_name[0] == '\t' && (from_name[1] == 'E' || from_name[1] == 'J')) {
|
||||||
from_name = from_name.substr(2);
|
from_name = from_name.substr(2);
|
||||||
@@ -3124,7 +3116,8 @@ static void on_E0_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
static void on_E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
static void on_E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||||
const auto& cmd = check_size_t<C_PlayerPreviewRequest_BB_E3>(data);
|
const auto& cmd = check_size_t<C_PlayerPreviewRequest_BB_E3>(data);
|
||||||
|
|
||||||
c->game_data.bb_character_index = cmd.character_index;
|
c->bb_character_index = cmd.character_index;
|
||||||
|
c->unload_character();
|
||||||
|
|
||||||
if (c->bb_connection_phase != 0x00) {
|
if (c->bb_connection_phase != 0x00) {
|
||||||
send_approve_player_choice_bb(c);
|
send_approve_player_choice_bb(c);
|
||||||
@@ -3136,14 +3129,8 @@ static void on_E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
|
|
||||||
ClientGameData temp_gd(s->player_files_manager);
|
|
||||||
temp_gd.guild_card_number = c->license->serial_number;
|
|
||||||
temp_gd.set_bb_username(c->license->bb_username);
|
|
||||||
temp_gd.bb_character_index = cmd.character_index;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto preview = temp_gd.character()->disp.to_preview();
|
auto preview = c->character()->disp.to_preview();
|
||||||
send_player_preview_bb(c, cmd.character_index, &preview);
|
send_player_preview_bb(c, cmd.character_index, &preview);
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
@@ -3157,7 +3144,7 @@ static void on_E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
static void on_E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
|
static void on_E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
|
||||||
constexpr size_t max_count = sizeof(PSOBBGuildCardFile::entries) / sizeof(PSOBBGuildCardFile::Entry);
|
constexpr size_t max_count = sizeof(PSOBBGuildCardFile::entries) / sizeof(PSOBBGuildCardFile::Entry);
|
||||||
constexpr size_t max_blocked = sizeof(PSOBBGuildCardFile::blocked) / sizeof(GuildCardBB);
|
constexpr size_t max_blocked = sizeof(PSOBBGuildCardFile::blocked) / sizeof(GuildCardBB);
|
||||||
auto gcf = c->game_data.guild_cards();
|
auto gcf = c->guild_card_file();
|
||||||
bool should_save = false;
|
bool should_save = false;
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 0x01E8: { // Check guild card file checksum
|
case 0x01E8: { // Check guild card file checksum
|
||||||
@@ -3287,7 +3274,7 @@ static void on_E8_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
|
|||||||
throw invalid_argument("invalid command");
|
throw invalid_argument("invalid command");
|
||||||
}
|
}
|
||||||
if (should_save) {
|
if (should_save) {
|
||||||
c->game_data.save_guild_card_file();
|
c->save_guild_card_file();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3322,17 +3309,17 @@ static void on_E5_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c->game_data.character(false).get()) {
|
if (c->character(false).get()) {
|
||||||
throw runtime_error("player already exists");
|
throw runtime_error("player already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
c->game_data.bb_character_index = -1;
|
c->bb_character_index = -1;
|
||||||
c->game_data.system(); // Ensure system file is loaded
|
c->system_file(); // Ensure system file is loaded
|
||||||
c->game_data.bb_character_index = cmd.character_index;
|
c->bb_character_index = cmd.character_index;
|
||||||
|
|
||||||
if (c->bb_connection_phase == 0x03) { // Dressing room
|
if (c->bb_connection_phase == 0x03) { // Dressing room
|
||||||
try {
|
try {
|
||||||
c->game_data.character()->disp.apply_dressing_room(cmd.preview);
|
c->character()->disp.apply_dressing_room(cmd.preview);
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
send_message_box(c, string_printf("$C6Character could not be modified:\n%s", e.what()));
|
send_message_box(c, string_printf("$C6Character could not be modified:\n%s", e.what()));
|
||||||
return;
|
return;
|
||||||
@@ -3340,7 +3327,7 @@ static void on_E5_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
c->game_data.create_character_file(c->license->serial_number, c->language(), cmd.preview, s->level_table);
|
c->create_character_file(c->license->serial_number, c->language(), cmd.preview, s->level_table);
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
send_message_box(c, string_printf("$C6New character could not be created:\n%s", e.what()));
|
send_message_box(c, string_printf("$C6New character could not be created:\n%s", e.what()));
|
||||||
return;
|
return;
|
||||||
@@ -3351,8 +3338,8 @@ static void on_E5_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void on_ED_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
|
static void on_ED_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& data) {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto sys = c->game_data.system();
|
auto sys = c->system_file();
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 0x01ED: {
|
case 0x01ED: {
|
||||||
const auto& cmd = check_size_t<C_UpdateOptionFlags_BB_01ED>(data);
|
const auto& cmd = check_size_t<C_UpdateOptionFlags_BB_01ED>(data);
|
||||||
@@ -3372,13 +3359,13 @@ static void on_ED_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
|
|||||||
case 0x04ED: {
|
case 0x04ED: {
|
||||||
const auto& cmd = check_size_t<C_UpdateKeyConfig_BB_04ED>(data);
|
const auto& cmd = check_size_t<C_UpdateKeyConfig_BB_04ED>(data);
|
||||||
sys->key_config = cmd.key_config;
|
sys->key_config = cmd.key_config;
|
||||||
c->game_data.save_system_file();
|
c->save_system_file();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x05ED: {
|
case 0x05ED: {
|
||||||
const auto& cmd = check_size_t<C_UpdatePadConfig_BB_05ED>(data);
|
const auto& cmd = check_size_t<C_UpdatePadConfig_BB_05ED>(data);
|
||||||
sys->joystick_config = cmd.pad_config;
|
sys->joystick_config = cmd.pad_config;
|
||||||
c->game_data.save_system_file();
|
c->save_system_file();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x06ED: {
|
case 0x06ED: {
|
||||||
@@ -3407,18 +3394,18 @@ static void on_E7_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
// TODO: In the future, we shouldn't need to trust any of the client's data
|
// 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
|
// here. We should instead verify our copy of the player against what the
|
||||||
// client sent, and alert on anything that's out of sync.
|
// client sent, and alert on anything that's out of sync.
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
p->challenge_records = cmd.char_file.challenge_records;
|
p->challenge_records = cmd.char_file.challenge_records;
|
||||||
p->battle_records = cmd.char_file.battle_records;
|
p->battle_records = cmd.char_file.battle_records;
|
||||||
p->death_count = cmd.char_file.death_count;
|
p->death_count = cmd.char_file.death_count;
|
||||||
*c->game_data.system() = cmd.system_file.base;
|
*c->system_file() = cmd.system_file.base;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_E2_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
static void on_E2_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||||
auto& cmd = check_size_t<PSOBBFullSystemFile>(data);
|
auto& cmd = check_size_t<PSOBBFullSystemFile>(data);
|
||||||
auto sys = c->game_data.system();
|
auto sys = c->system_file();
|
||||||
*sys = cmd.base;
|
*sys = cmd.base;
|
||||||
c->game_data.save_system_file();
|
c->save_system_file();
|
||||||
|
|
||||||
S_SystemFileCreated_00E1_BB out_cmd = {1};
|
S_SystemFileCreated_00E1_BB out_cmd = {1};
|
||||||
send_command_t(c, 0x00E1, 0x00000000, out_cmd);
|
send_command_t(c, 0x00E1, 0x00000000, out_cmd);
|
||||||
@@ -3453,7 +3440,7 @@ static void on_DF_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
|
|||||||
}
|
}
|
||||||
for (auto lc : l->clients) {
|
for (auto lc : l->clients) {
|
||||||
if (lc) {
|
if (lc) {
|
||||||
lc->game_data.create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table);
|
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table);
|
||||||
lc->log.info("Created challenge overlay");
|
lc->log.info("Created challenge overlay");
|
||||||
l->assign_inventory_and_bank_item_ids(lc);
|
l->assign_inventory_and_bank_item_ids(lc);
|
||||||
}
|
}
|
||||||
@@ -3499,7 +3486,7 @@ static void on_DF_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
|
|||||||
|
|
||||||
case 0x07DF: {
|
case 0x07DF: {
|
||||||
const auto& cmd = check_size_t<C_CreateChallengeModeAwardItem_BB_07DF>(data);
|
const auto& cmd = check_size_t<C_CreateChallengeModeAwardItem_BB_07DF>(data);
|
||||||
auto p = c->game_data.character(true, false);
|
auto p = c->character(true, false);
|
||||||
auto& award_state = (l->episode == Episode::EP2)
|
auto& award_state = (l->episode == Episode::EP2)
|
||||||
? p->challenge_records.ep2_online_award_state
|
? p->challenge_records.ep2_online_award_state
|
||||||
: p->challenge_records.ep1_online_award_state;
|
: p->challenge_records.ep1_online_award_state;
|
||||||
@@ -3541,7 +3528,7 @@ static void on_C0(shared_ptr<Client> c, uint16_t, uint32_t, string&) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void on_C2(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
static void on_C2(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||||
c->game_data.character()->choice_search_config = check_size_t<ChoiceSearchConfig>(data);
|
c->character()->choice_search_config = check_size_t<ChoiceSearchConfig>(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename ResultT>
|
template <typename ResultT>
|
||||||
@@ -3551,7 +3538,7 @@ static void on_choice_search_t(shared_ptr<Client> c, const ChoiceSearchConfig& c
|
|||||||
vector<ResultT> results;
|
vector<ResultT> results;
|
||||||
for (const auto& l : s->all_lobbies()) {
|
for (const auto& l : s->all_lobbies()) {
|
||||||
for (const auto& lc : l->clients) {
|
for (const auto& lc : l->clients) {
|
||||||
if (!lc || lc->game_data.character()->choice_search_config.disabled) {
|
if (!lc || lc->character()->choice_search_config.disabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3572,7 +3559,7 @@ static void on_choice_search_t(shared_ptr<Client> c, const ChoiceSearchConfig& c
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_match) {
|
if (is_match) {
|
||||||
auto lp = lc->game_data.character();
|
auto lp = lc->character();
|
||||||
auto& result = results.emplace_back();
|
auto& result = results.emplace_back();
|
||||||
result.guild_card_number = lc->license->serial_number;
|
result.guild_card_number = lc->license->serial_number;
|
||||||
result.name.encode(lp->disp.name.decode(lc->language()), c->language());
|
result.name.encode(lp->disp.name.decode(lc->language()), c->language());
|
||||||
@@ -3680,15 +3667,13 @@ static void on_81(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// If the sender is blocked, don't forward the mail
|
// If the sender is blocked, don't forward the mail
|
||||||
for (size_t y = 0; y < 30; y++) {
|
if (target->blocked_senders.count(c->license->serial_number)) {
|
||||||
if (target->game_data.blocked_senders.data()[y] == c->license->serial_number) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the target has auto-reply enabled, send the autoreply. Note that we also
|
// If the target has auto-reply enabled, send the autoreply. Note that we also
|
||||||
// forward the message in this case.
|
// forward the message in this case.
|
||||||
auto target_p = target->game_data.character();
|
auto target_p = target->character();
|
||||||
if (!target_p->auto_reply.empty()) {
|
if (!target_p->auto_reply.empty()) {
|
||||||
send_simple_mail(
|
send_simple_mail(
|
||||||
c,
|
c,
|
||||||
@@ -3701,7 +3686,7 @@ static void on_81(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
send_simple_mail(
|
send_simple_mail(
|
||||||
target,
|
target,
|
||||||
c->license->serial_number,
|
c->license->serial_number,
|
||||||
c->game_data.character()->disp.name.decode(c->language()),
|
c->character()->disp.name.decode(c->language()),
|
||||||
message);
|
message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3717,7 +3702,7 @@ void on_D9(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
if (is_w && (data.size() & 1)) {
|
if (is_w && (data.size() & 1)) {
|
||||||
data.push_back(0);
|
data.push_back(0);
|
||||||
}
|
}
|
||||||
c->game_data.character(true, false)->info_board.encode(tt_decode_marked(data, c->language(), is_w), c->language());
|
c->character(true, false)->info_board.encode(tt_decode_marked(data, c->language(), is_w), c->language());
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_C7(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
void on_C7(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||||
@@ -3726,26 +3711,22 @@ void on_C7(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
if (is_w && (data.size() & 1)) {
|
if (is_w && (data.size() & 1)) {
|
||||||
data.push_back(0);
|
data.push_back(0);
|
||||||
}
|
}
|
||||||
c->game_data.character(true, false)->auto_reply.encode(tt_decode_marked(data, c->language(), is_w), c->language());
|
c->character(true, false)->auto_reply.encode(tt_decode_marked(data, c->language(), is_w), c->language());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_C8(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
static void on_C8(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||||
check_size_v(data.size(), 0);
|
check_size_v(data.size(), 0);
|
||||||
c->game_data.character(true, false)->auto_reply.clear();
|
c->character(true, false)->auto_reply.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_C6(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
static void on_C6(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||||
c->game_data.blocked_senders.fill(0);
|
c->blocked_senders.clear();
|
||||||
if (c->version() == Version::BB_V4) {
|
if (c->version() == Version::BB_V4) {
|
||||||
const auto& cmd = check_size_t<C_SetBlockedSenders_BB_C6>(data);
|
const auto& cmd = check_size_t<C_SetBlockedSenders_BB_C6>(data);
|
||||||
for (size_t z = 0; z < cmd.blocked_senders.size(); z++) {
|
c->import_blocked_senders(cmd.blocked_senders);
|
||||||
c->game_data.blocked_senders[z] = cmd.blocked_senders[z];
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const auto& cmd = check_size_t<C_SetBlockedSenders_V3_C6>(data);
|
const auto& cmd = check_size_t<C_SetBlockedSenders_V3_C6>(data);
|
||||||
for (size_t z = 0; z < cmd.blocked_senders.size(); z++) {
|
c->import_blocked_senders(cmd.blocked_senders);
|
||||||
c->game_data.blocked_senders[z] = cmd.blocked_senders[z];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3805,7 +3786,7 @@ shared_ptr<Lobby> create_game_generic(
|
|||||||
throw runtime_error("invalid episode");
|
throw runtime_error("invalid episode");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
if (!(c->license->flags & License::Flag::FREE_JOIN_GAMES) &&
|
if (!(c->license->flags & License::Flag::FREE_JOIN_GAMES) &&
|
||||||
(min_level > p->disp.stats.level)) {
|
(min_level > p->disp.stats.level)) {
|
||||||
// Note: We don't throw here because this is a situation players might
|
// Note: We don't throw here because this is a situation players might
|
||||||
@@ -4311,7 +4292,7 @@ static void on_99(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
static void on_D0_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
static void on_D0_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||||
const auto& cmd = check_size_t<SC_TradeItems_D0_D3>(data);
|
const auto& cmd = check_size_t<SC_TradeItems_D0_D3>(data);
|
||||||
|
|
||||||
if (c->game_data.pending_item_trade) {
|
if (c->pending_item_trade) {
|
||||||
throw runtime_error("player started a trade when one is already pending");
|
throw runtime_error("player started a trade when one is already pending");
|
||||||
}
|
}
|
||||||
if (cmd.item_count > 0x20) {
|
if (cmd.item_count > 0x20) {
|
||||||
@@ -4327,10 +4308,10 @@ static void on_D0_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
|
|||||||
throw runtime_error("trade command sent to missing player");
|
throw runtime_error("trade command sent to missing player");
|
||||||
}
|
}
|
||||||
|
|
||||||
c->game_data.pending_item_trade = make_unique<PendingItemTrade>();
|
c->pending_item_trade = make_unique<Client::PendingItemTrade>();
|
||||||
c->game_data.pending_item_trade->other_client_id = cmd.target_client_id;
|
c->pending_item_trade->other_client_id = cmd.target_client_id;
|
||||||
for (size_t x = 0; x < cmd.item_count; x++) {
|
for (size_t x = 0; x < cmd.item_count; x++) {
|
||||||
auto& item = c->game_data.pending_item_trade->items.emplace_back(cmd.item_datas[x]);
|
auto& item = c->pending_item_trade->items.emplace_back(cmd.item_datas[x]);
|
||||||
item.decode_for_version(c->version());
|
item.decode_for_version(c->version());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4343,7 +4324,7 @@ static void on_D0_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
|
|||||||
// See the description of the D0 command in CommandFormats.hh for more
|
// See the description of the D0 command in CommandFormats.hh for more
|
||||||
// information on how this sequence is supposed to work.
|
// information on how this sequence is supposed to work.
|
||||||
send_command(target_c, 0xD1, 0x00);
|
send_command(target_c, 0xD1, 0x00);
|
||||||
if (target_c->game_data.pending_item_trade) {
|
if (target_c->pending_item_trade) {
|
||||||
send_command(c, 0xD1, 0x00);
|
send_command(c, 0xD1, 0x00);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4351,7 +4332,7 @@ static void on_D0_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
|
|||||||
static void on_D2_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
static void on_D2_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||||
check_size_v(data.size(), 0);
|
check_size_v(data.size(), 0);
|
||||||
|
|
||||||
if (!c->game_data.pending_item_trade) {
|
if (!c->pending_item_trade) {
|
||||||
throw runtime_error("player executed a trade with none pending");
|
throw runtime_error("player executed a trade with none pending");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4359,11 +4340,11 @@ static void on_D2_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
|
|||||||
if (!l->is_game()) {
|
if (!l->is_game()) {
|
||||||
throw runtime_error("trade command received in non-game lobby");
|
throw runtime_error("trade command received in non-game lobby");
|
||||||
}
|
}
|
||||||
auto target_c = l->clients.at(c->game_data.pending_item_trade->other_client_id);
|
auto target_c = l->clients.at(c->pending_item_trade->other_client_id);
|
||||||
if (!target_c) {
|
if (!target_c) {
|
||||||
throw runtime_error("target player is missing");
|
throw runtime_error("target player is missing");
|
||||||
}
|
}
|
||||||
if (!target_c->game_data.pending_item_trade) {
|
if (!target_c->pending_item_trade) {
|
||||||
throw runtime_error("player executed a trade with no other side pending");
|
throw runtime_error("player executed a trade with no other side pending");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4371,9 +4352,9 @@ static void on_D2_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
|
|||||||
if (c->version() == Version::BB_V4) {
|
if (c->version() == Version::BB_V4) {
|
||||||
// On BB, the server is expected to generate the delete item and create
|
// On BB, the server is expected to generate the delete item and create
|
||||||
// item commands
|
// item commands
|
||||||
auto to_p = to_c->game_data.character();
|
auto to_p = to_c->character();
|
||||||
auto from_p = from_c->game_data.character();
|
auto from_p = from_c->character();
|
||||||
for (const auto& trade_item : from_c->game_data.pending_item_trade->items) {
|
for (const auto& trade_item : from_c->pending_item_trade->items) {
|
||||||
size_t amount = trade_item.stack_size();
|
size_t amount = trade_item.stack_size();
|
||||||
|
|
||||||
auto item = from_p->remove_item(trade_item.id, amount, false);
|
auto item = from_p->remove_item(trade_item.id, amount, false);
|
||||||
@@ -4396,18 +4377,18 @@ static void on_D2_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
|
|||||||
} else {
|
} else {
|
||||||
// On V3, the clients will handle it; we just send their final trade lists
|
// On V3, the clients will handle it; we just send their final trade lists
|
||||||
// to each other
|
// to each other
|
||||||
send_execute_item_trade(to_c, target_c->game_data.pending_item_trade->items);
|
send_execute_item_trade(to_c, target_c->pending_item_trade->items);
|
||||||
}
|
}
|
||||||
|
|
||||||
send_command(to_c, 0xD4, 0x01);
|
send_command(to_c, 0xD4, 0x01);
|
||||||
};
|
};
|
||||||
|
|
||||||
c->game_data.pending_item_trade->confirmed = true;
|
c->pending_item_trade->confirmed = true;
|
||||||
if (target_c->game_data.pending_item_trade->confirmed) {
|
if (target_c->pending_item_trade->confirmed) {
|
||||||
complete_trade_for_side(c, target_c);
|
complete_trade_for_side(c, target_c);
|
||||||
complete_trade_for_side(target_c, c);
|
complete_trade_for_side(target_c, c);
|
||||||
c->game_data.pending_item_trade.reset();
|
c->pending_item_trade.reset();
|
||||||
target_c->game_data.pending_item_trade.reset();
|
target_c->pending_item_trade.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4418,11 +4399,11 @@ static void on_D4_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
|
|||||||
// trade sequence, the client can get into a state where it sends this command
|
// trade sequence, the client can get into a state where it sends this command
|
||||||
// many times in a row. To deal with this, we just do nothing if the client
|
// many times in a row. To deal with this, we just do nothing if the client
|
||||||
// has no trade pending.
|
// has no trade pending.
|
||||||
if (!c->game_data.pending_item_trade) {
|
if (!c->pending_item_trade) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint8_t other_client_id = c->game_data.pending_item_trade->other_client_id;
|
uint8_t other_client_id = c->pending_item_trade->other_client_id;
|
||||||
c->game_data.pending_item_trade.reset();
|
c->pending_item_trade.reset();
|
||||||
send_command(c, 0xD4, 0x00);
|
send_command(c, 0xD4, 0x00);
|
||||||
|
|
||||||
// Cancel the other side of the trade too, if it's open
|
// Cancel the other side of the trade too, if it's open
|
||||||
@@ -4434,10 +4415,10 @@ static void on_D4_V3_BB(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
|
|||||||
if (!target_c) {
|
if (!target_c) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!target_c->game_data.pending_item_trade) {
|
if (!target_c->pending_item_trade) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
target_c->game_data.pending_item_trade.reset();
|
target_c->pending_item_trade.reset();
|
||||||
send_command(target_c, 0xD4, 0x00);
|
send_command(target_c, 0xD4, 0x00);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4453,7 +4434,7 @@ static void on_EE_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& dat
|
|||||||
if (flag == 0xD0) {
|
if (flag == 0xD0) {
|
||||||
auto& cmd = check_size_t<SC_TradeCards_GC_Ep3_EE_FlagD0_FlagD3>(data);
|
auto& cmd = check_size_t<SC_TradeCards_GC_Ep3_EE_FlagD0_FlagD3>(data);
|
||||||
|
|
||||||
if (c->game_data.pending_card_trade) {
|
if (c->pending_card_trade) {
|
||||||
throw runtime_error("player started a card trade when one is already pending");
|
throw runtime_error("player started a card trade when one is already pending");
|
||||||
}
|
}
|
||||||
if (cmd.entry_count > 4) {
|
if (cmd.entry_count > 4) {
|
||||||
@@ -4468,10 +4449,10 @@ static void on_EE_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& dat
|
|||||||
throw runtime_error("card trade target is not Episode 3");
|
throw runtime_error("card trade target is not Episode 3");
|
||||||
}
|
}
|
||||||
|
|
||||||
c->game_data.pending_card_trade = make_unique<PendingCardTrade>();
|
c->pending_card_trade = make_unique<Client::PendingCardTrade>();
|
||||||
c->game_data.pending_card_trade->other_client_id = cmd.target_client_id;
|
c->pending_card_trade->other_client_id = cmd.target_client_id;
|
||||||
for (size_t x = 0; x < cmd.entry_count; x++) {
|
for (size_t x = 0; x < cmd.entry_count; x++) {
|
||||||
c->game_data.pending_card_trade->card_to_count.emplace_back(
|
c->pending_card_trade->card_to_count.emplace_back(
|
||||||
make_pair(cmd.entries[x].card_type, cmd.entries[x].count));
|
make_pair(cmd.entries[x].card_type, cmd.entries[x].count));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4485,45 +4466,45 @@ static void on_EE_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& dat
|
|||||||
// is analogous to Episodes 1&2's D0 command.)
|
// is analogous to Episodes 1&2's D0 command.)
|
||||||
S_AdvanceCardTradeState_GC_Ep3_EE_FlagD1 resp = {0};
|
S_AdvanceCardTradeState_GC_Ep3_EE_FlagD1 resp = {0};
|
||||||
send_command_t(target_c, 0xEE, 0xD1, resp);
|
send_command_t(target_c, 0xEE, 0xD1, resp);
|
||||||
if (target_c->game_data.pending_card_trade) {
|
if (target_c->pending_card_trade) {
|
||||||
send_command_t(c, 0xEE, 0xD1, resp);
|
send_command_t(c, 0xEE, 0xD1, resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (flag == 0xD2) {
|
} else if (flag == 0xD2) {
|
||||||
check_size_v(data.size(), 0);
|
check_size_v(data.size(), 0);
|
||||||
|
|
||||||
if (!c->game_data.pending_card_trade) {
|
if (!c->pending_card_trade) {
|
||||||
throw runtime_error("player executed a card trade with none pending");
|
throw runtime_error("player executed a card trade with none pending");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto target_c = l->clients.at(c->game_data.pending_card_trade->other_client_id);
|
auto target_c = l->clients.at(c->pending_card_trade->other_client_id);
|
||||||
if (!target_c) {
|
if (!target_c) {
|
||||||
throw runtime_error("card trade target player is missing");
|
throw runtime_error("card trade target player is missing");
|
||||||
}
|
}
|
||||||
if (!target_c->game_data.pending_card_trade) {
|
if (!target_c->pending_card_trade) {
|
||||||
throw runtime_error("player executed a card trade with no other side pending");
|
throw runtime_error("player executed a card trade with no other side pending");
|
||||||
}
|
}
|
||||||
|
|
||||||
c->game_data.pending_card_trade->confirmed = true;
|
c->pending_card_trade->confirmed = true;
|
||||||
if (target_c->game_data.pending_card_trade->confirmed) {
|
if (target_c->pending_card_trade->confirmed) {
|
||||||
send_execute_card_trade(c, target_c->game_data.pending_card_trade->card_to_count);
|
send_execute_card_trade(c, target_c->pending_card_trade->card_to_count);
|
||||||
send_execute_card_trade(target_c, c->game_data.pending_card_trade->card_to_count);
|
send_execute_card_trade(target_c, c->pending_card_trade->card_to_count);
|
||||||
S_CardTradeComplete_GC_Ep3_EE_FlagD4 resp = {1};
|
S_CardTradeComplete_GC_Ep3_EE_FlagD4 resp = {1};
|
||||||
send_command_t(c, 0xEE, 0xD4, resp);
|
send_command_t(c, 0xEE, 0xD4, resp);
|
||||||
send_command_t(target_c, 0xEE, 0xD4, resp);
|
send_command_t(target_c, 0xEE, 0xD4, resp);
|
||||||
c->game_data.pending_card_trade.reset();
|
c->pending_card_trade.reset();
|
||||||
target_c->game_data.pending_card_trade.reset();
|
target_c->pending_card_trade.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (flag == 0xD4) {
|
} else if (flag == 0xD4) {
|
||||||
check_size_v(data.size(), 0);
|
check_size_v(data.size(), 0);
|
||||||
|
|
||||||
// See the D4 handler for why this check exists (and why it doesn't throw)
|
// See the D4 handler for why this check exists (and why it doesn't throw)
|
||||||
if (!c->game_data.pending_card_trade) {
|
if (!c->pending_card_trade) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint8_t other_client_id = c->game_data.pending_card_trade->other_client_id;
|
uint8_t other_client_id = c->pending_card_trade->other_client_id;
|
||||||
c->game_data.pending_card_trade.reset();
|
c->pending_card_trade.reset();
|
||||||
S_CardTradeComplete_GC_Ep3_EE_FlagD4 resp = {0};
|
S_CardTradeComplete_GC_Ep3_EE_FlagD4 resp = {0};
|
||||||
send_command_t(c, 0xEE, 0xD4, resp);
|
send_command_t(c, 0xEE, 0xD4, resp);
|
||||||
|
|
||||||
@@ -4532,10 +4513,10 @@ static void on_EE_Ep3(shared_ptr<Client> c, uint16_t, uint32_t flag, string& dat
|
|||||||
if (!target_c) {
|
if (!target_c) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!target_c->game_data.pending_card_trade) {
|
if (!target_c->pending_card_trade) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
target_c->game_data.pending_card_trade.reset();
|
target_c->pending_card_trade.reset();
|
||||||
send_command_t(target_c, 0xEE, 0xD4, resp);
|
send_command_t(target_c, 0xEE, 0xD4, resp);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -4570,7 +4551,7 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
// TODO: What's the right error code to use here?
|
// TODO: What's the right error code to use here?
|
||||||
send_command(c, 0x02EA, 0x00000001);
|
send_command(c, 0x02EA, 0x00000001);
|
||||||
} else {
|
} else {
|
||||||
string player_name = c->game_data.character()->disp.name.decode(c->language());
|
string player_name = c->character()->disp.name.decode(c->language());
|
||||||
auto team = s->team_index->create(team_name, c->license->serial_number, player_name);
|
auto team = s->team_index->create(team_name, c->license->serial_number, player_name);
|
||||||
c->license->bb_team_id = team->team_id;
|
c->license->bb_team_id = team->team_id;
|
||||||
c->license->save();
|
c->license->save();
|
||||||
@@ -4611,7 +4592,7 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
s->team_index->add_member(
|
s->team_index->add_member(
|
||||||
team->team_id,
|
team->team_id,
|
||||||
added_c->license->serial_number,
|
added_c->license->serial_number,
|
||||||
added_c->game_data.character()->disp.name.decode(added_c->language()));
|
added_c->character()->disp.name.decode(added_c->language()));
|
||||||
|
|
||||||
send_update_team_metadata_for_client(added_c);
|
send_update_team_metadata_for_client(added_c);
|
||||||
send_team_membership_info(added_c);
|
send_team_membership_info(added_c);
|
||||||
@@ -4811,7 +4792,7 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!reward.reward_item.empty()) {
|
if (!reward.reward_item.empty()) {
|
||||||
c->game_data.current_bank().add_item(reward.reward_item);
|
c->current_bank().add_item(reward.reward_item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
+68
-69
@@ -13,7 +13,6 @@
|
|||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
#include "Map.hh"
|
#include "Map.hh"
|
||||||
#include "PSOProtocol.hh"
|
#include "PSOProtocol.hh"
|
||||||
#include "Player.hh"
|
|
||||||
#include "SendCommands.hh"
|
#include "SendCommands.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
@@ -324,7 +323,7 @@ static void on_sync_joining_player_item_state(shared_ptr<Client> c, uint8_t comm
|
|||||||
}
|
}
|
||||||
auto* floor_items = reinterpret_cast<FloorItem*>(decompressed.data() + sizeof(G_SyncItemState_6x6D_Decompressed));
|
auto* floor_items = reinterpret_cast<FloorItem*>(decompressed.data() + sizeof(G_SyncItemState_6x6D_Decompressed));
|
||||||
|
|
||||||
size_t target_num_items = target->game_data.character()->inventory.num_items;
|
size_t target_num_items = target->character()->inventory.num_items;
|
||||||
for (size_t z = 0; z < 12; z++) {
|
for (size_t z = 0; z < 12; z++) {
|
||||||
uint32_t client_next_id = decompressed_cmd->next_item_id_per_player[z];
|
uint32_t client_next_id = decompressed_cmd->next_item_id_per_player[z];
|
||||||
uint32_t server_next_id = l->next_item_id[z];
|
uint32_t server_next_id = l->next_item_id[z];
|
||||||
@@ -549,12 +548,12 @@ static void on_send_guild_card(shared_ptr<Client> c, uint8_t command, uint8_t fl
|
|||||||
case Version::DC_V1:
|
case Version::DC_V1:
|
||||||
case Version::DC_V2: {
|
case Version::DC_V2: {
|
||||||
const auto& cmd = check_size_t<G_SendGuildCard_DC_6x06>(data, size);
|
const auto& cmd = check_size_t<G_SendGuildCard_DC_6x06>(data, size);
|
||||||
c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
|
c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Version::PC_V2: {
|
case Version::PC_V2: {
|
||||||
const auto& cmd = check_size_t<G_SendGuildCard_PC_6x06>(data, size);
|
const auto& cmd = check_size_t<G_SendGuildCard_PC_6x06>(data, size);
|
||||||
c->game_data.character(true, false)->guild_card.description = cmd.guild_card.description;
|
c->character(true, false)->guild_card.description = cmd.guild_card.description;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Version::GC_NTE:
|
case Version::GC_NTE:
|
||||||
@@ -562,12 +561,12 @@ static void on_send_guild_card(shared_ptr<Client> c, uint8_t command, uint8_t fl
|
|||||||
case Version::GC_EP3_TRIAL_EDITION:
|
case Version::GC_EP3_TRIAL_EDITION:
|
||||||
case Version::GC_EP3: {
|
case Version::GC_EP3: {
|
||||||
const auto& cmd = check_size_t<G_SendGuildCard_GC_6x06>(data, size);
|
const auto& cmd = check_size_t<G_SendGuildCard_GC_6x06>(data, size);
|
||||||
c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
|
c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Version::XB_V3: {
|
case Version::XB_V3: {
|
||||||
const auto& cmd = check_size_t<G_SendGuildCard_XB_6x06>(data, size);
|
const auto& cmd = check_size_t<G_SendGuildCard_XB_6x06>(data, size);
|
||||||
c->game_data.character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
|
c->character(true, false)->guild_card.description.encode(cmd.guild_card.description.decode(c->language()), c->language());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Version::BB_V4:
|
case Version::BB_V4:
|
||||||
@@ -651,7 +650,7 @@ static void on_word_select_t(shared_ptr<Client> c, uint8_t command, uint8_t, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
string name = c->game_data.character()->disp.name.decode(c->language());
|
string name = c->character()->disp.name.decode(c->language());
|
||||||
lc->log.warning("Untranslatable Word Select message: %s", e.what());
|
lc->log.warning("Untranslatable Word Select message: %s", e.what());
|
||||||
send_text_message_printf(lc, "$C4Untranslatable Word\nSelect message from\n%s", name.c_str());
|
send_text_message_printf(lc, "$C4Untranslatable Word\nSelect message from\n%s", name.c_str());
|
||||||
}
|
}
|
||||||
@@ -720,7 +719,7 @@ static void on_player_died(shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
|||||||
|
|
||||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||||
try {
|
try {
|
||||||
auto& inventory = c->game_data.character()->inventory;
|
auto& inventory = c->character()->inventory;
|
||||||
size_t mag_index = inventory.find_equipped_item(EquipSlot::MAG);
|
size_t mag_index = inventory.find_equipped_item(EquipSlot::MAG);
|
||||||
auto& data = inventory.items[mag_index].data;
|
auto& data = inventory.items[mag_index].data;
|
||||||
data.data2[0] = max<int8_t>(static_cast<int8_t>(data.data2[0] - 5), 0);
|
data.data2[0] = max<int8_t>(static_cast<int8_t>(data.data2[0] - 5), 0);
|
||||||
@@ -874,7 +873,7 @@ static void on_player_drop_item(shared_ptr<Client> c, uint8_t command, uint8_t f
|
|||||||
|
|
||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto item = p->remove_item(cmd.item_id, 0, c->version() != Version::BB_V4);
|
auto item = p->remove_item(cmd.item_id, 0, c->version() != Version::BB_V4);
|
||||||
l->add_item(item, cmd.floor, cmd.x, cmd.z);
|
l->add_item(item, cmd.floor, cmd.x, cmd.z);
|
||||||
|
|
||||||
@@ -933,7 +932,7 @@ static void on_create_inventory_item_t(shared_ptr<Client> c, uint8_t command, ui
|
|||||||
|
|
||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
ItemData item = cmd.item_data;
|
ItemData item = cmd.item_data;
|
||||||
item.decode_for_version(c->version());
|
item.decode_for_version(c->version());
|
||||||
l->on_item_id_generated_externally(item.id);
|
l->on_item_id_generated_externally(item.id);
|
||||||
@@ -992,7 +991,7 @@ static void on_drop_partial_stack_t(shared_ptr<Client> c, uint8_t command, uint8
|
|||||||
string name = s->describe_item(c->version(), item, true);
|
string name = s->describe_item(c->version(), item, true);
|
||||||
send_text_message_printf(c, "$C5SPLIT %08" PRIX32 "\n%s", item.id.load(), name.c_str());
|
send_text_message_printf(c, "$C5SPLIT %08" PRIX32 "\n%s", item.id.load(), name.c_str());
|
||||||
}
|
}
|
||||||
c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index);
|
c->character()->print_inventory(stderr, c->version(), s->item_name_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
forward_subcommand_with_item_transcode_t(c, command, flag, cmd);
|
forward_subcommand_with_item_transcode_t(c, command, flag, cmd);
|
||||||
@@ -1021,7 +1020,7 @@ static void on_drop_partial_stack_bb(shared_ptr<Client> c, uint8_t command, uint
|
|||||||
throw logic_error("item tracking not enabled in BB game");
|
throw logic_error("item tracking not enabled in BB game");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
||||||
|
|
||||||
// If a stack was split, the original item still exists, so the dropped item
|
// If a stack was split, the original item still exists, so the dropped item
|
||||||
@@ -1070,7 +1069,7 @@ static void on_buy_shop_item(shared_ptr<Client> c, uint8_t command, uint8_t flag
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
ItemData item = cmd.item_data;
|
ItemData item = cmd.item_data;
|
||||||
item.data2d = 0; // Clear the price field
|
item.data2d = 0; // Clear the price field
|
||||||
item.decode_for_version(c->version());
|
item.decode_for_version(c->version());
|
||||||
@@ -1169,7 +1168,7 @@ static void on_pick_up_item(shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
|||||||
|
|
||||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto effective_p = effective_c->game_data.character();
|
auto effective_p = effective_c->character();
|
||||||
|
|
||||||
// It seems the client just plays it fast and loose with these commands.
|
// It seems the client just plays it fast and loose with these commands.
|
||||||
// There can be multiple 6x5A (request to pick up item) commands in flight,
|
// There can be multiple 6x5A (request to pick up item) commands in flight,
|
||||||
@@ -1238,7 +1237,7 @@ static void on_pick_up_item_request(shared_ptr<Client> c, uint8_t command, uint8
|
|||||||
throw logic_error("item tracking not enabled in BB game");
|
throw logic_error("item tracking not enabled in BB game");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto item = l->remove_item(cmd.item_id);
|
auto item = l->remove_item(cmd.item_id);
|
||||||
p->add_item(item);
|
p->add_item(item);
|
||||||
|
|
||||||
@@ -1268,7 +1267,7 @@ static void on_equip_item(shared_ptr<Client> c, uint8_t command, uint8_t flag, c
|
|||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||||
EquipSlot slot = static_cast<EquipSlot>(cmd.equip_slot.load());
|
EquipSlot slot = static_cast<EquipSlot>(cmd.equip_slot.load());
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
p->inventory.equip_item_id(cmd.item_id, slot);
|
p->inventory.equip_item_id(cmd.item_id, slot);
|
||||||
c->log.info("Equipped item %08" PRIX32, cmd.item_id.load());
|
c->log.info("Equipped item %08" PRIX32, cmd.item_id.load());
|
||||||
} else if (l->base_version == Version::BB_V4) {
|
} else if (l->base_version == Version::BB_V4) {
|
||||||
@@ -1287,7 +1286,7 @@ static void on_unequip_item(shared_ptr<Client> c, uint8_t command, uint8_t flag,
|
|||||||
|
|
||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
p->inventory.unequip_item_id(cmd.item_id);
|
p->inventory.unequip_item_id(cmd.item_id);
|
||||||
c->log.info("Unequipped item %08" PRIX32, cmd.item_id.load());
|
c->log.info("Unequipped item %08" PRIX32, cmd.item_id.load());
|
||||||
} else if (l->base_version == Version::BB_V4) {
|
} else if (l->base_version == Version::BB_V4) {
|
||||||
@@ -1311,7 +1310,7 @@ static void on_use_item(
|
|||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
size_t index = p->inventory.find_item(cmd.item_id);
|
size_t index = p->inventory.find_item(cmd.item_id);
|
||||||
string name, colored_name;
|
string name, colored_name;
|
||||||
{
|
{
|
||||||
@@ -1349,7 +1348,7 @@ static void on_feed_mag(
|
|||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
|
|
||||||
size_t mag_index = p->inventory.find_item(cmd.mag_item_id);
|
size_t mag_index = p->inventory.find_item(cmd.mag_item_id);
|
||||||
size_t fed_index = p->inventory.find_item(cmd.fed_item_id);
|
size_t fed_index = p->inventory.find_item(cmd.fed_item_id);
|
||||||
@@ -1404,21 +1403,21 @@ static void on_open_shop_bb_or_ep3_battle_subs(shared_ptr<Client> c, uint8_t com
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
size_t level = c->game_data.character()->disp.stats.level + 1;
|
size_t level = c->character()->disp.stats.level + 1;
|
||||||
switch (cmd.shop_type) {
|
switch (cmd.shop_type) {
|
||||||
case 0:
|
case 0:
|
||||||
c->game_data.shop_contents[0] = l->item_creator->generate_tool_shop_contents(level);
|
c->bb_shop_contents[0] = l->item_creator->generate_tool_shop_contents(level);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
c->game_data.shop_contents[1] = l->item_creator->generate_weapon_shop_contents(level);
|
c->bb_shop_contents[1] = l->item_creator->generate_weapon_shop_contents(level);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
c->game_data.shop_contents[2] = l->item_creator->generate_armor_shop_contents(level);
|
c->bb_shop_contents[2] = l->item_creator->generate_armor_shop_contents(level);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw runtime_error("invalid shop type");
|
throw runtime_error("invalid shop type");
|
||||||
}
|
}
|
||||||
for (auto& item : c->game_data.shop_contents[cmd.shop_type]) {
|
for (auto& item : c->bb_shop_contents[cmd.shop_type]) {
|
||||||
item.id = 0xFFFFFFFF;
|
item.id = 0xFFFFFFFF;
|
||||||
item.data2d = s->item_parameter_table_for_version(c->version())->price_for_item(item);
|
item.data2d = s->item_parameter_table_for_version(c->version())->price_for_item(item);
|
||||||
}
|
}
|
||||||
@@ -1452,8 +1451,8 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint
|
|||||||
throw logic_error("item tracking not enabled in BB game");
|
throw logic_error("item tracking not enabled in BB game");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto& bank = c->game_data.current_bank();
|
auto& bank = c->current_bank();
|
||||||
if (cmd.action == 0) { // Deposit
|
if (cmd.action == 0) { // Deposit
|
||||||
if (cmd.item_id == 0xFFFFFFFF) { // Deposit Meseta
|
if (cmd.item_id == 0xFFFFFFFF) { // Deposit Meseta
|
||||||
if (cmd.meseta_amount > p->disp.stats.meseta) {
|
if (cmd.meseta_amount > p->disp.stats.meseta) {
|
||||||
@@ -1484,7 +1483,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint
|
|||||||
string name = s->item_name_index->describe_item(Version::BB_V4, item);
|
string name = s->item_name_index->describe_item(Version::BB_V4, item);
|
||||||
l->log.info("Player %hu deposited item %08" PRIX32 " (x%hhu) (%s) in the bank",
|
l->log.info("Player %hu deposited item %08" PRIX32 " (x%hhu) (%s) in the bank",
|
||||||
c->lobby_client_id, cmd.item_id.load(), cmd.item_amount, name.c_str());
|
c->lobby_client_id, cmd.item_id.load(), cmd.item_amount, name.c_str());
|
||||||
c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index);
|
c->character()->print_inventory(stderr, c->version(), s->item_name_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (cmd.action == 1) { // Take
|
} else if (cmd.action == 1) { // Take
|
||||||
@@ -1511,7 +1510,7 @@ static void on_ep3_private_word_select_bb_bank_action(shared_ptr<Client> c, uint
|
|||||||
string name = s->item_name_index->describe_item(Version::BB_V4, item);
|
string name = s->item_name_index->describe_item(Version::BB_V4, item);
|
||||||
l->log.info("Player %hu withdrew item %08" PRIX32 " (x%hhu) (%s) from the bank",
|
l->log.info("Player %hu withdrew item %08" PRIX32 " (x%hhu) (%s) from the bank",
|
||||||
c->lobby_client_id, item.id.load(), cmd.item_amount, name.c_str());
|
c->lobby_client_id, item.id.load(), cmd.item_amount, name.c_str());
|
||||||
c->game_data.character()->print_inventory(stderr, c->version(), s->item_name_index);
|
c->character()->print_inventory(stderr, c->version(), s->item_name_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (cmd.action == 3) { // Leave bank counter
|
} else if (cmd.action == 3) { // Leave bank counter
|
||||||
@@ -1532,7 +1531,7 @@ static void on_sort_inventory_bb(shared_ptr<Client> c, uint8_t, uint8_t, const v
|
|||||||
throw logic_error("item tracking not enabled in BB game");
|
throw logic_error("item tracking not enabled in BB game");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
|
|
||||||
// Make sure the set of item IDs passed in by the client exactly matches the
|
// Make sure the set of item IDs passed in by the client exactly matches the
|
||||||
// set of item IDs present in the inventory
|
// set of item IDs present in the inventory
|
||||||
@@ -1745,7 +1744,7 @@ static void on_set_quest_flag(shared_ptr<Client> c, uint8_t command, uint8_t fla
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should we allow overlays here?
|
// TODO: Should we allow overlays here?
|
||||||
auto p = c->game_data.character(true, false);
|
auto p = c->character(true, false);
|
||||||
|
|
||||||
// The client explicitly checks for both 0 and 1 - any other value means no
|
// The client explicitly checks for both 0 and 1 - any other value means no
|
||||||
// operation is performed.
|
// operation is performed.
|
||||||
@@ -1898,7 +1897,7 @@ static void on_charge_attack_bb(shared_ptr<Client> c, uint8_t command, uint8_t f
|
|||||||
forward_subcommand(c, command, flag, data, size);
|
forward_subcommand(c, command, flag, data, size);
|
||||||
|
|
||||||
const auto& cmd = check_size_t<G_ChargeAttack_BB_6xC7>(data, size);
|
const auto& cmd = check_size_t<G_ChargeAttack_BB_6xC7>(data, size);
|
||||||
auto& disp = c->game_data.character()->disp;
|
auto& disp = c->character()->disp;
|
||||||
if (cmd.meseta_amount > disp.stats.meseta) {
|
if (cmd.meseta_amount > disp.stats.meseta) {
|
||||||
disp.stats.meseta = 0;
|
disp.stats.meseta = 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -1914,7 +1913,7 @@ static void on_level_up(shared_ptr<Client> c, uint8_t command, uint8_t flag, con
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
p->disp.stats.char_stats.atp = cmd.atp;
|
p->disp.stats.char_stats.atp = cmd.atp;
|
||||||
p->disp.stats.char_stats.mst = cmd.mst;
|
p->disp.stats.char_stats.mst = cmd.mst;
|
||||||
p->disp.stats.char_stats.evp = cmd.evp;
|
p->disp.stats.char_stats.evp = cmd.evp;
|
||||||
@@ -1928,7 +1927,7 @@ static void on_level_up(shared_ptr<Client> c, uint8_t command, uint8_t flag, con
|
|||||||
|
|
||||||
static void add_player_exp(shared_ptr<Client> c, uint32_t exp) {
|
static void add_player_exp(shared_ptr<Client> c, uint32_t exp) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
|
|
||||||
p->disp.stats.experience += exp;
|
p->disp.stats.experience += exp;
|
||||||
send_give_experience(c, exp);
|
send_give_experience(c, exp);
|
||||||
@@ -1963,7 +1962,7 @@ static void on_steal_exp_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void*
|
|||||||
|
|
||||||
const auto& cmd = check_size_t<G_StealEXP_BB_6xC6>(data, size);
|
const auto& cmd = check_size_t<G_StealEXP_BB_6xC6>(data, size);
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
const auto& enemy = l->map->enemies.at(cmd.enemy_index);
|
const auto& enemy = l->map->enemies.at(cmd.enemy_index);
|
||||||
const auto& inventory = p->inventory;
|
const auto& inventory = p->inventory;
|
||||||
const auto& weapon = inventory.items[inventory.find_equipped_item(EquipSlot::WEAPON)];
|
const auto& weapon = inventory.items[inventory.find_equipped_item(EquipSlot::WEAPON)];
|
||||||
@@ -2073,14 +2072,14 @@ static void on_enemy_exp_request_bb(shared_ptr<Client> c, uint8_t, uint8_t, cons
|
|||||||
(!cmd.is_killer == !is_killer) ? "" : "$C6!K$C5 ",
|
(!cmd.is_killer == !is_killer) ? "" : "$C6!K$C5 ",
|
||||||
name_for_enum(e.type));
|
name_for_enum(e.type));
|
||||||
}
|
}
|
||||||
if (c->game_data.character()->disp.stats.level < 199) {
|
if (c->character()->disp.stats.level < 199) {
|
||||||
add_player_exp(c, player_exp);
|
add_player_exp(c, player_exp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update kill counts on unsealable items
|
// Update kill counts on unsealable items
|
||||||
auto& inventory = c->game_data.character()->inventory;
|
auto& inventory = c->character()->inventory;
|
||||||
for (size_t z = 0; z < inventory.num_items; z++) {
|
for (size_t z = 0; z < inventory.num_items; z++) {
|
||||||
auto& item = inventory.items[z];
|
auto& item = inventory.items[z];
|
||||||
if ((item.flags & 0x08) &&
|
if ((item.flags & 0x08) &&
|
||||||
@@ -2093,7 +2092,7 @@ static void on_enemy_exp_request_bb(shared_ptr<Client> c, uint8_t, uint8_t, cons
|
|||||||
void on_meseta_reward_request_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
|
void on_meseta_reward_request_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
|
||||||
const auto& cmd = check_size_t<G_MesetaRewardRequest_BB_6xC9>(data, size);
|
const auto& cmd = check_size_t<G_MesetaRewardRequest_BB_6xC9>(data, size);
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
if (cmd.amount < 0) {
|
if (cmd.amount < 0) {
|
||||||
if (-cmd.amount > static_cast<int32_t>(p->disp.stats.meseta.load())) {
|
if (-cmd.amount > static_cast<int32_t>(p->disp.stats.meseta.load())) {
|
||||||
p->disp.stats.meseta = 0;
|
p->disp.stats.meseta = 0;
|
||||||
@@ -2119,7 +2118,7 @@ void on_item_reward_request_bb(shared_ptr<Client> c, uint8_t, uint8_t, const voi
|
|||||||
ItemData item;
|
ItemData item;
|
||||||
item = cmd.item_data;
|
item = cmd.item_data;
|
||||||
item.id = l->generate_item_id(c->lobby_client_id);
|
item.id = l->generate_item_id(c->lobby_client_id);
|
||||||
c->game_data.character()->add_item(item);
|
c->character()->add_item(item);
|
||||||
send_create_inventory_item(c, item);
|
send_create_inventory_item(c, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2148,7 +2147,7 @@ void on_transfer_item_via_mail_message_bb(shared_ptr<Client> c, uint8_t command,
|
|||||||
forward_subcommand(c, command, flag, data, size);
|
forward_subcommand(c, command, flag, data, size);
|
||||||
|
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
||||||
|
|
||||||
auto name = s->describe_item(c->version(), item, false);
|
auto name = s->describe_item(c->version(), item, false);
|
||||||
@@ -2167,10 +2166,10 @@ void on_transfer_item_via_mail_message_bb(shared_ptr<Client> c, uint8_t command,
|
|||||||
auto target_c = s->find_client(nullptr, cmd.target_guild_card_number);
|
auto target_c = s->find_client(nullptr, cmd.target_guild_card_number);
|
||||||
if (target_c &&
|
if (target_c &&
|
||||||
(target_c->version() == Version::BB_V4) &&
|
(target_c->version() == Version::BB_V4) &&
|
||||||
(target_c->game_data.character(false) != nullptr) &&
|
(target_c->character(false) != nullptr) &&
|
||||||
!target_c->config.check_flag(Client::Flag::AT_BANK_COUNTER)) {
|
!target_c->config.check_flag(Client::Flag::AT_BANK_COUNTER)) {
|
||||||
try {
|
try {
|
||||||
target_c->game_data.current_bank().add_item(item);
|
target_c->current_bank().add_item(item);
|
||||||
item_sent = true;
|
item_sent = true;
|
||||||
} catch (const runtime_error&) {
|
} catch (const runtime_error&) {
|
||||||
}
|
}
|
||||||
@@ -2210,7 +2209,7 @@ void on_exchange_item_for_team_points_bb(shared_ptr<Client> c, uint8_t command,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
||||||
|
|
||||||
size_t points = s->item_parameter_table_v4->get_item_team_points(item);
|
size_t points = s->item_parameter_table_v4->get_item_team_points(item);
|
||||||
@@ -2241,7 +2240,7 @@ static void on_destroy_inventory_item(shared_ptr<Client> c, uint8_t command, uin
|
|||||||
|
|
||||||
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
if (l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
||||||
auto name = s->describe_item(c->version(), item, false);
|
auto name = s->describe_item(c->version(), item, false);
|
||||||
l->log.info("Player %hhu destroyed inventory item %hu:%08" PRIX32 " (%s)",
|
l->log.info("Player %hhu destroyed inventory item %hu:%08" PRIX32 " (%s)",
|
||||||
@@ -2293,7 +2292,7 @@ static void on_identify_item_bb(shared_ptr<Client> c, uint8_t command, uint8_t f
|
|||||||
throw logic_error("received item identify subcommand without item creator present");
|
throw logic_error("received item identify subcommand without item creator present");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
size_t x = p->inventory.find_item(cmd.item_id);
|
size_t x = p->inventory.find_item(cmd.item_id);
|
||||||
if (p->inventory.items[x].data.data1[0] != 0) {
|
if (p->inventory.items[x].data.data1[0] != 0) {
|
||||||
throw runtime_error("non-weapon items cannot be unidentified");
|
throw runtime_error("non-weapon items cannot be unidentified");
|
||||||
@@ -2305,9 +2304,9 @@ static void on_identify_item_bb(shared_ptr<Client> c, uint8_t command, uint8_t f
|
|||||||
// ID instead.
|
// ID instead.
|
||||||
l->generate_item_id(c->lobby_client_id);
|
l->generate_item_id(c->lobby_client_id);
|
||||||
p->disp.stats.meseta -= 100;
|
p->disp.stats.meseta -= 100;
|
||||||
c->game_data.identify_result = p->inventory.items[x].data;
|
c->bb_identify_result = p->inventory.items[x].data;
|
||||||
c->game_data.identify_result.data1[4] &= 0x7F;
|
c->bb_identify_result.data1[4] &= 0x7F;
|
||||||
l->item_creator->apply_tekker_deltas(c->game_data.identify_result, p->disp.visual.section_id);
|
l->item_creator->apply_tekker_deltas(c->bb_identify_result, p->disp.visual.section_id);
|
||||||
send_item_identify_result(c);
|
send_item_identify_result(c);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -2324,15 +2323,15 @@ static void on_accept_identify_item_bb(shared_ptr<Client> c, uint8_t command, ui
|
|||||||
throw logic_error("item tracking not enabled in BB game");
|
throw logic_error("item tracking not enabled in BB game");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!c->game_data.identify_result.id || (c->game_data.identify_result.id == 0xFFFFFFFF)) {
|
if (!c->bb_identify_result.id || (c->bb_identify_result.id == 0xFFFFFFFF)) {
|
||||||
throw runtime_error("no identify result present");
|
throw runtime_error("no identify result present");
|
||||||
}
|
}
|
||||||
if (c->game_data.identify_result.id != cmd.item_id) {
|
if (c->bb_identify_result.id != cmd.item_id) {
|
||||||
throw runtime_error("accepted item ID does not match previous identify request");
|
throw runtime_error("accepted item ID does not match previous identify request");
|
||||||
}
|
}
|
||||||
c->game_data.character()->add_item(c->game_data.identify_result);
|
c->character()->add_item(c->bb_identify_result);
|
||||||
send_create_inventory_item(c, c->game_data.identify_result);
|
send_create_inventory_item(c, c->bb_identify_result);
|
||||||
c->game_data.identify_result.clear();
|
c->bb_identify_result.clear();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
forward_subcommand(c, command, flag, data, size);
|
forward_subcommand(c, command, flag, data, size);
|
||||||
@@ -2350,7 +2349,7 @@ static void on_sell_item_at_shop_bb(shared_ptr<Client> c, uint8_t command, uint8
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
auto item = p->remove_item(cmd.item_id, cmd.amount, c->version() != Version::BB_V4);
|
||||||
size_t price = (s->item_parameter_table_for_version(c->version())->price_for_item(item) >> 3) * cmd.amount;
|
size_t price = (s->item_parameter_table_for_version(c->version())->price_for_item(item) >> 3) * cmd.amount;
|
||||||
p->add_meseta(price);
|
p->add_meseta(price);
|
||||||
@@ -2378,7 +2377,7 @@ static void on_buy_shop_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const vo
|
|||||||
}
|
}
|
||||||
|
|
||||||
ItemData item;
|
ItemData item;
|
||||||
item = c->game_data.shop_contents.at(cmd.shop_type).at(cmd.item_index);
|
item = c->bb_shop_contents.at(cmd.shop_type).at(cmd.item_index);
|
||||||
if (item.is_stackable()) {
|
if (item.is_stackable()) {
|
||||||
item.data1[5] = cmd.amount;
|
item.data1[5] = cmd.amount;
|
||||||
} else if (cmd.amount != 1) {
|
} else if (cmd.amount != 1) {
|
||||||
@@ -2387,7 +2386,7 @@ static void on_buy_shop_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const vo
|
|||||||
|
|
||||||
size_t price = item.data2d * cmd.amount;
|
size_t price = item.data2d * cmd.amount;
|
||||||
item.data2d = 0;
|
item.data2d = 0;
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
p->remove_meseta(price, false);
|
p->remove_meseta(price, false);
|
||||||
|
|
||||||
item.id = l->generate_item_id(c->lobby_client_id);
|
item.id = l->generate_item_id(c->lobby_client_id);
|
||||||
@@ -2410,7 +2409,7 @@ static void on_buy_shop_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const vo
|
|||||||
static void on_medical_center_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void*, size_t) {
|
static void on_medical_center_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void*, size_t) {
|
||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (l->is_game() && (l->base_version == Version::BB_V4)) {
|
if (l->is_game() && (l->base_version == Version::BB_V4)) {
|
||||||
c->game_data.character()->remove_meseta(10, false);
|
c->character()->remove_meseta(10, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2436,8 +2435,8 @@ static void on_battle_restart_bb(shared_ptr<Client> c, uint8_t, uint8_t, const v
|
|||||||
|
|
||||||
for (auto& lc : l->clients) {
|
for (auto& lc : l->clients) {
|
||||||
if (lc) {
|
if (lc) {
|
||||||
lc->game_data.delete_overlay();
|
lc->delete_overlay();
|
||||||
lc->game_data.create_battle_overlay(new_rules, s->level_table);
|
lc->create_battle_overlay(new_rules, s->level_table);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
l->map->clear();
|
l->map->clear();
|
||||||
@@ -2462,7 +2461,7 @@ static void on_battle_level_up_bb(shared_ptr<Client> c, uint8_t, uint8_t, const
|
|||||||
auto lc = l->clients.at(cmd.header.client_id);
|
auto lc = l->clients.at(cmd.header.client_id);
|
||||||
if (lc) {
|
if (lc) {
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto lp = lc->game_data.character();
|
auto lp = lc->character();
|
||||||
uint32_t target_level = lp->disp.stats.level + cmd.num_levels;
|
uint32_t target_level = lp->disp.stats.level + cmd.num_levels;
|
||||||
uint32_t before_exp = lp->disp.stats.experience;
|
uint32_t before_exp = lp->disp.stats.experience;
|
||||||
lp->disp.stats.advance_to_level(lp->disp.visual.char_class, target_level, s->level_table);
|
lp->disp.stats.advance_to_level(lp->disp.visual.char_class, target_level, s->level_table);
|
||||||
@@ -2502,7 +2501,7 @@ static void on_quest_exchange_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, co
|
|||||||
const auto& cmd = check_size_t<G_ExchangeItemInQuest_BB_6xD5>(data, size);
|
const auto& cmd = check_size_t<G_ExchangeItemInQuest_BB_6xD5>(data, size);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
|
|
||||||
size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier());
|
size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier());
|
||||||
auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, false);
|
auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, false);
|
||||||
@@ -2529,7 +2528,7 @@ static void on_wrap_item_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void*
|
|||||||
if (l->is_game() && (l->base_version == Version::BB_V4)) {
|
if (l->is_game() && (l->base_version == Version::BB_V4)) {
|
||||||
const auto& cmd = check_size_t<G_WrapItem_BB_6xD6>(data, size);
|
const auto& cmd = check_size_t<G_WrapItem_BB_6xD6>(data, size);
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto item = p->remove_item(cmd.item.id, 1, false);
|
auto item = p->remove_item(cmd.item.id, 1, false);
|
||||||
send_destroy_item(c, item.id, 1);
|
send_destroy_item(c, item.id, 1);
|
||||||
item.wrap();
|
item.wrap();
|
||||||
@@ -2544,7 +2543,7 @@ static void on_photon_drop_exchange_for_item_bb(shared_ptr<Client> c, uint8_t, u
|
|||||||
const auto& cmd = check_size_t<G_PaganiniPhotonDropExchange_BB_6xD7>(data, size);
|
const auto& cmd = check_size_t<G_PaganiniPhotonDropExchange_BB_6xD7>(data, size);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
|
|
||||||
size_t found_index = p->inventory.find_item_by_primary_identifier(0x031000);
|
size_t found_index = p->inventory.find_item_by_primary_identifier(0x031000);
|
||||||
auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 0, false);
|
auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 0, false);
|
||||||
@@ -2572,7 +2571,7 @@ static void on_photon_drop_exchange_for_s_rank_special_bb(shared_ptr<Client> c,
|
|||||||
const auto& cmd = check_size_t<G_AddSRankWeaponSpecial_BB_6xD8>(data, size);
|
const auto& cmd = check_size_t<G_AddSRankWeaponSpecial_BB_6xD8>(data, size);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
|
|
||||||
static const array<uint8_t, 0x10> costs({60, 60, 20, 20, 30, 30, 30, 50, 40, 50, 40, 40, 50, 40, 40, 40});
|
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);
|
uint8_t cost = costs.at(cmd.special_type);
|
||||||
@@ -2610,7 +2609,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr<Client> c, uint8_t,
|
|||||||
throw runtime_error("no secret lottery results are defined");
|
throw runtime_error("no secret lottery results are defined");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
ssize_t slt_index = -1;
|
ssize_t slt_index = -1;
|
||||||
try {
|
try {
|
||||||
slt_index = p->inventory.find_item_by_primary_identifier(0x031003); // Secret Lottery Ticket
|
slt_index = p->inventory.find_item_by_primary_identifier(0x031003); // Secret Lottery Ticket
|
||||||
@@ -2659,7 +2658,7 @@ static void on_photon_crystal_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t
|
|||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
||||||
check_size_t<G_ExchangePhotonCrystals_BB_6xDF>(data, size);
|
check_size_t<G_ExchangePhotonCrystals_BB_6xDF>(data, size);
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
size_t index = p->inventory.find_item_by_primary_identifier(0x031002);
|
size_t index = p->inventory.find_item_by_primary_identifier(0x031002);
|
||||||
auto item = p->remove_item(p->inventory.items[index].data.id, 1, false);
|
auto item = p->remove_item(p->inventory.items[index].data.id, 1, false);
|
||||||
send_destroy_item(c, item.id, 1);
|
send_destroy_item(c, item.id, 1);
|
||||||
@@ -2701,7 +2700,7 @@ static void on_quest_F95F_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, cons
|
|||||||
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
||||||
const auto& cmd = check_size_t<G_ExchangePhotonTickets_BB_6xE1>(data, size);
|
const auto& cmd = check_size_t<G_ExchangePhotonTickets_BB_6xE1>(data, size);
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
|
|
||||||
const auto& result = s->quest_F95F_results.at(cmd.result_index);
|
const auto& result = s->quest_F95F_results.at(cmd.result_index);
|
||||||
if (result.second.empty()) {
|
if (result.second.empty()) {
|
||||||
@@ -2739,7 +2738,7 @@ static void on_momoka_item_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t, c
|
|||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
||||||
const auto& cmd = check_size_t<G_MomokaItemExchange_BB_6xD9>(data, size);
|
const auto& cmd = check_size_t<G_MomokaItemExchange_BB_6xD9>(data, size);
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
try {
|
try {
|
||||||
size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier());
|
size_t found_index = p->inventory.find_item_by_primary_identifier(cmd.find_item.primary_identifier());
|
||||||
auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, false);
|
auto found_item = p->remove_item(p->inventory.items[found_index].data.id, 1, false);
|
||||||
@@ -2768,7 +2767,7 @@ static void on_upgrade_weapon_attribute_bb(shared_ptr<Client> c, uint8_t, uint8_
|
|||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
||||||
const auto& cmd = check_size_t<G_UpgradeWeaponAttribute_BB_6xDA>(data, size);
|
const auto& cmd = check_size_t<G_UpgradeWeaponAttribute_BB_6xDA>(data, size);
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
try {
|
try {
|
||||||
size_t item_index = p->inventory.find_item(cmd.item_id);
|
size_t item_index = p->inventory.find_item(cmd.item_id);
|
||||||
auto& item = p->inventory.items[item_index].data;
|
auto& item = p->inventory.items[item_index].data;
|
||||||
@@ -2819,7 +2818,7 @@ static void on_upgrade_weapon_attribute_bb(shared_ptr<Client> c, uint8_t, uint8_
|
|||||||
|
|
||||||
static void on_write_quest_global_flag_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
|
static void on_write_quest_global_flag_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
|
||||||
const auto& cmd = check_size_t<G_SetQuestGlobalFlag_BB_6xD2>(data, size);
|
const auto& cmd = check_size_t<G_SetQuestGlobalFlag_BB_6xD2>(data, size);
|
||||||
c->game_data.character()->quest_global_flags[cmd.index] = cmd.value;
|
c->character()->quest_global_flags[cmd.index] = cmd.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
+36
-36
@@ -498,7 +498,7 @@ void send_system_file_bb(shared_ptr<Client> c) {
|
|||||||
auto team = c->team();
|
auto team = c->team();
|
||||||
|
|
||||||
PSOBBFullSystemFile cmd;
|
PSOBBFullSystemFile cmd;
|
||||||
cmd.base = *c->game_data.system();
|
cmd.base = *c->system_file();
|
||||||
if (team) {
|
if (team) {
|
||||||
cmd.team_membership = team->membership_for_member(c->license->serial_number);
|
cmd.team_membership = team->membership_for_member(c->license->serial_number);
|
||||||
}
|
}
|
||||||
@@ -518,7 +518,7 @@ void send_player_preview_bb(shared_ptr<Client> c, int8_t character_index, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
void send_guild_card_header_bb(shared_ptr<Client> c) {
|
void send_guild_card_header_bb(shared_ptr<Client> c) {
|
||||||
uint32_t checksum = c->game_data.guild_cards()->checksum();
|
uint32_t checksum = c->guild_card_file()->checksum();
|
||||||
S_GuildCardHeader_BB_01DC cmd = {1, sizeof(PSOBBGuildCardFile), checksum};
|
S_GuildCardHeader_BB_01DC cmd = {1, sizeof(PSOBBGuildCardFile), checksum};
|
||||||
send_command_t(c, 0x01DC, 0x00000000, cmd);
|
send_command_t(c, 0x01DC, 0x00000000, cmd);
|
||||||
}
|
}
|
||||||
@@ -535,7 +535,7 @@ void send_guild_card_chunk_bb(shared_ptr<Client> c, size_t chunk_index) {
|
|||||||
cmd.unknown = 0;
|
cmd.unknown = 0;
|
||||||
cmd.chunk_index = chunk_index;
|
cmd.chunk_index = chunk_index;
|
||||||
cmd.data.assign_range(
|
cmd.data.assign_range(
|
||||||
reinterpret_cast<const uint8_t*>(c->game_data.guild_cards().get()) + chunk_offset,
|
reinterpret_cast<const uint8_t*>(c->guild_card_file().get()) + chunk_offset,
|
||||||
data_size, 0);
|
data_size, 0);
|
||||||
|
|
||||||
send_command(c, 0x02DC, 0x00000000, &cmd, sizeof(cmd) - sizeof(cmd.data) + data_size);
|
send_command(c, 0x02DC, 0x00000000, &cmd, sizeof(cmd) - sizeof(cmd.data) + data_size);
|
||||||
@@ -621,13 +621,13 @@ void send_stream_file_chunk_bb(shared_ptr<Client> c, uint32_t chunk_index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void send_approve_player_choice_bb(shared_ptr<Client> c) {
|
void send_approve_player_choice_bb(shared_ptr<Client> c) {
|
||||||
S_ApprovePlayerChoice_BB_00E4 cmd = {c->game_data.bb_character_index, 1};
|
S_ApprovePlayerChoice_BB_00E4 cmd = {c->bb_character_index, 1};
|
||||||
send_command_t(c, 0x00E4, 0x00000000, cmd);
|
send_command_t(c, 0x00E4, 0x00000000, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_complete_player_bb(shared_ptr<Client> c) {
|
void send_complete_player_bb(shared_ptr<Client> c) {
|
||||||
auto p = c->game_data.character(true, false);
|
auto p = c->character(true, false);
|
||||||
auto sys = c->game_data.system(true);
|
auto sys = c->system_file(true);
|
||||||
auto team = c->team();
|
auto team = c->team();
|
||||||
if (c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB)) {
|
if (c->config.check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB)) {
|
||||||
p->inventory.language = 1;
|
p->inventory.language = 1;
|
||||||
@@ -969,7 +969,7 @@ void send_info_board_t(shared_ptr<Client> c) {
|
|||||||
if (!other_c.get()) {
|
if (!other_c.get()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto other_p = other_c->game_data.character(true, false);
|
auto other_p = other_c->character(true, false);
|
||||||
auto& e = entries.emplace_back();
|
auto& e = entries.emplace_back();
|
||||||
e.name.encode(other_p->disp.name.decode(other_p->inventory.language), c->language());
|
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());
|
e.message.encode(add_color(other_p->info_board.decode(other_p->inventory.language)), c->language());
|
||||||
@@ -1055,7 +1055,7 @@ void send_card_search_result_t(
|
|||||||
cmd.location_string.encode(location_string, c->language());
|
cmd.location_string.encode(location_string, c->language());
|
||||||
cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY;
|
cmd.extension.lobby_refs[0].menu_id = MenuID::LOBBY;
|
||||||
cmd.extension.lobby_refs[0].item_id = result_lobby->lobby_id;
|
cmd.extension.lobby_refs[0].item_id = result_lobby->lobby_id;
|
||||||
auto rp = result->game_data.character(true, false);
|
auto rp = result->character(true, false);
|
||||||
cmd.extension.player_name.encode(rp->disp.name.decode(rp->inventory.language), c->language());
|
cmd.extension.player_name.encode(rp->disp.name.decode(rp->inventory.language), c->language());
|
||||||
|
|
||||||
send_command_t(c, 0x41, 0x00, cmd);
|
send_command_t(c, 0x41, 0x00, cmd);
|
||||||
@@ -1209,7 +1209,7 @@ void send_guild_card(shared_ptr<Client> c, shared_ptr<Client> source) {
|
|||||||
throw runtime_error("source player does not have a license");
|
throw runtime_error("source player does not have a license");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto source_p = source->game_data.character(true, false);
|
auto source_p = source->character(true, false);
|
||||||
auto source_team = source->team();
|
auto source_team = source->team();
|
||||||
|
|
||||||
uint64_t xb_user_id = source->license->xb_user_id
|
uint64_t xb_user_id = source->license->xb_user_id
|
||||||
@@ -1543,7 +1543,7 @@ template <typename EntryT>
|
|||||||
void send_player_records_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Client> joining_client) {
|
void send_player_records_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Client> joining_client) {
|
||||||
vector<EntryT> entries;
|
vector<EntryT> entries;
|
||||||
auto add_client = [&](shared_ptr<Client> lc) -> void {
|
auto add_client = [&](shared_ptr<Client> lc) -> void {
|
||||||
auto lp = lc->game_data.character(true, false);
|
auto lp = lc->character(true, false);
|
||||||
auto& e = entries.emplace_back();
|
auto& e = entries.emplace_back();
|
||||||
e.client_id = lc->lobby_client_id;
|
e.client_id = lc->lobby_client_id;
|
||||||
e.challenge = lp->challenge_records;
|
e.challenge = lp->challenge_records;
|
||||||
@@ -1595,7 +1595,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
|
|||||||
if (!wc) {
|
if (!wc) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto wc_p = wc->game_data.character();
|
auto wc_p = wc->character();
|
||||||
auto& p = cmd.players[z];
|
auto& p = cmd.players[z];
|
||||||
p.lobby_data.player_tag = 0x00010000;
|
p.lobby_data.player_tag = 0x00010000;
|
||||||
p.lobby_data.guild_card_number = wc->license->serial_number;
|
p.lobby_data.guild_card_number = wc->license->serial_number;
|
||||||
@@ -1611,8 +1611,8 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
|
|||||||
e.guild_card_number = wc->license->serial_number;
|
e.guild_card_number = wc->license->serial_number;
|
||||||
e.name.encode(wc_p->disp.name.decode(wc_p->inventory.language), c->language());
|
e.name.encode(wc_p->disp.name.decode(wc_p->inventory.language), c->language());
|
||||||
e.present = 1;
|
e.present = 1;
|
||||||
e.level = wc->game_data.ep3_config
|
e.level = wc->ep3_config
|
||||||
? (wc->game_data.ep3_config->online_clv_exp / 100)
|
? (wc->ep3_config->online_clv_exp / 100)
|
||||||
: wc_p->disp.stats.level.load();
|
: wc_p->disp.stats.level.load();
|
||||||
e.name_color = wc_p->disp.visual.name_color;
|
e.name_color = wc_p->disp.visual.name_color;
|
||||||
|
|
||||||
@@ -1662,7 +1662,7 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
|
|||||||
for (size_t z = 4; z < 12; z++) {
|
for (size_t z = 4; z < 12; z++) {
|
||||||
if (l->clients[z]) {
|
if (l->clients[z]) {
|
||||||
auto other_c = l->clients[z];
|
auto other_c = l->clients[z];
|
||||||
auto other_p = other_c->game_data.character();
|
auto other_p = other_c->character();
|
||||||
auto& cmd_p = cmd.spectator_players[z - 4];
|
auto& cmd_p = cmd.spectator_players[z - 4];
|
||||||
auto& cmd_e = cmd.entries[z];
|
auto& cmd_e = cmd.entries[z];
|
||||||
cmd_p.lobby_data.player_tag = 0x00010000;
|
cmd_p.lobby_data.player_tag = 0x00010000;
|
||||||
@@ -1677,8 +1677,8 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
|
|||||||
cmd_e.guild_card_number = other_c->license->serial_number;
|
cmd_e.guild_card_number = other_c->license->serial_number;
|
||||||
cmd_e.name = cmd_p.lobby_data.name;
|
cmd_e.name = cmd_p.lobby_data.name;
|
||||||
cmd_e.present = 1;
|
cmd_e.present = 1;
|
||||||
cmd_e.level = other_c->game_data.ep3_config
|
cmd_e.level = other_c->ep3_config
|
||||||
? (other_c->game_data.ep3_config->online_clv_exp / 100)
|
? (other_c->ep3_config->online_clv_exp / 100)
|
||||||
: other_p->disp.stats.level.load();
|
: other_p->disp.stats.level.load();
|
||||||
cmd_e.name_color = other_p->disp.visual.name_color;
|
cmd_e.name_color = other_p->disp.visual.name_color;
|
||||||
|
|
||||||
@@ -1704,7 +1704,7 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
|||||||
cmd.lobby_data[x].player_tag = 0x00010000;
|
cmd.lobby_data[x].player_tag = 0x00010000;
|
||||||
cmd.lobby_data[x].guild_card_number = lc->license->serial_number;
|
cmd.lobby_data[x].guild_card_number = lc->license->serial_number;
|
||||||
cmd.lobby_data[x].client_id = lc->lobby_client_id;
|
cmd.lobby_data[x].client_id = lc->lobby_client_id;
|
||||||
cmd.lobby_data[x].name.encode(lc->game_data.character()->disp.name.decode(lc->language()), c->language());
|
cmd.lobby_data[x].name.encode(lc->character()->disp.name.decode(lc->language()), c->language());
|
||||||
player_count++;
|
player_count++;
|
||||||
} else {
|
} else {
|
||||||
cmd.lobby_data[x].clear();
|
cmd.lobby_data[x].clear();
|
||||||
@@ -1784,7 +1784,7 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
|||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
for (size_t x = 0; x < 4; x++) {
|
for (size_t x = 0; x < 4; x++) {
|
||||||
if (l->clients[x]) {
|
if (l->clients[x]) {
|
||||||
auto other_p = l->clients[x]->game_data.character();
|
auto other_p = l->clients[x]->character();
|
||||||
cmd.players_ep3[x].inventory = other_p->inventory;
|
cmd.players_ep3[x].inventory = other_p->inventory;
|
||||||
cmd.players_ep3[x].inventory.encode_for_client(c);
|
cmd.players_ep3[x].inventory.encode_for_client(c);
|
||||||
cmd.players_ep3[x].disp = convert_player_disp_data<PlayerDispDataDCPCV3>(other_p->disp, c->language(), other_p->inventory.language);
|
cmd.players_ep3[x].disp = convert_player_disp_data<PlayerDispDataDCPCV3>(other_p->disp, c->language(), other_p->inventory.language);
|
||||||
@@ -1900,7 +1900,7 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cli
|
|||||||
|
|
||||||
size_t used_entries = 0;
|
size_t used_entries = 0;
|
||||||
for (const auto& lc : lobby_clients) {
|
for (const auto& lc : lobby_clients) {
|
||||||
auto lp = lc->game_data.character();
|
auto lp = lc->character();
|
||||||
auto& e = cmd.entries[used_entries++];
|
auto& e = cmd.entries[used_entries++];
|
||||||
e.lobby_data.player_tag = 0x00010000;
|
e.lobby_data.player_tag = 0x00010000;
|
||||||
e.lobby_data.guild_card_number = lc->license->serial_number;
|
e.lobby_data.guild_card_number = lc->license->serial_number;
|
||||||
@@ -1915,8 +1915,8 @@ void send_join_lobby_t(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cli
|
|||||||
}
|
}
|
||||||
e.inventory = lp->inventory;
|
e.inventory = lp->inventory;
|
||||||
e.inventory.encode_for_client(c);
|
e.inventory.encode_for_client(c);
|
||||||
if ((lc == c) && is_v1_or_v2(c->version()) && lc->game_data.last_reported_disp_v1_v2) {
|
if ((lc == c) && is_v1_or_v2(c->version()) && lc->v1_v2_last_reported_disp) {
|
||||||
e.disp = convert_player_disp_data<DispDataT>(*lc->game_data.last_reported_disp_v1_v2, c->language(), lp->inventory.language);
|
e.disp = convert_player_disp_data<DispDataT>(*lc->v1_v2_last_reported_disp, c->language(), lp->inventory.language);
|
||||||
} else {
|
} else {
|
||||||
e.disp = convert_player_disp_data<DispDataT>(lp->disp, c->language(), lp->inventory.language);
|
e.disp = convert_player_disp_data<DispDataT>(lp->disp, c->language(), lp->inventory.language);
|
||||||
e.disp.enforce_lobby_join_limits_for_client(c);
|
e.disp.enforce_lobby_join_limits_for_client(c);
|
||||||
@@ -1979,7 +1979,7 @@ void send_join_lobby_xb(shared_ptr<Client> c, shared_ptr<Lobby> l, shared_ptr<Cl
|
|||||||
|
|
||||||
size_t used_entries = 0;
|
size_t used_entries = 0;
|
||||||
for (const auto& lc : lobby_clients) {
|
for (const auto& lc : lobby_clients) {
|
||||||
auto lp = lc->game_data.character();
|
auto lp = lc->character();
|
||||||
auto& e = cmd.entries[used_entries++];
|
auto& e = cmd.entries[used_entries++];
|
||||||
e.lobby_data.player_tag = 0x00010000;
|
e.lobby_data.player_tag = 0x00010000;
|
||||||
e.lobby_data.guild_card_number = lc->license->serial_number;
|
e.lobby_data.guild_card_number = lc->license->serial_number;
|
||||||
@@ -2032,7 +2032,7 @@ void send_join_lobby_dc_nte(shared_ptr<Client> c, shared_ptr<Lobby> l,
|
|||||||
|
|
||||||
size_t used_entries = 0;
|
size_t used_entries = 0;
|
||||||
for (const auto& lc : lobby_clients) {
|
for (const auto& lc : lobby_clients) {
|
||||||
auto lp = lc->game_data.character();
|
auto lp = lc->character();
|
||||||
auto& e = cmd.entries[used_entries++];
|
auto& e = cmd.entries[used_entries++];
|
||||||
e.lobby_data.player_tag = 0x00010000;
|
e.lobby_data.player_tag = 0x00010000;
|
||||||
e.lobby_data.guild_card_number = lc->license->serial_number;
|
e.lobby_data.guild_card_number = lc->license->serial_number;
|
||||||
@@ -2354,7 +2354,7 @@ void send_item_identify_result(shared_ptr<Client> c) {
|
|||||||
res.header.subcommand = 0xB9;
|
res.header.subcommand = 0xB9;
|
||||||
res.header.size = sizeof(res) / 4;
|
res.header.size = sizeof(res) / 4;
|
||||||
res.header.client_id = c->lobby_client_id;
|
res.header.client_id = c->lobby_client_id;
|
||||||
res.item_data = c->game_data.identify_result;
|
res.item_data = c->bb_identify_result;
|
||||||
send_command_t(l, 0x60, 0x00, res);
|
send_command_t(l, 0x60, 0x00, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2363,8 +2363,8 @@ void send_bank(shared_ptr<Client> c) {
|
|||||||
throw logic_error("6xBC can only be sent to BB clients");
|
throw logic_error("6xBC can only be sent to BB clients");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
auto& bank = c->game_data.current_bank();
|
auto& bank = c->current_bank();
|
||||||
bank.sort();
|
bank.sort();
|
||||||
const auto* items_it = bank.items.data();
|
const auto* items_it = bank.items.data();
|
||||||
vector<PlayerBankItem> items(items_it, items_it + bank.num_items);
|
vector<PlayerBankItem> items(items_it, items_it + bank.num_items);
|
||||||
@@ -2383,7 +2383,7 @@ void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
|
|||||||
throw logic_error("6xB6 can only be sent to BB clients");
|
throw logic_error("6xB6 can only be sent to BB clients");
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& contents = c->game_data.shop_contents.at(shop_type);
|
const auto& contents = c->bb_shop_contents.at(shop_type);
|
||||||
|
|
||||||
G_ShopContents_BB_6xB6 cmd = {
|
G_ShopContents_BB_6xB6 cmd = {
|
||||||
{0xB6, static_cast<uint8_t>(2 + (sizeof(ItemData) >> 2) * contents.size()), 0x0000},
|
{0xB6, static_cast<uint8_t>(2 + (sizeof(ItemData) >> 2) * contents.size()), 0x0000},
|
||||||
@@ -2401,7 +2401,7 @@ void send_shop(shared_ptr<Client> c, uint8_t shop_type) {
|
|||||||
|
|
||||||
void send_level_up(shared_ptr<Client> c) {
|
void send_level_up(shared_ptr<Client> c) {
|
||||||
auto l = c->require_lobby();
|
auto l = c->require_lobby();
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
CharacterStats stats = p->disp.stats.char_stats;
|
CharacterStats stats = p->disp.stats.char_stats;
|
||||||
|
|
||||||
const ItemData* mag = nullptr;
|
const ItemData* mag = nullptr;
|
||||||
@@ -2667,7 +2667,7 @@ string ep3_description_for_client(shared_ptr<Client> c) {
|
|||||||
if (!is_ep3(c->version())) {
|
if (!is_ep3(c->version())) {
|
||||||
throw runtime_error("client is not Episode 3");
|
throw runtime_error("client is not Episode 3");
|
||||||
}
|
}
|
||||||
auto p = c->game_data.character();
|
auto p = c->character();
|
||||||
return string_printf(
|
return string_printf(
|
||||||
"%s CLv%" PRIu32 " %c",
|
"%s CLv%" PRIu32 " %c",
|
||||||
name_for_char_class(p->disp.visual.char_class),
|
name_for_char_class(p->disp.visual.char_class),
|
||||||
@@ -2715,7 +2715,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
|||||||
if (player.is_human()) {
|
if (player.is_human()) {
|
||||||
try {
|
try {
|
||||||
auto other_c = serial_number_to_client.at(player.serial_number);
|
auto other_c = serial_number_to_client.at(player.serial_number);
|
||||||
entry.name.encode(other_c->game_data.character()->disp.name.decode(other_c->language()), c->language());
|
entry.name.encode(other_c->character()->disp.name.decode(other_c->language()), c->language());
|
||||||
entry.description.encode(ep3_description_for_client(other_c), c->language());
|
entry.description.encode(ep3_description_for_client(other_c), c->language());
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
entry.name.encode(player.player_name, c->language());
|
entry.name.encode(player.player_name, c->language());
|
||||||
@@ -2736,7 +2736,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
|||||||
for (auto spec_c : l->clients) {
|
for (auto spec_c : l->clients) {
|
||||||
if (spec_c) {
|
if (spec_c) {
|
||||||
auto& entry = cmd.spectator_entries[cmd.num_spectators++];
|
auto& entry = cmd.spectator_entries[cmd.num_spectators++];
|
||||||
entry.name.encode(spec_c->game_data.character()->disp.name.decode(spec_c->language()), c->language());
|
entry.name.encode(spec_c->character()->disp.name.decode(spec_c->language()), c->language());
|
||||||
entry.description.encode(ep3_description_for_client(spec_c), c->language());
|
entry.description.encode(ep3_description_for_client(spec_c), c->language());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2753,7 +2753,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
|||||||
size_t num_players = 0;
|
size_t num_players = 0;
|
||||||
for (const auto& opp_c : primary_lobby->clients) {
|
for (const auto& opp_c : primary_lobby->clients) {
|
||||||
if (opp_c) {
|
if (opp_c) {
|
||||||
cmd.player_entries[num_players].name.encode(opp_c->game_data.character()->disp.name.decode(opp_c->language()), c->language());
|
cmd.player_entries[num_players].name.encode(opp_c->character()->disp.name.decode(opp_c->language()), c->language());
|
||||||
cmd.player_entries[num_players].description.encode(ep3_description_for_client(opp_c), c->language());
|
cmd.player_entries[num_players].description.encode(ep3_description_for_client(opp_c), c->language());
|
||||||
num_players++;
|
num_players++;
|
||||||
}
|
}
|
||||||
@@ -2766,7 +2766,7 @@ void send_ep3_game_details(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
|||||||
for (auto spec_c : l->clients) {
|
for (auto spec_c : l->clients) {
|
||||||
if (spec_c) {
|
if (spec_c) {
|
||||||
auto& entry = cmd.spectator_entries[num_spectators++];
|
auto& entry = cmd.spectator_entries[num_spectators++];
|
||||||
entry.name.encode(spec_c->game_data.character()->disp.name.decode(spec_c->language()), c->language());
|
entry.name.encode(spec_c->character()->disp.name.decode(spec_c->language()), c->language());
|
||||||
entry.description.encode(ep3_description_for_client(spec_c), c->language());
|
entry.description.encode(ep3_description_for_client(spec_c), c->language());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2877,7 +2877,7 @@ void send_ep3_tournament_match_result(shared_ptr<Lobby> l, uint32_t meseta_rewar
|
|||||||
if (player.is_human()) {
|
if (player.is_human()) {
|
||||||
try {
|
try {
|
||||||
auto pc = serial_number_to_client.at(player.serial_number);
|
auto pc = serial_number_to_client.at(player.serial_number);
|
||||||
entry.player_names[z].encode(pc->game_data.character()->disp.name.decode(pc->language()), lc->language());
|
entry.player_names[z].encode(pc->character()->disp.name.decode(pc->language()), lc->language());
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
entry.player_names[z].encode(player.player_name, lc->language());
|
entry.player_names[z].encode(player.player_name, lc->language());
|
||||||
}
|
}
|
||||||
@@ -3329,7 +3329,7 @@ static S_TeamInfoForPlayer_BB_13EA_15EA_Entry team_metadata_for_client(shared_pt
|
|||||||
S_TeamInfoForPlayer_BB_13EA_15EA_Entry cmd;
|
S_TeamInfoForPlayer_BB_13EA_15EA_Entry cmd;
|
||||||
cmd.lobby_client_id = c->lobby_client_id;
|
cmd.lobby_client_id = c->lobby_client_id;
|
||||||
cmd.guild_card_number2 = c->license->serial_number;
|
cmd.guild_card_number2 = c->license->serial_number;
|
||||||
cmd.player_name = c->game_data.character()->disp.name;
|
cmd.player_name = c->character()->disp.name;
|
||||||
if (team) {
|
if (team) {
|
||||||
cmd.guild_card_number = c->license->serial_number;
|
cmd.guild_card_number = c->license->serial_number;
|
||||||
cmd.team_id = team->team_id;
|
cmd.team_id = team->team_id;
|
||||||
@@ -3468,7 +3468,7 @@ void send_team_reward_list(std::shared_ptr<Client> c, bool show_purchased) {
|
|||||||
}
|
}
|
||||||
auto s = c->require_server_state();
|
auto s = c->require_server_state();
|
||||||
|
|
||||||
bool show_item_rewards = show_purchased || (c->game_data.current_bank().num_items < 200);
|
bool show_item_rewards = show_purchased || (c->current_bank().num_items < 200);
|
||||||
|
|
||||||
vector<S_TeamRewardList_BB_19EA_1AEA::Entry> entries;
|
vector<S_TeamRewardList_BB_19EA_1AEA::Entry> entries;
|
||||||
for (const auto& reward : s->team_index->reward_definitions()) {
|
for (const auto& reward : s->team_index->reward_definitions()) {
|
||||||
|
|||||||
+1
-1
@@ -315,7 +315,7 @@ vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto p = c->game_data.character(false, false);
|
auto p = c->character(false, false);
|
||||||
if (p && p->disp.name.eq(ident, p->inventory.language)) {
|
if (p && p->disp.name.eq(ident, p->inventory.language)) {
|
||||||
results.emplace_back(std::move(c));
|
results.emplace_back(std::move(c));
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "License.hh"
|
#include "License.hh"
|
||||||
#include "Lobby.hh"
|
#include "Lobby.hh"
|
||||||
#include "Menu.hh"
|
#include "Menu.hh"
|
||||||
|
#include "PlayerFilesManager.hh"
|
||||||
#include "Quest.hh"
|
#include "Quest.hh"
|
||||||
#include "TeamIndex.hh"
|
#include "TeamIndex.hh"
|
||||||
#include "WordSelectTable.hh"
|
#include "WordSelectTable.hh"
|
||||||
|
|||||||
Reference in New Issue
Block a user