diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index f845205f..2a911c20 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -224,7 +224,7 @@ static asio::awaitable server_command_announce_inner(const Args& a, bool m send_text_or_scrolling_message(s, a.text, a.text); } } else { - auto from_name = a.c->character_file()->disp.name.decode(a.c->language()); + auto from_name = a.c->character_file()->disp.visual.name.decode(a.c->language()); if (mail) { send_simple_mail(s, 0, from_name, a.text); } else { @@ -333,7 +333,7 @@ ChatCommandDefinition cc_auction( static std::string name_for_client(std::shared_ptr c) { auto player = c->character_file(false); if (player.get()) { - return escape_player_name(player->disp.name.decode(player->inventory.language)); + return escape_player_name(player->disp.visual.name.decode(player->inventory.language)); } if (c->login) { @@ -514,13 +514,9 @@ static asio::awaitable server_command_bbchar_savechar(const Args& a, bool // Client sent 61; generate a BB-format player from the information we have and save that instead if (ch.character) { auto bb_player = PSOBBCharacterFile::create_from_config( - a.c->login->account->account_id, - a.c->language(), - ch.character->disp.visual, - ch.character->disp.name.decode(a.c->language()), - s->level_table(a.c->version())); - bb_player->disp.visual.version = 4; - bb_player->disp.visual.name_color_checksum = 0x00000000; + a.c->login->account->account_id, a.c->language(), ch.character->disp.visual, s->level_table(a.c->version())); + bb_player->disp.visual.sh.version = 4; + bb_player->disp.visual.sh.name_color_checksum = 0x00000000; bb_player->inventory = ch.character->inventory; // Before V3, player stats can't be correctly computed from other fields because material usage isn't stored // anywhere. For these versions, we have to trust the stats field from the player's data. @@ -530,7 +526,7 @@ static asio::awaitable server_command_bbchar_savechar(const Args& a, bool bb_player->import_tethealla_material_usage(level_table); } else { level_table->advance_to_level( - bb_player->disp.stats, ch.character->disp.stats.level, bb_player->disp.visual.char_class); + bb_player->disp.stats, ch.character->disp.stats.level, bb_player->disp.visual.sh.char_class); bb_player->disp.stats.char_stats.atp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::POWER) * 2; bb_player->disp.stats.char_stats.mst += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::MIND) * 2; bb_player->disp.stats.char_stats.evp += bb_player->get_material_usage(PSOBBCharacterFile::MaterialType::EVADE) * 2; @@ -629,15 +625,15 @@ ChatCommandDefinition cc_checkchar( auto ch = phosg::load_object_file(filename); send_text_message_fmt(a.c, "Slot {}: $C6{}$C7\n{} {}\nCLv: on {}.{}, off {}.{}", index + 1, ch.disp.visual.name.decode(), - name_for_section_id(ch.disp.visual.section_id), name_for_char_class(ch.disp.visual.char_class), + name_for_section_id(ch.disp.visual.sh.section_id), name_for_char_class(ch.disp.visual.sh.char_class), (ch.ep3_config.online_clv_exp / 100) + 1, ch.ep3_config.online_clv_exp % 100, (ch.ep3_config.offline_clv_exp / 100) + 1, ch.ep3_config.offline_clv_exp % 100); } else { std::string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, false); auto ch = PSOCHARFile::load_shared(filename, false).character_file; send_text_message_fmt(a.c, "Slot {}: $C6{}$C7\n{} {}\nLevel {}", - index + 1, ch->disp.name.decode(), - name_for_section_id(ch->disp.visual.section_id), name_for_char_class(ch->disp.visual.char_class), + index + 1, ch->disp.visual.name.decode(), + name_for_section_id(ch->disp.visual.sh.section_id), name_for_char_class(ch->disp.visual.sh.char_class), ch->disp.stats.level + 1); } } catch (const phosg::cannot_open_file&) { @@ -955,7 +951,7 @@ ChatCommandDefinition cc_edit( } p->recompute_stats(s->level_table(a.c->version()), false); } else if (tokens.at(0) == "namecolor") { - p->disp.visual.name_color = std::stoul(tokens.at(1), nullptr, 16); + p->disp.visual.sh.name_color = std::stoul(tokens.at(1), nullptr, 16); } else if (tokens.at(0) == "language" || tokens.at(0) == "lang") { if (tokens.at(1).size() != 1) { throw std::runtime_error("invalid language"); @@ -976,24 +972,24 @@ ChatCommandDefinition cc_edit( if (secid == 0xFF) { throw precondition_failed("$C6No such section ID"); } else { - p->disp.visual.section_id = secid; + p->disp.visual.sh.section_id = secid; } } else if (tokens.at(0) == "name") { std::vector orig_tokens = phosg::split(a.text, ' ', 1); - p->disp.name.encode(orig_tokens.at(1), p->inventory.language); + p->disp.visual.name.encode(orig_tokens.at(1), p->inventory.language); } else if (tokens.at(0) == "npc") { if (tokens.at(1) == "none") { - p->disp.visual.extra_model = 0; - p->disp.visual.validation_flags &= 0xFD; - p->disp.visual.restore_npc_saved_fields(); + p->disp.visual.sh.extra_model = 0; + p->disp.visual.sh.validation_flags &= 0xFD; + p->disp.visual.sh.restore_npc_saved_fields(); } else { uint8_t npc = npc_for_name(tokens.at(1), a.c->version()); if (npc == 0xFF) { throw precondition_failed("$C6No such NPC"); } - p->disp.visual.backup_npc_saved_fields(); - p->disp.visual.extra_model = npc; - p->disp.visual.validation_flags |= 0x02; + p->disp.visual.sh.backup_npc_saved_fields(); + p->disp.visual.sh.extra_model = npc; + p->disp.visual.sh.validation_flags |= 0x02; } } else if (tokens.at(0) == "tech" && (cheats_allowed || !s->cheat_flags.edit_stats)) { uint8_t level = std::stoul(tokens.at(2)) - 1; @@ -2701,7 +2697,7 @@ ChatCommandDefinition cc_surrender( if (!ps || !ps->is_alive()) { throw precondition_failed("$C6Defeated players\ncannot surrender"); } - std::string name = remove_color(a.c->character_file()->disp.name.decode(a.c->language())); + std::string name = remove_color(a.c->character_file()->disp.visual.name.decode(a.c->language())); send_text_message_fmt(l, "$C6{} has\nsurrendered", name); for (const auto& watcher_l : l->watcher_lobbies) { send_text_message_fmt(watcher_l, "$C6{} has\nsurrendered", name); @@ -3114,7 +3110,7 @@ ChatCommandDefinition cc_where( if (!a.c->proxy_session && l && l->is_game()) { for (auto lc : l->clients) { if (lc && (lc != a.c)) { - std::string name = lc->character_file()->disp.name.decode(lc->language()); + std::string name = lc->character_file()->disp.visual.name.decode(lc->language()); send_text_message_fmt(a.c, "$C6{}$C7 {:X}:{}", name, lc->floor, FloorDefinition::get(l->episode, lc->floor).short_name); } diff --git a/src/ChoiceSearch.cc b/src/ChoiceSearch.cc index bae1d078..5d45cd79 100644 --- a/src/ChoiceSearch.cc +++ b/src/ChoiceSearch.cc @@ -78,13 +78,13 @@ const std::vector CHOICE_SEARCH_CATEGORIES({ case 0x0000: return true; case 0x0010: - return target_c->character_file()->disp.visual.class_flags & 0x20; + return target_c->character_file()->disp.visual.sh.class_flags & 0x20; case 0x0011: - return target_c->character_file()->disp.visual.class_flags & 0x40; + return target_c->character_file()->disp.visual.sh.class_flags & 0x40; case 0x0012: - return target_c->character_file()->disp.visual.class_flags & 0x80; + return target_c->character_file()->disp.visual.sh.class_flags & 0x80; default: - return ((choice_id - 1) == target_c->character_file()->disp.visual.char_class); + return ((choice_id - 1) == target_c->character_file()->disp.visual.sh.char_class); } }, }, diff --git a/src/Client.cc b/src/Client.cc index 441b7134..09601ae8 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -242,7 +242,7 @@ void Client::update_channel_name() { auto player = this->character_file(false, false); if (player) { this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}", - this->id, player->disp.name.decode(this->language()), player->disp.stats.level + 1, default_name); + this->id, player->disp.visual.name.decode(this->language()), player->disp.stats.level + 1, default_name); } else { this->channel->name = std::format("C-{:X} @ {}", this->id, default_name); } @@ -349,7 +349,7 @@ std::shared_ptr Client::team() const { // The team membership is valid, but the player name may be different; update the team membership if needed if (p) { auto& m = member_it->second; - std::string name = p->disp.name.decode(this->language()); + std::string name = p->disp.visual.name.decode(this->language()); if (m.name != name) { this->log.info_f("Updating player name in team config"); s->team_index->update_member_name(this->login->account->account_id, name); @@ -625,10 +625,10 @@ void Client::save_character_file() { void Client::create_character_file( uint32_t guild_card_number, Language language, - const PlayerDispDataBBPreview& preview, + const PlayerVisualConfigV4& visual, std::shared_ptr level_table) { this->log.info_f("Creating new character file"); - this->character_data = PSOBBCharacterFile::create_from_preview(guild_card_number, language, preview, level_table); + this->character_data = PSOBBCharacterFile::create_from_config(guild_card_number, language, visual, level_table); this->save_character_file(); this->log.info_f("Deleting bank file"); this->bank_data.reset(); @@ -654,7 +654,7 @@ void Client::create_battle_overlay(std::shared_ptr rules, std this->overlay_character_data->inventory.tp_from_materials = 0; uint32_t target_level = std::clamp(rules->char_level, 0, 199); - uint8_t char_class = this->overlay_character_data->disp.visual.char_class; + uint8_t char_class = this->overlay_character_data->disp.visual.sh.char_class; auto& stats = this->overlay_character_data->disp.stats; level_table->reset_to_base(stats, char_class); @@ -687,7 +687,7 @@ void Client::create_battle_overlay(std::shared_ptr rules, std void Client::create_challenge_overlay( Version version, size_t template_index, std::shared_ptr level_table) { auto p = this->character_file(true, false); - const auto& tpl = get_challenge_template_definition(version, p->disp.visual.class_flags, template_index); + const auto& tpl = get_challenge_template_definition(version, p->disp.visual.sh.class_flags, template_index); this->overlay_character_data = std::make_shared(*p); auto overlay = this->overlay_character_data; @@ -704,11 +704,11 @@ void Client::create_challenge_overlay( overlay->inventory.items[13].extension_data2 = 1; - level_table->reset_to_base(overlay->disp.stats, overlay->disp.visual.char_class); - level_table->advance_to_level(overlay->disp.stats, tpl.level, overlay->disp.visual.char_class); + level_table->reset_to_base(overlay->disp.stats, overlay->disp.visual.sh.char_class); + level_table->advance_to_level(overlay->disp.stats, tpl.level, overlay->disp.visual.sh.char_class); const auto& stats_delta = level_table->stats_delta_for_level( - overlay->disp.visual.char_class, overlay->disp.stats.level); + overlay->disp.visual.sh.char_class, overlay->disp.stats.level); overlay->disp.stats.esp = 40; overlay->disp.stats.attack_range = 10.0; overlay->disp.stats.exp = stats_delta.exp; @@ -968,12 +968,12 @@ void Client::load_all_files() { 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->login->account->account_id; - this->character_data->guild_card.name = nsc_data.disp.name; + this->character_data->guild_card.name = nsc_data.disp.visual.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->guild_card.section_id = nsc_data.disp.visual.sh.section_id; + this->character_data->guild_card.char_class = nsc_data.disp.visual.sh.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; @@ -1023,7 +1023,7 @@ void Client::load_all_files() { if (this->character_data) { // Clear legacy play_time field - this->character_data->disp.name.clear_after_bytes(0x18); + this->character_data->disp.visual.name.clear_after_bytes(0x18); this->character_data->inventory.enforce_stack_limits(stack_limits); this->login->account->auto_reply_message = this->character_data->auto_reply.decode(); this->login->account->save(); diff --git a/src/Client.hh b/src/Client.hh index 56f37535..ac20e65a 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -187,7 +187,7 @@ public: }; bool should_update_play_time; std::unordered_set blocked_senders; - std::unique_ptr v1_v2_last_reported_disp; + std::unique_ptr v1_v2_last_reported_disp; std::shared_ptr last_reported_6x70; std::unordered_set expected_game_state_sync_commands; // (command_num << 8) | target_client_id // These are null unless the client is within the trade sequence (D0-D4 or EE commands) @@ -312,7 +312,7 @@ public: void create_character_file( uint32_t guild_card_number, Language language, - const PlayerDispDataBBPreview& preview, + const PlayerVisualConfigV4& visual, std::shared_ptr level_table); void create_battle_overlay(std::shared_ptr rules, std::shared_ptr level_table); void create_challenge_overlay(Version version, size_t template_index, std::shared_ptr level_table); diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 6bdafa01..a39d07b3 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -412,18 +412,18 @@ struct S_UpdateClientConfig_DC_PC_04 { le_uint32_t guild_card_number = 0; } __packed_ws__(S_UpdateClientConfig_DC_PC_04, 8); -struct S_UpdateClientConfig_V3_04 { +template +struct S_UpdateClientConfigT_V3_BB_04 { le_uint32_t player_tag = 0x00010000; le_uint32_t guild_card_number = 0; // This field is opaque to the client; it will send back the contents verbatim in subsequent 9E or 9F commands. - parray client_config; -} __packed_ws__(S_UpdateClientConfig_V3_04, 0x28); + parray client_config; +} __attribute__((packed)); -struct S_UpdateClientConfig_BB_04 { - le_uint32_t player_tag = 0x00010000; - le_uint32_t guild_card_number = 0; - parray client_config; -} __packed_ws__(S_UpdateClientConfig_BB_04, 0x30); +using S_UpdateClientConfig_V3_04 = S_UpdateClientConfigT_V3_BB_04<0x20>; +using S_UpdateClientConfig_BB_04 = S_UpdateClientConfigT_V3_BB_04<0x28>; +check_struct_size(S_UpdateClientConfig_V3_04, 0x28); +check_struct_size(S_UpdateClientConfig_BB_04, 0x30); // 05: Disconnect // Internal name: SndLogout @@ -1053,13 +1053,13 @@ struct PlayerRecordsEntry_BB { struct C_CharacterData_DCv1_61_98 { /* 0000 */ PlayerInventory inventory; - /* 034C */ PlayerDispDataDCPCV3 disp; + /* 034C */ PlayerDispDataV123 disp; /* 041C */ } __packed_ws__(C_CharacterData_DCv1_61_98, 0x041C); struct C_CharacterData_DCv2_61_98 { /* 0000 */ PlayerInventory inventory; - /* 034C */ PlayerDispDataDCPCV3 disp; + /* 034C */ PlayerDispDataV123 disp; /* 041C */ PlayerRecordsEntry_DC records; /* 04D8 */ ChoiceSearchConfig choice_search_config; /* 04F0 */ @@ -1067,7 +1067,7 @@ struct C_CharacterData_DCv2_61_98 { struct C_CharacterData_PC_61_98 { /* 0000 */ PlayerInventory inventory; - /* 034C */ PlayerDispDataDCPCV3 disp; + /* 034C */ PlayerDispDataV123 disp; /* 041C */ PlayerRecordsEntry_PC records; /* 0510 */ ChoiceSearchConfig choice_search_config; /* 0528 */ parray blocked_senders; @@ -1079,7 +1079,7 @@ struct C_CharacterData_PC_61_98 { struct C_CharacterData_GCNTE_61_98 { /* 0000 */ PlayerInventory inventory; - /* 034C */ PlayerDispDataDCPCV3 disp; + /* 034C */ PlayerDispDataV123 disp; /* 041C */ PlayerRecordsEntry_DC records; /* 04D8 */ ChoiceSearchConfig choice_search_config; /* 04F0 */ parray blocked_senders; @@ -1091,7 +1091,7 @@ struct C_CharacterData_GCNTE_61_98 { struct C_CharacterData_V3_61_98 { /* 0000 */ PlayerInventory inventory; - /* 034C */ PlayerDispDataDCPCV3 disp; + /* 034C */ PlayerDispDataV123 disp; /* 041C */ PlayerRecordsEntry_V3 records; /* 0538 */ ChoiceSearchConfig choice_search_config; /* 0550 */ pstring info_board; @@ -1104,7 +1104,7 @@ struct C_CharacterData_V3_61_98 { struct C_CharacterData_Ep3_61_98 { /* 0000 */ PlayerInventory inventory; - /* 034C */ PlayerDispDataDCPCV3 disp; + /* 034C */ PlayerDispDataV123 disp; /* 041C */ PlayerRecordsEntry_V3 records; /* 0538 */ ChoiceSearchConfig choice_search_config; /* 0550 */ pstring info_board; @@ -1117,7 +1117,7 @@ struct C_CharacterData_Ep3_61_98 { struct C_CharacterData_BB_61_98 { /* 0000 */ PlayerInventory inventory; - /* 034C */ PlayerDispDataBB disp; + /* 034C */ PlayerDispDataV4 disp; /* 04DC */ PlayerRecordsEntry_BB records; /* 0638 */ ChoiceSearchConfig choice_search_config; /* 0650 */ pstring info_board; @@ -1215,7 +1215,7 @@ struct S_JoinGame_Ep3_64 : S_JoinGame_GC_64 { // four of these are always present and they are filled in in slot positions. struct Ep3PlayerEntry { PlayerInventory inventory; - PlayerDispDataDCPCV3 disp; + PlayerDispDataV123 disp; } __packed_ws__(Ep3PlayerEntry, 0x41C); parray players_ep3; } __packed_ws__(S_JoinGame_Ep3_64, 0x1180); @@ -1292,10 +1292,10 @@ struct S_JoinLobbyT { return offsetof(S_JoinLobbyT, entries) + used_entries * sizeof(Entry); } } __attribute__((packed)); -using S_JoinLobby_DCNTE_65_67_68 = S_JoinLobbyT; -using S_JoinLobby_PC_65_67_68 = S_JoinLobbyT; -using S_JoinLobby_DC_GC_65_67_68_Ep3_EB = S_JoinLobbyT; -using S_JoinLobby_BB_65_67_68 = S_JoinLobbyT; +using S_JoinLobby_DCNTE_65_67_68 = S_JoinLobbyT; +using S_JoinLobby_PC_65_67_68 = S_JoinLobbyT; +using S_JoinLobby_DC_GC_65_67_68_Ep3_EB = S_JoinLobbyT; +using S_JoinLobby_BB_65_67_68 = S_JoinLobbyT; check_struct_size(S_JoinLobby_DCNTE_65_67_68, 0x32D4); check_struct_size(S_JoinLobby_PC_65_67_68, 0x339C); check_struct_size(S_JoinLobby_DC_GC_65_67_68_Ep3_EB, 0x32DC); @@ -1307,7 +1307,7 @@ struct S_JoinLobby_XB_65_67_68 { struct Entry { PlayerLobbyDataXB lobby_data; PlayerInventory inventory; - PlayerDispDataDCPCV3 disp; + PlayerDispDataV123 disp; } __packed_ws__(Entry, 0x468); // Note: not all of these will be filled in and sent if the lobby isn't full (the command size will be shorter than // this struct's size) @@ -2948,7 +2948,7 @@ struct S_CardBattleTableConfirmation_Ep3_E5 { struct SC_PlayerPreview_CreateCharacter_BB_00E5 { le_int32_t character_index = 0; - PlayerDispDataBBPreview preview; + PlayerDispDataV4Preview preview; } __packed_ws__(SC_PlayerPreview_CreateCharacter_BB_00E5, 0x80); // E6 (C->S): Spectator team control (Episode 3) @@ -3034,7 +3034,7 @@ struct S_JoinSpectatorTeam_Ep3_E8 { struct PlayerEntry { /* 0000 */ PlayerLobbyDataDCGC lobby_data; /* 0020 */ PlayerInventory inventory; - /* 036C */ PlayerDispDataDCPCV3 disp; + /* 036C */ PlayerDispDataV123 disp; /* 043C */ } __packed_ws__(PlayerEntry, 0x43C); /* 0080 */ parray players; @@ -4761,7 +4761,7 @@ struct G_SyncPlayerDispAndInventory_DCNTE_6x70 { /* 0054 */ PlayerHoldState_DCProtos hold_state; /* 0064 */ le_uint32_t area = 0; /* 0068 */ le_uint32_t player_flags = 0; - /* 006C */ PlayerVisualConfig visual; + /* 006C */ PlayerVisualConfigV123 visual; /* 00BC */ PlayerStats stats; /* 00E0 */ le_uint32_t num_items = 0; /* 00E4 */ parray items; @@ -4779,7 +4779,7 @@ struct G_SyncPlayerDispAndInventory_DC112000_6x70 { /* 0060 */ PlayerHoldState_DCProtos hold_state; /* 0070 */ le_uint32_t area = 0; /* 0074 */ le_uint32_t player_flags = 0; - /* 0078 */ PlayerVisualConfig visual; + /* 0078 */ PlayerVisualConfigV123 visual; /* 00C8 */ PlayerStats stats; /* 00EC */ le_uint32_t num_items = 0; /* 00F0 */ parray items; @@ -4810,13 +4810,13 @@ struct G_6x70_Base_V1 { /* 00AC */ le_uint32_t area = 0; /* 00B0 */ le_uint32_t player_flags = 0; /* 00B4 */ parray technique_levels_v1 = 0xFF; // Last byte is uninitialized - /* 00C8 */ PlayerVisualConfig visual; - /* 0118 */ -} __packed_ws__(G_6x70_Base_V1, 0x118); + /* 00C8 */ +} __packed_ws__(G_6x70_Base_V1, 0xC8); struct G_SyncPlayerDispAndInventory_DC_PC_6x70 { /* 0000 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_DC_PC_6x70)}; /* 0008 */ G_6x70_Base_V1 base; + /* 00D0 */ PlayerVisualConfigV123 visual; /* 0120 */ PlayerStats stats; /* 0144 */ le_uint32_t num_items = 0; /* 0148 */ parray items; @@ -4827,6 +4827,7 @@ struct G_SyncPlayerDispAndInventory_DC_PC_6x70 { struct G_SyncPlayerDispAndInventory_GC_6x70 { /* 0000 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_GC_6x70)}; /* 0008 */ G_6x70_Base_V1 base; + /* 00D0 */ PlayerVisualConfigV123 visual; /* 0120 */ PlayerStats stats; /* 0144 */ le_uint32_t num_items = 0; /* 0148 */ parray items; @@ -4837,6 +4838,7 @@ struct G_SyncPlayerDispAndInventory_GC_6x70 { struct G_SyncPlayerDispAndInventory_XB_6x70 { /* 0000 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_XB_6x70)}; /* 0008 */ G_6x70_Base_V1 base; + /* 00D0 */ PlayerVisualConfigV123 visual; /* 0120 */ PlayerStats stats; /* 0144 */ le_uint32_t num_items = 0; /* 0148 */ parray items; @@ -4850,7 +4852,7 @@ struct G_SyncPlayerDispAndInventory_XB_6x70 { struct G_SyncPlayerDispAndInventory_BB_6x70 { /* 0000 */ G_ExtendedHeaderT header = {{0x70, 0x00, 0x0000}, sizeof(G_SyncPlayerDispAndInventory_BB_6x70)}; /* 0008 */ G_6x70_Base_V1 base; - /* 0120 */ pstring name; + /* 00D0 */ PlayerVisualConfigV4 visual; /* 0140 */ PlayerStats stats; /* 0164 */ le_uint32_t num_items = 0; /* 0168 */ parray items; diff --git a/src/DownloadSession.cc b/src/DownloadSession.cc index 36794bc3..93a708b1 100644 --- a/src/DownloadSession.cc +++ b/src/DownloadSession.cc @@ -149,7 +149,7 @@ void DownloadSession::send_93_9D_9E(bool extended) { ret.serial_number.encode(std::format("{:08X}", this->serial_number)); ret.access_key.encode(this->access_key); ret.serial_number2.encode(std::format("{:08X}", this->serial_number2)); - ret.login_character_name.encode(this->character->disp.name.decode()); + ret.login_character_name.encode(this->character->disp.visual.name.decode()); this->channel->send(0x93, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_LoginV1_DC_93)); } else if (is_v2(this->version)) { @@ -164,7 +164,7 @@ void DownloadSession::send_93_9D_9E(bool extended) { ret.access_key.encode(this->access_key); ret.serial_number2 = ret.serial_number; ret.access_key2 = ret.access_key; - ret.login_character_name.encode(this->character->disp.name.decode()); + ret.login_character_name.encode(this->character->disp.visual.name.decode()); size_t data_size = extended ? ((this->version == Version::PC_V2) ? sizeof(ret) : sizeof(C_LoginExtended_DC_GC_9D)) : sizeof(C_Login_DC_PC_GC_9D); @@ -182,7 +182,7 @@ void DownloadSession::send_93_9D_9E(bool extended) { ret.access_key.encode(this->access_key); ret.serial_number2 = ret.serial_number; ret.access_key2 = ret.access_key; - ret.login_character_name.encode(this->character->disp.name.decode()); + ret.login_character_name.encode(this->character->disp.visual.name.decode()); ret.client_config = this->client_config; this->channel->send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_PC_GC_9E)); @@ -198,7 +198,7 @@ void DownloadSession::send_93_9D_9E(bool extended) { ret.access_key.encode(std::format("{:016X}", this->xb_user_id)); ret.serial_number2 = ret.serial_number; ret.access_key2 = ret.access_key; - ret.login_character_name.encode(this->character->disp.name.decode()); + ret.login_character_name.encode(this->character->disp.visual.name.decode()); ret.xb_netloc.internal_ipv4_address = phosg::random_object(); ret.xb_netloc.external_ipv4_address = phosg::random_object(); ret.xb_netloc.port = 9500; @@ -222,13 +222,13 @@ void DownloadSession::send_61_98(bool is_98) { if (is_v1(this->version)) { C_CharacterData_DCv1_61_98 ret; ret.inventory = this->character->inventory; - ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); + ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); this->channel->send(command, 0x01, ret); } else if (this->version == Version::DC_V2) { C_CharacterData_DCv2_61_98 ret; ret.inventory = this->character->inventory; - ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); + ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); ret.records.challenge = this->character->challenge_records; ret.records.battle = this->character->battle_records; ret.choice_search_config = this->character->choice_search_config; @@ -237,7 +237,7 @@ void DownloadSession::send_61_98(bool is_98) { } else if (this->version == Version::PC_V2) { C_CharacterData_PC_61_98 ret; ret.inventory = this->character->inventory; - ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); + ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); ret.records.challenge = this->character->challenge_records; ret.records.battle = this->character->battle_records; ret.choice_search_config = this->character->choice_search_config; @@ -246,7 +246,7 @@ void DownloadSession::send_61_98(bool is_98) { } else if (is_v3(this->version)) { C_CharacterData_V3_61_98 ret; ret.inventory = this->character->inventory; - ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); + ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); ret.records.challenge = this->character->challenge_records; ret.records.battle = this->character->battle_records; ret.choice_search_config = this->character->choice_search_config; diff --git a/src/Episode3/BattleRecord.cc b/src/Episode3/BattleRecord.cc index 0c0fc769..f3ecb853 100644 --- a/src/Episode3/BattleRecord.cc +++ b/src/Episode3/BattleRecord.cc @@ -170,7 +170,7 @@ std::string BattleRecord::serialize() const { void BattleRecord::add_player( const PlayerLobbyDataDCGC& lobby_data, const PlayerInventory& inventory, - const PlayerDispDataDCPCV3& disp, + const PlayerDispDataV123& disp, uint32_t level) { if (!this->is_writable) { throw std::logic_error("cannot write to battle record"); diff --git a/src/Episode3/BattleRecord.hh b/src/Episode3/BattleRecord.hh index 23a23730..ba37749b 100644 --- a/src/Episode3/BattleRecord.hh +++ b/src/Episode3/BattleRecord.hh @@ -22,7 +22,7 @@ public: struct PlayerEntry { PlayerLobbyDataDCGC lobby_data; PlayerInventory inventory; - PlayerDispDataDCPCV3 disp; + PlayerDispDataV123 disp; le_uint32_t level; void print(FILE* stream) const; @@ -85,7 +85,7 @@ public: void add_player( const PlayerLobbyDataDCGC& lobby_data, const PlayerInventory& inventory, - const PlayerDispDataDCPCV3& disp, + const PlayerDispDataV123& disp, uint32_t level); void delete_player(uint8_t client_id); void add_command(Event::Type type, const void* data, size_t size); diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index 41ce2911..37ca8dfe 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -905,7 +905,7 @@ struct PlayerConfig { /* 2270:211C */ be_uint32_t unknown_t3; // This visual config is copied to the player's main visual config when the player's name or proportions have // changed, or when certain buttons on the controller (L, R, X, Y) are held at game start time. - /* 2274:2120 */ PlayerVisualConfig backup_visual; + /* 2274:2120 */ PlayerVisualConfigV123 backup_visual; /* 22C4:2170 */ parray unknown_a14; /* 2350:21FC */ diff --git a/src/Episode3/Tournament.cc b/src/Episode3/Tournament.cc index d51a86a5..cf0a91cc 100644 --- a/src/Episode3/Tournament.cc +++ b/src/Episode3/Tournament.cc @@ -15,7 +15,7 @@ Tournament::PlayerEntry::PlayerEntry(uint32_t account_id, const std::string& pla Tournament::PlayerEntry::PlayerEntry(std::shared_ptr c) : account_id(c->login->account->account_id), client(c), - player_name(c->character_file()->disp.name.decode(c->language())) {} + player_name(c->character_file()->disp.visual.name.decode(c->language())) {} Tournament::PlayerEntry::PlayerEntry(std::shared_ptr com_deck) : account_id(0), com_deck(com_deck) {} diff --git a/src/GameServer.cc b/src/GameServer.cc index 9711d73f..14bf5fbe 100644 --- a/src/GameServer.cc +++ b/src/GameServer.cc @@ -93,7 +93,7 @@ std::vector> GameServer::get_clients_by_identifier(const } auto p = c->character_file(false, false); - if (p && p->disp.name.eq(ident, p->inventory.language)) { + if (p && p->disp.visual.name.eq(ident, p->inventory.language)) { results.emplace_back(c); continue; } diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index 0309503b..20072588 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -160,22 +160,22 @@ HTTPServer::HTTPServer(std::shared_ptr state) client_json.emplace("TechniqueLevels", std::move(tech_levels_json)); } client_json.emplace("Level", p->disp.stats.level.load() + 1); - client_json.emplace("NameColor", p->disp.visual.name_color.load()); - client_json.emplace("ExtraModel", (p->disp.visual.validation_flags & 2) ? p->disp.visual.extra_model : phosg::JSON(nullptr)); - client_json.emplace("SectionID", name_for_section_id(p->disp.visual.section_id)); - client_json.emplace("CharClass", name_for_char_class(p->disp.visual.char_class)); - client_json.emplace("Costume", p->disp.visual.costume.load()); - client_json.emplace("Skin", p->disp.visual.skin.load()); - client_json.emplace("Face", p->disp.visual.face.load()); - client_json.emplace("Head", p->disp.visual.head.load()); - client_json.emplace("Hair", p->disp.visual.hair.load()); - client_json.emplace("HairR", p->disp.visual.hair_r.load()); - client_json.emplace("HairG", p->disp.visual.hair_g.load()); - client_json.emplace("HairB", p->disp.visual.hair_b.load()); - client_json.emplace("ProportionX", p->disp.visual.proportion_x.load()); - client_json.emplace("ProportionY", p->disp.visual.proportion_y.load()); + client_json.emplace("NameColor", p->disp.visual.sh.name_color.load()); + client_json.emplace("ExtraModel", (p->disp.visual.sh.validation_flags & 2) ? p->disp.visual.sh.extra_model : phosg::JSON(nullptr)); + client_json.emplace("SectionID", name_for_section_id(p->disp.visual.sh.section_id)); + client_json.emplace("CharClass", name_for_char_class(p->disp.visual.sh.char_class)); + client_json.emplace("Costume", p->disp.visual.sh.costume.load()); + client_json.emplace("Skin", p->disp.visual.sh.skin.load()); + client_json.emplace("Face", p->disp.visual.sh.face.load()); + client_json.emplace("Head", p->disp.visual.sh.head.load()); + client_json.emplace("Hair", p->disp.visual.sh.hair.load()); + client_json.emplace("HairR", p->disp.visual.sh.hair_r.load()); + client_json.emplace("HairG", p->disp.visual.sh.hair_g.load()); + client_json.emplace("HairB", p->disp.visual.sh.hair_b.load()); + client_json.emplace("ProportionX", p->disp.visual.sh.proportion_x.load()); + client_json.emplace("ProportionY", p->disp.visual.sh.proportion_y.load()); - client_json.emplace("Name", p->disp.name.decode(c->language())); + client_json.emplace("Name", p->disp.visual.name.decode(c->language())); client_json.emplace("PlayTimeSeconds", p->play_time_seconds.load()); client_json.emplace("AutoReply", p->auto_reply.decode(c->language())); @@ -589,12 +589,12 @@ HTTPServer::HTTPServer(std::shared_ptr state) clients_json.emplace_back(phosg::JSON::dict({ {"ID", c->id}, {"AccountID", c->login ? c->login->account->account_id : phosg::JSON(nullptr)}, - {"Name", p ? p->disp.name.decode(c->language()) : phosg::JSON(nullptr)}, + {"Name", p ? p->disp.visual.name.decode(c->language()) : phosg::JSON(nullptr)}, {"Version", phosg::name_for_enum(c->version())}, {"Language", name_for_language(c->language())}, {"Level", p ? (p->disp.stats.level + 1) : phosg::JSON(nullptr)}, - {"Class", p ? name_for_char_class(p->disp.visual.char_class) : phosg::JSON(nullptr)}, - {"SectionID", p ? name_for_section_id(p->disp.visual.section_id) : phosg::JSON(nullptr)}, + {"Class", p ? name_for_char_class(p->disp.visual.sh.char_class) : phosg::JSON(nullptr)}, + {"SectionID", p ? name_for_section_id(p->disp.visual.sh.section_id) : phosg::JSON(nullptr)}, {"LobbyID", l ? l->lobby_id : phosg::JSON(nullptr)}, {"IsOnProxy", c->proxy_session ? true : false}, })); diff --git a/src/Items.cc b/src/Items.cc index b1e67cef..26230c6b 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -22,7 +22,7 @@ void player_use_item(std::shared_ptr c, size_t item_index, std::shared_p } else if ((primary_identifier & 0xFFFF0000) == 0x03020000) { // Technique disk auto item_parameter_table = s->item_parameter_table(c->version()); - uint8_t max_level = item_parameter_table->get_max_tech_level(player->disp.visual.char_class, item.data.data1[4]); + uint8_t max_level = item_parameter_table->get_max_tech_level(player->disp.visual.sh.char_class, item.data.data1[4]); if (item.data.data1[2] > max_level) { throw std::runtime_error("technique level too high"); } @@ -135,10 +135,10 @@ void player_use_item(std::shared_ptr c, size_t item_index, std::shared_p if (evolution_number < 4) { switch (item.data.data1[2]) { case 0x00: // Cell of MAG 502 - mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x1D : 0x21; + mag.data.data1[1] = (player->disp.visual.sh.section_id & 1) ? 0x1D : 0x21; break; case 0x01: // Cell of MAG 213 - mag.data.data1[1] = (player->disp.visual.section_id & 1) ? 0x27 : 0x22; + mag.data.data1[1] = (player->disp.visual.sh.section_id & 1) ? 0x27 : 0x22; break; case 0x02: // Parts of RoboChao mag.data.data1[1] = 0x28; @@ -215,7 +215,7 @@ void player_use_item(std::shared_ptr c, size_t item_index, std::shared_p try { auto item_parameter_table = s->item_parameter_table(c->version()); const auto& combo = item_parameter_table->get_item_combination(item.data, inv_item.data); - if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.char_class) { + if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.sh.char_class) { throw std::runtime_error("item combination requires specific char_class"); } if (combo.mag_level != 0xFF) { @@ -490,7 +490,7 @@ void player_feed_mag(std::shared_ptr c, size_t mag_item_index, size_t fe player->inventory.items[fed_item_index].data, s->item_parameter_table(c->version()), s->mag_evolution_table(c->version()), - player->disp.visual.char_class, - player->disp.visual.section_id, + player->disp.visual.sh.char_class, + player->disp.visual.sh.section_id, !is_v1_or_v2(c->version())); } diff --git a/src/Lobby.cc b/src/Lobby.cc index b1e55bf8..f9b980e2 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -242,7 +242,7 @@ uint8_t Lobby::effective_section_id() const { } auto leader = this->clients.at(this->leader_id); if (leader) { - return leader->character_file()->disp.visual.section_id; + return leader->character_file()->disp.visual.sh.section_id; } return 0xFF; } @@ -459,11 +459,11 @@ void Lobby::add_client(std::shared_ptr c, ssize_t required_client_id) { PlayerLobbyDataDCGC lobby_data; lobby_data.player_tag = 0x00010000; lobby_data.guild_card_number = c->login->account->account_id; - lobby_data.name.encode(p->disp.name.decode(c->language()), c->language()); + lobby_data.name.encode(p->disp.visual.name.decode(c->language()), c->language()); this->battle_record->add_player( lobby_data, p->inventory, - p->disp.to_dcpcv3(c->language(), c->language()), + p->disp.to_v123(c->language(), c->language()), c->ep3_config ? (c->ep3_config->online_clv_exp / 100) : 0); } @@ -591,7 +591,7 @@ std::shared_ptr Lobby::find_client(const std::string* identifier, uint64 if (account_id && lc->login && (lc->login->account->account_id == account_id)) { return lc; } - if (identifier && (lc->character_file()->disp.name.eq(*identifier, lc->language()))) { + if (identifier && (lc->character_file()->disp.visual.name.eq(*identifier, lc->language()))) { return lc; } } diff --git a/src/Map.cc b/src/Map.cc index 6b2751a7..3d6760d1 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -2890,7 +2890,7 @@ static const std::vector dat_enemy_definitions({ // Delsaber. Params: // param1 = jump distance delta (value used is param1 + 100) - // param2 = prejudice flag (these correspond to the bits in PlayerVisualConfig::class_flags): + // param2 = prejudice flag (these correspond to the bits in PlayerVisualConfigV123T::class_flags): // 0 = males // 1 = females // 2 = humans diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 6d0d90e8..fd492a98 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -20,26 +20,26 @@ #include "Text.hh" #include "Version.hh" -void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) { - this->visual.name_color = pre.visual.name_color; - this->visual.extra_model = pre.visual.extra_model; - this->visual.name_color_checksum = pre.visual.name_color_checksum; - this->visual.section_id = pre.visual.section_id; - this->visual.char_class = pre.visual.char_class; - this->visual.validation_flags = pre.visual.validation_flags; - this->visual.version = pre.visual.version; - this->visual.class_flags = pre.visual.class_flags; - this->visual.costume = pre.visual.costume; - this->visual.skin = pre.visual.skin; - this->visual.face = pre.visual.face; - this->visual.head = pre.visual.head; - this->visual.hair = pre.visual.hair; - this->visual.hair_r = pre.visual.hair_r; - this->visual.hair_g = pre.visual.hair_g; - this->visual.hair_b = pre.visual.hair_b; - this->visual.proportion_x = pre.visual.proportion_x; - this->visual.proportion_y = pre.visual.proportion_y; - this->name = pre.name; +void PlayerVisualConfigV4::apply_dressing_room(const PlayerVisualConfigV4& new_visual) { + this->sh.name_color = new_visual.sh.name_color; + this->sh.extra_model = new_visual.sh.extra_model; + this->sh.name_color_checksum = new_visual.sh.name_color_checksum; + this->sh.section_id = new_visual.sh.section_id; + this->sh.char_class = new_visual.sh.char_class; + this->sh.validation_flags = new_visual.sh.validation_flags; + this->sh.version = new_visual.sh.version; + this->sh.class_flags = new_visual.sh.class_flags; + this->sh.costume = new_visual.sh.costume; + this->sh.skin = new_visual.sh.skin; + this->sh.face = new_visual.sh.face; + this->sh.head = new_visual.sh.head; + this->sh.hair = new_visual.sh.hair; + this->sh.hair_r = new_visual.sh.hair_r; + this->sh.hair_g = new_visual.sh.hair_g; + this->sh.hair_b = new_visual.sh.hair_b; + this->sh.proportion_x = new_visual.sh.proportion_x; + this->sh.proportion_y = new_visual.sh.proportion_y; + this->name = new_visual.name; } void GuildCardBB::clear() { diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 9704112c..a8b59daf 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -23,64 +23,64 @@ class Client; class ItemParameterTable; -struct PlayerDispDataBB; +struct PlayerDispDataV4; +struct PlayerVisualConfigV4; template -struct PlayerVisualConfigT { - /* 00 */ pstring name; - /* 10 */ parray unknown_a2; - /* 18 */ U32T name_color = 0xFFFFFFFF; // ARGB - /* 1C */ uint8_t extra_model = 0; +struct PlayerVisualConfigSharedT { + /* 00 */ parray unknown_a2; + /* 08 */ U32T name_color = 0xFFFFFFFF; // ARGB + /* 0C */ uint8_t extra_model = 0; // Some NPCs can crash the client if the character's class is incorrect. To handle this, we save the affected fields // in the unused bytes after extra_model. This is a newserv-specific extension; it appears the following 15 bytes // were simply never used by Sega. - /* 1D */ uint8_t npc_saved_data_type = 0; - /* 1E */ uint8_t npc_saved_costume = 0; - /* 1F */ uint8_t npc_saved_skin = 0; - /* 20 */ uint8_t npc_saved_face = 0; - /* 21 */ uint8_t npc_saved_head = 0; - /* 22 */ uint8_t npc_saved_hair = 0; - /* 23 */ uint8_t npc_saved_hair_r = 0; - /* 24 */ uint8_t npc_saved_hair_g = 0; - /* 25 */ uint8_t npc_saved_hair_b = 0; - /* 26 */ parray unused; - /* 28 */ F32T npc_saved_proportion_y = 0.0; + /* 0D */ uint8_t npc_saved_data_type = 0; + /* 0E */ uint8_t npc_saved_costume = 0; + /* 0F */ uint8_t npc_saved_skin = 0; + /* 10 */ uint8_t npc_saved_face = 0; + /* 11 */ uint8_t npc_saved_head = 0; + /* 12 */ uint8_t npc_saved_hair = 0; + /* 13 */ uint8_t npc_saved_hair_r = 0; + /* 14 */ uint8_t npc_saved_hair_g = 0; + /* 15 */ uint8_t npc_saved_hair_b = 0; + /* 16 */ parray unused; + /* 18 */ F32T npc_saved_proportion_y = 0.0; // See compute_name_color_checksum for details on how this is computed. If the value is incorrect, V1 and V2 will // ignore the name_color field and use the default color instead. This field is ignored on GC; on BB (and presumably // Xbox), if this has a nonzero value, the "Change Name" option appears in the character selection menu. - /* 2C */ U32T name_color_checksum = 0; - /* 30 */ uint8_t section_id = 0; - /* 31 */ uint8_t char_class = 0; + /* 1C */ U32T name_color_checksum = 0; + /* 20 */ uint8_t section_id = 0; + /* 21 */ uint8_t char_class = 0; // validation_flags specifies that some parts of this structure are not valid and should be ignored. The bits are: // -----FCS // F = class_flags is incorrect for the character's char_class value // C = char_class is out of range // S = section_id is out of range - /* 32 */ uint8_t validation_flags = 0; - /* 33 */ uint8_t version = 0; + /* 22 */ uint8_t validation_flags = 0; + /* 23 */ uint8_t version = 0; // class_flags specifies features of the character's class. The bits are: // -------- -------- -------- FRHANMfm // F = force, R = ranger, H = hunter // A = android, N = newman, M = human // f = female, m = male - // Enemies also have a class_flags field, though it isn't part of PlayerVisualConfig. The bits for enemies are: + // Enemies also have a class_flags field, though it isn't part of PlayerVisualConfigV123T. The bits for enemies are: // -------- -------- -------- ----DMAN // D = Dark attribute // M = Machine attribute // A = Altered Beast attribute // N = Native attribute - /* 34 */ U32T class_flags = 0; - /* 38 */ U16T costume = 0; - /* 3A */ U16T skin = 0; - /* 3C */ U16T face = 0; - /* 3E */ U16T head = 0; - /* 40 */ U16T hair = 0; - /* 42 */ U16T hair_r = 0; - /* 44 */ U16T hair_g = 0; - /* 46 */ U16T hair_b = 0; - /* 48 */ F32T proportion_x = 0.0; - /* 4C */ F32T proportion_y = 0.0; - /* 50 */ + /* 24 */ U32T class_flags = 0; + /* 28 */ U16T costume = 0; + /* 2A */ U16T skin = 0; + /* 2C */ U16T face = 0; + /* 2E */ U16T head = 0; + /* 30 */ U16T hair = 0; + /* 32 */ U16T hair_r = 0; + /* 34 */ U16T hair_g = 0; + /* 36 */ U16T hair_b = 0; + /* 38 */ F32T proportion_x = 0.0; + /* 3C */ F32T proportion_y = 0.0; + /* 40 */ static uint32_t compute_name_color_checksum(uint32_t name_color) { uint8_t x = (phosg::random_object() % 0xFF) + 1; @@ -283,12 +283,10 @@ struct PlayerVisualConfigT { this->name_color_checksum = 0; } this->class_flags = class_flags_for_class(this->char_class); - this->name.clear_after_bytes(0x0C); } - operator PlayerVisualConfigT() const { - PlayerVisualConfigT ret; - ret.name = this->name; + operator PlayerVisualConfigSharedT() const { + PlayerVisualConfigSharedT ret; ret.unknown_a2 = this->unknown_a2; ret.name_color = this->name_color; ret.extra_model = this->extra_model; @@ -311,14 +309,65 @@ struct PlayerVisualConfigT { ret.proportion_y = this->proportion_y; return ret; } -} __packed_ws_be__(PlayerVisualConfigT, 0x50); -using PlayerVisualConfig = PlayerVisualConfigT; -using PlayerVisualConfigBE = PlayerVisualConfigT; +} __packed_ws_be__(PlayerVisualConfigSharedT, 0x40); template -struct PlayerDispDataDCPCV3T { +struct PlayerVisualConfigV123T { + /* 00 */ pstring name; + /* 10 */ PlayerVisualConfigSharedT sh; + /* 50 */ + + operator PlayerVisualConfigV123T() const { + PlayerVisualConfigV123T ret; + ret.name = this->name; + ret.sh = this->sh; + return ret; + } + + void enforce_lobby_join_limits_for_version(Version v) { + this->sh.enforce_lobby_join_limits_for_version(v); + this->name.clear_after_bytes(0x0C); + } + + PlayerVisualConfigV4 to_v4(Language to_language, Language from_language) const; +} __packed_ws_be__(PlayerVisualConfigV123T, 0x50); +using PlayerVisualConfigV123 = PlayerVisualConfigV123T; +using PlayerVisualConfigV123BE = PlayerVisualConfigV123T; + +struct PlayerVisualConfigV4 { + /* 00 */ pstring guild_card_number; + /* 10 */ PlayerVisualConfigSharedT sh; + /* 50 */ pstring name; + /* 70 */ + + void enforce_lobby_join_limits_for_version(Version v) { + this->sh.enforce_lobby_join_limits_for_version(v); + this->guild_card_number.clear_after_bytes(0x0C); + } + void apply_dressing_room(const PlayerVisualConfigV4& new_visual); + + template + PlayerVisualConfigV123T to_v123(Language to_language, Language from_language) const { + PlayerVisualConfigV123T ret; + ret.name.encode(this->name.decode(from_language), to_language); + ret.sh = this->sh; + return ret; + } +} __packed_ws__(PlayerVisualConfigV4, 0x70); + +template +PlayerVisualConfigV4 PlayerVisualConfigV123T::to_v4(Language to_language, Language from_language) const { + PlayerVisualConfigV4 ret; + ret.guild_card_number.encode(" 0", Language::ENGLISH); + ret.sh = this->sh; + ret.name.encode(this->name.decode(from_language), to_language); + return ret; +} + +template +struct PlayerDispDataV123T { /* 00 */ PlayerStatsT stats; - /* 24 */ PlayerVisualConfigT visual; + /* 24 */ PlayerVisualConfigV123T visual; /* 74 */ parray config; /* BC */ parray technique_levels_v1; /* D0 */ @@ -327,60 +376,50 @@ struct PlayerDispDataDCPCV3T { this->visual.enforce_lobby_join_limits_for_version(v); } - PlayerDispDataBB to_bb(Language to_language, Language from_language) const; -} __packed_ws_be__(PlayerDispDataDCPCV3T, 0xD0); -using PlayerDispDataDCPCV3 = PlayerDispDataDCPCV3T; -using PlayerDispDataDCPCV3BE = PlayerDispDataDCPCV3T; + PlayerDispDataV4 to_v4(Language to_language, Language from_language) const; +} __packed_ws_be__(PlayerDispDataV123T, 0xD0); +using PlayerDispDataV123 = PlayerDispDataV123T; +using PlayerDispDataV123BE = PlayerDispDataV123T; -struct PlayerDispDataBBPreview { +struct PlayerDispDataV4Preview { /* 00 */ le_uint32_t exp = 0; /* 04 */ le_uint32_t level = 0; // The name field in this structure is used for the player's Guild Card number, apparently (possibly because it's a // char array and this is BB) - /* 08 */ PlayerVisualConfig visual; - /* 58 */ pstring name; + /* 08 */ PlayerVisualConfigV4 visual; /* 78 */ uint32_t play_time_seconds = 0; /* 7C */ -} __packed_ws__(PlayerDispDataBBPreview, 0x7C); +} __packed_ws__(PlayerDispDataV4Preview, 0x7C); // BB player appearance and stats data -struct PlayerDispDataBB { +struct PlayerDispDataV4 { /* 0000 */ PlayerStats stats; - /* 0024 */ PlayerVisualConfig visual; - /* 0074 */ pstring name; + /* 0024 */ PlayerVisualConfigV4 visual; /* 0094 */ parray config; /* 017C */ parray technique_levels_v1; /* 0190 */ void enforce_lobby_join_limits_for_version(Version v) { - this->visual.enforce_lobby_join_limits_for_version(v); - this->name.clear_after_bytes(0x18); // 12 characters + this->visual.sh.enforce_lobby_join_limits_for_version(v); + this->visual.name.clear_after_bytes(0x18); // 12 characters } template - PlayerDispDataDCPCV3T to_dcpcv3(Language to_language, Language from_language) const { - PlayerDispDataDCPCV3T ret; + PlayerDispDataV123T to_v123(Language to_language, Language from_language) const { + PlayerDispDataV123T ret; ret.stats = this->stats; - ret.visual = this->visual; - std::string decoded_name = this->name.decode(from_language); - ret.visual.name.encode(decoded_name, to_language); + ret.visual = this->visual.to_v123(to_language, from_language); ret.config = this->config; ret.technique_levels_v1 = this->technique_levels_v1; return ret; } - - void apply_preview(const PlayerDispDataBBPreview&); - void apply_dressing_room(const PlayerDispDataBBPreview&); -} __packed_ws__(PlayerDispDataBB, 0x190); +} __packed_ws__(PlayerDispDataV4, 0x190); template -PlayerDispDataBB PlayerDispDataDCPCV3T::to_bb(Language to_language, Language from_language) const { - PlayerDispDataBB bb; +PlayerDispDataV4 PlayerDispDataV123T::to_v4(Language to_language, Language from_language) const { + PlayerDispDataV4 bb; bb.stats = this->stats; - bb.visual = this->visual; - bb.visual.name.encode(" 0"); - std::string decoded_name = this->visual.name.decode(from_language); - bb.name.encode(decoded_name, to_language); + bb.visual = this->visual.to_v4(to_language, from_language); bb.config = this->config; bb.technique_levels_v1 = this->technique_levels_v1; return bb; @@ -834,25 +873,25 @@ DestT convert_player_disp_data(const SrcT&, Language, Language) { } template <> -inline PlayerDispDataDCPCV3 convert_player_disp_data( - const PlayerDispDataDCPCV3& src, Language, Language) { +inline PlayerDispDataV123 convert_player_disp_data( + const PlayerDispDataV123& src, Language, Language) { return src; } template <> -inline PlayerDispDataDCPCV3 convert_player_disp_data( - const PlayerDispDataBB& src, Language to_language, Language from_language) { - return src.to_dcpcv3(to_language, from_language); +inline PlayerDispDataV123 convert_player_disp_data( + const PlayerDispDataV4& src, Language to_language, Language from_language) { + return src.to_v123(to_language, from_language); } template <> -inline PlayerDispDataBB convert_player_disp_data( - const PlayerDispDataDCPCV3& src, Language to_language, Language from_language) { - return src.to_bb(to_language, from_language); +inline PlayerDispDataV4 convert_player_disp_data( + const PlayerDispDataV123& src, Language to_language, Language from_language) { + return src.to_v4(to_language, from_language); } template <> -inline PlayerDispDataBB convert_player_disp_data(const PlayerDispDataBB& src, Language, Language) { +inline PlayerDispDataV4 convert_player_disp_data(const PlayerDispDataV4& src, Language, Language) { return src; } diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 09bca756..92f93b83 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1637,8 +1637,8 @@ static asio::awaitable S_65_67_68_EB(std::shared_ptr c, C p.guild_card_number = entry.lobby_data.guild_card_number; p.name = name; p.language = entry.inventory.language; - p.section_id = entry.disp.visual.section_id; - p.char_class = entry.disp.visual.char_class; + p.section_id = entry.disp.visual.sh.section_id; + p.char_class = entry.disp.visual.sh.char_class; c->log.info_f("Added lobby player: ({}) {} {}", index, p.guild_card_number, p.name); } } @@ -1763,7 +1763,7 @@ static asio::awaitable S_64(std::shared_ptr c, Channel::M } else { c->proxy_session->lobby_event = 0; c->proxy_session->lobby_difficulty = Difficulty::NORMAL; - c->proxy_session->lobby_section_id = c->character_file()->disp.visual.section_id; + c->proxy_session->lobby_section_id = c->character_file()->disp.visual.sh.section_id; c->proxy_session->lobby_mode = GameMode::NORMAL; c->proxy_session->lobby_random_seed = phosg::random_object(); } @@ -1814,8 +1814,8 @@ static asio::awaitable S_64(std::shared_ptr c, Channel::M const auto& p_ep3 = cmd_ep3->players_ep3[x]; p.language = p_ep3.inventory.language; p.name = p_ep3.disp.visual.name.decode(p.language); - p.section_id = p_ep3.disp.visual.section_id; - p.char_class = p_ep3.disp.visual.char_class; + p.section_id = p_ep3.disp.visual.sh.section_id; + p.char_class = p_ep3.disp.visual.sh.char_class; } else { p.name.clear(); } @@ -1873,8 +1873,8 @@ static asio::awaitable S_E8(std::shared_ptr c, Channel::M p.guild_card_number = player_entry.lobby_data.guild_card_number; p.language = player_entry.inventory.language; p.name = player_entry.disp.visual.name.decode(p.language); - p.section_id = player_entry.disp.visual.section_id; - p.char_class = player_entry.disp.visual.char_class; + p.section_id = player_entry.disp.visual.sh.section_id; + p.char_class = player_entry.disp.visual.sh.char_class; c->log.info_f("Added lobby player: ({}) {} {}", x, p.guild_card_number, p.name); } diff --git a/src/QuestScript.cc b/src/QuestScript.cc index d6bc38e0..fd975dd3 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -1398,7 +1398,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = { {0xF83E, {"set_current_area_number", "delete_area_title?"}, {I32}, F_V2_V4 | F_ARGS}, // Loads a custom visual config for creating NPCs. Generally the sequence should go like this: - // prepare_npc_visual label_containing_PlayerVisualConfig + // prepare_npc_visual label_containing_PlayerVisualConfig (either V123 or V4) // enable_npc_visual // // After any NPC is created, the effects of these opcodes are undone; if the script wants to create another NPC @@ -2958,34 +2958,41 @@ std::string disassemble_quest_script( // Phase 4: Disassemble all referenced label regions, starting with label 0 + auto add = [](std::shared_ptr