implement most remaining BB team functions
This commit is contained in:
+4
-3
@@ -245,7 +245,7 @@ shared_ptr<Lobby> Client::require_lobby() const {
|
||||
return l;
|
||||
}
|
||||
|
||||
shared_ptr<TeamIndex::Team> Client::team() {
|
||||
shared_ptr<const TeamIndex::Team> Client::team() {
|
||||
if (!this->license) {
|
||||
throw logic_error("Client::team called on client with no license");
|
||||
}
|
||||
@@ -258,6 +258,7 @@ shared_ptr<TeamIndex::Team> Client::team() {
|
||||
auto s = this->require_server_state();
|
||||
auto team = s->team_index->get_by_id(this->license->bb_team_id);
|
||||
if (!team) {
|
||||
this->log.info("License contains a team ID, but the team does not exist; clearing team ID from license");
|
||||
this->license->bb_team_id = 0;
|
||||
this->license->save();
|
||||
return nullptr;
|
||||
@@ -265,6 +266,7 @@ shared_ptr<TeamIndex::Team> Client::team() {
|
||||
|
||||
auto member_it = team->members.find(this->license->serial_number);
|
||||
if (member_it == team->members.end()) {
|
||||
this->log.info("License contains a team ID, but the team does not contain this member; clearing team ID from license");
|
||||
this->license->bb_team_id = 0;
|
||||
this->license->save();
|
||||
return nullptr;
|
||||
@@ -277,8 +279,7 @@ shared_ptr<TeamIndex::Team> Client::team() {
|
||||
string name = p->disp.name.decode(this->language());
|
||||
if (m.name != name) {
|
||||
this->log.info("Updating player name in team config");
|
||||
m.name = name;
|
||||
team->save_config();
|
||||
s->team_index->update_member_name(this->license->serial_number, name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -54,7 +54,6 @@ struct Client : public std::enable_shared_from_this<Client> {
|
||||
HAS_EP3_MEDIA_UPDATES = 0x0000000010000000,
|
||||
USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000,
|
||||
HAS_GUILD_CARD_NUMBER = 0x0000000040000000,
|
||||
ACCEPTED_TEAM_INVITATION = 0x0000000080000000,
|
||||
|
||||
// Cheat mode flags
|
||||
SWITCH_ASSIST_ENABLED = 0x0000000100000000,
|
||||
@@ -232,7 +231,7 @@ struct Client : public std::enable_shared_from_this<Client> {
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
std::shared_ptr<Lobby> require_lobby() const;
|
||||
|
||||
std::shared_ptr<TeamIndex::Team> team();
|
||||
std::shared_ptr<const TeamIndex::Team> team();
|
||||
|
||||
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
|
||||
void save_game_data();
|
||||
|
||||
+35
-21
@@ -3254,9 +3254,8 @@ struct C_AddOrRemoveTeamMember_BB_03EA_05EA {
|
||||
|
||||
struct SC_TeamChat_BB_07EA {
|
||||
pstring<TextEncoding::UTF16, 0x10> sender_name;
|
||||
// It seems there are no real limits on the message length, other than the
|
||||
// overall command length limit of 0x7C00 bytes.
|
||||
// Text follows here
|
||||
// Text follows here. The message is truncated by the client if it is longer
|
||||
// than 0x8F wchar_ts.
|
||||
} __packed__;
|
||||
|
||||
// 08EA (C->S): Get team member list
|
||||
@@ -3267,8 +3266,8 @@ struct SC_TeamChat_BB_07EA {
|
||||
struct S_TeamMemberList_BB_09EA {
|
||||
le_uint32_t entry_count = 0;
|
||||
struct Entry {
|
||||
// This is displayed as "<%04d> %s" % (value, message)
|
||||
le_uint32_t index = 0;
|
||||
// This is displayed as "<%04d> %s" % (rank, name)
|
||||
le_uint32_t rank = 0;
|
||||
le_uint32_t privilege_level = 0; // 0x10 or 0x20 = green, 0x30 = blue, 0x40 = red, anything else = white
|
||||
le_uint32_t guild_card_number = 0;
|
||||
pstring<TextEncoding::UTF16, 0x10> name;
|
||||
@@ -3278,16 +3277,17 @@ struct S_TeamMemberList_BB_09EA {
|
||||
} __packed__;
|
||||
|
||||
// 0CEA (S->C): Unknown
|
||||
// The client appears to ignore this command.
|
||||
|
||||
struct S_Unknown_BB_0CEA {
|
||||
parray<uint8_t, 0x20> unknown_a1;
|
||||
// Text follows here
|
||||
} __packed__;
|
||||
|
||||
// 0DEA (C->S): Unknown
|
||||
// 0DEA (C->S): Get team name
|
||||
// No arguments
|
||||
|
||||
// 0EEA (S->C): Unknown
|
||||
// 0EEA (S->C): Team name
|
||||
|
||||
struct S_Unknown_BB_0EEA {
|
||||
parray<uint8_t, 0x10> unused;
|
||||
@@ -3306,8 +3306,10 @@ struct C_SetTeamFlag_BB_0FEA {
|
||||
|
||||
// 11EA: Change team member privilege level
|
||||
// The format below is used only when the client sends this command; when the
|
||||
// server sends it, only header.flag is used.
|
||||
// server sends it, only header.flag is used. As with various other team
|
||||
// commands, header.flag specifies the error code in this case.
|
||||
// header.flag specifies the new privilege level for the specified team member.
|
||||
// Known values: 0 = normal, 0x30 = leader, 0x40 = master
|
||||
|
||||
struct C_ChangeTeamMemberPrivilegeLevel_BB_11EA {
|
||||
le_uint32_t guild_card_number = 0;
|
||||
@@ -3333,6 +3335,8 @@ struct S_TeamMembershipInformation_BB_12EA {
|
||||
// header.flag specifies the number of entries.
|
||||
|
||||
struct S_TeamInfoForPlayer_BB_13EA_15EA_Entry {
|
||||
// The client uses the first four of these to determine if the player is in a
|
||||
// team or not - if they are all zero, the player is not in a team.
|
||||
le_uint32_t guild_card_number = 0;
|
||||
le_uint32_t team_id = 0;
|
||||
le_uint32_t unknown_a3 = 0;
|
||||
@@ -3345,20 +3349,20 @@ struct S_TeamInfoForPlayer_BB_13EA_15EA_Entry {
|
||||
parray<le_uint16_t, 0x20 * 0x20> flag_data;
|
||||
} __packed__;
|
||||
|
||||
// 14EA (C->S): Unknown
|
||||
// 14EA (C->S): Get team info for lobby players
|
||||
// No arguments. Client always sends 1 in the header.flag field.
|
||||
|
||||
// 15EA (S->C): Unknown
|
||||
// 15EA (S->C): Team info for lobby players
|
||||
// header.flag specifies the number of entries. The entry format appears to be
|
||||
// the same as for the 13EA command.
|
||||
|
||||
// 16EA (S->C): Unknown
|
||||
// No arguments except header.flag.
|
||||
|
||||
// 18EA: Team ranking information
|
||||
// 18EA: Intra-team ranking information
|
||||
// No arguments (C->S)
|
||||
|
||||
struct S_TeamRankingInformation_BB_18EA {
|
||||
struct S_IntraTeamRanking_BB_18EA {
|
||||
/* 0000 */ le_uint32_t ranking_points = 0;
|
||||
/* 0004 */ le_uint32_t unknown_a2 = 0;
|
||||
/* 0008 */ le_uint32_t points_remaining = 0;
|
||||
@@ -3378,13 +3382,7 @@ struct S_TeamRankingInformation_BB_18EA {
|
||||
// 19EA: Team reward list
|
||||
// No arguments (C->S)
|
||||
|
||||
struct S_TeamRewardList_BB_19EA {
|
||||
le_uint32_t num_rewards_unlocked = 0;
|
||||
} __packed__;
|
||||
|
||||
// 1AEA: Team rewards available for purchase
|
||||
|
||||
struct S_TeamRewardsAvailableForPurchase_BB_1AEA {
|
||||
struct S_TeamRewardList_BB_19EA_1AEA {
|
||||
le_uint32_t num_entries;
|
||||
struct Entry {
|
||||
/* 0000 */ pstring<TextEncoding::UTF16, 0x40> name;
|
||||
@@ -3397,13 +3395,28 @@ struct S_TeamRewardsAvailableForPurchase_BB_1AEA {
|
||||
// Entry entries[num_entries];
|
||||
} __packed__;
|
||||
|
||||
// 1AEA: Team rewards available for purchase
|
||||
// Same format as 19EA.
|
||||
|
||||
// 1BEA (C->S): Buy team reward
|
||||
// No arguments except header.flag, which specifies a reward_id from a preceding
|
||||
// 1AEA command.
|
||||
|
||||
// 1CEA: Ranking information
|
||||
// 1CEA: Cross-team ranking information
|
||||
// No arguments when sent by the client.
|
||||
|
||||
struct S_CrossTeamRanking_BB_1CEA {
|
||||
le_uint32_t num_entries;
|
||||
struct Entry {
|
||||
/* 00 */ pstring<TextEncoding::UTF16, 0x10> team_name;
|
||||
/* 20 */ le_uint32_t team_points = 0;
|
||||
/* 24 */ le_uint32_t unknown_a1 = 0;
|
||||
/* 28 */
|
||||
} __packed__;
|
||||
// Variable length field:
|
||||
// Entry entries[num_entries];
|
||||
} __packed__;
|
||||
|
||||
// 1DEA (S->C): Update team rewards bitmask
|
||||
// header.flag specifies the new rewards bitmask.
|
||||
|
||||
@@ -3415,7 +3428,8 @@ struct C_Unknown_BB_1EEA {
|
||||
} __packed__;
|
||||
|
||||
// 1FEA (S->C): Action result
|
||||
// This command behaves exactly like 02EA.
|
||||
// This command behaves exactly like 02EA. This command is presumably the
|
||||
// response to whatever 1EEA does.
|
||||
|
||||
// 20EA: Unknown
|
||||
// header.flag is used, but no other arguments
|
||||
|
||||
+44
-94
@@ -4528,23 +4528,27 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
try {
|
||||
added_c = s->find_client(nullptr, cmd.guild_card_number);
|
||||
} catch (const out_of_range&) {
|
||||
send_lobby_message_box(c, "Player is offline");
|
||||
send_command(c, 0x04EA, 0x00000006);
|
||||
}
|
||||
|
||||
if (added_c) {
|
||||
auto added_c_team = added_c->team();
|
||||
if (added_c_team) {
|
||||
send_lobby_message_box(c, "Player is already\nin another team");
|
||||
} else if (!added_c->config.check_flag(Client::Flag::ACCEPTED_TEAM_INVITATION)) {
|
||||
send_lobby_message_box(c, "Player did not accept\nteam invitation");
|
||||
send_command(c, 0x04EA, 0x00000001);
|
||||
send_command(added_c, 0x04EA, 0x00000001);
|
||||
|
||||
} else if (!team->can_add_member()) {
|
||||
// Send "team is full" error
|
||||
send_command(c, 0x04EA, 0x00000005);
|
||||
send_command(added_c, 0x04EA, 0x00000005);
|
||||
|
||||
} else {
|
||||
added_c->license->bb_team_id = team->team_id;
|
||||
added_c->license->save();
|
||||
added_c->config.clear_flag(Client::Flag::ACCEPTED_TEAM_INVITATION);
|
||||
s->team_index->add_member(
|
||||
team->team_id,
|
||||
added_c->license->serial_number,
|
||||
added_c->game_data.character()->disp.name.decode(added_c->language()));
|
||||
|
||||
send_update_team_metadata_for_client(added_c);
|
||||
send_team_membership_info(added_c);
|
||||
@@ -4623,8 +4627,7 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
auto team = c->team();
|
||||
if (team && team->members.at(c->license->serial_number).check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
const auto& cmd = check_size_t<C_SetTeamFlag_BB_0FEA>(data);
|
||||
team->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>(cmd.flag_data));
|
||||
team->save_flag();
|
||||
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.serial_number);
|
||||
@@ -4660,37 +4663,29 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
throw runtime_error("this command cannot be used to modify your own permissions");
|
||||
}
|
||||
|
||||
auto& this_m = team->members.at(c->license->serial_number);
|
||||
if (!this_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
break;
|
||||
}
|
||||
auto& other_m = team->members.at(cmd.guild_card_number);
|
||||
|
||||
// The client only sends this command with flag = 0x00, 0x30, or 0x40
|
||||
bool send_updates_for_this_m = false;
|
||||
bool send_updates_for_other_m = false;
|
||||
switch (flag) {
|
||||
case 0x00: // Demote member
|
||||
if (other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER)) {
|
||||
other_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
if (s->team_index->demote_leader(c->license->serial_number, cmd.guild_card_number)) {
|
||||
send_command(c, 0x11EA, 0x00000000);
|
||||
send_updates_for_other_m = true;
|
||||
} else {
|
||||
send_command(c, 0x11EA, 0x00000005);
|
||||
}
|
||||
break;
|
||||
case 0x30: // Promote member
|
||||
if (!other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER) && team->can_promote_leader()) {
|
||||
other_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
if (s->team_index->promote_leader(c->license->serial_number, cmd.guild_card_number)) {
|
||||
send_command(c, 0x11EA, 0x00000000);
|
||||
send_updates_for_other_m = true;
|
||||
} else {
|
||||
send_command(c, 0x11EA, 0x00000005);
|
||||
}
|
||||
break;
|
||||
case 0x40: // Transfer master
|
||||
this_m.clear_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
|
||||
this_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
other_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
other_m.set_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
|
||||
s->team_index->change_master(c->license->serial_number, cmd.guild_card_number);
|
||||
send_command(c, 0x11EA, 0x00000000);
|
||||
send_updates_for_this_m = true;
|
||||
send_updates_for_other_m = true;
|
||||
break;
|
||||
@@ -4705,8 +4700,8 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
if (send_updates_for_other_m) {
|
||||
try {
|
||||
auto other_c = s->find_client(nullptr, cmd.guild_card_number);
|
||||
send_update_team_metadata_for_client(c);
|
||||
send_team_membership_info(c);
|
||||
send_update_team_metadata_for_client(other_c);
|
||||
send_team_membership_info(other_c);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
@@ -4720,83 +4715,37 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
send_all_nearby_team_metadatas_to_client(c, false);
|
||||
break;
|
||||
case 0x18EA: // Ranking information
|
||||
send_team_rank_info(c);
|
||||
send_intra_team_ranking(c);
|
||||
break;
|
||||
case 0x19EA: {
|
||||
S_TeamRewardList_BB_19EA cmd = {0};
|
||||
send_command_t(c, 0x19EA, 0x00000000, cmd);
|
||||
break;
|
||||
}
|
||||
case 0x1AEA: // Get team rewards available for purchase
|
||||
send_team_rewards_available_for_purchase(c);
|
||||
case 0x19EA: // List purchased team rewards
|
||||
case 0x1AEA: // List team rewards available for purchase
|
||||
send_team_reward_list(c, (command == 0x19EA));
|
||||
break;
|
||||
case 0x1BEA: { // Buy team reward
|
||||
auto team = c->team();
|
||||
if (team) {
|
||||
check_size_v(data.size(), 0); // No data should be sent
|
||||
bool should_send_update_reward_flags = false;
|
||||
switch (flag) {
|
||||
case TeamRewardMenuItemID::TEAM_FLAG:
|
||||
team->set_reward_flag(TeamIndex::Team::RewardFlag::TEAM_FLAG);
|
||||
should_send_update_reward_flags = true;
|
||||
break;
|
||||
case TeamRewardMenuItemID::DRESSING_ROOM:
|
||||
team->set_reward_flag(TeamIndex::Team::RewardFlag::DRESSING_ROOM);
|
||||
should_send_update_reward_flags = true;
|
||||
break;
|
||||
case TeamRewardMenuItemID::MEMBERS_20_LEADERS_3:
|
||||
if (team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_20_LEADERS_3) ||
|
||||
team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_40_LEADERS_5) ||
|
||||
team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_70_LEADERS_8) ||
|
||||
team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_100_LEADERS_10)) {
|
||||
throw runtime_error("team size upgrades purchased out of order");
|
||||
}
|
||||
team->set_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_20_LEADERS_3);
|
||||
should_send_update_reward_flags = true;
|
||||
break;
|
||||
case TeamRewardMenuItemID::MEMBERS_40_LEADERS_5:
|
||||
if (!team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_20_LEADERS_3) ||
|
||||
team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_40_LEADERS_5) ||
|
||||
team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_70_LEADERS_8) ||
|
||||
team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_100_LEADERS_10)) {
|
||||
throw runtime_error("team size upgrades purchased out of order");
|
||||
}
|
||||
team->set_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_40_LEADERS_5);
|
||||
should_send_update_reward_flags = true;
|
||||
break;
|
||||
case TeamRewardMenuItemID::MEMBERS_70_LEADERS_8:
|
||||
if (!team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_20_LEADERS_3) ||
|
||||
!team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_40_LEADERS_5) ||
|
||||
team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_70_LEADERS_8) ||
|
||||
team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_100_LEADERS_10)) {
|
||||
throw runtime_error("team size upgrades purchased out of order");
|
||||
}
|
||||
team->set_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_70_LEADERS_8);
|
||||
should_send_update_reward_flags = true;
|
||||
break;
|
||||
case TeamRewardMenuItemID::MEMBERS_100_LEADERS_10:
|
||||
if (!team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_20_LEADERS_3) ||
|
||||
!team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_40_LEADERS_5) ||
|
||||
!team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_70_LEADERS_8) ||
|
||||
team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_100_LEADERS_10)) {
|
||||
throw runtime_error("team size upgrades purchased out of order");
|
||||
}
|
||||
team->set_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_100_LEADERS_10);
|
||||
should_send_update_reward_flags = true;
|
||||
break;
|
||||
// TODO: Implement all the following cases
|
||||
// case POINT_OF_DISASTER:
|
||||
// case TOYS_TWILIGHT:
|
||||
// case COMMANDER_BLADE:
|
||||
// case UNION_GUARD:
|
||||
// case TEAM_POINTS_500:
|
||||
// case TEAM_POINTS_1000:
|
||||
// case TEAM_POINTS_5000:
|
||||
// case TEAM_POINTS_10000:
|
||||
default:
|
||||
throw runtime_error("incorrect team reward ID");
|
||||
const auto& reward = s->team_index->reward_definitions().at(flag);
|
||||
|
||||
for (const auto& key : reward.prerequisite_keys) {
|
||||
if (!team->has_reward(key)) {
|
||||
throw runtime_error("not all prerequisite rewards have been purchased");
|
||||
}
|
||||
}
|
||||
if (should_send_update_reward_flags) {
|
||||
if (reward.is_unique && team->has_reward(reward.key)) {
|
||||
throw runtime_error("team reward already purchased");
|
||||
}
|
||||
|
||||
if (!reward.reward_item.empty()) {
|
||||
// TODO: How do we do this? Do we just send a 6xBE in the lobby?
|
||||
// (Once this is figured out, don't forget to move this block to after
|
||||
// the reward is actually purchased)
|
||||
throw runtime_error("team reward items are not implemented");
|
||||
}
|
||||
|
||||
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.serial_number);
|
||||
@@ -4809,7 +4758,8 @@ static void on_EA_BB(shared_ptr<Client> c, uint16_t command, uint32_t flag, stri
|
||||
break;
|
||||
}
|
||||
case 0x1CEA:
|
||||
throw runtime_error("team subcommand is not yet implemented");
|
||||
send_cross_team_ranking(c);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid team command");
|
||||
}
|
||||
|
||||
@@ -2137,8 +2137,7 @@ void on_exchange_item_for_team_points_bb(shared_ptr<Client> c, uint8_t command,
|
||||
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);
|
||||
team->members.at(c->license->serial_number).points += points;
|
||||
team->save_config();
|
||||
s->team_index->add_member_points(c->license->serial_number, points);
|
||||
|
||||
auto name = s->describe_item(c->version(), item, false);
|
||||
l->log.info("Player %hhu exchanged inventory item %hu:%08" PRIX32 " (%s) for %zu team points",
|
||||
@@ -2917,8 +2916,8 @@ SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6xBE */ {0x00, 0x00, 0xBE, on_ep3_sound_chat},
|
||||
/* 6xBF */ {0x00, 0x00, 0xBF, on_forward_check_size_ep3_lobby},
|
||||
/* 6xC0 */ {0x00, 0x00, 0xC0, on_sell_item_at_shop_bb},
|
||||
/* 6xC1 */ {0x00, 0x00, 0xC1, nullptr},
|
||||
/* 6xC2 */ {0x00, 0x00, 0xC2, nullptr},
|
||||
/* 6xC1 */ {0x00, 0x00, 0xC1, on_forward_check_size},
|
||||
/* 6xC2 */ {0x00, 0x00, 0xC2, on_forward_check_size},
|
||||
/* 6xC3 */ {0x00, 0x00, 0xC3, on_drop_partial_stack_bb},
|
||||
/* 6xC4 */ {0x00, 0x00, 0xC4, on_sort_inventory_bb},
|
||||
/* 6xC5 */ {0x00, 0x00, 0xC5, on_medical_center_bb},
|
||||
@@ -2929,8 +2928,8 @@ SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6xCA */ {0x00, 0x00, 0xCA, on_item_reward_request_bb},
|
||||
/* 6xCB */ {0x00, 0x00, 0xCB, nullptr},
|
||||
/* 6xCC */ {0x00, 0x00, 0xCC, on_exchange_item_for_team_points_bb},
|
||||
/* 6xCD */ {0x00, 0x00, 0xCD, nullptr},
|
||||
/* 6xCE */ {0x00, 0x00, 0xCE, nullptr},
|
||||
/* 6xCD */ {0x00, 0x00, 0xCD, on_forward_check_size},
|
||||
/* 6xCE */ {0x00, 0x00, 0xCE, on_forward_check_size},
|
||||
/* 6xCF */ {0x00, 0x00, 0xCF, on_battle_restart_bb},
|
||||
/* 6xD0 */ {0x00, 0x00, 0xD0, on_battle_level_up_bb},
|
||||
/* 6xD1 */ {0x00, 0x00, 0xD1, on_request_challenge_grave_recovery_item_bb},
|
||||
|
||||
+90
-120
@@ -3313,10 +3313,10 @@ static S_TeamInfoForPlayer_BB_13EA_15EA_Entry team_metadata_for_client(shared_pt
|
||||
auto team = c->team();
|
||||
S_TeamInfoForPlayer_BB_13EA_15EA_Entry cmd;
|
||||
cmd.lobby_client_id = c->lobby_client_id;
|
||||
cmd.guild_card_number = c->license->serial_number;
|
||||
cmd.guild_card_number2 = c->license->serial_number;
|
||||
cmd.player_name = c->game_data.character()->disp.name;
|
||||
if (team) {
|
||||
cmd.guild_card_number = c->license->serial_number;
|
||||
cmd.team_id = team->team_id;
|
||||
cmd.privilege_level = team->members.at(c->license->serial_number).privilege_level();
|
||||
cmd.team_name.encode(team->name);
|
||||
@@ -3356,29 +3356,6 @@ void send_team_member_list(shared_ptr<Client> c) {
|
||||
throw runtime_error("client is not in a team");
|
||||
}
|
||||
|
||||
S_TeamMemberList_BB_09EA header;
|
||||
header.entry_count = team->members.size();
|
||||
|
||||
vector<S_TeamMemberList_BB_09EA::Entry> entries;
|
||||
entries.reserve(header.entry_count);
|
||||
for (auto& it : team->members) {
|
||||
auto& m = it.second;
|
||||
auto& e = entries.emplace_back();
|
||||
e.index = entries.size();
|
||||
e.privilege_level = m.privilege_level();
|
||||
e.guild_card_number = m.serial_number;
|
||||
e.name.encode(m.name, c->language());
|
||||
}
|
||||
|
||||
send_command_t_vt(c, 0x09EA, 0x00000000, header, entries);
|
||||
}
|
||||
|
||||
void send_team_rank_info(std::shared_ptr<Client> c) {
|
||||
auto team = c->team();
|
||||
if (!team) {
|
||||
throw runtime_error("client is not in a team");
|
||||
}
|
||||
|
||||
vector<const TeamIndex::Team::Member*> members;
|
||||
for (const auto& it : team->members) {
|
||||
members.emplace_back(&it.second);
|
||||
@@ -3388,10 +3365,45 @@ void send_team_rank_info(std::shared_ptr<Client> c) {
|
||||
};
|
||||
sort(members.begin(), members.end(), rank_fn);
|
||||
|
||||
S_TeamRankingInformation_BB_18EA cmd;
|
||||
cmd.points_remaining = 0;
|
||||
S_TeamMemberList_BB_09EA header;
|
||||
header.entry_count = members.size();
|
||||
|
||||
vector<S_TeamRankingInformation_BB_18EA::Entry> entries;
|
||||
vector<S_TeamMemberList_BB_09EA::Entry> entries;
|
||||
entries.reserve(header.entry_count);
|
||||
for (size_t z = 0; z < members.size(); z++) {
|
||||
const auto* m = members[z];
|
||||
auto& e = entries.emplace_back();
|
||||
e.rank = z + 1;
|
||||
e.privilege_level = m->privilege_level();
|
||||
e.guild_card_number = m->serial_number;
|
||||
e.name.encode(m->name, c->language());
|
||||
}
|
||||
|
||||
send_command_t_vt(c, 0x09EA, 0x00000000, header, entries);
|
||||
}
|
||||
|
||||
void send_intra_team_ranking(std::shared_ptr<Client> c) {
|
||||
auto team = c->team();
|
||||
if (!team) {
|
||||
throw runtime_error("client is not in a team");
|
||||
}
|
||||
|
||||
// TODO: At some point we should maintain a sorted index instead of sorting
|
||||
// these on-demand.
|
||||
vector<const TeamIndex::Team::Member*> members;
|
||||
for (const auto& it : team->members) {
|
||||
members.emplace_back(&it.second);
|
||||
}
|
||||
auto rank_fn = +[](const TeamIndex::Team::Member* a, const TeamIndex::Team::Member* b) {
|
||||
return a->points > b->points;
|
||||
};
|
||||
sort(members.begin(), members.end(), rank_fn);
|
||||
|
||||
S_IntraTeamRanking_BB_18EA cmd;
|
||||
cmd.points_remaining = team->points - team->spent_points;
|
||||
cmd.num_entries = members.size();
|
||||
|
||||
vector<S_IntraTeamRanking_BB_18EA::Entry> entries;
|
||||
for (size_t z = 0; z < members.size(); z++) {
|
||||
const auto* m = members[z];
|
||||
cmd.ranking_points += m->points;
|
||||
@@ -3402,111 +3414,69 @@ void send_team_rank_info(std::shared_ptr<Client> c) {
|
||||
e.player_name.encode(m->name);
|
||||
e.points = m->points;
|
||||
}
|
||||
cmd.num_entries = entries.size();
|
||||
|
||||
send_command_t_vt(c, 0x18EA, 0x00000000, cmd, entries);
|
||||
}
|
||||
|
||||
void send_team_rewards_available_for_purchase(std::shared_ptr<Client> c) {
|
||||
void send_cross_team_ranking(std::shared_ptr<Client> c) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
// TODO: At some point we should maintain a sorted index instead of sorting
|
||||
// these on-demand.
|
||||
auto teams = s->team_index->all();
|
||||
auto rank_fn = +[](const shared_ptr<const TeamIndex::Team>& a, const shared_ptr<const TeamIndex::Team>& b) {
|
||||
return a->points > b->points;
|
||||
};
|
||||
sort(teams.begin(), teams.end(), rank_fn);
|
||||
|
||||
size_t num_to_send = min<size_t>(teams.size(), 0x300);
|
||||
|
||||
S_CrossTeamRanking_BB_1CEA cmd;
|
||||
cmd.num_entries = num_to_send;
|
||||
|
||||
vector<S_CrossTeamRanking_BB_1CEA::Entry> entries;
|
||||
for (size_t z = 0; z < num_to_send; z++) {
|
||||
auto t = teams[z];
|
||||
auto& e = entries.emplace_back();
|
||||
e.team_name.encode(t->name, c->language());
|
||||
e.team_points = t->points;
|
||||
e.unknown_a1 = 0x01020304;
|
||||
}
|
||||
|
||||
send_command_t_vt(c, 0x1CEA, 0x00000000, cmd, entries);
|
||||
}
|
||||
|
||||
void send_team_reward_list(std::shared_ptr<Client> c, bool show_purchased) {
|
||||
auto team = c->team();
|
||||
if (!team) {
|
||||
throw runtime_error("user is not in a team");
|
||||
}
|
||||
auto s = c->require_server_state();
|
||||
|
||||
vector<S_TeamRewardsAvailableForPurchase_BB_1AEA::Entry> entries;
|
||||
if (!team->check_reward_flag(TeamIndex::Team::RewardFlag::TEAM_FLAG)) {
|
||||
vector<S_TeamRewardList_BB_19EA_1AEA::Entry> entries;
|
||||
for (const auto& reward : s->team_index->reward_definitions()) {
|
||||
if (team->has_reward(reward.key) != show_purchased) {
|
||||
continue;
|
||||
}
|
||||
bool has_all_prerequisites = true;
|
||||
for (const auto& key : reward.prerequisite_keys) {
|
||||
if (!team->has_reward(key)) {
|
||||
has_all_prerequisites = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_all_prerequisites) {
|
||||
continue;
|
||||
}
|
||||
auto& e = entries.emplace_back();
|
||||
e.name.encode("Team flag");
|
||||
e.description.encode("Show a custom banner\nabove your team\'s\nplayers in the lobby");
|
||||
e.reward_id = TeamRewardMenuItemID::TEAM_FLAG;
|
||||
e.team_points = 2500;
|
||||
}
|
||||
if (!team->check_reward_flag(TeamIndex::Team::RewardFlag::DRESSING_ROOM)) {
|
||||
auto& e = entries.emplace_back();
|
||||
e.name.encode("Dressing room");
|
||||
e.description.encode("Unlock the ability to\nchange your character\'s\nappearance");
|
||||
e.reward_id = TeamRewardMenuItemID::DRESSING_ROOM;
|
||||
e.team_points = 3000;
|
||||
}
|
||||
if (!team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_20_LEADERS_3)) {
|
||||
auto& e = entries.emplace_back();
|
||||
e.name.encode("20 team members");
|
||||
e.description.encode("Increase your team\'s\nsize limit to 30 members\nand 3 leaders");
|
||||
e.reward_id = TeamRewardMenuItemID::MEMBERS_20_LEADERS_3;
|
||||
e.team_points = 1500;
|
||||
} else if (!team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_40_LEADERS_5)) {
|
||||
auto& e = entries.emplace_back();
|
||||
e.name.encode("40 team members");
|
||||
e.description.encode("Increase your team\'s\nsize limit to 40 members\nand 3 leaders");
|
||||
e.reward_id = TeamRewardMenuItemID::MEMBERS_40_LEADERS_5;
|
||||
e.team_points = 4000;
|
||||
} else if (!team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_70_LEADERS_8)) {
|
||||
auto& e = entries.emplace_back();
|
||||
e.name.encode("70 team members");
|
||||
e.description.encode("Increase your team\'s\nsize limit to 70 members\nand 8 leaders");
|
||||
e.reward_id = TeamRewardMenuItemID::MEMBERS_70_LEADERS_8;
|
||||
e.team_points = 9000;
|
||||
} else if (!team->check_reward_flag(TeamIndex::Team::RewardFlag::MEMBERS_100_LEADERS_10)) {
|
||||
auto& e = entries.emplace_back();
|
||||
e.name.encode("100 team members");
|
||||
e.description.encode("Increase your team\'s\nsize limit to 100 members\nand 10 leaders");
|
||||
e.reward_id = TeamRewardMenuItemID::MEMBERS_100_LEADERS_10;
|
||||
e.team_points = 18000;
|
||||
e.name.encode(reward.name, c->language());
|
||||
e.description.encode(reward.description, c->language());
|
||||
e.reward_id = reward.menu_item_id;
|
||||
e.team_points = reward.team_points;
|
||||
}
|
||||
|
||||
// TODO: Implement these. Currently we don't have a good way to conditionally
|
||||
// unlock quests, and especially not from a team reward flag.
|
||||
// if (!point_of_disaster_unlocked) {
|
||||
// auto& e = entries.emplace_back();
|
||||
// e.name.encode("Quest: Point of Disaster");
|
||||
// e.description.encode("Unlock the quest\nPoint of Disaster\nfor your team");
|
||||
// e.team_points = 1000;
|
||||
// e.reward_id = TeamRewardMenuItemID::POINT_OF_DISASTER;
|
||||
// }
|
||||
// if (!toys_twilight_unlocked) {
|
||||
// auto& e = entries.emplace_back();
|
||||
// e.name.encode("Quest: Toys Twilight");
|
||||
// e.description.encode("Unlock the quest\nToys Twilight\nfor your team");
|
||||
// e.team_points = 1000;
|
||||
// e.reward_id = TeamRewardMenuItemID::TOYS_TWILIGHT;
|
||||
// }
|
||||
|
||||
// TODO: How should these be implemented? There has to be a way to create
|
||||
// items in the lobby, presumably...
|
||||
// auto& e = entries.emplace_back();
|
||||
// e.name.encode("Commander Blade");
|
||||
// e.description.encode("Create a Commander\nBlade weapon");
|
||||
// e.team_points = 8000;
|
||||
// e.reward_id = TeamRewardMenuItemID::COMMANDER_BLADE;
|
||||
// auto& e = entries.emplace_back();
|
||||
// e.name.encode("Union Guard");
|
||||
// e.description.encode("Create a Union Guard\nshield");
|
||||
// e.team_points = 100;
|
||||
// e.reward_id = TeamRewardMenuItemID::UNION_GUARD;
|
||||
|
||||
// auto& e = entries.emplace_back();
|
||||
// e.name.encode("Team Points Ticket 500");
|
||||
// e.description.encode("Create a 500-point ticket");
|
||||
// e.team_points = 500;
|
||||
// e.reward_id = TeamRewardMenuItemID::TEAM_POINTS_500;
|
||||
// auto& e = entries.emplace_back();
|
||||
// e.name.encode("Team Points Ticket 1000");
|
||||
// e.description.encode("Create a 1000-point ticket");
|
||||
// e.team_points = 1000;
|
||||
// e.reward_id = TeamRewardMenuItemID::TEAM_POINTS_1000;
|
||||
// auto& e = entries.emplace_back();
|
||||
// e.name.encode("Team Points Ticket 5000");
|
||||
// e.description.encode("Create a 5000-point ticket");
|
||||
// e.team_points = 5000;
|
||||
// e.reward_id = TeamRewardMenuItemID::TEAM_POINTS_5000;
|
||||
// auto& e = entries.emplace_back();
|
||||
// e.name.encode("Team Points Ticket 10000");
|
||||
// e.description.encode("Create a 10000-point ticket");
|
||||
// e.team_points = 10000;
|
||||
// e.reward_id = TeamRewardMenuItemID::TEAM_POINTS_10000;
|
||||
|
||||
S_TeamRewardsAvailableForPurchase_BB_1AEA cmd;
|
||||
S_TeamRewardList_BB_19EA_1AEA cmd;
|
||||
cmd.num_entries = entries.size();
|
||||
|
||||
send_command_t_vt(c, 0x1AEA, 0x00000000, cmd, entries);
|
||||
send_command_t_vt(c, show_purchased ? 0x19EA : 0x1AEA, 0x00000000, cmd, entries);
|
||||
}
|
||||
|
||||
+3
-2
@@ -395,5 +395,6 @@ void send_update_team_metadata_for_client(std::shared_ptr<Client> c); // 15EA (t
|
||||
void send_all_nearby_team_metadatas_to_client(std::shared_ptr<Client> c, bool is_13EA); // 13EA/15EA (to only c, with all lobby clients' data)
|
||||
void send_update_team_reward_flags(std::shared_ptr<Client> c); // 1DEA
|
||||
void send_team_member_list(std::shared_ptr<Client> c); // 09EA
|
||||
void send_team_rank_info(std::shared_ptr<Client> c); // 18EA
|
||||
void send_team_rewards_available_for_purchase(std::shared_ptr<Client> c); // 1AEA
|
||||
void send_intra_team_ranking(std::shared_ptr<Client> c); // 18EA
|
||||
void send_team_reward_list(std::shared_ptr<Client> c, bool show_purchased); // 19EA, 1AEA
|
||||
void send_cross_team_ranking(std::shared_ptr<Client> c); // 1CEA
|
||||
|
||||
@@ -314,6 +314,7 @@ Proxy session commands:\n\
|
||||
auto config_json = this->state->load_config();
|
||||
this->state->parse_config(config_json, true);
|
||||
this->state->resolve_ep3_card_names();
|
||||
this->state->load_teams();
|
||||
} else {
|
||||
throw invalid_argument("incorrect data type");
|
||||
}
|
||||
|
||||
+7
-1
@@ -898,6 +898,11 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
|
||||
this->welcome_message = json.get_string("WelcomeMessage", "");
|
||||
this->pc_patch_server_message = json.get_string("PCPatchServerMessage", "");
|
||||
this->bb_patch_server_message = json.get_string("BBPatchServerMessage", "");
|
||||
|
||||
try {
|
||||
this->team_reward_defs_json = std::move(json.at("TeamRewards"));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
void ServerState::load_bb_private_keys() {
|
||||
@@ -919,7 +924,8 @@ void ServerState::load_licenses() {
|
||||
|
||||
void ServerState::load_teams() {
|
||||
config_log.info("Indexing teams");
|
||||
this->team_index.reset(new TeamIndex("system/teams"));
|
||||
this->team_index.reset(new TeamIndex("system/teams", this->team_reward_defs_json));
|
||||
this->team_reward_defs_json = nullptr;
|
||||
}
|
||||
|
||||
void ServerState::load_patch_indexes() {
|
||||
|
||||
@@ -143,6 +143,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
|
||||
std::shared_ptr<LicenseIndex> license_index;
|
||||
std::shared_ptr<TeamIndex> team_index;
|
||||
JSON team_reward_defs_json;
|
||||
|
||||
std::shared_ptr<const Menu> information_menu_v2;
|
||||
std::shared_ptr<const Menu> information_menu_v3;
|
||||
|
||||
+134
-7
@@ -52,11 +52,20 @@ string TeamIndex::Team::flag_filename() const {
|
||||
void TeamIndex::Team::load_config() {
|
||||
auto json = JSON::parse(load_file(this->json_filename()));
|
||||
this->name = json.get_string("Name");
|
||||
this->spent_points = json.get_int("SpentPoints");
|
||||
this->points = 0;
|
||||
for (const auto& member_it : json.get_list("Members")) {
|
||||
Member m(*member_it);
|
||||
this->points += m.points;
|
||||
uint32_t serial_number = m.serial_number;
|
||||
this->members.emplace(serial_number, std::move(m));
|
||||
}
|
||||
try {
|
||||
for (const auto& it : json.get_list("RewardKeys")) {
|
||||
this->reward_keys.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
this->reward_flags = json.get_int("RewardFlags");
|
||||
}
|
||||
|
||||
@@ -65,9 +74,15 @@ void TeamIndex::Team::save_config() const {
|
||||
for (const auto& it : this->members) {
|
||||
members_json.emplace_back(it.second.json());
|
||||
}
|
||||
JSON reward_keys_json = JSON::list();
|
||||
for (const auto& it : this->reward_keys) {
|
||||
reward_keys_json.emplace_back(it);
|
||||
}
|
||||
JSON root = JSON::dict({
|
||||
{"Name", this->name},
|
||||
{"SpentPoints", this->spent_points},
|
||||
{"Members", std::move(members_json)},
|
||||
{"RewardKeys", std::move(reward_keys_json)},
|
||||
{"RewardFlags", this->reward_flags},
|
||||
});
|
||||
save_file(this->json_filename(), root.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS));
|
||||
@@ -128,6 +143,10 @@ PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t serial_numbe
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool TeamIndex::Team::has_reward(const string& key) const {
|
||||
return this->reward_keys.count(key);
|
||||
}
|
||||
|
||||
size_t TeamIndex::Team::num_members() const {
|
||||
return this->members.size();
|
||||
}
|
||||
@@ -178,9 +197,37 @@ bool TeamIndex::Team::can_promote_leader() const {
|
||||
return this->num_leaders() < this->max_leaders();
|
||||
}
|
||||
|
||||
TeamIndex::TeamIndex(const string& directory)
|
||||
TeamIndex::Reward::Reward(uint32_t menu_item_id, const JSON& def_json)
|
||||
: menu_item_id(menu_item_id),
|
||||
key(def_json.get_string("Key")),
|
||||
name(def_json.get_string("Name")),
|
||||
description(def_json.get_string("Description")),
|
||||
is_unique(def_json.get_bool("IsUnique", true)),
|
||||
team_points(def_json.get_int("Points")) {
|
||||
try {
|
||||
for (const auto& it : def_json.get_list("PrerequisiteKeys")) {
|
||||
this->prerequisite_keys.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->reward_flag = static_cast<Team::RewardFlag>(def_json.get_int("RewardFlag"));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->reward_item = ItemData::from_data(def_json.get_string("RewardItem"));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
TeamIndex::TeamIndex(const string& directory, const JSON& reward_defs_json)
|
||||
: directory(directory),
|
||||
next_team_id(1) {
|
||||
uint32_t reward_menu_item_id = 0;
|
||||
for (const auto& it : reward_defs_json.as_list()) {
|
||||
this->reward_defs.emplace_back(reward_menu_item_id++, *it);
|
||||
}
|
||||
|
||||
if (!isdir(this->directory)) {
|
||||
mkdir(this->directory.c_str(), 0755);
|
||||
return;
|
||||
@@ -214,7 +261,7 @@ size_t TeamIndex::count() const {
|
||||
return this->id_to_team.size();
|
||||
}
|
||||
|
||||
shared_ptr<TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) {
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) const {
|
||||
try {
|
||||
return this->id_to_team.at(team_id);
|
||||
} catch (const out_of_range&) {
|
||||
@@ -222,7 +269,7 @@ shared_ptr<TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) {
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<TeamIndex::Team> TeamIndex::get_by_name(const string& name) {
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_name(const string& name) const {
|
||||
try {
|
||||
return this->name_to_team.at(name);
|
||||
} catch (const out_of_range&) {
|
||||
@@ -230,7 +277,7 @@ shared_ptr<TeamIndex::Team> TeamIndex::get_by_name(const string& name) {
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<TeamIndex::Team> TeamIndex::get_by_serial_number(uint32_t serial_number) {
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_serial_number(uint32_t serial_number) const {
|
||||
try {
|
||||
return this->serial_number_to_team.at(serial_number);
|
||||
} catch (const out_of_range&) {
|
||||
@@ -238,15 +285,15 @@ shared_ptr<TeamIndex::Team> TeamIndex::get_by_serial_number(uint32_t serial_numb
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<TeamIndex::Team>> TeamIndex::all() {
|
||||
vector<shared_ptr<Team>> ret;
|
||||
vector<shared_ptr<const TeamIndex::Team>> TeamIndex::all() const {
|
||||
vector<shared_ptr<const Team>> ret;
|
||||
for (const auto& it : this->id_to_team) {
|
||||
ret.emplace_back(it.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<TeamIndex::Team> TeamIndex::create(string& name, uint32_t master_serial_number, const string& master_name) {
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::create(string& name, uint32_t master_serial_number, const string& master_name) {
|
||||
shared_ptr<Team> team(new Team(this->next_team_id++));
|
||||
save_file(this->directory + "/base.json", JSON::dict({{"NextTeamID", this->next_team_id}}).serialize());
|
||||
|
||||
@@ -301,6 +348,86 @@ void TeamIndex::remove_member(uint32_t serial_number) {
|
||||
}
|
||||
}
|
||||
|
||||
void TeamIndex::update_member_name(uint32_t serial_number, const std::string& name) {
|
||||
auto team = this->serial_number_to_team.at(serial_number);
|
||||
auto& m = team->members.at(serial_number);
|
||||
m.name = name;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::add_member_points(uint32_t serial_number, uint32_t points) {
|
||||
auto team = this->serial_number_to_team.at(serial_number);
|
||||
team->members.at(serial_number).points += points;
|
||||
team->points += points;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
team->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>(flag_data));
|
||||
team->save_flag();
|
||||
}
|
||||
|
||||
bool TeamIndex::promote_leader(uint32_t master_serial_number, uint32_t leader_serial_number) {
|
||||
auto team = this->serial_number_to_team.at(master_serial_number);
|
||||
auto& master_m = team->members.at(master_serial_number);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master serial number");
|
||||
}
|
||||
auto& other_m = team->members.at(leader_serial_number);
|
||||
|
||||
if (other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER) || !team->can_promote_leader()) {
|
||||
return false;
|
||||
}
|
||||
other_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
team->save_config();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TeamIndex::demote_leader(uint32_t master_serial_number, uint32_t leader_serial_number) {
|
||||
auto team = this->serial_number_to_team.at(master_serial_number);
|
||||
auto& master_m = team->members.at(master_serial_number);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master serial number");
|
||||
}
|
||||
auto& other_m = team->members.at(leader_serial_number);
|
||||
|
||||
if (!other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER)) {
|
||||
return false;
|
||||
}
|
||||
other_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
team->save_config();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TeamIndex::change_master(uint32_t master_serial_number, uint32_t new_master_serial_number) {
|
||||
auto team = this->serial_number_to_team.at(master_serial_number);
|
||||
auto& master_m = team->members.at(master_serial_number);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master serial number");
|
||||
}
|
||||
auto& new_master_m = team->members.at(new_master_serial_number);
|
||||
|
||||
master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
|
||||
master_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
new_master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER);
|
||||
new_master_m.set_flag(TeamIndex::Team::Member::Flag::IS_MASTER);
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::buy_reward(uint32_t team_id, const string& key, uint32_t points, Team::RewardFlag reward_flag) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
if (team->spent_points + points > team->points) {
|
||||
throw runtime_error("not enough points available");
|
||||
}
|
||||
team->reward_keys.emplace(key);
|
||||
team->spent_points += points;
|
||||
if (reward_flag != Team::RewardFlag::NONE) {
|
||||
team->set_reward_flag(reward_flag);
|
||||
}
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::add_to_indexes(shared_ptr<Team> team) {
|
||||
if (!this->id_to_team.emplace(team->team_id, team).second) {
|
||||
throw runtime_error("team ID is already in use");
|
||||
|
||||
+39
-7
@@ -47,6 +47,7 @@ public:
|
||||
enum class RewardFlag {
|
||||
// Only 0x00000001 and 0x00000002 are used by the client; the rest are
|
||||
// free to be used however the server chooses.
|
||||
NONE = 0x00000000,
|
||||
TEAM_FLAG = 0x00000001,
|
||||
DRESSING_ROOM = 0x00000002,
|
||||
MEMBERS_20_LEADERS_3 = 0x00000004,
|
||||
@@ -56,9 +57,12 @@ public:
|
||||
};
|
||||
|
||||
uint32_t team_id = 0;
|
||||
uint32_t points = 0;
|
||||
uint32_t spent_points = 0;
|
||||
std::string name;
|
||||
std::unordered_map<uint32_t, Member> members;
|
||||
uint32_t reward_flags = 0;
|
||||
std::unordered_set<std::string> reward_keys;
|
||||
std::shared_ptr<parray<le_uint16_t, 0x20 * 0x20>> flag_data;
|
||||
|
||||
Team() = default;
|
||||
@@ -86,6 +90,8 @@ public:
|
||||
this->reward_flags &= (~static_cast<uint8_t>(flag));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool has_reward(const std::string& key) const;
|
||||
|
||||
size_t num_members() const;
|
||||
size_t num_leaders() const;
|
||||
size_t max_members() const;
|
||||
@@ -94,20 +100,45 @@ public:
|
||||
bool can_promote_leader() const;
|
||||
};
|
||||
|
||||
explicit TeamIndex(const std::string& directory);
|
||||
struct Reward {
|
||||
uint32_t menu_item_id = 0;
|
||||
std::string key;
|
||||
std::string name;
|
||||
std::string description;
|
||||
std::unordered_set<std::string> prerequisite_keys;
|
||||
bool is_unique = true;
|
||||
uint32_t team_points = 0;
|
||||
Team::RewardFlag reward_flag = Team::RewardFlag::NONE;
|
||||
ItemData reward_item;
|
||||
|
||||
Reward(uint32_t menu_item_id, const JSON& def_json);
|
||||
};
|
||||
|
||||
TeamIndex(const std::string& directory, const JSON& reward_defs_json);
|
||||
~TeamIndex() = default;
|
||||
|
||||
size_t count() const;
|
||||
std::shared_ptr<Team> get_by_id(uint32_t team_id);
|
||||
std::shared_ptr<Team> get_by_name(const std::string& name);
|
||||
std::shared_ptr<Team> get_by_serial_number(uint32_t serial_number);
|
||||
std::vector<std::shared_ptr<Team>> all();
|
||||
inline const std::vector<Reward>& reward_definitions() const {
|
||||
return this->reward_defs;
|
||||
}
|
||||
|
||||
std::shared_ptr<Team> create(std::string& name, uint32_t master_serial_number, const std::string& master_name);
|
||||
size_t count() const;
|
||||
std::shared_ptr<const Team> get_by_id(uint32_t team_id) const;
|
||||
std::shared_ptr<const Team> get_by_name(const std::string& name) const;
|
||||
std::shared_ptr<const Team> get_by_serial_number(uint32_t serial_number) const;
|
||||
std::vector<std::shared_ptr<const Team>> all() const;
|
||||
|
||||
std::shared_ptr<const Team> create(std::string& name, uint32_t master_serial_number, const std::string& master_name);
|
||||
void disband(uint32_t team_id);
|
||||
|
||||
void add_member(uint32_t team_id, uint32_t serial_number, const std::string& name);
|
||||
void remove_member(uint32_t serial_number);
|
||||
void update_member_name(uint32_t serial_number, const std::string& name);
|
||||
void add_member_points(uint32_t serial_number, uint32_t points);
|
||||
void set_flag_data(uint32_t team_id, const parray<le_uint16_t, 0x20 * 0x20>& flag_data);
|
||||
bool promote_leader(uint32_t master_serial_number, uint32_t leader_serial_number);
|
||||
bool demote_leader(uint32_t master_serial_number, uint32_t leader_serial_number);
|
||||
void change_master(uint32_t master_serial_number, uint32_t new_master_serial_number);
|
||||
void buy_reward(uint32_t team_id, const std::string& key, uint32_t points, Team::RewardFlag reward_flag);
|
||||
|
||||
protected:
|
||||
std::string directory;
|
||||
@@ -115,6 +146,7 @@ protected:
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Team>> id_to_team;
|
||||
std::unordered_map<std::string, std::shared_ptr<Team>> name_to_team;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Team>> serial_number_to_team;
|
||||
std::vector<Reward> reward_defs;
|
||||
|
||||
void add_to_indexes(std::shared_ptr<Team> team);
|
||||
void remove_from_indexes(std::shared_ptr<Team> team);
|
||||
|
||||
Reference in New Issue
Block a user