diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index d0a280d5..ae715ae8 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -3044,8 +3044,9 @@ struct S_TournamentEntryList_Ep3_E2 { // E2 (S->C): Set system file contents (BB) struct S_SyncSystemFile_BB_E2 { - PSOBBBaseSystemFile system_file; - PSOBBTeamMembership team_membership; + /* 0000 */ PSOBBBaseSystemFile system_file; + /* 02B8 */ PSOBBFullTeamMembership team_membership; + /* 0AF0 */ } __packed_ws__(S_SyncSystemFile_BB_E2, 0xAF0); // E3 (S->C): Game or tournament info (Episode 3) @@ -3244,7 +3245,7 @@ struct C_CreateSpectatorTeam_Ep3_E7 { struct SC_SyncSaveFiles_BB_E7 { /* 0000 */ PSOBBCharacterFile char_file; /* 2EA4 */ PSOBBBaseSystemFile system_file; - /* 30DC */ PSOBBTeamMembership team_membership; + /* 315C */ PSOBBFullTeamMembership team_membership; /* 3994 */ } __packed_ws__(SC_SyncSaveFiles_BB_E7, 0x3994); @@ -3488,19 +3489,13 @@ struct C_ChangeTeamMemberPrivilegeLevel_BB_11EA { } __packed_ws__(C_ChangeTeamMemberPrivilegeLevel_BB_11EA, 4); // 12EA (S->C): Team membership information -// If the client is not in a team, all fields should be zero. +// This updates the client's view of its system file. struct S_TeamMembershipInformation_BB_12EA { - le_uint32_t unknown_a1 = 0; - le_uint32_t guild_card_number = 0; - le_uint32_t team_id = 0; - le_uint32_t unknown_a4 = 0; - le_uint32_t unknown_a6 = 0; - uint8_t privilege_level = 0; - uint8_t team_member_count = 0; - uint8_t unknown_a8 = 0; - uint8_t unknown_a9 = 0; - pstring team_name; + // If skip_update_system_file is not zero, the client ignores the command. + // It's not clear why this field exists. + le_uint32_t skip_update_system_file = 0; + PSOBBBaseTeamMembership membership; } __packed_ws__(S_TeamMembershipInformation_BB_12EA, 0x38); // 13EA: Team info for lobby players diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 0fcc01cb..ed1c770e 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -5237,9 +5237,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri c->login->account->save(); send_command(c, 0x02EA, 0x00000000); - send_update_team_metadata_for_client(c); - send_team_membership_info(c); - send_update_team_reward_flags(c); + send_team_membership_change_notifications(c); } break; } @@ -5274,8 +5272,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri added_c->login->account->account_id, added_c->character()->disp.name.decode(added_c->language())); - send_update_team_metadata_for_client(added_c); - send_team_membership_info(added_c); + send_team_membership_change_notifications(added_c); send_command(c, 0x04EA, 0x00000000); send_command(added_c, 0x04EA, 0x00000000); } @@ -5306,8 +5303,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri } } if (removed_c) { - send_update_team_metadata_for_client(removed_c); - send_team_membership_info(removed_c); + send_team_membership_change_notifications(removed_c); } } else { // TODO: Figure out the right error code to use here. @@ -5352,13 +5348,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri if (team && team->members.at(c->login->account->account_id).check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { const auto& cmd = check_size_t(data); s->team_index->set_flag_data(team->team_id, cmd.flag_data); - for (const auto& it : team->members) { - try { - auto member_c = s->find_client(nullptr, it.second.account_id); - send_update_team_metadata_for_client(member_c); - } catch (const out_of_range&) { - } - } + send_team_metadata_change_notifications(s, team, TeamMetadataChange::FLAG_DATA); } break; } @@ -5366,16 +5356,8 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri auto team = c->team(); if (team && team->members.at(c->login->account->account_id).check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { s->team_index->disband(team->team_id); - send_command(c, 0x10EA, 0x00000000); - for (const auto& it : team->members) { - try { - auto member_c = s->find_client(nullptr, it.second.account_id); - send_update_team_metadata_for_client(member_c); - send_team_membership_info(member_c); - } catch (const out_of_range&) { - } - } + send_team_metadata_change_notifications(s, team, TeamMetadataChange::TEAM_DISBANDED); } break; } @@ -5429,14 +5411,12 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri } } if (send_updates_for_this_m) { - send_update_team_metadata_for_client(c); - send_team_membership_info(c); + send_team_membership_change_notifications(c); } if (send_updates_for_other_m) { try { auto other_c = s->find_client(nullptr, cmd.guild_card_number); - send_update_team_metadata_for_client(other_c); - send_team_membership_info(other_c); + send_team_membership_change_notifications(other_c); } catch (const out_of_range&) { } } @@ -5474,13 +5454,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri s->team_index->buy_reward(team->team_id, reward.key, reward.team_points, reward.reward_flag); if (reward.reward_flag != TeamIndex::Team::RewardFlag::NONE) { - for (const auto& it : team->members) { - try { - auto member_c = s->find_client(nullptr, it.second.account_id); - send_update_team_reward_flags(member_c); - } catch (const out_of_range&) { - } - } + send_team_metadata_change_notifications(s, team, TeamMetadataChange::REWARD_FLAGS); } if (!reward.reward_item.empty()) { c->current_bank().add_item(reward.reward_item, *s->item_stack_limits(c->version())); @@ -5503,14 +5477,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri } else { s->team_index->rename(team->team_id, new_team_name); send_command(c, 0x1FEA, 0x00000000); - for (const auto& it : team->members) { - try { - auto member_c = s->find_client(nullptr, it.second.account_id); - send_update_team_metadata_for_client(c); - send_team_membership_info(c); - } catch (const out_of_range&) { - } - } + send_team_metadata_change_notifications(s, team, TeamMetadataChange::TEAM_NAME); } break; } diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index 98a22ca0..9a7f53c6 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -1173,7 +1173,7 @@ PSOCHARFile::LoadSharedResult PSOCHARFile::load_shared(const string& filename, b if (header.flag != 0x00000000) { throw runtime_error("incorrect flag in character file header"); } - static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership) == 0x3994, ".psochar size is incorrect"); + static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBFullTeamMembership) == 0x3994, ".psochar size is incorrect"); LoadSharedResult ret; ret.character_file = make_shared(phosg::freadx(f.get())); @@ -1188,7 +1188,7 @@ void PSOCHARFile::save( std::shared_ptr system, std::shared_ptr character) { auto f = phosg::fopen_unique(filename, "wb"); - PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBTeamMembership), 0x00E7, 0x00000000}; + PSOCommandHeaderBB header = {sizeof(PSOCommandHeaderBB) + sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBFullTeamMembership), 0x00E7, 0x00000000}; phosg::fwritex(f.get(), header); phosg::fwritex(f.get(), *character); phosg::fwritex(f.get(), *system); @@ -1202,7 +1202,7 @@ void PSOCHARFile::save( // 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; + static const PSOBBFullTeamMembership empty_membership; phosg::fwritex(f.get(), empty_membership); } diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index e411fe1e..ac4e9344 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -198,22 +198,29 @@ check_struct_size(SaveFileShortcutEntryGC, 0x54); check_struct_size(SaveFileShortcutEntryXB, 0x54); check_struct_size(SaveFileShortcutEntryBB, 0xA4); -struct PSOBBTeamMembership { - /* 0000 */ le_uint32_t team_master_guild_card_number = 0; - /* 0004 */ le_uint32_t team_id = 0; - /* 0008 */ le_uint32_t unknown_a5 = 0; - /* 000C */ le_uint32_t unknown_a6 = 0; - /* 0010 */ uint8_t privilege_level = 0; - /* 0011 */ uint8_t unknown_a7 = 0; - /* 0012 */ uint8_t unknown_a8 = 0; - /* 0013 */ uint8_t unknown_a9 = 0; - /* 0014 */ pstring team_name; +struct PSOBBBaseTeamMembership { + /* 00 */ le_uint32_t team_master_guild_card_number = 0; + /* 04 */ le_uint32_t team_id = 0; + /* 08 */ le_uint32_t unknown_a5 = 0; + /* 0C */ le_uint32_t unknown_a6 = 0; + /* 10 */ uint8_t privilege_level = 0; + /* 11 */ uint8_t team_member_count = 0; + /* 12 */ uint8_t unknown_a8 = 0; + /* 13 */ uint8_t unknown_a9 = 0; + /* 14 */ pstring team_name; + /* 34 */ + + PSOBBBaseTeamMembership() = default; +} __packed_ws__(PSOBBBaseTeamMembership, 0x34); + +struct PSOBBFullTeamMembership { + /* 0000 */ PSOBBBaseTeamMembership base; /* 0034 */ parray flag_data; /* 0834 */ le_uint32_t reward_flags = 0; /* 0838 */ - PSOBBTeamMembership() = default; -} __packed_ws__(PSOBBTeamMembership, 0x838); + PSOBBFullTeamMembership() = default; +} __packed_ws__(PSOBBFullTeamMembership, 0x838); //////////////////////////////////////////////////////////////////////////////// // System files @@ -847,7 +854,7 @@ struct PSOCHARFile { /* 0000 */ PSOCommandHeaderBB header; // command = 0x00E7, size = 0x399C, flag = 0 /* 0008 */ PSOBBCharacterFile character; /* 2EAC */ PSOBBBaseSystemFile system; - /* 3164 */ PSOBBTeamMembership team_membership; + /* 3164 */ PSOBBFullTeamMembership team_membership; /* 399C */ struct LoadSharedResult { @@ -1010,7 +1017,7 @@ struct LegacySavedAccountDataBB { // .nsa file format /* 0040 */ parray blocked_senders; /* 00B8 */ PSOBBGuildCardFile guild_card_file; /* D648 */ PSOBBBaseSystemFile system_file; - /* D880 */ PSOBBTeamMembership team_membership; + /* D880 */ PSOBBFullTeamMembership team_membership; /* E138 */ le_uint32_t unused = 0; /* E13C */ le_uint32_t option_flags = 0x00040058; /* E140 */ parray shortcuts; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index ac13f589..e2eb6d72 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -623,7 +623,7 @@ void send_system_file_bb(shared_ptr c) { S_SyncSystemFile_BB_E2 cmd; cmd.system_file = *c->system_file(); if (team) { - cmd.team_membership = team->membership_for_member(c->login->account->account_id); + cmd.team_membership = team->full_membership_for_member(c->login->account->account_id); } send_command_t(c, 0x00E2, 0x00000000, cmd); } @@ -757,7 +757,7 @@ void send_complete_player_bb(shared_ptr c) { cmd.char_file = *p; cmd.system_file = *sys; if (team) { - cmd.team_membership = team->membership_for_member(c->login->account->account_id); + cmd.team_membership = team->full_membership_for_member(c->login->account->account_id); } send_command_t(c, 0x00E7, 0x00000000, cmd); } @@ -2358,7 +2358,7 @@ void send_player_join_notification(shared_ptr c, void send_update_lobby_data_bb(std::shared_ptr c) { auto l = c->require_lobby(); for (auto lc : l->clients) { - if (lc) { + if (lc && lc->version() == Version::BB_V4) { PlayerLobbyDataBB cmd; populate_lobby_data_for_client(cmd, c, lc); send_command_t(lc, 0x00F0, 0x00000000, cmd); @@ -4122,11 +4122,7 @@ void send_team_membership_info(shared_ptr c) { auto team = c->team(); S_TeamMembershipInformation_BB_12EA cmd; if (team) { - cmd.guild_card_number = c->login->account->account_id; - cmd.team_id = team->team_id; - cmd.privilege_level = team->members.at(c->login->account->account_id).privilege_level(); - cmd.team_member_count = min(team->members.size(), 100); - cmd.team_name.encode(team->name); + cmd.membership = team->base_membership_for_member(c->login->account->account_id); } send_command_t(c, 0x12EA, 0x00000000, cmd); } @@ -4152,7 +4148,12 @@ static S_TeamInfoForPlayer_BB_13EA_15EA_Entry team_metadata_for_client(shared_pt void send_update_team_metadata_for_client(shared_ptr c) { auto l = c->require_lobby(); - send_command_t(l, 0x15EA, 0x00000001, team_metadata_for_client(c)); + auto metadata = team_metadata_for_client(c); + for (auto lc : l->clients) { + if (lc && lc->version() == Version::BB_V4) { + send_command_t(lc, 0x15EA, 0x00000001, metadata); + } + } } void send_all_nearby_team_metadatas_to_client(shared_ptr c, bool is_13EA) { @@ -4312,3 +4313,35 @@ void send_team_reward_list(shared_ptr c, bool show_purchased) { send_command_t_vt(c, show_purchased ? 0x19EA : 0x1AEA, 0x00000000, cmd, entries); } + +void send_team_metadata_change_notifications( + shared_ptr s, + shared_ptr team, + uint8_t what) { + using TMC = TeamMetadataChange; + for (const auto& it : team->members) { + try { + auto member_c = s->find_client(nullptr, it.second.account_id); + if (what & TMC::TEAM_MASTER) { + send_update_lobby_data_bb(member_c); + } + if (what & (TMC::TEAM_MASTER | TMC::TEAM_NAME)) { + send_team_membership_info(member_c); + } + if (what & (TMC::TEAM_MASTER | TMC::FLAG_DATA | TMC::TEAM_NAME)) { + send_update_team_metadata_for_client(member_c); + } + if (what & TMC::REWARD_FLAGS) { + send_update_team_reward_flags(member_c); + } + } catch (const out_of_range&) { + } + } +} + +void send_team_membership_change_notifications(shared_ptr changed_c) { + send_update_lobby_data_bb(changed_c); + send_update_team_metadata_for_client(changed_c); + send_team_membership_info(changed_c); + send_update_team_reward_flags(changed_c); +} diff --git a/src/SendCommands.hh b/src/SendCommands.hh index a1783d7c..e334b265 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -455,3 +455,17 @@ void send_team_member_list(std::shared_ptr c); // 09EA void send_intra_team_ranking(std::shared_ptr c); // 18EA void send_team_reward_list(std::shared_ptr c, bool show_purchased); // 19EA, 1AEA void send_cross_team_ranking(std::shared_ptr c); // 1CEA + +enum TeamMetadataChange : uint8_t { + TEAM_MASTER = 0x01, + FLAG_DATA = 0x02, + REWARD_FLAGS = 0x04, + TEAM_NAME = 0x08, + TEAM_DISBANDED = 0xFF, +}; + +void send_team_metadata_change_notifications( + std::shared_ptr s, + std::shared_ptr team, + uint8_t what); +void send_team_membership_change_notifications(std::shared_ptr changed_c); diff --git a/src/TeamIndex.cc b/src/TeamIndex.cc index e6ce4dac..d25e2be5 100644 --- a/src/TeamIndex.cc +++ b/src/TeamIndex.cc @@ -129,19 +129,25 @@ void TeamIndex::Team::delete_files() const { remove(flag_filename.c_str()); } -PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t account_id) const { +PSOBBBaseTeamMembership TeamIndex::Team::base_membership_for_member(uint32_t account_id) const { const auto& m = this->members.at(account_id); - PSOBBTeamMembership ret; + PSOBBBaseTeamMembership ret; ret.team_master_guild_card_number = this->master_account_id; ret.team_id = this->team_id; ret.unknown_a5 = 0; ret.unknown_a6 = 0; ret.privilege_level = m.privilege_level(); - ret.unknown_a7 = 0; + ret.team_member_count = this->members.size(); ret.unknown_a8 = 0; ret.unknown_a9 = 0; ret.team_name.encode(this->name); + return ret; +} + +PSOBBFullTeamMembership TeamIndex::Team::full_membership_for_member(uint32_t account_id) const { + PSOBBFullTeamMembership ret; + ret.base = base_membership_for_member(account_id); if (this->flag_data) { ret.flag_data = *this->flag_data; } else { diff --git a/src/TeamIndex.hh b/src/TeamIndex.hh index edc2e37a..64b54d05 100644 --- a/src/TeamIndex.hh +++ b/src/TeamIndex.hh @@ -79,7 +79,8 @@ public: void save_flag() const; void delete_files() const; - PSOBBTeamMembership membership_for_member(uint32_t account_id) const; + PSOBBBaseTeamMembership base_membership_for_member(uint32_t account_id) const; + PSOBBFullTeamMembership full_membership_for_member(uint32_t account_id) const; [[nodiscard]] inline bool check_reward_flag(RewardFlag flag) const { return !!(static_cast(flag) & this->reward_flags);