diff --git a/TODO.md b/TODO.md index ef93d9f1..f542c4a0 100644 --- a/TODO.md +++ b/TODO.md @@ -11,7 +11,6 @@ - Figure out what causes the corruption message on PC proxy sessions and fix it - Make $edit for DC/PC - Add an idle connection timeout for proxy sessions -- Make the proxy work with the various trial and beta versions ## Episode 3 diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 55515aae..e2e8068f 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3755,17 +3755,17 @@ shared_ptr create_game_generic( auto current_lobby = c->require_lobby(); - uint8_t min_level; + size_t min_level; // A player's actual level is their displayed level - 1, so the minimums for // Episode 1 (for example) are actually 1, 20, 40, 80. switch (episode) { case Episode::EP1: { - static const uint32_t min_levels[4] = {0, 19, 39, 79}; + const auto& min_levels = (c->version() == Version::BB_V4) ? s->min_levels_v4[0] : DEFAULT_MIN_LEVELS_EP1; min_level = min_levels[difficulty]; break; } case Episode::EP2: { - static const uint32_t min_levels[4] = {0, 29, 49, 89}; + const auto& min_levels = (c->version() == Version::BB_V4) ? s->min_levels_v4[1] : DEFAULT_MIN_LEVELS_EP2; min_level = min_levels[difficulty]; break; } @@ -3773,7 +3773,7 @@ shared_ptr create_game_generic( min_level = 0; break; case Episode::EP4: { - static const uint32_t min_levels[4] = {0, 39, 79, 109}; + const auto& min_levels = (c->version() == Version::BB_V4) ? s->min_levels_v4[2] : DEFAULT_MIN_LEVELS_EP4; min_level = min_levels[difficulty]; break; } @@ -3935,7 +3935,7 @@ shared_ptr create_game_generic( if (game->mode == GameMode::CHALLENGE) { game->rare_enemy_rates = s->rare_enemy_rates_challenge; } else { - game->rare_enemy_rates = s->rare_enemy_rates.at(game->difficulty); + game->rare_enemy_rates = s->rare_enemy_rates_by_difficulty.at(game->difficulty); } if (game->base_version == Version::BB_V4) { diff --git a/src/ServerState.cc b/src/ServerState.cc index 69e18b15..9c5725ca 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -287,7 +287,7 @@ void ServerState::remove_lobby(uint32_t lobby_id) { this->id_to_lobby.erase(lobby_it); } -shared_ptr ServerState::find_client(const std::string* identifier, uint64_t serial_number, shared_ptr l) { +shared_ptr ServerState::find_client(const string* identifier, uint64_t serial_number, shared_ptr l) { if ((serial_number == 0) && identifier) { try { serial_number = stoull(*identifier, nullptr, 0); @@ -315,7 +315,7 @@ shared_ptr ServerState::find_client(const std::string* identifier, uint6 throw out_of_range("client not found"); } -uint32_t ServerState::connect_address_for_client(std::shared_ptr c) const { +uint32_t ServerState::connect_address_for_client(shared_ptr c) const { if (c->channel.is_virtual_connection) { if (c->channel.remote_addr.ss_family != AF_INET) { throw logic_error("virtual connection is missing remote IPv4 address"); @@ -334,7 +334,7 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr c) cons } } -std::shared_ptr ServerState::information_menu_for_version(Version version) const { +shared_ptr ServerState::information_menu_for_version(Version version) const { if (is_v1_or_v2(version)) { return this->information_menu_v2; } else if (is_v3(version)) { @@ -385,7 +385,7 @@ const vector>& ServerState::proxy_destinations_for_versio } } -std::shared_ptr ServerState::item_parameter_table_for_version(Version version) const { +shared_ptr ServerState::item_parameter_table_for_version(Version version) const { switch (version) { case Version::DC_NTE: case Version::DC_V1_11_2000_PROTOTYPE: @@ -406,7 +406,7 @@ std::shared_ptr ServerState::item_parameter_table_for_ } } -std::string ServerState::describe_item(Version version, const ItemData& item, bool include_color_codes) const { +string ServerState::describe_item(Version version, const ItemData& item, bool include_color_codes) const { return this->item_name_index->describe_item( version, item, @@ -448,9 +448,9 @@ void ServerState::set_port_configuration( } shared_ptr ServerState::load_bb_file( - const std::string& patch_index_filename, - const std::string& gsl_filename, - const std::string& bb_directory_filename) const { + const string& patch_index_filename, + const string& gsl_filename, + const string& bb_directory_filename) const { if (this->bb_patch_file_index) { // First, look in the patch tree's data directory @@ -919,10 +919,10 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { try { string key = "RareEnemyRates-"; key += token_name_for_difficulty(z); - this->rare_enemy_rates[z] = make_shared(json.at(key)); - prev = this->rare_enemy_rates[z]; + this->rare_enemy_rates_by_difficulty[z] = make_shared(json.at(key)); + prev = this->rare_enemy_rates_by_difficulty[z]; } catch (const out_of_range&) { - this->rare_enemy_rates[z] = prev; + this->rare_enemy_rates_by_difficulty[z] = prev; } } try { @@ -930,6 +930,32 @@ void ServerState::parse_config(const JSON& json, bool is_reload) { } catch (const out_of_range&) { this->rare_enemy_rates_challenge = Map::DEFAULT_RARE_ENEMIES; } + + this->min_levels_v4[0] = DEFAULT_MIN_LEVELS_EP1; + this->min_levels_v4[1] = DEFAULT_MIN_LEVELS_EP2; + this->min_levels_v4[2] = DEFAULT_MIN_LEVELS_EP4; + try { + for (const auto& ep_it : json.get_dict("BBMinimumLevels")) { + array levels({0, 0, 0, 0}); + for (size_t z = 0; z < 4; z++) { + levels[z] = ep_it.second->get_int(z) - 1; + } + switch (episode_for_token_name(ep_it.first)) { + case Episode::EP1: + this->min_levels_v4[0] = levels; + break; + case Episode::EP2: + this->min_levels_v4[1] = levels; + break; + case Episode::EP4: + this->min_levels_v4[2] = levels; + break; + default: + throw runtime_error("unknown episode"); + } + } + } catch (const out_of_range&) { + } } void ServerState::load_bb_private_keys() { diff --git a/src/ServerState.hh b/src/ServerState.hh index a255c9b7..9e7a337d 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -114,8 +114,9 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr mag_evolution_table; std::shared_ptr item_name_index; std::shared_ptr word_select_table; - std::array, 4> rare_enemy_rates; + std::array, 4> rare_enemy_rates_by_difficulty; std::shared_ptr rare_enemy_rates_challenge; + std::array, 3> min_levels_v4; // Indexed as [episode][difficulty] // Indexed as [type][difficulty][random_choice] std::vector>> quest_F95E_results; diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index b41f9ee9..c0279869 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -42,6 +42,22 @@ const char* token_name_for_episode(Episode ep) { } } +Episode episode_for_token_name(const string& name) { + if (name == "Episode1") { + return Episode::EP1; + } + if (name == "Episode2") { + return Episode::EP2; + } + if (name == "Episode3") { + return Episode::EP3; + } + if (name == "Episode4") { + return Episode::EP4; + } + throw runtime_error("unknown episode"); +} + const char* abbreviation_for_episode(Episode ep) { switch (ep) { case Episode::NONE: @@ -764,3 +780,7 @@ char char_for_challenge_rank(uint8_t rank) { } return "BAS"[rank]; } + +const array DEFAULT_MIN_LEVELS_EP1({0, 19, 39, 79}); +const array DEFAULT_MIN_LEVELS_EP2({0, 29, 49, 89}); +const array DEFAULT_MIN_LEVELS_EP4({0, 39, 79, 109}); diff --git a/src/StaticGameData.hh b/src/StaticGameData.hh index b3ddf1c7..cd5c48a0 100644 --- a/src/StaticGameData.hh +++ b/src/StaticGameData.hh @@ -20,6 +20,7 @@ bool episode_has_arpg_semantics(Episode ep); const char* name_for_episode(Episode ep); const char* token_name_for_episode(Episode ep); const char* abbreviation_for_episode(Episode ep); +Episode episode_for_token_name(const std::string& name); enum class GameMode { NORMAL = 0, @@ -78,3 +79,7 @@ const char* name_for_floor(Episode episode, uint8_t floor); uint32_t class_flags_for_class(uint8_t char_class); char char_for_challenge_rank(uint8_t rank); + +extern const std::array DEFAULT_MIN_LEVELS_EP1; +extern const std::array DEFAULT_MIN_LEVELS_EP2; +extern const std::array DEFAULT_MIN_LEVELS_EP4; diff --git a/system/config.example.json b/system/config.example.json index 3a92fc39..1a8e1c78 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -730,7 +730,9 @@ // rates significantly. // If no rates are specified for a difficulty, the previous difficulty's rates // will be used. (In the default configuration, only Normal is specified, so - // all difficulties use the same rates.) + // all difficulties use the same rates.) If the Challenge set is not + // specified, the default rates are used for Challenge mode, which is 1/512 + // for all enemies. "RareEnemyRates-Normal": { // These are probabilities out of 0xFFFFFFFF - so 0 means that rare enemy // will never appear, and 0xFFFFFFFF means it will always appear (until 16 @@ -749,6 +751,14 @@ // "RareEnemyRates-Ultimate": {...}, // "RareEnemyRates-Challenge": {...}, + // You can override the minimum character levels required to make BB games in + // each episode and difficulty level here. + "BBMinimumLevels": { + "Episode1": [1, 20, 50, 90], + "Episode2": [1, 30, 60, 100], + "Episode4": [1, 40, 70, 110], + }, + // Whether to enable certain exception handling. Disabling this causes // newserv to abort when any client causes an exception, which is generally // only useful for debugging newserv itself. This setting should usually be