diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index f4ff6917..605bc8f5 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -1338,8 +1338,8 @@ static void server_command_ep3_infinite_time(shared_ptr c, const std::u1 return; } - l->ep3_server->behavior_flags ^= Episode3::BehaviorFlag::DISABLE_TIME_LIMITS; - bool infinite_time_enabled = (l->ep3_server->behavior_flags & Episode3::BehaviorFlag::DISABLE_TIME_LIMITS); + l->ep3_server->options.behavior_flags ^= Episode3::BehaviorFlag::DISABLE_TIME_LIMITS; + bool infinite_time_enabled = (l->ep3_server->options.behavior_flags & Episode3::BehaviorFlag::DISABLE_TIME_LIMITS); send_text_message(l, infinite_time_enabled ? u"$C6Infinite time enabled" : u"$C6Infinite time disabled"); } diff --git a/src/Episode3/CardSpecial.cc b/src/Episode3/CardSpecial.cc index d2b28ab4..f4830d3a 100644 --- a/src/Episode3/CardSpecial.cc +++ b/src/Episode3/CardSpecial.cc @@ -897,7 +897,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( // the Gifoie card's ID (00D9) for compute_effective_range. // TODO: We should fix this so it doesn't rely on a fixed card definition. parray range; - compute_effective_range(range, this->server()->card_index, 0x00D9, target_card_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, 0x00D9, target_card_loc, this->server()->map_and_rules); auto card_refs_in_parry_range = target_ps->get_all_cards_within_range( range, target_card_loc, 0xFF); @@ -2651,7 +2651,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( if (ce && ps) { uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); add_card_refs(ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM)); } } @@ -2661,7 +2661,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( if (ce && ps) { uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id())); } } @@ -2701,7 +2701,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( if (ce && ps) { uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id())); } } @@ -2771,7 +2771,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // should fix this eventually. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -2797,7 +2797,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); log23.debug("effective range card ID is #%04hX", range_card_id); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules, &log23); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules, &log23); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); log23.debug("%zu result card refs", result_card_refs.size()); for (uint16_t result_card_ref : result_card_refs) { @@ -2874,7 +2874,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: Again with the Gifoie hardcoding... uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -2928,7 +2928,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: Yet another Gifoie hardcode location :( uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -2955,7 +2955,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: Sigh. Gifoie again. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -3051,7 +3051,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // Slay instead of Gifoie uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x009C, card2); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -3080,7 +3080,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: Sigh. Gifoie. Sigh. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -3117,7 +3117,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: One more Gifoie here. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->card_index, range_card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, range_card_id, card1_loc, this->server()->map_and_rules); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -3541,7 +3541,7 @@ void CardSpecial::check_for_defense_interference( shared_ptr target_card, int16_t* inout_unknown_p4) { // Note: This check is not part of the original implementation. - if (this->server()->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { + if (this->server()->options.behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { return; } @@ -3574,7 +3574,7 @@ void CardSpecial::check_for_defense_interference( } auto ally_hes = this->server()->ruler_server->get_hand_and_equip_state_for_client_id(target_ally_client_id); - if (!ally_hes || (!(this->server()->behavior_flags & BehaviorFlag::ALLOW_NON_COM_INTERFERENCE) && !ally_hes->is_cpu_player)) { + if (!ally_hes || (!(this->server()->options.behavior_flags & BehaviorFlag::ALLOW_NON_COM_INTERFERENCE) && !ally_hes->is_cpu_player)) { return; } @@ -4317,7 +4317,7 @@ vector> CardSpecial::filter_cards_by_range( // TODO: Remove hardcoded card ID here (Earthquake) uint16_t card_id = this->get_card_id_with_effective_range(card1, 0x00ED, card2); parray range; - compute_effective_range(range, this->server()->card_index, card_id, card1_loc, this->server()->map_and_rules); + compute_effective_range(range, this->server()->options.card_index, card_id, card1_loc, this->server()->map_and_rules); auto card_refs_in_range = ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM); for (auto card : cards) { @@ -4539,7 +4539,7 @@ void CardSpecial::unknown_8024A9D8(const ActionState& pa, uint16_t action_card_r void CardSpecial::check_for_attack_interference(shared_ptr unknown_p2) { // Note: This check is not part of the original implementation. - if (this->server()->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { + if (this->server()->options.behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { return; } @@ -4568,7 +4568,7 @@ void CardSpecial::check_for_attack_interference(shared_ptr unknown_p2) { } auto ally_hes = this->server()->ruler_server->get_hand_and_equip_state_for_client_id(ally_client_id); - if (!ally_hes || (!(this->server()->behavior_flags & BehaviorFlag::ALLOW_NON_COM_INTERFERENCE) && !ally_hes->is_cpu_player)) { + if (!ally_hes || (!(this->server()->options.behavior_flags & BehaviorFlag::ALLOW_NON_COM_INTERFERENCE) && !ally_hes->is_cpu_player)) { return; } diff --git a/src/Episode3/PlayerState.cc b/src/Episode3/PlayerState.cc index 18d8c019..f29c1770 100644 --- a/src/Episode3/PlayerState.cc +++ b/src/Episode3/PlayerState.cc @@ -51,7 +51,7 @@ void PlayerState::init() { this->deck_state.reset(new DeckState( this->client_id, s->deck_entries[client_id]->card_ids, - s->random_crypt)); + s->options.random_crypt)); if (s->map_and_rules->rules.disable_deck_shuffle) { this->deck_state->disable_shuffle(); } diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index 84f8d7a2..3d761e41 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -24,19 +24,10 @@ void Server::PresenceEntry::clear() { this->is_cpu_player = 0; } -Server::Server(shared_ptr lobby, - shared_ptr card_index, - shared_ptr map_index, - uint32_t behavior_flags, - shared_ptr random_crypt, - shared_ptr tournament) +Server::Server(shared_ptr lobby, Options&& options) : lobby(lobby), - card_index(card_index), - map_index(map_index), - behavior_flags(behavior_flags), - random_crypt(random_crypt), - last_chosen_map(tournament ? tournament->get_map() : nullptr), - tournament(tournament), + options(std::move(options)), + last_chosen_map(this->options.tournament ? this->options.tournament->get_map() : nullptr), tournament_match_result_sent(false), override_environment_number(0xFF), battle_finished(false), @@ -204,7 +195,7 @@ void Server::send(const void* data, size_t size) const { } string masked_data; - if (!(this->behavior_flags & BehaviorFlag::DISABLE_MASKING)) { + if (!(this->options.behavior_flags & BehaviorFlag::DISABLE_MASKING)) { if (size >= 8) { masked_data.assign(reinterpret_cast(data), size); uint8_t mask_key = (random_object() % 0xFF) + 1; @@ -238,12 +229,16 @@ void Server::send_6xB4x46() const { G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46; cmd46.version_signature = VERSION_SIGNATURE; - cmd46.date_str1 = format_time(this->card_index->definitions_mtime() * 1000000); + cmd46.date_str1 = format_time(this->options.card_index->definitions_mtime() * 1000000); + string date_str2 = string_printf( + "Lobby:%08" PRIX32 " Random:%08" PRIX32 "+%08" PRIX32, + l->lobby_id, + this->options.random_crypt->seed(), + this->options.random_crypt->absolute_offset()); if (this->last_chosen_map) { - cmd46.date_str2 = string_printf("Lobby:%08" PRIX32 " Random:%08" PRIX32 "+%08" PRIX32 " Map:%08" PRIX32, l->lobby_id, this->random_crypt->seed(), this->random_crypt->absolute_offset(), this->last_chosen_map->map.map_number.load()); - } else { - cmd46.date_str2 = string_printf("Lobby:%08" PRIX32 " Random:%08" PRIX32 "+%08" PRIX32, l->lobby_id, this->random_crypt->seed(), this->random_crypt->absolute_offset()); + date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map.map_number.load()); } + cmd46.date_str2 = date_str2; this->send(cmd46); } @@ -307,7 +302,7 @@ void Server::send_commands_for_joining_spectator(Channel& c, bool is_trial) cons __attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(const char* fmt, ...) const { auto l = this->lobby.lock(); - if (l && (this->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) { + if (l && (this->options.behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) { va_list va; va_start(va, fmt); std::string buf = string_vprintf(fmt, va); @@ -408,7 +403,7 @@ void Server::draw_phase_before() { shared_ptr Server::definition_for_card_ref(uint16_t card_ref) const { try { - return this->card_index->definition_for_id(this->card_id_for_card_ref(card_ref)); + return this->options.card_index->definition_for_id(this->card_id_for_card_ref(card_ref)); } catch (const out_of_range&) { return nullptr; } @@ -667,7 +662,7 @@ void Server::copy_player_states_to_prev_states() { shared_ptr Server::definition_for_card_id(uint16_t card_id) const { try { - return this->card_index->definition_for_id(card_id); + return this->options.card_index->definition_for_id(card_id); } catch (const out_of_range&) { return nullptr; } @@ -971,12 +966,12 @@ uint32_t Server::get_random(uint32_t max) { // The original implementation was essentially: // return (static_cast(this->random_crypt->next() >> 16) / 65536.0) * max // This is unnecessarily complicated, so we instead just do this: - return this->random_crypt->next() % max; + return this->options.random_crypt->next() % max; } float Server::get_random_float_0_1() { // This lacks some precision, but matches the original implementation. - return (static_cast(this->random_crypt->next() >> 16) / 65536.0); + return (static_cast(this->options.random_crypt->next() >> 16) / 65536.0); } uint32_t Server::get_round_num() const { @@ -1020,45 +1015,55 @@ void Server::move_phase_after() { continue; } - static const uint16_t TRAP_CARD_IDS[5][5] = { - // Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace - {0x00F7, 0x010F, 0x012E, 0x013B, 0x013C}, - // Gold Rush, Charity, Requiem - {0x0131, 0x012B, 0x0133, 0x0000, 0x0000}, - // Powerless Rain, Trash 1, Empty Hand, Skip Draw - {0x00FA, 0x0125, 0x0126, 0x0137, 0x0000}, - // Brave Wind, Homesick, Fly - {0x00FB, 0x014E, 0x0107, 0x0000, 0x0000}, - // Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix - {0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}}; - static size_t TRAP_CARD_ID_COUNTS[5] = {5, 3, 4, 3, 5}; + static const array, 5> default_trap_card_ids = { + // Red: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace + vector{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C}, + // Blue: Gold Rush, Charity, Requiem + vector{0x0131, 0x012B, 0x0133}, + // Purple: Powerless Rain, Trash 1, Empty Hand, Skip Draw + vector{0x00FA, 0x0125, 0x0126, 0x0137}, + // Green: Brave Wind, Homesick, Fly + vector{0x00FB, 0x014E, 0x0107}, + // Yellow: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix + vector{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}}; + + const vector* trap_card_ids = &this->options.trap_card_ids.at(trap_type); + if (trap_card_ids->empty()) { + trap_card_ids = &default_trap_card_ids.at(trap_type); + } // This is the original implementation. We do something smarter instead. // uint16_t trap_card_id = 0; // while (trap_card_id == 0) { // trap_card_id = TRAP_CARD_IDS[trap_type][this->get_random(5)]; // } - size_t trap_card_id_index = this->get_random(TRAP_CARD_ID_COUNTS[trap_type]); - uint16_t trap_card_id = TRAP_CARD_IDS[trap_type][trap_card_id_index]; + uint16_t trap_card_id = 0xFFFF; + if (trap_card_ids->size() == 1) { + trap_card_id = trap_card_ids->at(0); + } else if (trap_card_ids->size() > 1) { + trap_card_id = trap_card_ids->at(this->get_random(trap_card_ids->size())); + } - for (size_t client_id = 0; client_id < 4; client_id++) { - auto ps = this->player_states[client_id]; - if (ps) { - auto sc_card = ps->get_sc_card(); - if (sc_card && - (abs(sc_card->loc.x - trap_x) < 2) && - (abs(sc_card->loc.y - trap_y) < 2) && - ps->replace_assist_card_by_id(trap_card_id)) { - G_Unknown_GC_Ep3_6xB4x2C cmd; - cmd.change_type = 0x01; - cmd.client_id = client_id; - cmd.card_refs.clear(0xFFFF); - cmd.loc.x = trap_x; - cmd.loc.y = trap_y; - cmd.loc.direction = static_cast(trap_type); - cmd.unknown_a2[0] = trap_card_id; - cmd.unknown_a2[1] = 0xFFFFFFFF; - this->send(cmd); + if (trap_card_id != 0xFFFF) { + for (size_t client_id = 0; client_id < 4; client_id++) { + auto ps = this->player_states[client_id]; + if (ps) { + auto sc_card = ps->get_sc_card(); + if (sc_card && + (abs(sc_card->loc.x - trap_x) < 2) && + (abs(sc_card->loc.y - trap_y) < 2) && + ps->replace_assist_card_by_id(trap_card_id)) { + G_Unknown_GC_Ep3_6xB4x2C cmd; + cmd.change_type = 0x01; + cmd.client_id = client_id; + cmd.card_refs.clear(0xFFFF); + cmd.loc.x = trap_x; + cmd.loc.y = trap_y; + cmd.loc.direction = static_cast(trap_type); + cmd.unknown_a2[0] = trap_card_id; + cmd.unknown_a2[1] = 0xFFFFFFFF; + this->send(cmd); + } } } } @@ -1543,7 +1548,7 @@ G_SetStateFlags_GC_Ep3_6xB4x03 Server::prepare_6xB4x03() const { cmd.state.team_dice_boost[0] = this->team_dice_boost[0]; cmd.state.team_dice_boost[1] = this->team_dice_boost[1]; cmd.state.first_team_turn = this->first_team_turn; - cmd.state.tournament_flag = this->tournament ? 1 : 0; + cmd.state.tournament_flag = this->options.tournament ? 1 : 0; for (size_t z = 0; z < 4; z++) { auto ps = this->player_states[z]; if (!ps) { @@ -1952,8 +1957,8 @@ void Server::handle_CAx13_update_map_during_setup(const string& data) { // If this match is part of a tournament, ignore the rules sent by the // client and use the tournament rules instead. - if (this->tournament) { - this->map_and_rules->rules = this->tournament->get_rules(); + if (this->options.tournament) { + this->map_and_rules->rules = this->options.tournament->get_rules(); } if (this->override_environment_number != 0xFF) { @@ -1961,7 +1966,7 @@ void Server::handle_CAx13_update_map_during_setup(const string& data) { this->override_environment_number = 0xFF; } this->overlay_state = in_cmd.overlay_state; - if (this->behavior_flags & BehaviorFlag::DISABLE_TIME_LIMITS) { + if (this->options.behavior_flags & BehaviorFlag::DISABLE_TIME_LIMITS) { this->map_and_rules->rules.overall_time_limit = 0; this->map_and_rules->rules.phase_time_limit = 0; } @@ -1989,9 +1994,9 @@ void Server::handle_CAx14_update_deck_during_setup(const string& data) { } DeckEntry entry = in_cmd.entry; int32_t verify_error = 0; - if (!(this->behavior_flags & BehaviorFlag::SKIP_DECK_VERIFY)) { + if (!(this->options.behavior_flags & BehaviorFlag::SKIP_DECK_VERIFY)) { // Note: Sega's original implementation doesn't use the card counts here - if (this->behavior_flags & BehaviorFlag::IGNORE_CARD_COUNTS) { + if (this->options.behavior_flags & BehaviorFlag::IGNORE_CARD_COUNTS) { verify_error = this->ruler_server->verify_deck(entry.card_ids); } else { verify_error = this->ruler_server->verify_deck(entry.card_ids, @@ -2001,7 +2006,7 @@ void Server::handle_CAx14_update_deck_during_setup(const string& data) { if (verify_error) { throw runtime_error(string_printf("invalid deck: -0x%" PRIX32, verify_error)); } - if (!(this->behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) { + if (!(this->options.behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) { this->ruler_server->replace_D1_D2_rank_cards_with_Attack(entry.card_ids); } *this->deck_entries[in_cmd.client_id] = in_cmd.entry; @@ -2309,7 +2314,7 @@ void Server::handle_CAx40_map_list_request(const string& data) { throw runtime_error("lobby is deleted"); } - const auto& list_data = this->map_index->get_compressed_list(l->count_clients()); + const auto& list_data = this->options.map_index->get_compressed_list(l->count_clients()); StringWriter w; uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3); @@ -2337,7 +2342,7 @@ void Server::handle_CAx41_map_request(const string& data) { throw runtime_error("lobby is deleted"); } - this->last_chosen_map = this->map_index->definition_for_number(cmd.map_number); + this->last_chosen_map = this->options.map_index->definition_for_number(cmd.map_number); auto out_cmd = this->prepare_6xB6x41_map_definition(this->last_chosen_map, l->flags & Lobby::Flag::IS_EP3_TRIAL); send_command(l, 0x6C, 0x00, out_cmd); for (auto watcher_l : l->watcher_lobbies) { diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index 744e8335..865669c1 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -66,12 +66,15 @@ class Server : public std::enable_shared_from_this { // it appears that that command is never sent by the client, so we combine // the two classes into one in our implementation. public: - Server(std::shared_ptr lobby, - std::shared_ptr card_index, - std::shared_ptr map_index, - uint32_t behavior_flags, - std::shared_ptr random_crypt, - std::shared_ptr tournament); + struct Options { + std::shared_ptr card_index; + std::shared_ptr map_index; + uint32_t behavior_flags; + std::shared_ptr random_crypt; + std::shared_ptr tournament; + std::array, 5> trap_card_ids; + }; + Server(std::shared_ptr lobby, Options&& options); ~Server() noexcept(false); void init(); @@ -236,12 +239,8 @@ private: public: // These fields are not part of the original implementation std::weak_ptr lobby; - std::shared_ptr card_index; - std::shared_ptr map_index; - uint32_t behavior_flags; - std::shared_ptr random_crypt; + Options options; std::shared_ptr last_chosen_map; - std::shared_ptr tournament; bool tournament_match_result_sent; uint8_t override_environment_number; mutable std::deque logger_stack; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 2cc6b505..f5d61b17 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1303,13 +1303,15 @@ static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, const string& da } auto tourn = l->tournament_match ? l->tournament_match->tournament.lock() : nullptr; bool is_trial = (l->flags & Lobby::Flag::IS_EP3_TRIAL); - l->ep3_server = make_shared( - l, - is_trial ? s->ep3_card_index_trial : s->ep3_card_index, - s->ep3_map_index, - s->ep3_behavior_flags, - l->random_crypt, - tourn); + Episode3::Server::Options options = { + .card_index = is_trial ? s->ep3_card_index_trial : s->ep3_card_index, + .map_index = s->ep3_map_index, + .behavior_flags = s->ep3_behavior_flags, + .random_crypt = l->random_crypt, + .tournament = tourn, + .trap_card_ids = s->ep3_trap_card_ids, + }; + l->ep3_server = make_shared(l, std::move(options)); l->ep3_server->init(); if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) { diff --git a/src/ServerShell.cc b/src/ServerShell.cc index c320fa9b..3916a5bf 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -296,7 +296,7 @@ Proxy session commands:\n\ } else if (type == "config") { auto config_json = this->state->load_config(); this->state->parse_config(config_json, true); - this->state->resolve_ep3_card_auction_pool(); + this->state->resolve_ep3_card_names(); } else { throw invalid_argument("incorrect data type"); } diff --git a/src/ServerState.cc b/src/ServerState.cc index 9ca8ebf9..e40aa03b 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -101,7 +101,7 @@ void ServerState::init() { this->load_level_table(); this->load_item_tables(); this->load_ep3_data(); - this->resolve_ep3_card_auction_pool(); + this->resolve_ep3_card_names(); this->load_quest_index(); this->compile_functions(); this->load_dol_files(); @@ -589,6 +589,20 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { .card_name = it.first}); } + const auto& ep3_trap_cards_json = json.get("Episode3TrapCards", JSON::list()).as_list(); + if (!ep3_trap_cards_json.empty()) { + if (ep3_trap_cards_json.size() != 5) { + throw runtime_error("Episode3TrapCards must be a list of 5 lists"); + } + this->ep3_trap_card_names.clear(); + for (const auto& trap_type_it : ep3_trap_cards_json) { + auto& names = this->ep3_trap_card_names.emplace_back(); + for (const auto& card_it : trap_type_it->as_list()) { + names.emplace_back(card_it->as_string()); + } + } + } + if (!this->is_replay) { for (const auto& it : json.get("Episode3LobbyBanners", JSON::list()).as_list()) { Image img("system/ep3/banners/" + it->at(2).as_string()); @@ -923,14 +937,33 @@ void ServerState::load_ep3_data() { config_log.info("Loaded Episode 3 tournament state"); } -void ServerState::resolve_ep3_card_auction_pool() { - config_log.info("Resolving Episode 3 card auction pool"); +void ServerState::resolve_ep3_card_names() { + config_log.info("Resolving Episode 3 card names"); for (auto& e : this->ep3_card_auction_pool) { try { const auto& card = this->ep3_card_index->definition_for_name_normalized(e.card_name); e.card_id = card->def.card_id; } catch (const out_of_range&) { - throw runtime_error(string_printf("Ep3 card \"%s\" does not exist", e.card_name.c_str())); + throw runtime_error(string_printf("Ep3 card \"%s\" in auction pool does not exist", e.card_name.c_str())); + } + } + + for (size_t z = 0; z < this->ep3_trap_card_ids.size(); z++) { + auto& ids = this->ep3_trap_card_ids[z]; + ids.clear(); + if (z < this->ep3_trap_card_names.size()) { + auto& names = this->ep3_trap_card_names[z]; + for (const auto& name : names) { + try { + const auto& card = this->ep3_card_index->definition_for_name_normalized(name); + if (card->def.type != Episode3::CardType::ASSIST) { + throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list is not an assist card", name.c_str())); + } + ids.emplace_back(card->def.card_id); + } catch (const out_of_range&) { + throw runtime_error(string_printf("Ep3 card \"%s\" in trap card list does not exist", name.c_str())); + } + } } } } diff --git a/src/ServerState.hh b/src/ServerState.hh index 86412e4d..2f0d4077 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -109,6 +109,8 @@ struct ServerState : public std::enable_shared_from_this { std::string card_name; }; std::vector ep3_card_auction_pool; + std::vector> ep3_trap_card_names; + std::array, 5> ep3_trap_card_ids; struct Ep3LobbyBannerEntry { uint32_t type = 1; uint32_t which; // See B9 documentation in CommandFormats.hh @@ -207,7 +209,7 @@ struct ServerState : public std::enable_shared_from_this { void load_level_table(); void load_item_tables(); void load_ep3_data(); - void resolve_ep3_card_auction_pool(); + void resolve_ep3_card_names(); void load_quest_index(); void compile_functions(); void load_dol_files(); diff --git a/system/config.example.json b/system/config.example.json index 8e7c1549..5d3827da 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -322,6 +322,17 @@ // 0x0200 => Allow interference even when neither player is a COM "Episode3BehaviorFlags": 0x0002, + // Trap assist cards for each trap type in Episode 3 battles. These are the + // default values used offline, but you can change the trap types online here. + // Only assist cards may be used as trap cards. + "Episode3TrapCards": [ + ["Dice Fever", "Heavy Fog", "Muscular", "Immortality", "Snail Pace"], // Red + ["Gold Rush", "Charity", "Requiem"], // Blue + ["Powerless Rain", "Trash 1", "Empty Hand", "Skip Draw"], // Purple + ["Brave Wind", "Homesick", "Fly"], // Green + ["Dice+1", "Battle Royale", "Reverse Card", "Giant Garden", "Fix"], // Yellow + ], + // Episode 3 EX result values. This allows you to set the amount of EX players // will get for winning or losing online matches. Each set of numbers is a // list of thresholds; the first number in each pair is the level difference