From 6af0527498cc3c3b10acaa8e6665505966c9f17c Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 25 Nov 2023 23:12:09 -0800 Subject: [PATCH] implement team points and member ranking --- src/CommandFormats.hh | 10 ++++----- src/ItemParameterTable.cc | 27 ++++++++++++++++++++++++ src/ItemParameterTable.hh | 1 + src/ReceiveSubcommands.cc | 43 ++++++++++++++++++++++++++++++++++++++- src/SendCommands.cc | 29 +++++++++++++++++++++----- 5 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index f83c2480..7cd1f1ec 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -3363,16 +3363,16 @@ struct S_TeamInfoForPlayer_BB_13EA_15EA_Entry { // No arguments (C->S) struct S_TeamRankingInformation_BB_18EA { - /* 0000 */ le_uint32_t unknown_a1 = 0; + /* 0000 */ le_uint32_t ranking_points = 0; /* 0004 */ le_uint32_t unknown_a2 = 0; - /* 0008 */ le_uint32_t unknown_a3 = 0; + /* 0008 */ le_uint32_t points_remaining = 0; /* 000C */ le_uint32_t num_entries = 1; struct Entry { - /* 00 */ le_uint32_t unknown_a1 = 0; + /* 00 */ le_uint32_t rank = 0; /* 04 */ le_uint32_t privilege_level = 0; /* 08 */ le_uint32_t guild_card_number = 0; /* 0C */ pstring player_name; - /* 2C */ le_uint32_t unknown_a2 = 0; + /* 2C */ le_uint32_t points = 0; /* 30 */ } __packed__; // Variable-length field: @@ -5612,7 +5612,7 @@ struct G_ItemTransferRequest_BB_6xCB { struct G_ExchangeItemForTeamPoints_BB_6xCC { G_ClientIDHeader header; le_uint32_t item_id = 0; - le_uint32_t unknown_a2 = 0; + le_uint32_t amount = 0; } __packed__; // 6xCD: Transfer master (BB) diff --git a/src/ItemParameterTable.cc b/src/ItemParameterTable.cc index 58a7a94e..2b708132 100644 --- a/src/ItemParameterTable.cc +++ b/src/ItemParameterTable.cc @@ -610,6 +610,33 @@ uint32_t ItemParameterTable::get_item_id(const ItemData& item) const { } } +uint32_t ItemParameterTable::get_item_team_points(const ItemData& item) const { + switch (item.data1[0]) { + case 0: + return this->get_weapon(item.data1[1], item.data1[2]).base.team_points; + case 1: + if (item.data1[1] == 3) { + return this->get_unit(item.data1[2]).base.team_points; + } else if ((item.data1[1] == 1) || (item.data1[1] == 2)) { + return this->get_armor_or_shield(item.data1[1], item.data1[2]).base.team_points; + } + throw runtime_error("invalid item"); + case 2: + return this->get_mag(item.data1[1]).base.team_points; + case 3: + if (item.data1[1] == 2) { + return this->get_tool(2, item.data1[4]).base.team_points; + } else { + return this->get_tool(item.data1[1], item.data1[2]).base.team_points; + } + throw logic_error("this should be impossible"); + case 4: + throw runtime_error("item is meseta and therefore has no definition"); + default: + throw runtime_error("invalid item"); + } +} + uint8_t ItemParameterTable::get_item_base_stars(const ItemData& item) const { if (item.data1[0] == 2) { return (item.data1[1] >= this->first_rare_mag_index) ? 12 : 0; diff --git a/src/ItemParameterTable.hh b/src/ItemParameterTable.hh index b3a7ff8a..a1dd1273 100644 --- a/src/ItemParameterTable.hh +++ b/src/ItemParameterTable.hh @@ -324,6 +324,7 @@ public: uint8_t get_weapon_v1_replacement(uint8_t data1_1) const; uint32_t get_item_id(const ItemData& item) const; + uint32_t get_item_team_points(const ItemData& item) const; uint8_t get_item_base_stars(const ItemData& item) const; uint8_t get_item_adjusted_stars(const ItemData& item) const; bool is_item_rare(const ItemData& item) const; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index ea370ee9..67b78c23 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -2075,6 +2075,47 @@ void on_item_reward_request_bb(shared_ptr c, uint8_t, uint8_t, const voi send_create_inventory_item(c, item); } +void on_exchange_item_for_team_points_bb(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { + const auto& cmd = check_size_t(data, size); + + auto team = c->team(); + if (!team) { + throw runtime_error("player is not in a team"); + } + auto l = c->require_lobby(); + if (!l->is_game()) { + return; + } + if (cmd.header.client_id != c->lobby_client_id) { + return; + } + + if (l->base_version != Version::BB_V4) { + throw runtime_error("item tracking not enabled in BB game"); + } + if (!l->check_flag(Lobby::Flag::ITEM_TRACKING_ENABLED)) { + throw runtime_error("item tracking not enabled in BB game"); + } + + auto s = c->require_server_state(); + auto p = c->game_data.character(); + 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; + + 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", + c->lobby_client_id, cmd.header.client_id.load(), cmd.item_id.load(), name.c_str(), points); + if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { + string name = s->describe_item(c->version(), item, true); + send_text_message_printf(c, "$C5EX/PT %08" PRIX32 "\n%s\n$C5+%zuPT", cmd.item_id.load(), name.c_str(), points); + } + p->print_inventory(stderr, c->version(), s->item_name_index); + + forward_subcommand(c, command, flag, data, size); +} + static void on_destroy_inventory_item(shared_ptr c, uint8_t command, uint8_t flag, const void* data, size_t size) { const auto& cmd = check_size_t(data, size); @@ -2914,7 +2955,7 @@ subcommand_handler_t subcommand_handlers[0x100] = { /* 6xC9 */ on_meseta_reward_request_bb, /* 6xCA */ on_item_reward_request_bb, /* 6xCB */ nullptr, - /* 6xCC */ nullptr, + /* 6xCC */ on_exchange_item_for_team_points_bb, /* 6xCD */ nullptr, /* 6xCE */ nullptr, /* 6xCF */ on_battle_restart_bb, diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 0ae42cf5..33c1e76e 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -3272,13 +3272,13 @@ 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); - cmd.guild_card_number2 = c->license->serial_number; - cmd.player_name = c->game_data.character()->disp.name; if (team->flag_data) { cmd.flag_data = *team->flag_data; } @@ -3338,11 +3338,30 @@ void send_team_rank_info(std::shared_ptr c) { throw runtime_error("client is not in a team"); } + vector 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_TeamRankingInformation_BB_18EA cmd; - cmd.num_entries = team->num_members(); + cmd.points_remaining = 0; vector entries; - // TODO: FIll in entries here + for (size_t z = 0; z < members.size(); z++) { + const auto* m = members[z]; + cmd.ranking_points += m->points; + auto& e = entries.emplace_back(); + e.rank = z + 1; + e.privilege_level = m->privilege_level(); + e.guild_card_number = m->serial_number; + e.player_name.encode(m->name); + e.points = m->points; + } + cmd.num_entries = entries.size(); send_command_t_vt(c, 0x18EA, 0x00000000, cmd, entries); }