implement 6xE2 subcommand
This commit is contained in:
+15
-8
@@ -5647,9 +5647,9 @@ struct G_EnemyEXPRequest_BB_6xC8 {
|
||||
parray<uint8_t, 3> unused;
|
||||
} __packed__;
|
||||
|
||||
// 6xC9: Request meseta reward from quest (BB; handled by server)
|
||||
// 6xC9: Adjust player Meseta (BB; handled by server)
|
||||
|
||||
struct G_MesetaRewardRequest_BB_6xC9 {
|
||||
struct G_AdjustPlayerMeseta_BB_6xC9 {
|
||||
G_UnusedHeader header;
|
||||
le_int32_t amount = 0;
|
||||
} __packed__;
|
||||
@@ -5866,19 +5866,26 @@ struct G_ExchangePhotonTickets_BB_6xE1 {
|
||||
le_uint16_t unknown_a5 = 0; // argsA[4]
|
||||
} __packed__;
|
||||
|
||||
// 6xE2: Coren actions (BB)
|
||||
// 6xE2: Get Meseta slot prize (BB)
|
||||
// The client sends this when it executes an F960 quest opcode.
|
||||
|
||||
struct G_CorenActions_BB_6xE2 {
|
||||
struct G_GetMesetaSlotPrize_BB_6xE2 {
|
||||
G_ClientIDHeader header;
|
||||
parray<uint8_t, 12> unknown_a1; // TODO: There might be uint16_ts and uint32_ts in here.
|
||||
uint8_t result_tier; // This contains the argument value from the F960 opcode
|
||||
uint8_t floor;
|
||||
uint8_t unknown_a2;
|
||||
uint8_t unused;
|
||||
le_float x; // TODO: Verify this guess
|
||||
le_float z; // TODO: Verify this guess
|
||||
} __packed__;
|
||||
|
||||
// 6xE3: Coren actions result (BB)
|
||||
// 6xE3: Set Meseta slot prize result (BB)
|
||||
// The client only uses this to populate the <meseta_slot_prize> quest text
|
||||
// replacement token.
|
||||
|
||||
struct G_CorenActionsResult_BB_6xE3 {
|
||||
struct G_SetMesetaSlotPrizeResult_BB_6xE3 {
|
||||
G_ClientIDHeader header;
|
||||
ItemData item_data;
|
||||
ItemData item;
|
||||
} __packed__;
|
||||
|
||||
// 6xE4: Invalid subcommand
|
||||
|
||||
@@ -390,9 +390,11 @@ ItemData ItemNameIndex::parse_item_description(Version version, const std::strin
|
||||
}
|
||||
} catch (const exception& ed) {
|
||||
if (strcmp(e1.what(), e2.what())) {
|
||||
throw runtime_error(string_printf("cannot parse item description (as text 1: %s) (as text 2: %s) (as data: %s)", e1.what(), e2.what(), ed.what()));
|
||||
throw runtime_error(string_printf("cannot parse item description \"%s\" in %s (as text 1: %s) (as text 2: %s) (as data: %s)",
|
||||
desc.c_str(), name_for_enum(version), e1.what(), e2.what(), ed.what()));
|
||||
} else {
|
||||
throw runtime_error(string_printf("cannot parse item description (as text: %s) (as data: %s)", e1.what(), ed.what()));
|
||||
throw runtime_error(string_printf("cannot parse item description \"%s\" in %s (as text: %s) (as data: %s)",
|
||||
desc.c_str(), name_for_enum(version), e1.what(), ed.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2082,8 +2082,8 @@ static void on_enemy_exp_request_bb(shared_ptr<Client> c, uint8_t, uint8_t, cons
|
||||
}
|
||||
}
|
||||
|
||||
void on_meseta_reward_request_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_MesetaRewardRequest_BB_6xC9>(data, size);
|
||||
void on_adjust_player_meseta_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
|
||||
const auto& cmd = check_size_t<G_AdjustPlayerMeseta_BB_6xC9>(data, size);
|
||||
|
||||
auto p = c->character();
|
||||
if (cmd.amount < 0) {
|
||||
@@ -2692,6 +2692,58 @@ static void on_quest_F95F_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, cons
|
||||
}
|
||||
}
|
||||
|
||||
static void on_quest_F960_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
|
||||
auto l = c->require_lobby();
|
||||
if (l->is_game() && (l->base_version == Version::BB_V4)) {
|
||||
const auto& cmd = check_size_t<G_GetMesetaSlotPrize_BB_6xE2>(data, size);
|
||||
auto s = c->require_server_state();
|
||||
auto p = c->character();
|
||||
|
||||
time_t t_secs = now() / 1000000;
|
||||
struct tm t_parsed;
|
||||
gmtime_r(&t_secs, &t_parsed);
|
||||
size_t weekday = t_parsed.tm_wday;
|
||||
|
||||
ItemData item;
|
||||
for (size_t num_failures = 0; num_failures <= cmd.result_tier; num_failures++) {
|
||||
size_t tier = cmd.result_tier - num_failures;
|
||||
const auto& results = s->quest_F960_success_results.at(tier);
|
||||
uint64_t probability = results.base_probability + num_failures * results.probability_upgrade;
|
||||
if (random_object<uint32_t>() <= probability) {
|
||||
c->log.info("Tier %zu yielded a prize", tier);
|
||||
const auto& result_items = results.results.at(weekday);
|
||||
item = result_items[random_object<uint32_t>() % result_items.size()];
|
||||
break;
|
||||
} else {
|
||||
c->log.info("Tier %zu did not yield a prize", tier);
|
||||
}
|
||||
}
|
||||
if (item.empty()) {
|
||||
c->log.info("Choosing result from failure tier");
|
||||
const auto& result_items = s->quest_F960_failure_results.results.at(weekday);
|
||||
item = result_items[random_object<uint32_t>() % result_items.size()];
|
||||
}
|
||||
if (item.empty()) {
|
||||
throw runtime_error("no item produced, even from failure tier");
|
||||
}
|
||||
|
||||
// The client sends a 6xC9 to remove Meseta before sending 6xE2, so we don't
|
||||
// have to deal with Meseta here.
|
||||
|
||||
item.id = l->generate_item_id(c->lobby_client_id);
|
||||
p->add_item(item);
|
||||
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item);
|
||||
|
||||
G_SetMesetaSlotPrizeResult_BB_6xE3 cmd_6xE3 = {{0xE3, sizeof(G_SetMesetaSlotPrizeResult_BB_6xE3) >> 2, 0x0000}, item};
|
||||
send_command_t(c, 0x60, 0x00, cmd_6xE3);
|
||||
|
||||
if (c->log.should_log(LogLevel::INFO)) {
|
||||
string name = s->item_name_index->describe_item(c->version(), item);
|
||||
c->log.info("Awarded item %s", name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void on_momoka_item_exchange_bb(shared_ptr<Client> c, uint8_t, uint8_t, const void* data, size_t size) {
|
||||
auto l = c->require_lobby();
|
||||
if (l->is_game() && (l->base_version == Version::BB_V4) && l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS)) {
|
||||
@@ -2984,7 +3036,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6xC6 */ {0x00, 0x00, 0xC6, on_steal_exp_bb},
|
||||
/* 6xC7 */ {0x00, 0x00, 0xC7, on_charge_attack_bb},
|
||||
/* 6xC8 */ {0x00, 0x00, 0xC8, on_enemy_exp_request_bb},
|
||||
/* 6xC9 */ {0x00, 0x00, 0xC9, on_meseta_reward_request_bb},
|
||||
/* 6xC9 */ {0x00, 0x00, 0xC9, on_adjust_player_meseta_bb},
|
||||
/* 6xCA */ {0x00, 0x00, 0xCA, on_item_reward_request_bb},
|
||||
/* 6xCB */ {0x00, 0x00, 0xCB, on_transfer_item_via_mail_message_bb},
|
||||
/* 6xCC */ {0x00, 0x00, 0xCC, on_exchange_item_for_team_points_bb},
|
||||
@@ -3009,7 +3061,7 @@ const SubcommandDefinition subcommand_definitions[0x100] = {
|
||||
/* 6xDF */ {0x00, 0x00, 0xDF, on_photon_crystal_exchange_bb},
|
||||
/* 6xE0 */ {0x00, 0x00, 0xE0, on_quest_F95E_result_bb},
|
||||
/* 6xE1 */ {0x00, 0x00, 0xE1, on_quest_F95F_result_bb},
|
||||
/* 6xE2 */ {0x00, 0x00, 0xE2, nullptr},
|
||||
/* 6xE2 */ {0x00, 0x00, 0xE2, on_quest_F960_result_bb},
|
||||
/* 6xE3 */ {0x00, 0x00, 0xE3, nullptr},
|
||||
/* 6xE4 */ {0x00, 0x00, 0xE4, nullptr},
|
||||
/* 6xE5 */ {0x00, 0x00, 0xE5, nullptr},
|
||||
|
||||
@@ -297,6 +297,7 @@ Proxy session commands:\n\
|
||||
} else if (type == "level-table") {
|
||||
this->state->load_level_table();
|
||||
} else if (type == "item-tables") {
|
||||
this->state->load_item_name_index();
|
||||
this->state->load_item_tables();
|
||||
} else if (type == "word-select") {
|
||||
this->state->load_word_select_table();
|
||||
|
||||
+27
-7
@@ -17,6 +17,18 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
ServerState::QuestF960Result::QuestF960Result(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index) {
|
||||
static const array<string, 7> day_names = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
|
||||
this->meseta_cost = json.get_int("MesetaCost", 0);
|
||||
this->base_probability = json.get_int("BaseProbability", 0);
|
||||
this->probability_upgrade = json.get_int("ProbabilityUpgrade", 0);
|
||||
for (size_t day = 0; day < 7; day++) {
|
||||
for (const auto& item_it : json.get_list(day_names[day])) {
|
||||
this->results[day].emplace_back(name_index->parse_item_description(Version::BB_V4, item_it->as_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ServerState::ServerState(shared_ptr<struct event_base> base, const string& config_filename, bool is_replay)
|
||||
: base(base),
|
||||
config_filename(config_filename),
|
||||
@@ -125,6 +137,7 @@ void ServerState::init() {
|
||||
// Load all the necessary data
|
||||
auto config = this->load_config();
|
||||
this->collect_network_addresses();
|
||||
this->load_item_name_index();
|
||||
this->parse_config(config, false);
|
||||
this->load_bb_private_keys();
|
||||
this->load_licenses();
|
||||
@@ -806,8 +819,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
|
||||
for (const auto& difficulty_it : type_it->as_list()) {
|
||||
auto& difficulty_res = type_res.emplace_back();
|
||||
for (const auto& item_it : difficulty_it->as_list()) {
|
||||
string data = parse_data_string(item_it->as_string());
|
||||
difficulty_res.emplace_back(ItemData::from_data(data));
|
||||
difficulty_res.emplace_back(this->item_name_index->parse_item_description(Version::BB_V4, item_it->as_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -818,16 +830,22 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
|
||||
for (const auto& it : json.get_list("QuestF95FResultItems")) {
|
||||
auto& list = it->as_list();
|
||||
size_t price = list.at(0)->as_int();
|
||||
string data = parse_data_string(list.at(1)->as_string());
|
||||
this->quest_F95F_results.emplace_back(make_pair(price, ItemData::from_data(data)));
|
||||
this->quest_F95F_results.emplace_back(make_pair(price, this->item_name_index->parse_item_description(Version::BB_V4, list.at(1)->as_string())));
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->quest_F960_success_results.clear();
|
||||
this->quest_F960_failure_results = QuestF960Result(json.at("QuestF960FailureResultItems"), this->item_name_index);
|
||||
for (const auto& it : json.get_list("QuestF960SuccessResultItems")) {
|
||||
this->quest_F960_success_results.emplace_back(*it, this->item_name_index);
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->secret_lottery_results.clear();
|
||||
for (const auto& it : json.get_list("SecretLotteryResultItems")) {
|
||||
string data = parse_data_string(it->as_string());
|
||||
this->secret_lottery_results.emplace_back(ItemData::from_data(data));
|
||||
this->secret_lottery_results.emplace_back(this->item_name_index->parse_item_description(Version::BB_V4, it->as_string()));
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
@@ -1086,13 +1104,15 @@ void ServerState::load_word_select_table() {
|
||||
this->word_select_table = make_shared<WordSelectTable>(JSON::parse(load_file("system/word-select-table.json")));
|
||||
}
|
||||
|
||||
void ServerState::load_item_tables() {
|
||||
void ServerState::load_item_name_index() {
|
||||
config_log.info("Loading item name index");
|
||||
this->item_name_index = make_shared<ItemNameIndex>(
|
||||
JSON::parse(load_file("system/item-tables/names-v2.json")),
|
||||
JSON::parse(load_file("system/item-tables/names-v3.json")),
|
||||
JSON::parse(load_file("system/item-tables/names-v4.json")));
|
||||
}
|
||||
|
||||
void ServerState::load_item_tables() {
|
||||
config_log.info("Loading rare item sets");
|
||||
unordered_map<string, shared_ptr<const RareItemSet>> new_rare_item_sets;
|
||||
for (const auto& filename : list_directory_sorted("system/item-tables")) {
|
||||
|
||||
@@ -141,9 +141,21 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge;
|
||||
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
|
||||
|
||||
struct QuestF960Result {
|
||||
uint32_t meseta_cost = 0;
|
||||
uint32_t base_probability = 0;
|
||||
uint32_t probability_upgrade = 0;
|
||||
std::array<std::vector<ItemData>, 7> results;
|
||||
|
||||
QuestF960Result() = default;
|
||||
QuestF960Result(const JSON& json, std::shared_ptr<const ItemNameIndex> name_index);
|
||||
};
|
||||
|
||||
// Indexed as [type][difficulty][random_choice]
|
||||
std::vector<std::vector<std::vector<ItemData>>> quest_F95E_results;
|
||||
std::vector<std::pair<size_t, ItemData>> quest_F95F_results; // [(num_photon_tickets, item)]
|
||||
std::vector<QuestF960Result> quest_F960_success_results;
|
||||
QuestF960Result quest_F960_failure_results;
|
||||
std::vector<ItemData> secret_lottery_results;
|
||||
uint16_t bb_global_exp_multiplier;
|
||||
|
||||
@@ -268,6 +280,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
void load_patch_indexes();
|
||||
void load_battle_params();
|
||||
void load_level_table();
|
||||
void load_item_name_index();
|
||||
void load_item_tables();
|
||||
void load_word_select_table();
|
||||
void load_ep3_data();
|
||||
|
||||
Reference in New Issue
Block a user